411 lines
13 KiB
Vue
411 lines
13 KiB
Vue
<template>
|
|
<div class="flex flex-col items-center lg:w-3/4">
|
|
<!-- Toolbar -->
|
|
<div class="flex flex-row flex-wrap gap-4 items-center">
|
|
<span>
|
|
<dropdown v-model.number="unit_index" v-if="Object.keys(getUnitToAdd).length > 0" >
|
|
<option :value="-1" disabled hidden>--- Select {{ unitsLabel }} ---</option>
|
|
<option
|
|
v-for="(target, index) in getUnitToAdd"
|
|
:key="target.summon"
|
|
:value="index"
|
|
>
|
|
{{ target.name }}
|
|
</option>
|
|
</dropdown>
|
|
<button class="btn btn-blue" @click="addUnit()" :disabled="unit_index < 0" v-if="Object.keys(getUnitToAdd).length > 0" >
|
|
Add
|
|
</button>
|
|
</span>
|
|
|
|
<checkbox v-model="splitMats">Split materials for each step</checkbox>
|
|
<checkbox v-model="hideCompletedMats">Hide completed materials</checkbox>
|
|
<label>Display:
|
|
<dropdown v-model.number="displayList">
|
|
<option :value="0">Grid</option>
|
|
<option :value="1">List</option>
|
|
</dropdown>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Unit box -->
|
|
<div v-for="(_, unitKey) in progress" :key="unitKey" class="flex flex-col mt-8 border-4 border-secondary rounded p-1 lg:p-4 bg-tertiary w-full">
|
|
|
|
<span class="flex flex-row justify-between text-3xl font-bold">
|
|
<div></div>
|
|
<a class="cursor-pointer" @click="toggleFolded(progress[unitKey])">
|
|
{{ getUnits[unitKey].name }}
|
|
<fa-icon v-if="progress[unitKey].fold" :icon="['fas', 'angle-right']" class="ml-2"></fa-icon>
|
|
<fa-icon v-else :icon="['fas', 'angle-down']" class="ml-2"></fa-icon>
|
|
</a>
|
|
<a class="cursor-pointer" @click="removeUnit(unitKey)" title="Remove">
|
|
<fa-icon :icon="['fas', 'trash']" class="ml-2"></fa-icon>
|
|
</a>
|
|
</span>
|
|
|
|
<span v-if=" ! progress[unitKey].fold" class="flex flex-col mt-6">
|
|
<!-- Options -->
|
|
<div class="flex flex-row flex-wrap self-center items-center gap-2">
|
|
<span>Completed step</span>
|
|
<dropdown v-model.number="progress[unitKey].from" @change="selectFromStep(unitKey)">
|
|
<option :value="-1">-- Nothing --</option>
|
|
<option
|
|
v-for="(step, index) in getUnitsMaterials.slice(0, -1)"
|
|
:key="step.name"
|
|
:value="index"
|
|
>
|
|
{{ step.name }}
|
|
</option>
|
|
</dropdown>
|
|
|
|
<span>Target step</span>
|
|
<dropdown v-model.number="progress[unitKey].to">
|
|
<option
|
|
v-for="(step, index) in getUnitsMaterials"
|
|
:key="step.name"
|
|
:value="index"
|
|
:disabled="progress[unitKey].from >= index"
|
|
>
|
|
{{ step.name }}
|
|
</option>
|
|
</dropdown>
|
|
</div>
|
|
|
|
<!-- Materials -->
|
|
<div v-for="(material, matKey) in getMaterialsFor(unitKey)" :key="matKey">
|
|
<h2 class="my-4">{{ material.name }}</h2>
|
|
|
|
<div v-if="displayList == 0" class="flex flex-row flex-wrap gap-1">
|
|
<calc-preview-item v-for="(item, keyItem) in filterCompletedItems(unitKey, matKey, material.items)" :key="keyItem"
|
|
:tag="item.item"
|
|
:name="item.name"
|
|
:quantity="getQuantityForItem(unitKey, matKey, item.item)"
|
|
@update:quantity="val => setQuantityForItem(unitKey, matKey, item, val)"
|
|
:max="item.max"
|
|
:animated="item.animated"
|
|
class="border-secondary border rounded"
|
|
></calc-preview-item>
|
|
</div>
|
|
<div v-else class="flex flex-col">
|
|
<calc-preview-list v-for="(item, keyItem) in filterCompletedItems(unitKey, matKey, material.items)" :key="keyItem"
|
|
:tag="item.item"
|
|
:name="item.name"
|
|
:quantity="getQuantityForItem(unitKey, matKey, item.item)"
|
|
@update:quantity="val => setQuantityForItem(unitKey, matKey, item, val)"
|
|
:max="item.max"
|
|
:animated="item.animated"
|
|
></calc-preview-list>
|
|
</div>
|
|
</div>
|
|
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import supplies from '@/js/supplies'
|
|
import utils from '@/js/utils'
|
|
|
|
import Checkbox from '@/components/common/Checkbox.vue'
|
|
import Dropdown from '@/components/common/Dropdown.vue'
|
|
import CalcPreviewItem from '@/components/CalcPreviewItem.vue'
|
|
import CalcPreviewList from '@/components/CalcPreviewList.vue'
|
|
|
|
class UnitProgress {
|
|
constructor(dataLength) {
|
|
this.from = -1;
|
|
this.to = 0;
|
|
this.fold = false;
|
|
this.materials = Array.from({length: dataLength}, _ => { return {} });
|
|
}
|
|
}
|
|
|
|
const sortMaterials = (lhs, rhs) => {
|
|
if (lhs.category === rhs.category) {
|
|
// Smaller quantity first
|
|
return lhs.max > rhs.max;
|
|
}
|
|
// Group by category
|
|
return lhs.category > rhs.category;
|
|
};
|
|
|
|
class MaterialStep {
|
|
constructor(name, items) {
|
|
this.name = name;
|
|
this.items = items;
|
|
}
|
|
}
|
|
|
|
export default {
|
|
components: {
|
|
Checkbox,
|
|
Dropdown,
|
|
CalcPreviewItem,
|
|
CalcPreviewList
|
|
},
|
|
props: {
|
|
// { 2040236: new UnitProgress([{chaotichaze: 0, ...}, ...]), ... }
|
|
unitsProgress: {
|
|
type: Object,
|
|
required: true
|
|
},
|
|
unitsData: {
|
|
type: Object,
|
|
required: true,
|
|
},
|
|
unitsLabel: {
|
|
type: String,
|
|
required: true
|
|
},
|
|
unitsSplitMats: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
unitsHideCompletedMats: {
|
|
type: Boolean,
|
|
required: true
|
|
},
|
|
unitsDisplayList: {
|
|
type: Number,
|
|
default: 0,
|
|
}
|
|
},
|
|
data() {
|
|
return {
|
|
unit_index: -1,
|
|
};
|
|
},
|
|
methods: {
|
|
addUnit() {
|
|
this.$set(this.progress, this.unit_index, new UnitProgress(this.unitsData.materials.length));
|
|
this.unit_index = -1;
|
|
},
|
|
removeUnit(unitKey) {
|
|
this.$delete(this.progress, unitKey);
|
|
},
|
|
getItemProgressFor(unitKey, item, materialStep) {
|
|
let itemRefs = [];
|
|
|
|
if (item.item) {
|
|
itemRefs.push(item.item);
|
|
}
|
|
else if (item.group) {
|
|
switch (supplies.groups[item.group].type) {
|
|
case 'element': {
|
|
const tmpRef = supplies.groups[item.group][this.unitsData.units[unitKey].element];
|
|
if (tmpRef instanceof Array) {
|
|
itemRefs = tmpRef;
|
|
}
|
|
else {
|
|
itemRefs.push(tmpRef);
|
|
}
|
|
} break;
|
|
case 'summon': {
|
|
const tmpRef = supplies.groups[item.group][unitKey];
|
|
if (tmpRef instanceof Array) {
|
|
itemRefs = tmpRef;
|
|
}
|
|
else {
|
|
itemRefs.push(tmpRef);
|
|
}
|
|
} break;
|
|
}
|
|
}
|
|
else {
|
|
console.log("Unknown type of item for:");
|
|
console.log(item);
|
|
}
|
|
|
|
let resultArray = [];
|
|
for (let itemRef of itemRefs) {
|
|
if (this.progress[unitKey].materials[materialStep][itemRef] === undefined) {
|
|
this.$set(this.progress[unitKey].materials[materialStep], itemRef, 0);
|
|
}
|
|
|
|
// Round non integer values
|
|
let max = item.q / itemRefs.length;
|
|
if (item.q % itemRefs.length > 0) {
|
|
if (max < 5) max = Math.ceil(max);
|
|
else max = Math.floor(max);
|
|
}
|
|
|
|
if (supplies.items[itemRef] === undefined) {
|
|
console.log(itemRef + ' does not exist.')
|
|
}
|
|
|
|
resultArray.push({
|
|
item: itemRef,
|
|
group: item.group,
|
|
name: supplies.items[itemRef].name,
|
|
category: supplies.items[itemRef].category,
|
|
max: max,
|
|
animated: supplies.items[itemRef].animated,
|
|
});
|
|
}
|
|
return resultArray;
|
|
},
|
|
getMaterialsFor(unitKey) {
|
|
const result = [];
|
|
|
|
// In case steps are added later
|
|
while (this.unitsData.materials.length > this.progress[unitKey].materials.length) {
|
|
this.progress[unitKey].materials.push({});
|
|
}
|
|
|
|
if (this.splitMats) {
|
|
this.unitsData.materials
|
|
.slice(this.progress[unitKey].from + 1, this.progress[unitKey].to + 1)
|
|
.forEach((items, index) => {
|
|
result.push(new MaterialStep(
|
|
items.name,
|
|
Object.values(items.items
|
|
.flatMap(item => this.getItemProgressFor(unitKey, item, index + this.progress[unitKey].from + 1))
|
|
.reduce((buffer, item) => {
|
|
// Merge materials
|
|
if (buffer.hasOwnProperty(item.item)) {
|
|
buffer[item.item].max += item.max;
|
|
}
|
|
else {
|
|
buffer[item.item] = item;
|
|
}
|
|
return buffer;
|
|
}, {})
|
|
)
|
|
.sort(sortMaterials)
|
|
));
|
|
});
|
|
}
|
|
else {
|
|
const buffer = {};
|
|
this.unitsData.materials
|
|
.slice(this.progress[unitKey].from + 1, this.progress[unitKey].to + 1)
|
|
.forEach((items, index) => {
|
|
items.items
|
|
.flatMap(item => this.getItemProgressFor(unitKey, item, index + this.progress[unitKey].from + 1))
|
|
.forEach(item => {
|
|
// Merge materials
|
|
if (buffer.hasOwnProperty(item.item)) {
|
|
buffer[item.item].max += item.max;
|
|
}
|
|
else {
|
|
buffer[item.item] = item;
|
|
}
|
|
});
|
|
});
|
|
|
|
result.push(new MaterialStep('Materials', Object.values(buffer)));
|
|
result[0].items.sort(sortMaterials);
|
|
}
|
|
|
|
return result;
|
|
},
|
|
selectFromStep(unitKey) {
|
|
// Dropdown takes some time to update
|
|
this.$nextTick().then(() => {
|
|
if (this.progress[unitKey].from >= this.progress[unitKey].to) {
|
|
this.progress[unitKey].to = this.progress[unitKey].from + 1;
|
|
}
|
|
});
|
|
},
|
|
filterCompletedItems(unitKey, matKey, items) {
|
|
if (this.hideCompletedMats) {
|
|
return items.filter(item => this.getQuantityForItem(unitKey, matKey, item.item) < item.max);
|
|
}
|
|
return items;
|
|
},
|
|
toggleFolded(progressForUnit) {
|
|
this.$set(progressForUnit, 'fold', ! progressForUnit.fold);
|
|
},
|
|
getQuantityForItem(unitKey, matKey, itemKey) {
|
|
if (this.splitMats) {
|
|
return this.progress[unitKey].materials[matKey + this.progress[unitKey].from + 1][itemKey];
|
|
}
|
|
return this.progress[unitKey].materials
|
|
.slice(matKey + this.progress[unitKey].from + 1, matKey + this.progress[unitKey].to + 1)
|
|
.reduce((acc, items) => {
|
|
if (items.hasOwnProperty(itemKey)) {
|
|
return acc + items[itemKey];
|
|
}
|
|
return acc;
|
|
}, 0);
|
|
},
|
|
setQuantityForItem(unitKey, matKey, item, value) {
|
|
if (this.splitMats) {
|
|
this.progress[unitKey].materials[matKey + this.progress[unitKey].from + 1][item.item] = value;
|
|
}
|
|
else {
|
|
let totalToAdd = value;
|
|
this.progress[unitKey].materials
|
|
.slice(matKey + this.progress[unitKey].from + 1, matKey + this.progress[unitKey].to + 1)
|
|
.forEach((items, index) => {
|
|
if (items.hasOwnProperty(item.item)) {
|
|
// Find max quantity of item
|
|
const foundItem = this.unitsData.materials[matKey + this.progress[unitKey].from + index + 1].items.find(i =>
|
|
i.item ? i.item == item.item : i.group == item.group
|
|
);
|
|
// Protection against bad localStorage data
|
|
if (foundItem) {
|
|
const max = foundItem.q;
|
|
const toAdd = Math.min(totalToAdd, max);
|
|
this.progress[unitKey].materials[matKey + this.progress[unitKey].from + index + 1][item.item] = toAdd;
|
|
totalToAdd -= toAdd;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
},
|
|
},
|
|
computed: {
|
|
getUnitToAdd() {
|
|
return utils.filterObject(this.unitsData.units, ([key, _]) => ! this.progress.hasOwnProperty(key));
|
|
},
|
|
getUnits() {
|
|
return this.unitsData.units;
|
|
},
|
|
getUnitsMaterials() {
|
|
return this.unitsData.materials;
|
|
},
|
|
localQuantity: {
|
|
get() {
|
|
return this.quantity;
|
|
},
|
|
set(value) {
|
|
this.$emit('update:quantity', value);
|
|
}
|
|
},
|
|
progress: {
|
|
get() {
|
|
return this.unitsProgress;
|
|
},
|
|
set(value) {
|
|
this.$emit('update:unitsProgress', value);
|
|
}
|
|
},
|
|
splitMats: {
|
|
get() {
|
|
return this.unitsSplitMats;
|
|
},
|
|
set(value) {
|
|
this.$emit('update:unitsSplitMats', value);
|
|
}
|
|
},
|
|
hideCompletedMats: {
|
|
get() {
|
|
return this.unitsHideCompletedMats;
|
|
},
|
|
set(value) {
|
|
this.$emit('update:unitsHideCompletedMats', value);
|
|
}
|
|
},
|
|
displayList: {
|
|
get() {
|
|
return this.unitsDisplayList;
|
|
},
|
|
set(value) {
|
|
this.$emit('update:unitsDisplayList', value);
|
|
}
|
|
},
|
|
},
|
|
};
|
|
</script> |