diff --git a/src/components/Encounter/CreatureList.vue b/src/components/Encounter/CreatureList.vue index c1a85bc..a2b8239 100644 --- a/src/components/Encounter/CreatureList.vue +++ b/src/components/Encounter/CreatureList.vue @@ -27,7 +27,7 @@ const debouncedCall = debounce(async function () { } } } - const partyLevels = party.getParty; + const partyLevels = party.getActiveParty.members; const post = { enemy_levels: enemyLevels, party_levels: partyLevels diff --git a/src/components/Encounter/CreaturesTable/EncounterBuilder.vue b/src/components/Encounter/CreaturesTable/EncounterBuilder.vue index 511f533..4ae1405 100644 --- a/src/components/Encounter/CreaturesTable/EncounterBuilder.vue +++ b/src/components/Encounter/CreaturesTable/EncounterBuilder.vue @@ -47,7 +47,7 @@ const restoreSettings = () => { const generateEncounter = async () => { saveChanges(); - const partyLevels = party.getParty; + const partyLevels = party.getActiveParty.members; const post = { traits: tmpFilters.value.traits, alignments: tmpFilters.value.alignment, @@ -219,19 +219,20 @@ defineExpose({ generateEncounter }); - + + import { ref } from 'vue'; -import { biXLg, biPlusLg, biDashLg } from '@quasar/extras/bootstrap-icons'; +import { party } from 'src/types/party'; +import { biXLg, biPlusLg, biDashLg, biTrash } from '@quasar/extras/bootstrap-icons'; +import { matArrowDropDown } from '@quasar/extras/material-icons'; + import { partyStore } from 'stores/store'; const party = partyStore(); -const tmpParty = ref(party.getParty); +const tmpParty = ref({ + name: party.getActiveParty.name, + members: [...party.getActiveParty.members] +}); +const parties = ref(party.getParties.map((party) => party.name)); const dialog = ref(false); +const newPartyDialog = ref(false); +const partyNameInput = ref(); +const newPartyName = ref(''); + +const removePartyDialog = ref(false); + const restoreParty = () => { dialog.value = true; - tmpParty.value = [...party.getParty]; + tmpParty.value = { + name: party.getActiveParty.name, + members: [...party.getActiveParty.members] + }; }; const validateLevel = (index: number) => { - const value = tmpParty.value[index]; + const value = tmpParty.value.members[index]; if (value < 1) { - tmpParty.value[index] = 1; + tmpParty.value.members[index] = 1; } if (value > 20) { - tmpParty.value[index] = 20; + tmpParty.value.members[index] = 20; } }; const addPlayer = () => { - tmpParty.value.push(1); + tmpParty.value.members.push(1); }; const removePlayer = (index: number) => { - if (tmpParty.value.length > 1) { - tmpParty.value.splice(index, 1); + if (tmpParty.value.members.length > 1) { + tmpParty.value.members.splice(index, 1); } }; +const closeDialog = () => { + newPartyDialog.value = false; + removePartyDialog.value = false; + newPartyName.value = ''; +}; + +const addParty = () => { + partyNameInput.value.validate(); + if (!partyNameInput.value.hasError) { + party.addParty(newPartyName.value); + parties.value = party.getParties.map((party) => party.name); + tmpParty.value = { + name: party.getActiveParty.name, + members: [...party.getActiveParty.members] + }; + saveChanges(); + newPartyName.value = ''; + newPartyDialog.value = false; + } +}; + +const removeParty = () => { + party.removeParty(); + parties.value = party.getParties.map((party) => party.name); + tmpParty.value = { + name: party.getActiveParty.name, + members: [...party.getActiveParty.members] + }; + saveChanges(); + removePartyDialog.value = false; +}; + +const changeActiveParty = (selected: string) => { + party.changeActiveParty(party.getPartyIndex(selected)); + tmpParty.value = { + name: party.getActiveParty.name, + members: [...party.getActiveParty.members] + }; +}; + const saveChanges = () => { - party.updateParty(tmpParty.value); - localStorage.setItem('party', JSON.stringify(tmpParty.value)); + party.updateParty(tmpParty.value.name, tmpParty.value.members); + localStorage.setItem('parties', JSON.stringify(party.getParties)); }; @@ -43,26 +99,129 @@ const saveChanges = () => { - -
Party Builder
- - + +
+
Party Builder
+ + +
+
+ + + + + + +
New party name
+
+ + + + + + + + + +
+
+ + + + + +
Remove this party?
+
+ + + + +
+
+
-
+
{ min="1" max="20" :label="'Player ' + (index + 1)" - v-model.number="tmpParty[index]" + v-model.number="tmpParty.members[index]" @update:model-value="validateLevel(index)" dense /> @@ -109,7 +268,7 @@ const saveChanges = () => { + + diff --git a/src/pages/EncounterPage.vue b/src/pages/EncounterPage.vue index 90c0b37..05fe533 100644 --- a/src/pages/EncounterPage.vue +++ b/src/pages/EncounterPage.vue @@ -2,6 +2,7 @@ import { shallowRef } from 'vue'; import { requestCreatures, requestFilters } from 'src/utils/api-calls'; import { partyStore, filtersStore, creaturesStore, encounterStore } from 'stores/store'; +import { party } from 'src/types/party'; import { creature } from 'src/types/creature'; import CreatureList from 'src/components/Encounter/CreatureList.vue'; @@ -11,19 +12,41 @@ const creatures = creaturesStore(); const encounter = encounterStore(); const currentComponent = shallowRef(); -const localParty = localStorage.getItem('party'); +const localParty = localStorage.getItem('parties'); if (localParty) { try { - const parsedParty: number[] = JSON.parse(localParty); - if (parsedParty && parsedParty.every((player) => player >= 1 && player <= 20)) { - party.updateParty(parsedParty); + const parsedParties = JSON.parse(localParty); + if (Array.isArray(parsedParties)) { + const isCompatible = parsedParties.every((p) => { + return ( + typeof p.name === 'string' && + Array.isArray(p.members) && + p.members.every((member: any) => typeof member === 'number') + ); + }); + if (isCompatible) { + const parties: party[] = parsedParties; + parties.forEach((p) => { + if (!p || !p.members.every((player) => player >= 1 && player <= 20)) { + throw 'Invalid saved party levels'; + } + }); + const partyNames = parties.map((p) => p.name); + if (new Set(partyNames).size !== partyNames.length) { + throw 'Duplicate saved party names'; + } + party.updateParties(parties); + } else { + throw 'Invalid saved party format'; + } } else { - throw 'Invalid saved party'; + throw 'Invalid saved party format'; } - } catch (_) { - console.error('Invalid saved party'); - localStorage.setItem('party', JSON.stringify([1, 1, 1, 1])); - party.updateParty([1, 1, 1, 1]); + } catch (error) { + console.error(error); + const defaultParty = { name: 'Default', members: [1, 1, 1, 1] }; + localStorage.setItem('parties', JSON.stringify([defaultParty])); + party.updateParties([defaultParty]); } } @@ -119,7 +142,8 @@ const steps = [ }, { target: '#v-step-1', - content: 'You can change your party size and the level of the individual players here.', + content: + 'Here you can change your party size and the level of the individual players. You can also add multiple parties and select the active one.', params: { placement: 'bottom' } @@ -135,14 +159,14 @@ const steps = [ { target: '#v-step-3', content: - 'Clicking this button will generate a new random encounter, using the settings previously described.', + 'Clicking this button will generate a new random encounter, based on the generator settings previously described and the currently active party.', params: { placement: 'bottom' } }, { target: '#v-step-4', - content: 'From this dropdown you can select which colums to show and hide.', + content: 'From this dropdown you can select which columns of the table to show and hide.', params: { placement: 'bottom' } @@ -157,7 +181,7 @@ const steps = [ { target: '#v-step-6', content: - 'This is the encounter list, where the creatures you added will be displayed. You can increase or decrease the number of creatures and change them to their Weak/Elite variant.', + 'This is the encounter list, where the creatures you added will be displayed. You can increase or decrease the number of each creature and change them to their Weak/Elite variant.', params: { placement: 'auto' } diff --git a/src/stores/store.ts b/src/stores/store.ts index 303aa73..a0148c0 100644 --- a/src/stores/store.ts +++ b/src/stores/store.ts @@ -1,21 +1,48 @@ +import { party } from 'src/types/party'; import { creature } from 'src/types/creature'; import { encounter } from 'src/types/encounter'; import { defineStore } from 'pinia'; export const partyStore = defineStore('party', { - state: () => ({ party: [1, 1, 1, 1] }), + state: () => ({ + parties: [{ name: 'Default', members: [1, 1, 1, 1] }] as party[], + activeParty: 0 + }), getters: { - getParty: (state) => state.party + getParties: (state) => state.parties, + getActive: (state) => state.activeParty, + getActiveParty: (state) => state.parties[state.activeParty] }, actions: { - updateParty(newParty: number[]) { - this.party = newParty; + getPartyIndex(partyName: string): number { + return this.parties.map((party) => party.name).indexOf(partyName); }, - addPlayer() { - this.party.push(1); + updateParty(partyName: string, newMembers: number[]) { + const partyIndex = this.getPartyIndex(partyName); + if (partyIndex >= 0) { + this.parties[partyIndex].members = newMembers; + } }, - removePlayer(index: number) { - this.party.splice(index, 1); + updateParties(newParties: party[]) { + this.parties = newParties; + }, + changeActiveParty(partyIndex: number) { + if (partyIndex >= this.parties.length || partyIndex < 0) { + this.activeParty = 0; + } else { + this.activeParty = partyIndex; + } + }, + addParty(partyName: string) { + this.parties.push({ name: partyName, members: [1, 1, 1, 1] }); + this.activeParty = this.parties.length - 1; + }, + removeParty() { + this.parties.splice(this.activeParty, 1); + this.activeParty = 0; + if (this.parties.length <= 0) { + this.parties = [{ name: 'Default', members: [1, 1, 1, 1] }]; + } } } }); diff --git a/src/types/party.ts b/src/types/party.ts new file mode 100644 index 0000000..46736d1 --- /dev/null +++ b/src/types/party.ts @@ -0,0 +1,4 @@ +export type party = { + name: string; + members: number[]; +};