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>
import { defineComponent } from 'vue';
import { defineComponent } from "vue";
import { mapState, mapActions } 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";
import usePresetStore from "@/stores/preset.js";
function settingsMatch(settings, preset) {
for (const settingName of Object.keys(settings)) {
if (settingName == "player_name") {
continue;
}
if (preset[settingName] != settings[settingName]) {
return false;
}
}
return true;
}
export default defineComponent({
data() {
return {
selected: "custom",
localPresets: [],
};
},
emits: [
@@ -19,79 +28,45 @@ export default defineComponent({
],
props: {
generator: null,
},
async mounted() {
this.localPresets = await localforage.getItem(`local_presets_${this.generator}`) ?? [];
for (const preset of this.localPresets) {
this.fillPreset(preset);
}
await this.updateLocalPresets();
prefix: null,
},
computed: {
settings() {
const settings = {};
for (const name of Object.keys(generatorSettings[this.generator])) {
var generatorValue = generatorSettings[this.generator][name]
if (generatorValue == "all") {
settings[name] = settingsData[name];
} 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;
...mapState(usePresetStore, {
allGlobalPresets: "globalPresets",
allLocalPresets: "localPresets",
}),
globalPresets() {
return this.allGlobalPresets[this.generator];
},
presets() {
const filledPresets = {};
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;
localPresets() {
return this.allLocalPresets[this.generator];
},
},
methods: {
...mapActions(usePresetStore, ["deleteLocalPreset", "saveLocalPreset"]),
change() {
if (this.selected && this.selected != "custom") {
if (this.selected.startsWith("local_")) {
this.$emit("selected", this.localPresets[this.selected.substring(6)]);
} 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) {
for (const presetName of Object.keys(this.presets)) {
const preset = this.presets[presetName];
if (this.settingsMatch(newSettings, preset)) {
for (const presetName of Object.keys(this.globalPresets)) {
const preset = this.globalPresets[presetName];
if (settingsMatch(newSettings, preset)) {
this.selected = presetName;
return;
}
}
for (const [idx, preset] of this.localPresets.entries()) {
if (this.settingsMatch(newSettings, preset)) {
this.selected = `local_${idx}`;
return;
if (this.localPresets) {
for (const [idx, preset] of this.localPresets.entries()) {
if (settingsMatch(newSettings, preset)) {
this.selected = `local_${idx}`;
return;
}
}
}
this.selected = "custom";
@@ -101,31 +76,12 @@ export default defineComponent({
},
async deleteClicked() {
const idx = this.selected.substring(6);
this.localPresets.splice(idx, 1);
this.selected = "custom";
await this.updateLocalPresets();
await this.deleteLocalPreset(this.generator, idk);
},
async savePreset(idx, settings) {
if (idx == null) {
idx = this.localPresets.length;
this.localPresets.push(settings);
} else {
this.localPresets[idx] = settings;
}
async savePreset(idx, preset) {
idx = await this.saveLocalPreset(this.generator, idx, preset);
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">
<option disabled="true" value="custom">Custom</option>
<optgroup label="Global Presets">
<template v-for="name of Object.keys(presets)">
<template v-for="name of Object.keys(globalPresets)">
<option :value="name">
{{ presets[name].display }}
{{ globalPresets[name].display }}
</option>
</template>
</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">
<option :value="`local_${idx}`">
{{ preset.display }}

View File

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

View File

@@ -1,4 +1,5 @@
import { createApp } from "vue";
import { createPinia } from "pinia";
import App from "./App.vue";
import router from "./router";
@@ -14,8 +15,10 @@ import "./assets/main.css";
import axios from "axios";
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 localforage from "localforage";
import { Modal } from "bootstrap";
import { Tab } from "bootstrap";
import SettingsPage from "@/components/SettingsPage.vue";
@@ -28,6 +28,7 @@ export default defineComponent({
for (var i = 0; i < this.worldCount; i++) {
this.worlds[i].player_name = await localforage.getItem(`${this.prefix}world_${i + 1}_setting_player_name`);
new Tab(this.$refs.tabs[i]);
}
},
methods: {
@@ -36,12 +37,17 @@ export default defineComponent({
this.worlds.push({player_name: newname});
this.worldCount++;
await this.$nextTick();
Tab.getOrCreateInstance(this.$refs.tabs[this.worldCount - 1]).show();
await new Promise(r => setTimeout(r, 100));
this.worlds[this.worldCount - 1].player_name = newname;
},
async removeWorld() {
this.worldCount--;
this.worlds.pop();
await this.$nextTick();
Tab.getOrCreateInstance(this.$refs.tabs[this.worldCount - 1]).show();
},
async playerNameUpdated(num) {
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">
<li v-for="(n, idx) in worldCount" class="nav-item" role="presentation">
<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">
{{ worlds[idx].player_name }}
</template>
<template v-else>
{{ n }}
</template>
<button v-if="n == worldCount && n > 2" @click="removeWorld"
class="badge btn rounded-pill text-bg-danger">
<i class="bi bi-trash"></i>
</button>
<div v-if="n == worldCount && n > 2" class="delete-player-button">
<button @click="removeWorld" class="badge btn rounded-pill text-bg-danger">
<i class="bi bi-trash"></i>
</button>
</div>
</button>
</li>
<button class="nav-link" type="button" role="tab" @click="addWorld">
@@ -132,3 +139,10 @@ export default defineComponent({
</div>
</div>
</template>
<style>
.delete-player-button {
display: inline;
margin-left: 1em;
}
</style>