Clean up multiworld preset handling

This commit is contained in:
2026-01-17 11:48:07 -06:00
parent 662ba87e52
commit 2b9a453f3a
5 changed files with 181 additions and 98 deletions

View File

@@ -1,16 +1,25 @@
<script> <script>
import { defineComponent } from 'vue'; import { defineComponent } from "vue";
import { mapState, mapActions } from "pinia";
import localforage from "localforage"; import usePresetStore from "@/stores/preset.js";
import settingsData from "@/data/settings.yaml";
import generatorSettings from "@/data/generator-settings.yaml"; function settingsMatch(settings, preset) {
import presets from "@/data/presets.yaml"; for (const settingName of Object.keys(settings)) {
if (settingName == "player_name") {
continue;
}
if (preset[settingName] != settings[settingName]) {
return false;
}
}
return true;
}
export default defineComponent({ export default defineComponent({
data() { data() {
return { return {
selected: "custom", selected: "custom",
localPresets: [],
}; };
}, },
emits: [ emits: [
@@ -19,81 +28,47 @@ export default defineComponent({
], ],
props: { props: {
generator: null, generator: null,
}, prefix: null,
async mounted() {
this.localPresets = await localforage.getItem(`local_presets_${this.generator}`) ?? [];
for (const preset of this.localPresets) {
this.fillPreset(preset);
}
await this.updateLocalPresets();
}, },
computed: { computed: {
settings() { ...mapState(usePresetStore, {
const settings = {}; allGlobalPresets: "globalPresets",
for (const name of Object.keys(generatorSettings[this.generator])) { allLocalPresets: "localPresets",
var generatorValue = generatorSettings[this.generator][name] }),
if (generatorValue == "all") { globalPresets() {
settings[name] = settingsData[name]; return this.allGlobalPresets[this.generator];
} else if (Array.isArray(generatorValue)) {
const { values, ...newObj } = settingsData[this.name];
newObj.values = {};
for (const value of generatorValue) {
newObj.values[value] = values[value];
}
settings[name] = newObj;
} else {
settings[name] = generatorValue;
}
}
return settings;
}, },
presets() { localPresets() {
const filledPresets = {}; return this.allLocalPresets[this.generator];
if (presets[this.generator]) {
for (const presetName of Object.keys(presets[this.generator])) {
const preset = JSON.parse(JSON.stringify(presets[this.generator][presetName]));
this.fillPreset(preset);
filledPresets[presetName] = preset;
}
}
return filledPresets;
}, },
}, },
methods: { methods: {
...mapActions(usePresetStore, ["deleteLocalPreset", "saveLocalPreset"]),
change() { change() {
if (this.selected && this.selected != "custom") { if (this.selected && this.selected != "custom") {
if (this.selected.startsWith("local_")) { if (this.selected.startsWith("local_")) {
this.$emit("selected", this.localPresets[this.selected.substring(6)]); this.$emit("selected", this.localPresets[this.selected.substring(6)]);
} else { } else {
this.$emit("selected", this.presets[this.selected]); this.$emit("selected", this.globalPresets[this.selected]);
} }
} }
}, },
settingsMatch(newSettings, preset) {
for (const settingName of Object.keys(newSettings)) {
if (settingName == "player_name") {
continue;
}
if (preset[settingName] != newSettings[settingName]) {
return false;
}
}
return true
},
settingChanged(newSettings) { settingChanged(newSettings) {
for (const presetName of Object.keys(this.presets)) { for (const presetName of Object.keys(this.globalPresets)) {
const preset = this.presets[presetName]; const preset = this.globalPresets[presetName];
if (this.settingsMatch(newSettings, preset)) { if (settingsMatch(newSettings, preset)) {
this.selected = presetName; this.selected = presetName;
return; return;
} }
} }
if (this.localPresets) {
for (const [idx, preset] of this.localPresets.entries()) { for (const [idx, preset] of this.localPresets.entries()) {
if (this.settingsMatch(newSettings, preset)) { if (settingsMatch(newSettings, preset)) {
this.selected = `local_${idx}`; this.selected = `local_${idx}`;
return; return;
} }
} }
}
this.selected = "custom"; this.selected = "custom";
}, },
saveClicked() { saveClicked() {
@@ -101,31 +76,12 @@ export default defineComponent({
}, },
async deleteClicked() { async deleteClicked() {
const idx = this.selected.substring(6); const idx = this.selected.substring(6);
this.localPresets.splice(idx, 1);
this.selected = "custom"; this.selected = "custom";
await this.updateLocalPresets(); await this.deleteLocalPreset(this.generator, idk);
}, },
async savePreset(idx, settings) { async savePreset(idx, preset) {
if (idx == null) { idx = await this.saveLocalPreset(this.generator, idx, preset);
idx = this.localPresets.length;
this.localPresets.push(settings);
} else {
this.localPresets[idx] = settings;
}
this.selected = `local_${idx}`; this.selected = `local_${idx}`;
await this.updateLocalPresets();
},
async updateLocalPresets() {
const copy = JSON.parse(JSON.stringify(this.localPresets));
await localforage.setItem(`local_presets_${this.generator}`, copy);
},
fillPreset(preset) {
for (const settingName of Object.keys(this.settings)) {
if (preset[settingName] == undefined) {
preset[settingName] = this.settings[settingName].default;
}
}
return preset;
}, },
}, },
}); });
@@ -139,13 +95,13 @@ export default defineComponent({
<select v-model="selected" class="form-select" id="presetSelector" @change="change"> <select v-model="selected" class="form-select" id="presetSelector" @change="change">
<option disabled="true" value="custom">Custom</option> <option disabled="true" value="custom">Custom</option>
<optgroup label="Global Presets"> <optgroup label="Global Presets">
<template v-for="name of Object.keys(presets)"> <template v-for="name of Object.keys(globalPresets)">
<option :value="name"> <option :value="name">
{{ presets[name].display }} {{ globalPresets[name].display }}
</option> </option>
</template> </template>
</optgroup> </optgroup>
<optgroup v-if="localPresets.length" label="Local Presets"> <optgroup v-if="localPresets && localPresets.length" label="Local Presets">
<template v-for="(preset, idx) of localPresets"> <template v-for="(preset, idx) of localPresets">
<option :value="`local_${idx}`"> <option :value="`local_${idx}`">
{{ preset.display }} {{ preset.display }}

View File

@@ -1,5 +1,6 @@
<script> <script>
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { mapActions } from "pinia";
import axios from "axios"; import axios from "axios";
import { Modal } from "bootstrap"; import { Modal } from "bootstrap";
@@ -10,6 +11,8 @@ import AccordionItem from "@/components/AccordionItem.vue";
import PresetPicker from "@/components/PresetPicker.vue"; import PresetPicker from "@/components/PresetPicker.vue";
import SettingPicker from "@/components/SettingPicker.vue"; import SettingPicker from "@/components/SettingPicker.vue";
import usePresetStore from "@/stores/preset.js";
export default defineComponent({ export default defineComponent({
components: { components: {
AccordionItem, AccordionItem,
@@ -38,8 +41,10 @@ export default defineComponent({
return generatorSettings[this.generator]; return generatorSettings[this.generator];
} }
}, },
mounted() { async mounted() {
this.modal = new Modal(document.getElementById("savePresetModal"), {}); this.modal = new Modal(this.$refs.savePresetModal, {});
await this.fetchLocalPresets();
this.$refs.preset.settingChanged(this.set);
}, },
watch: { watch: {
set: { set: {
@@ -51,6 +56,7 @@ export default defineComponent({
} }
}, },
methods: { methods: {
...mapActions(usePresetStore, ["fetchLocalPresets"]),
async generate(race) { async generate(race) {
const settings = { const settings = {
randomizer: this.generator, randomizer: this.generator,
@@ -94,7 +100,7 @@ export default defineComponent({
</script> </script>
<template> <template>
<div class="modal" tabindex="-1" id="savePresetModal"> <div class="modal" tabindex="-1" ref="savePresetModal">
<div class="modal-dialog"> <div class="modal-dialog">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@@ -137,7 +143,7 @@ export default defineComponent({
<div id="settings" class="accordion accordion-flush"> <div id="settings" class="accordion accordion-flush">
<AccordionItem> <AccordionItem>
<PresetPicker ref="preset" :generator="generator" @selected="presetSelected" <PresetPicker ref="preset" :generator="generator" @selected="presetSelected"
@save="savePreset" /> @save="savePreset" :prefix="prefix" />
</AccordionItem> </AccordionItem>
<AccordionItem :expanded="true"> <AccordionItem :expanded="true">
<template #header> <template #header>

View File

@@ -1,4 +1,5 @@
import { createApp } from "vue"; import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue"; import App from "./App.vue";
import router from "./router"; import router from "./router";
@@ -14,8 +15,10 @@ import "./assets/main.css";
import axios from "axios"; import axios from "axios";
axios.defaults.baseURL = import.meta.env.VITE_BACKEND_URL; axios.defaults.baseURL = import.meta.env.VITE_BACKEND_URL;
const app = createApp(App) const pinia = createPinia();
const app = createApp(App);
app.use(router) app.use(router);
app.use(pinia);
app.mount('#app') app.mount('#app');

104
src/stores/preset.js Normal file
View File

@@ -0,0 +1,104 @@
import { defineStore } from "pinia";
import localforage from "localforage";
import settingsData from "@/data/settings.yaml";
import generatorSettings from "@/data/generator-settings.yaml";
import presets from "@/data/presets.yaml";
function fillPreset(preset, settings) {
for (const settingName of Object.keys(settings)) {
if (preset[settingName] == undefined) {
preset[settingName] = settings[settingName].default;
}
}
return preset;
}
export default defineStore("preset", {
state() {
return {
localPresets: {},
};
},
getters: {
settings() {
const settings = {};
for (const generator of Object.keys(generatorSettings)) {
settings[generator] = {};
for (const name of Object.keys(generatorSettings[generator])) {
var generatorValue = generatorSettings[generator][name];
if (generatorValue == "all") {
settings[generator][name] = settingsData[name];
} else if (Array.isArray(generatorValue)) {
const { values, ...newObj } = settingsData[name];
newObj.values = {};
for (const value of generatorValue) {
newObj.values[value] = values[value];
}
settings[generator][name] = newObj;
} else {
settings[generator][name] = generatorValue;
}
}
}
return settings;
},
globalPresets() {
const filledPresets = {};
for (const generator of Object.keys(generatorSettings)) {
filledPresets[generator] = {};
if (presets[generator]) {
for (const presetName of Object.keys(presets[generator])) {
const preset = { ...presets[generator][presetName] };
fillPreset(preset, this.settings[generator]);
filledPresets[generator][presetName] = preset;
}
}
}
return filledPresets;
},
},
actions: {
async fetchLocalPresets() {
for (const generator of Object.keys(generatorSettings)) {
if (this.localPresets[generator]) {
continue;
}
await this.cacheLocalPresets(generator);
}
},
async cacheLocalPresets(generator) {
var generatorPresets = await localforage.getItem(`local_presets_${generator}`);
if (!Array.isArray(generatorPresets)) {
generatorPresets = [];
}
for (const preset of generatorPresets) {
fillPreset(preset, this.settings[generator]);
}
this.localPresets[generator] = generatorPresets;
await this.saveLocalPresets(generator);
},
async deleteLocalPreset(generator, index) {
this.localPresets[generator].splice(index, 1);
await this.saveLocalPresets(generator);
},
async saveLocalPreset(generator, index, preset) {
if (index == null) {
index = this.localPresets[generator].length;
this.localPresets[generator].push(preset);
} else {
this.localPresets[generator][index] = preset;
}
await this.saveLocalPresets(generator);
return index;
},
async saveLocalPresets(generator) {
const copy = JSON.parse(JSON.stringify(this.localPresets[generator]));
await localforage.setItem(`local_presets_${generator}`, copy);
},
},
});

View File

@@ -3,7 +3,7 @@ import { defineComponent } from "vue";
import axios from "axios"; import axios from "axios";
import localforage from "localforage"; import localforage from "localforage";
import { Modal } from "bootstrap"; import { Tab } from "bootstrap";
import SettingsPage from "@/components/SettingsPage.vue"; import SettingsPage from "@/components/SettingsPage.vue";
@@ -28,6 +28,7 @@ export default defineComponent({
for (var i = 0; i < this.worldCount; i++) { for (var i = 0; i < this.worldCount; i++) {
this.worlds[i].player_name = await localforage.getItem(`${this.prefix}world_${i + 1}_setting_player_name`); this.worlds[i].player_name = await localforage.getItem(`${this.prefix}world_${i + 1}_setting_player_name`);
new Tab(this.$refs.tabs[i]);
} }
}, },
methods: { methods: {
@@ -36,12 +37,17 @@ export default defineComponent({
this.worlds.push({player_name: newname}); this.worlds.push({player_name: newname});
this.worldCount++; this.worldCount++;
await this.$nextTick();
Tab.getOrCreateInstance(this.$refs.tabs[this.worldCount - 1]).show();
await new Promise(r => setTimeout(r, 100)); await new Promise(r => setTimeout(r, 100));
this.worlds[this.worldCount - 1].player_name = newname; this.worlds[this.worldCount - 1].player_name = newname;
}, },
async removeWorld() { async removeWorld() {
this.worldCount--; this.worldCount--;
this.worlds.pop(); this.worlds.pop();
await this.$nextTick();
Tab.getOrCreateInstance(this.$refs.tabs[this.worldCount - 1]).show();
}, },
async playerNameUpdated(num) { async playerNameUpdated(num) {
await localforage.setItem(`${this.prefix}world_${num + 1}_setting_player_name`, this.worlds[num].player_name); await localforage.setItem(`${this.prefix}world_${num + 1}_setting_player_name`, this.worlds[num].player_name);
@@ -83,17 +89,18 @@ export default defineComponent({
<ul class="nav nav-tabs ps-3 pt-3 pe-3"> <ul class="nav nav-tabs ps-3 pt-3 pe-3">
<li v-for="(n, idx) in worldCount" class="nav-item" role="presentation"> <li v-for="(n, idx) in worldCount" class="nav-item" role="presentation">
<button class="nav-link" :class="{active: idx == 0}" data-bs-toggle="tab" <button class="nav-link" :class="{active: idx == 0}" data-bs-toggle="tab"
:data-bs-target="`#world_page_${n}`" type="button" role="tab"> :data-bs-target="`#world_page_${n}`" type="button" role="tab" ref="tabs">
<template v-if="worlds[idx] && worlds[idx].player_name && worlds[idx].player_name.length"> <template v-if="worlds[idx] && worlds[idx].player_name && worlds[idx].player_name.length">
{{ worlds[idx].player_name }} {{ worlds[idx].player_name }}
</template> </template>
<template v-else> <template v-else>
{{ n }} {{ n }}
</template> </template>
<button v-if="n == worldCount && n > 2" @click="removeWorld" <div v-if="n == worldCount && n > 2" class="delete-player-button">
class="badge btn rounded-pill text-bg-danger"> <button @click="removeWorld" class="badge btn rounded-pill text-bg-danger">
<i class="bi bi-trash"></i> <i class="bi bi-trash"></i>
</button> </button>
</div>
</button> </button>
</li> </li>
<button class="nav-link" type="button" role="tab" @click="addWorld"> <button class="nav-link" type="button" role="tab" @click="addWorld">
@@ -132,3 +139,10 @@ export default defineComponent({
</div> </div>
</div> </div>
</template> </template>
<style>
.delete-player-button {
display: inline;
margin-left: 1em;
}
</style>