First commit

This commit is contained in:
2026-03-27 10:14:29 +03:00
commit ad29150770
10404 changed files with 962562 additions and 0 deletions

411
components/Calculator.vue Normal file
View 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:&nbsp;
<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>