First commit
This commit is contained in:
411
components/Calculator.vue
Normal file
411
components/Calculator.vue
Normal file
@@ -0,0 +1,411 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user