Add proper edit functionality support

This commit is contained in:
Lordmau5 2023-11-19 18:47:56 +01:00
parent fd4adedcb3
commit fcbdfd038f
16 changed files with 1433 additions and 392 deletions

View File

@ -198,6 +198,31 @@ module.exports = {
lang: 'ts' lang: 'ts'
} }
} }
],
'vue/max-attributes-per-line': [
'error',
{
singleline: {
max: 2
},
multiline: {
max: 1
}
}
],
'vue/first-attribute-linebreak': [
'error',
{
singleline: 'beside',
multiline: 'below'
}
],
'vue/html-closing-bracket-newline': [
'error',
{
singleline: 'never',
multiline: 'always'
}
] ]
} }
}; };

7
components.d.ts vendored
View File

@ -7,13 +7,18 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
copy: typeof import('./src/composables/EditGoalDialog copy.vue')['default']
EditGoal: typeof import('./src/composables/EditGoal.vue')['default'] EditGoal: typeof import('./src/composables/EditGoal.vue')['default']
EditGoalDialog: typeof import('./src/composables/EditGoalDialog.vue')['default'] EditGoalDialog: typeof import('./src/composables/EditGoalDialog.vue')['default']
EditGoalListDialog: typeof import('./src/composables/EditGoalListDialog.vue')['default']
EditorComponent: typeof import('./src/components/EditorComponent.vue')['default'] EditorComponent: typeof import('./src/components/EditorComponent.vue')['default']
GameEditor: typeof import('./src/composables/GameEditor.vue')['default']
GameEditorDialog: typeof import('./src/composables/GameEditorDialog.vue')['default']
GameList: typeof import('./src/composables/GameList.vue')['default'] GameList: typeof import('./src/composables/GameList.vue')['default']
GameListDialog__UNUSED_NEEDS_EDITS: typeof import('./src/composables/GameListDialog__UNUSED_NEEDS_EDITS.vue')['default']
GeneratorComponent: typeof import('./src/components/GeneratorComponent.vue')['default'] GeneratorComponent: typeof import('./src/components/GeneratorComponent.vue')['default']
GoalEditorDialog: typeof import('./src/components/GoalEditorDialog.vue')['default'] GoalEditorDialog: typeof import('./src/components/GoalEditorDialog.vue')['default']
MarkdownRenderer: typeof import('./src/components/MarkdownRenderer.vue')['default'] MarkdownRenderer: typeof import('./src/composables/MarkdownRenderer.vue')['default']
NavbarComponent: typeof import('./src/components/NavbarComponent.vue')['default'] NavbarComponent: typeof import('./src/components/NavbarComponent.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']

View File

@ -22,11 +22,20 @@
<q-separator /> <q-separator />
<q-card-actions> <q-card-actions>
<q-btn flat color="green" label="Add" /> <q-btn
flat
color="green"
label="Add"
/>
<q-space/> <q-space/>
<q-btn flat color="blue" icon="upload" label="Import" /> <q-btn
flat
color="blue"
icon="upload"
label="Import"
/>
</q-card-actions> </q-card-actions>
</q-card> </q-card>
</div> </div>
@ -37,16 +46,29 @@
<div class="col-12"> <div class="col-12">
<div class="row items-center justify-evenly"> <div class="row items-center justify-evenly">
<div class="col-4"> <div class="col-4">
<q-input filled v-model="search" label="Search for games..." /> <q-input
filled
v-model="search"
label="Search for games..."
/>
</div> </div>
</div> </div>
</div> </div>
<div class="col" v-for="(game, index) of filtered_games" :key="game.id" :style="{ 'max-width': '500px' }"> <div
class="col"
v-for="(game, index) of filtered_games"
:key="game.id"
:style="{ 'max-width': '500px' }"
>
<q-card flat bordered> <q-card flat bordered>
<q-card-section horizontal> <q-card-section horizontal>
<q-card-section> <q-card-section>
<q-badge v-if="game.is_local" outline color="orange"> <q-badge
v-if="game.is_local"
outline
color="orange"
>
<div class="text-weight-bold q-my-xs">Local Game</div> <div class="text-weight-bold q-my-xs">Local Game</div>
</q-badge> </q-badge>
@ -72,18 +94,34 @@
<q-separator /> <q-separator />
<q-card-actions> <q-card-actions>
<q-btn flat icon="help" @click="selected_game = game"> <q-btn
flat
icon="help"
@click="learnMore(game)"
>
<q-tooltip> <q-tooltip>
Learn more... Learn more...
</q-tooltip> </q-tooltip>
</q-btn> </q-btn>
<template v-if="game.is_local"> <template v-if="game.is_local">
<q-btn flat icon="edit" color="green" :loading="loading"> <q-btn
flat
icon="edit"
color="green"
:loading="loading"
@click="editGame(game)"
>
<q-tooltip> <q-tooltip>
Edit Edit
</q-tooltip> </q-tooltip>
</q-btn> </q-btn>
<q-btn flat icon="delete" color="red" :loading="loading" @click="deleteGameInternal(game.id)"> <q-btn
flat
icon="delete"
color="red"
:loading="loading"
@click="deleteGameInternal(game.id)"
>
<q-tooltip> <q-tooltip>
Delete Delete
</q-tooltip> </q-tooltip>
@ -91,10 +129,22 @@
<q-space/> <q-space/>
<q-btn flat color="blue" icon="download" label="Export" /> <q-btn
flat
color="blue"
icon="download"
label="Export"
/>
</template> </template>
<template v-else> <template v-else>
<q-btn flat icon="content_copy" color="green" :loading="loading" :disabled="localGames.hasGame(game.id)" @click="saveGameInternal(game)"> <q-btn
flat
icon="content_copy"
color="green"
:loading="loading"
:disabled="localGames.hasGame(game.id)"
@click="cloneGame(game)"
>
<q-tooltip> <q-tooltip>
Copy to local Copy to local
</q-tooltip> </q-tooltip>
@ -110,7 +160,11 @@
<q-card style="min-width: 700px; max-width: 80vw; min-height: 10vh; max-height: 80vh;"> <q-card style="min-width: 700px; max-width: 80vw; min-height: 10vh; max-height: 80vh;">
<q-card-section class="row items-start q-col-gutter-md"> <q-card-section class="row items-start q-col-gutter-md">
<div class="col-2"> <div class="col-2">
<q-avatar square size="200px" style="width: 100%; height: auto; aspect-ratio: 1/1;"> <q-avatar
square
size="200px"
style="width: 100%; height: auto; aspect-ratio: 1/1;"
>
<q-img <q-img
src="https://picsum.photos/200/200" src="https://picsum.photos/200/200"
width="100%" width="100%"
@ -131,6 +185,18 @@
</q-card-section> </q-card-section>
</q-card> </q-card>
</q-dialog> </q-dialog>
<q-dialog
v-model="edit_game"
persistent
>
<GameEditorDialog
v-if="selected_game"
:game="selected_game"
@cancel="edit_game = false"
@save-game="updateGame"
/>
</q-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -154,12 +220,31 @@ onMounted(async() => {
await localGames.fetchGames(); await localGames.fetchGames();
}); });
async function saveGameInternal(game: BingoGame) { const edit_game = ref(false);
function editGame(game: BingoGame) {
loading.value = true; loading.value = true;
localGames.addGame(game); selected_game.value = game;
// await saveGame(game); edit_game.value = true;
// local_games.value = await getLocalGames();
loading.value = false;
}
async function updateGame(game: BingoGame) {
loading.value = true;
await localGames.updateGame(game);
edit_game.value = false;
loading.value = false;
}
async function cloneGame(game: BingoGame) {
loading.value = true;
await localGames.addGame(game);
loading.value = false; loading.value = false;
} }
@ -167,7 +252,7 @@ async function saveGameInternal(game: BingoGame) {
async function deleteGameInternal(id: string) { async function deleteGameInternal(id: string) {
loading.value = true; loading.value = true;
localGames.deleteGame(id); await localGames.deleteGame(id);
loading.value = false; loading.value = false;
} }
@ -193,15 +278,8 @@ const filtered_games = computed(() => {
return combined_games.value.filter(game => game.name.toLocaleLowerCase().includes(search.value.toLocaleLowerCase())); return combined_games.value.filter(game => game.name.toLocaleLowerCase().includes(search.value.toLocaleLowerCase()));
}); });
// On selected game change, open popup function learnMore(game: BingoGame) {
watch(selected_game, newGame => { selected_game.value = game;
if (newGame) learn_more.value = true;
learn_more.value = true; }
});
// On popup close, reset selected game
watch(learn_more, newValue => {
if (!newValue)
selected_game.value = undefined;
});
</script> </script>

View File

@ -9,9 +9,22 @@
> >
<template v-slot:header-goal="prop"> <template v-slot:header-goal="prop">
<div class="row items-center"> <div class="row items-center">
<q-btn v-if="editMode" icon="edit" flat round size="sm" color="orange"/> <q-btn
v-if="editMode"
icon="edit"
flat
round
size="sm"
color="orange"
/>
<div>{{ prop.node.label }}</div> <div>{{ prop.node.label }}</div>
<q-badge outline v-for="tag in prop.node.goal.tags" :key="tag" :color="getColorForTag(tag)" class="q-ml-sm"> <q-badge
outline
v-for="tag in prop.node.goal.tags"
:key="tag"
:color="getColorForTag(tag)"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">{{ tag }}</div> <div class="text-weight-bold q-my-xs">{{ tag }}</div>
</q-badge> </q-badge>
</div> </div>
@ -19,9 +32,20 @@
<template v-slot:header-goal-list="prop"> <template v-slot:header-goal-list="prop">
<div class="row items-center"> <div class="row items-center">
<q-btn v-if="editMode" icon="edit" flat round size="sm" color="orange"/> <q-btn
v-if="editMode"
icon="edit"
flat
round
size="sm"
color="orange"
/>
<div>{{ prop.node.label }}</div> <div>{{ prop.node.label }}</div>
<q-badge outline color="orange" class="q-ml-sm"> <q-badge
outline
color="orange"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">Goal List</div> <div class="text-weight-bold q-my-xs">Goal List</div>
</q-badge> </q-badge>
</div> </div>
@ -29,9 +53,20 @@
<template v-slot:header-category="prop"> <template v-slot:header-category="prop">
<div class="row items-center"> <div class="row items-center">
<q-btn v-if="editMode" icon="edit" flat round size="sm" color="orange"/> <q-btn
v-if="editMode"
icon="edit"
flat
round
size="sm"
color="orange"
/>
<div>{{ prop.node.label }}</div> <div>{{ prop.node.label }}</div>
<q-badge outline color="blue" class="q-ml-sm"> <q-badge
outline
color="blue"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">Category</div> <div class="text-weight-bold q-my-xs">Category</div>
</q-badge> </q-badge>
</div> </div>
@ -40,21 +75,39 @@
<!-- Add Goal --> <!-- Add Goal -->
<template v-slot:header-add-goal="prop"> <template v-slot:header-add-goal="prop">
<div class="row items-center"> <div class="row items-center">
<q-btn outline icon="add" label="Goal" color="light-blue" size="sm"></q-btn> <q-btn
outline
icon="add"
label="Goal"
color="light-blue"
size="sm"
></q-btn>
</div> </div>
</template> </template>
<!-- Add Goal List --> <!-- Add Goal List -->
<template v-slot:header-add-goal-list="prop"> <template v-slot:header-add-goal-list="prop">
<div class="row items-center"> <div class="row items-center">
<q-btn outline icon="add" label="Goal List" color="light-blue" size="sm"></q-btn> <q-btn
outline
icon="add"
label="Goal List"
color="light-blue"
size="sm"
></q-btn>
</div> </div>
</template> </template>
<!-- Add Category --> <!-- Add Category -->
<template v-slot:header-add-category="prop"> <template v-slot:header-add-category="prop">
<div class="row items-center"> <div class="row items-center">
<q-btn outline icon="add" label="Category" color="light-blue" size="sm"></q-btn> <q-btn
outline
icon="add"
label="Category"
color="light-blue"
size="sm"
></q-btn>
</div> </div>
</template> </template>
</q-tree> </q-tree>
@ -126,8 +179,6 @@ const selectByTag = (tag: string) => {
ticked.value.push(...nodes.value.filter(node => { ticked.value.push(...nodes.value.filter(node => {
return getAllGoals(node).map(goal => goal.tags.includes(tag)); return getAllGoals(node).map(goal => goal.tags.includes(tag));
})); }));
console.log(ticked.value);
}; };
const nodes = computed(() => { const nodes = computed(() => {

View File

@ -1,6 +1,10 @@
<template> <template>
<q-card flat bordered style="min-width: 700px; max-width: 80vw; min-height: 10vh; max-height: 80vh;"> <q-card
flat
bordered
style="min-width: 700px; max-width: 80vw; min-height: 10vh; max-height: 80vh;"
>
<q-card-section class="q-gutter-y-md column"> <q-card-section class="q-gutter-y-md column">
<q-input <q-input
v-model="goal_name" v-model="goal_name"
@ -13,6 +17,28 @@
type="text" type="text"
></q-input> ></q-input>
<q-input
class="col-10"
v-model="goal_description"
square
filled
counter
autogrow
maxlength="500"
label="Description"
hint="Optional"
type="text"
>
<template v-slot:after>
<q-btn
label="Preview"
outline
color="green"
@click="markdown_preview = true"
/>
</template>
</q-input>
<q-select <q-select
label="Select Tags" label="Select Tags"
square square
@ -22,6 +48,7 @@
use-chips use-chips
multiple multiple
clearable clearable
hint="Optional"
hide-dropdown-icon hide-dropdown-icon
input-debounce="0" input-debounce="0"
:options="all_tags" :options="all_tags"
@ -32,14 +59,46 @@
<q-separator /> <q-separator />
<q-card-actions> <q-card-actions>
<q-btn v-if="data.goal" flat color="red" icon="delete" :label="delete_label" @click="deleteGoal()" :disabled="!delete_enabled" /> <q-btn
v-if="data.goal"
flat
color="red"
icon="delete"
:label="delete_label"
@click="deleteGoal()"
:disabled="!delete_enabled"
/>
<q-space/> <q-space/>
<q-btn flat color="red" icon="cancel" label="Cancel" @click="cancel()" /> <q-btn
<q-btn flat color="green" icon="save" label="Save" @click="emitGoal()" :disabled="!canSave()"/> flat
color="red"
icon="cancel"
label="Cancel"
@click="cancel()"
/>
<q-btn
flat
color="green"
icon="save"
label="Save"
@click="saveGoal()"
:disabled="!canSave()"
/>
</q-card-actions> </q-card-actions>
</q-card> </q-card>
<q-dialog v-model="markdown_preview">
<q-card
flat
bordered
>
<q-card-section>
<MarkdownRenderer :text="goal_description"/>
</q-card-section>
</q-card>
</q-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -47,13 +106,12 @@ import BingoGoal from '@/js/lib/BingoGoal.ts';
import { import {
stringCompare stringCompare
} from '@/js/lib/Util.ts'; } from '@/js/lib/Util.ts';
import type BingoCategory from '@/js/lib/BingoCategory.ts';
import type BingoGoalList from '@/js/lib/BingoGoalList.ts';
const emit = defineEmits([ const emit = defineEmits([
'cancel', 'cancel',
'deleteGoal', 'createGoal',
'emitGoal' 'updateGoal',
'deleteGoal'
]); ]);
const { const {
@ -61,16 +119,14 @@ const {
} = defineProps<{ } = defineProps<{
data: { data: {
all_tags: string[], all_tags: string[],
reserved_names: string[],
goal?: BingoGoal, goal?: BingoGoal,
}, },
}>(); }>();
const goal_name = ref('');
const all_tags: Ref<string[]> = ref([ ...data.all_tags ]); const all_tags: Ref<string[]> = ref([ ...data.all_tags ]);
const goal_tags: Ref<string[]> = ref([ ]);
goal_name.value = data.goal?.name ?? ''; const markdown_preview = ref(false);
goal_tags.value = data.goal?.tags ?? [];
const delete_label = ref('Delete (5)'); const delete_label = ref('Delete (5)');
const delete_enabled = ref(false); const delete_enabled = ref(false);
@ -91,8 +147,17 @@ onMounted(() => {
}, 1000); }, 1000);
}); });
const goal_name = ref('');
const goal_description = ref('');
const goal_tags: Ref<string[]> = ref([ ]);
goal_name.value = data.goal?.name ?? '';
goal_description.value = data.goal?.description ?? '';
goal_tags.value = data.goal?.tags ?? [];
function canSave() { function canSave() {
return goal_name.value?.length > 0; return goal_name.value?.length > 0
&& !data.reserved_names.some(name => name !== data.goal?.name && stringCompare(name, goal_name.value));
} }
function addTag(value: string, done: Function) { function addTag(value: string, done: Function) {
@ -111,24 +176,29 @@ function addTag(value: string, done: Function) {
} }
done(value, 'toggle'); done(value, 'toggle');
console.log('All tags', all_tags);
console.log('Goal tags', goal_tags);
}
function deleteGoal() {
emit('deleteGoal', data.goal);
} }
function cancel() { function cancel() {
emit('cancel'); emit('cancel');
} }
function emitGoal() { function saveGoal() {
const goal = new BingoGoal(goal_name.value); const goal = new BingoGoal(goal_name.value);
goal.description = goal_description.value;
goal.tags = goal_tags.value; goal.tags = goal_tags.value;
emit('emitGoal', goal); // Create new goal
if (!data.goal) {
emit('createGoal', goal);
}
// Update existing goal
else {
emit('updateGoal', data.goal, goal);
}
}
function deleteGoal() {
emit('deleteGoal', data.goal);
} }
</script> </script>

View File

@ -0,0 +1,204 @@
<template>
<q-card
flat
bordered
style="min-width: 700px; max-width: 80vw; min-height: 10vh; max-height: 80vh;"
>
<q-card-section class="q-gutter-y-md column">
<q-input
v-model="goal_name"
square
filled
counter
clearable
maxlength="40"
label="Name"
type="text"
></q-input>
<q-input
class="col-10"
v-model="goal_description"
square
filled
counter
autogrow
maxlength="500"
label="Description"
hint="Optional"
type="text"
>
<template v-slot:after>
<q-btn
label="Preview"
outline
color="green"
@click="markdown_preview = true"
/>
</template>
</q-input>
<q-select
label="Select Tags"
square
filled
v-model="goal_tags"
use-input
use-chips
multiple
clearable
hint="Optional"
hide-dropdown-icon
input-debounce="0"
:options="all_tags"
@new-value="addTag"
></q-select>
</q-card-section>
<q-separator />
<q-card-actions>
<q-btn
v-if="data.goal"
flat
color="red"
icon="delete"
:label="delete_label"
@click="deleteGoal()"
:disabled="!delete_enabled"
/>
<q-space/>
<q-btn
flat
color="red"
icon="cancel"
label="Cancel"
@click="cancel()"
/>
<q-btn
flat
color="green"
icon="save"
label="Save"
@click="saveGoal()"
:disabled="!canSave()"
/>
</q-card-actions>
</q-card>
<q-dialog v-model="markdown_preview">
<q-card
flat
bordered
>
<q-card-section>
<MarkdownRenderer :text="goal_description"/>
</q-card-section>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import BingoGoal from '@/js/lib/BingoGoal.ts';
import {
stringCompare
} from '@/js/lib/Util.ts';
const emit = defineEmits([
'cancel',
'createGoal',
'updateGoal',
'deleteGoal'
]);
const {
data
} = defineProps<{
data: {
all_tags: string[],
reserved_names: string[],
goal?: BingoGoal,
},
}>();
const all_tags: Ref<string[]> = ref([ ...data.all_tags ]);
const markdown_preview = ref(false);
const delete_label = ref('Delete (5)');
const delete_enabled = ref(false);
onMounted(() => {
let countdown = 5;
let interval = setInterval(() => {
if (--countdown <= 0) {
clearInterval(interval);
delete_label.value = 'Delete';
delete_enabled.value = true;
return;
}
else {
delete_label.value = `Delete (${ countdown })`;
}
}, 1000);
});
const goal_name = ref('');
const goal_description = ref('');
const goal_tags: Ref<string[]> = ref([ ]);
goal_name.value = data.goal?.name ?? '';
goal_description.value = data.goal?.description ?? '';
goal_tags.value = data.goal?.tags ?? [];
function canSave() {
return goal_name.value?.length > 0
&& !data.reserved_names.some(name => name !== data.goal?.name && stringCompare(name, goal_name.value));
}
function addTag(value: string, done: Function) {
// Exit if tag already exists
if (goal_tags.value.some(tag => stringCompare(tag, value))) {
done();
return;
}
const tag = all_tags.value.find(tag => stringCompare(tag, value));
value = tag ?? value;
if (!data.all_tags.some(tag => stringCompare(tag, value))) {
all_tags.value.push(value);
}
done(value, 'toggle');
}
function cancel() {
emit('cancel');
}
function saveGoal() {
const goal = new BingoGoal(goal_name.value);
goal.description = goal_description.value;
goal.tags = goal_tags.value;
// Create new goal
if (!data.goal) {
emit('createGoal', goal);
}
// Update existing goal
else {
emit('updateGoal', data.goal, goal);
}
}
function deleteGoal() {
emit('deleteGoal', data.goal);
}
</script>

View File

@ -0,0 +1,517 @@
<template>
<q-card>
<q-card-section>
<q-tree
:nodes="nodes"
node-key="label"
v-model:expanded="expanded"
>
<template v-slot:header-goal="prop">
<div class="row items-center">
<q-btn
icon="edit"
flat
round
size="sm"
color="orange"
@click="openEditGoalDialog(prop.node)"
/>
<div>{{ prop.node.item?.name || prop.node.label }}</div>
<q-btn
v-if="prop.node.goal?.description?.length"
icon="help_outline"
flat
round
size="sm"
@click="openInfoDialog(prop.node.goal.description)"
></q-btn>
<q-badge
outline
v-for="tag in prop.node.goal.tags"
:key="tag"
:color="getColorForString(tag)"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">{{ tag }}</div>
</q-badge>
</div>
</template>
<template v-slot:header-goal-list="prop">
<div class="row items-center">
<q-btn
icon="edit"
flat
round
size="sm"
color="orange"
@click="event => openEditGoalListDialog(prop.node, event)"
/>
<div>{{ prop.node.item?.name || prop.node.label }}</div>
<q-badge
outline
color="orange"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">Goal List</div>
</q-badge>
</div>
</template>
<template v-slot:header-category="prop">
<div class="row items-center">
<q-btn
icon="edit"
flat
round
size="sm"
color="orange"
@click="event => openEditCategoryDialog(prop.node, event)"
/>
<div>{{ prop.node.item?.name || prop.node.label }}</div>
<q-badge
outline
color="blue"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">Category</div>
</q-badge>
</div>
</template>
<!-- Add Goal -->
<template v-slot:header-add-goal="prop">
<div class="row items-center">
<q-btn
outline
icon="add"
label="Goal"
color="light-blue"
size="sm"
@click="openEditGoalDialog(prop.node)"
></q-btn>
</div>
</template>
<!-- Add Goal List -->
<template v-slot:header-add-goal-list="prop">
<div class="row items-center">
<q-btn
outline
icon="add"
label="Goal List"
color="light-blue"
size="sm"
@click="event => openEditGoalListDialog(prop.node, event)"
></q-btn>
</div>
</template>
<!-- Add Category -->
<template v-slot:header-add-category="prop">
<div class="row items-center">
<q-btn
outline
icon="add"
label="Category"
color="light-blue"
size="sm"
@click="event => openEditCategoryDialog(prop.node, event)"
></q-btn>
</div>
</template>
</q-tree>
</q-card-section>
<q-separator />
<q-card-actions>
<q-space/>
<q-btn
flat
color="red"
icon="cancel"
label="Cancel"
:loading="loading"
@click="cancel"
/>
<q-btn
flat
color="green"
icon="save"
label="Save"
:loading="loading"
@click="saveGame"
/>
</q-card-actions>
</q-card>
<q-dialog v-model="info_dialog">
<q-card
flat
bordered
>
<q-card-section>
<MarkdownRenderer :text="info_dialog_text"/>
</q-card-section>
</q-card>
</q-dialog>
<q-dialog v-model="edit_goal_dialog" persistent>
<EditGoalDialog
:data="edit_goal_data"
@cancel="edit_goal_dialog = false"
@create-goal="create_goal"
@update-goal="update_goal"
@delete-goal="delete_goal"
/>
</q-dialog>
<!-- <q-dialog v-model="edit_goal_list_dialog" persistent>
<EditGoalListDialog
:data="edit_goal_list_data"
@cancel="edit_goal_list_dialog = false"
@delete-goal-list="delete_goal_list"
@emit-goal-list="emit_goal_list"
/>
</q-dialog> -->
</template>
<script setup lang="ts">
interface Node {
label: string;
item?: BingoGoal | BingoGoalList | BingoCategory;
header?: string; // 'goal' | 'goal-list' | 'category' | 'add-goal' | 'add-goal-list' | 'add-category'
children?: Node[];
goal?: BingoGoal;
parent?: BingoCategory | BingoGoalList;
}
import {
getColorForString
} from '@/js/lib/Util.ts';
import BingoCategory from '@/js/lib/BingoCategory.ts';
import type BingoGame from '@/js/lib/BingoGame.ts';
import BingoGoal from '@/js/lib/BingoGoal.ts';
import BingoGoalList from '@/js/lib/BingoGoalList.ts';
import Parser from '@/js/lib/Parser.ts';
const props = defineProps<{
game: BingoGame;
}>();
const emit = defineEmits([
'cancel',
'saveGame'
]);
const game = ref(Parser.getCopy(props.game));
game.value.is_local = true;
const expanded = ref<Node[]>([]);
const loading = ref(false);
function cancel() {
loading.value = true;
emit('cancel');
}
function saveGame() {
loading.value = true;
emit('saveGame', game.value);
}
/* Info Dialog */
const info_dialog = ref(false);
const info_dialog_text = ref('');
function openInfoDialog(text: string) {
info_dialog_text.value = text;
info_dialog.value = true;
}
/* End Info Dialog */
/* Edit Goal Dialog */
interface EditGoalData {
goal?: BingoGoal;
all_tags: string[];
reserved_names: string[];
parent_group: BingoCategory | BingoGoalList | undefined;
}
const edit_goal_data = ref<EditGoalData>({
all_tags: [] as string[],
reserved_names: [] as string[],
parent_group: undefined
});
const edit_goal_dialog = ref(false);
const openEditGoalDialog = (node: Node) => {
edit_goal_data.value.goal = node.goal;
edit_goal_data.value.reserved_names = game.value?.reserved_names ?? [];
edit_goal_data.value.parent_group = node.parent;
edit_goal_data.value.all_tags = game.value?.getAllTags() ?? [];
edit_goal_dialog.value = true;
};
const create_goal = (goal: BingoGoal) => {
// We always assume parent group is a goal list. You can't add goals to categories.
const goal_list = edit_goal_data.value.parent_group as BingoGoalList;
goal_list.goals.push(goal);
// Reset values
edit_goal_data.value = {
goal: undefined,
all_tags: [],
reserved_names: [],
parent_group: undefined
};
edit_goal_dialog.value = false;
};
const update_goal = (old_goal: BingoGoal, goal: BingoGoal) => {
// We always assume parent group is a goal list. You can't add goals to categories.
const goal_list = edit_goal_data.value.parent_group as BingoGoalList;
const index = goal_list.goals.findIndex(g => g === old_goal);
goal_list.goals.splice(index, 1, goal);
// Reset values
edit_goal_data.value = {
goal: undefined,
all_tags: [],
reserved_names: [],
parent_group: undefined
};
edit_goal_dialog.value = false;
};
const delete_goal = (goal: BingoGoal) => {
// We always assume parent group is a goal list. You can't add goals to categories.
const goal_list = edit_goal_data.value.parent_group as BingoGoalList;
const index = goal_list.goals.findIndex(g => g === goal);
goal_list.goals.splice(index, 1);
// Reset values
edit_goal_data.value = {
goal: undefined,
all_tags: [],
reserved_names: [],
parent_group: undefined
};
edit_goal_dialog.value = false;
};
/* End Edit Goal Dialog */
/* Edit Goal List Dialog */
interface EditGoalListData {
goal_list?: BingoGoalList;
parent_group: BingoCategory | undefined;
}
const edit_goal_list_data = ref<EditGoalListData>({
parent_group: undefined
});
const edit_goal_list_dialog = ref(false);
function openEditGoalListDialog(node: Node, event: Event) {
// Stop expanding of the tree
event.stopPropagation();
edit_goal_list_data.value.goal_list = node.item as BingoGoalList;
edit_goal_list_data.value.parent_group = node.parent as BingoCategory;
edit_goal_list_dialog.value = true;
}
const emit_goal_list = (goal_list: BingoGoalList) => {
// We always assume parent group is a category. You can't add goal lists to goal lists.
const category = edit_goal_list_data.value.parent_group as BingoCategory;
// Add new goal list
if (!edit_goal_list_data.value.goal_list) {
category.goal_lists.push(goal_list);
}
// Replace existing goal list
else {
const index = category.goal_lists.findIndex(g => g === goal_list);
category.goal_lists.splice(index, 1, goal_list);
}
// Reset values
edit_goal_list_data.value = {
parent_group: undefined
};
edit_goal_list_dialog.value = false;
};
const delete_goal_list = (goal_list: BingoGoalList) => {
// Prevent deletion if goal list still has goals
if (!goal_list.goals.length) {
// We always assume parent group is a category. You can't add goal lists to goal lists.
const category = edit_goal_list_data.value.parent_group as BingoCategory;
const index = category.goal_lists.findIndex(g => g === goal_list);
category.goal_lists.splice(index, 1);
}
// Reset values
edit_goal_list_data.value = {
parent_group: undefined
};
edit_goal_list_dialog.value = false;
};
/* End Edit Goal List Dialog */
/* Edit Category Dialog */
interface EditCategoryData {
category?: BingoCategory;
}
const edit_category_data = ref<EditCategoryData>({
});
const edit_category_dialog = ref(false);
function openEditCategoryDialog(node: Node, event: Event) {
// Stop expanding of the tree
event.stopPropagation();
edit_category_data.value.category = node.item as BingoCategory;
edit_category_dialog.value = true;
}
const emit_category = (category: BingoCategory) => {
// Add new category
if (!edit_category_data.value.category) {
game.value?.items.push(category);
}
// Replace existing category
else {
const index = game.value?.items.findIndex(g => g === category);
game.value?.items.splice(index, 1, category);
}
// Reset values
edit_category_data.value = {
};
edit_category_dialog.value = false;
};
const delete_category = (category: BingoCategory) => {
const index = game.value?.items.findIndex(g => g === category);
game.value?.items.splice(index, 1);
// Reset values
edit_category_data.value = {
};
edit_category_dialog.value = false;
};
/* End Edit Category Dialog */
const nodes = computed(() => {
const results: Node[] = [];
if (!game.value) {
return results;
}
results.push(...game.value?.items.map(item => {
const is_category = item instanceof BingoCategory;
const is_goal_list = item instanceof BingoGoalList;
const group_node: Node = {
label: item.name,
item,
header: is_category
? 'category'
: is_goal_list
? 'goal-list'
: '',
children: []
};
if (is_category) {
group_node.children = item.goal_lists.map(goal_list => {
const goal_list_node: Node = {
label: goal_list.name,
item: goal_list,
header: 'goal-list',
children: []
};
goal_list_node.children?.push(...goal_list.goals.map(goal => {
const goal_node: Node = {
label: goal.name,
item: goal,
header: 'goal',
goal,
parent: goal_list
};
return goal_node;
}));
goal_list_node.children?.push({
label: 'Add Goal',
header: 'add-goal',
parent: goal_list
});
return goal_list_node;
});
group_node.children?.push({
label: 'Add Goal List',
header: 'add-goal-list'
});
}
else if (is_goal_list) {
group_node.children = item.goals.map(goal => {
const goal_node: Node = {
label: goal.name,
item: goal,
header: 'goal',
goal,
parent: item
};
return goal_node;
});
group_node.children.push({
label: 'Add Goal',
header: 'add-goal',
parent: item
});
}
return group_node;
}));
results.push({
label: 'Add Goal List',
header: 'add-goal-list'
});
results.push({
label: 'Add Category',
header: 'add-category'
});
return results;
});
</script>

View File

@ -1,308 +0,0 @@
<template>
<q-tree
:nodes="nodes"
node-key="label"
tick-strategy="leaf"
v-model:selected="selected"
v-model:ticked="ticked"
v-model:expanded="expanded"
>
<template v-slot:header-goal="prop">
<div class="row items-center">
<q-btn
v-if="editMode"
icon="edit"
flat
round
size="sm"
color="orange"
@click="openEditGoalDialog(prop.node)"
/>
<div>{{ prop.node.label }}</div>
<q-badge outline v-for="tag in prop.node.goal.tags" :key="tag" :color="getColorForString(tag)" class="q-ml-sm">
<div class="text-weight-bold q-my-xs">{{ tag }}</div>
</q-badge>
</div>
</template>
<template v-slot:header-goal-list="prop">
<div class="row items-center">
<q-btn v-if="editMode" icon="edit" flat round size="sm" color="orange"/>
<div>{{ prop.node.label }}</div>
<q-badge outline color="orange" class="q-ml-sm">
<div class="text-weight-bold q-my-xs">Goal List</div>
</q-badge>
</div>
</template>
<template v-slot:header-category="prop">
<div class="row items-center">
<q-btn v-if="editMode" icon="edit" flat round size="sm" color="orange"/>
<div>{{ prop.node.label }}</div>
<q-badge outline color="blue" class="q-ml-sm">
<div class="text-weight-bold q-my-xs">Category</div>
</q-badge>
</div>
</template>
<!-- Add Goal -->
<template v-slot:header-add-goal="prop">
<div class="row items-center">
<q-btn outline icon="add" label="Goal" color="light-blue" size="sm" @click="openEditGoalDialog(prop.node)"></q-btn>
</div>
</template>
<!-- Add Goal List -->
<template v-slot:header-add-goal-list="prop">
<div class="row items-center">
<q-btn outline icon="add" label="Goal List" color="light-blue" size="sm"></q-btn>
</div>
</template>
<!-- Add Category -->
<template v-slot:header-add-category="prop">
<div class="row items-center">
<q-btn outline icon="add" label="Category" color="light-blue" size="sm"></q-btn>
</div>
</template>
</q-tree>
<q-dialog v-model="edit_goal_dialog" persistent>
<EditGoalDialog
:data="edit_goal_data"
@cancel="edit_goal_dialog = false"
@delete-goal="delete_goal"
@emit-goal="emit_goal"
/>
</q-dialog>
</template>
<script setup lang="ts">
interface Node {
label: string;
header?: string; // 'goal' | 'goal-list' | 'category' | 'add-goal' | 'add-goal-list' | 'add-category'
children?: Node[];
goal?: BingoGoal;
parent?: BingoCategory | BingoGoalList;
tickable?: boolean;
noTick?: boolean;
}
import {
getColorForString
} from '@/js/lib/Util.ts';
import BingoCategory from '@/js/lib/BingoCategory.ts';
import type BingoGame from '@/js/lib/BingoGame.ts';
import BingoGoal from '@/js/lib/BingoGoal.ts';
import BingoGoalList from '@/js/lib/BingoGoalList.ts';
const props = defineProps<{
editMode: boolean;
game: BingoGame | undefined;
}>();
const editMode = props.editMode;
const selected = ref<Node[]>([]);
const ticked = ref<Node[]>([]);
const expanded = ref<Node[]>([]);
/* Edit Goal Dialog */
interface EditGoalData {
goal?: BingoGoal;
all_tags: string[];
parent_group: BingoCategory | BingoGoalList | undefined;
}
const edit_goal_data = ref<EditGoalData>({
all_tags: [] as string[],
parent_group: undefined
});
const edit_goal_dialog = ref(false);
const openEditGoalDialog = (node: Node) => {
edit_goal_data.value.goal = node.goal;
edit_goal_data.value.parent_group = node.parent;
edit_goal_data.value.all_tags = props.game?.getAllTags() ?? [];
edit_goal_dialog.value = true;
};
const delete_goal = (goal: BingoGoal) => {
// We always assume parent group is a goal list. You can't add goals to categories.
const goal_list = edit_goal_data.value.parent_group as BingoGoalList;
const index = goal_list.goals.findIndex(g => g === goal);
goal_list.goals.splice(index, 1);
// Reset values
edit_goal_data.value = {
goal: undefined,
all_tags: [],
parent_group: undefined
};
edit_goal_dialog.value = false;
};
const emit_goal = (goal: BingoGoal) => {
// We always assume parent group is a goal list. You can't add goals to categories.
const goal_list = edit_goal_data.value.parent_group as BingoGoalList;
// Add new goal
if (!edit_goal_data.value.goal) {
goal_list.goals.push(goal);
}
// Replace existing goal
else {
const index = goal_list.goals.findIndex(g => g === edit_goal_data.value.goal);
goal_list.goals.splice(index, 1, goal);
}
// Reset values
edit_goal_data.value = {
goal: undefined,
all_tags: [],
parent_group: undefined
};
edit_goal_dialog.value = false;
};
// const getAllGoals = (node: Node): BingoGoal[] => {
// const goals: BingoGoal[] = [];
// if (node.children) {
// for (const child of node.children) {
// goals.push(...getAllGoals(child));
// }
// }
// else if (node.goal) {
// goals.push(node.goal);
// }
// return goals;
// };
// eslint-disable-next-line @typescript-eslint/no-unused-vars
// const selectByTag = (tag: string) => {
// ticked.value.length = 0;
// ticked.value.push(...nodes.value.filter(node => {
// return getAllGoals(node).map(goal => goal.tags.includes(tag));
// }));
// console.log(ticked.value);
// };
const nodes = computed(() => {
const results: Node[] = [];
if (!props.game) {
return results;
}
results.push(...props.game.items.map(item => {
const is_category = item instanceof BingoCategory;
const is_goal_list = item instanceof BingoGoalList;
const group_node: Node = {
label: item.name,
header: is_category
? 'category'
: is_goal_list
? 'goal-list'
: '',
children: [],
tickable: !editMode,
noTick: editMode
};
if (is_category) {
group_node.children = item.goal_lists.map(goal_list => {
const goal_list_node: Node = {
label: goal_list.name,
header: 'goal-list',
children: [],
tickable: !editMode,
noTick: editMode
};
goal_list_node.children?.push(...goal_list.goals.map(goal => {
const goal_node: Node = {
label: goal.name,
header: 'goal',
goal,
parent: goal_list,
tickable: !editMode,
noTick: editMode
};
return goal_node;
}));
if (editMode) {
goal_list_node.children?.push({
label: 'Add Goal',
header: 'add-goal',
parent: goal_list,
tickable: false,
noTick: true
});
}
return goal_list_node;
});
if (editMode) {
group_node.children?.push({
label: 'Add Goal List',
header: 'add-goal-list',
tickable: false,
noTick: true
});
}
}
else if (is_goal_list) {
group_node.children = item.goals.map(goal => {
const goal_node: Node = {
label: goal.name,
header: 'goal',
goal,
parent: item,
tickable: !editMode,
noTick: editMode
};
return goal_node;
});
if (editMode) {
group_node.children.push({
label: 'Add Goal',
header: 'add-goal',
parent: item,
tickable: false,
noTick: true
});
}
}
return group_node;
}));
if (editMode) {
results.push({
label: 'Add Goal List',
header: 'add-goal-list',
tickable: false,
noTick: true
});
results.push({
label: 'Add Category',
header: 'add-category',
tickable: false,
noTick: true
});
}
return results;
});
</script>

View File

@ -0,0 +1,355 @@
<template>
<span>TODO: Show a big list with all games and let user tick things. Use that for generation.</span>
<!-- <q-tree
:nodes="nodes"
node-key="label"
tick-strategy="leaf"
v-model:selected="selected"
v-model:ticked="ticked"
v-model:expanded="expanded"
>
<template v-slot:header-goal="prop">
<div class="row items-center">
<q-btn
v-if="editMode"
icon="edit"
flat
round
size="sm"
color="orange"
@click="openEditGoalDialog(prop.node)"
/>
<div>{{ prop.node.label }}</div>
<q-badge
outline
v-for="tag in prop.node.goal.tags"
:key="tag"
:color="getColorForString(tag)"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">{{ tag }}</div>
</q-badge>
</div>
</template>
<template v-slot:header-goal-list="prop">
<div class="row items-center">
<q-btn
v-if="editMode"
icon="edit"
flat
round
size="sm"
color="orange"
/>
<div>{{ prop.node.label }}</div>
<q-badge
outline
color="orange"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">Goal List</div>
</q-badge>
</div>
</template>
<template v-slot:header-category="prop">
<div class="row items-center">
<q-btn
v-if="editMode"
icon="edit"
flat
round
size="sm"
color="orange"
/>
<div>{{ prop.node.label }}</div>
<q-badge
outline
color="blue"
class="q-ml-sm"
>
<div class="text-weight-bold q-my-xs">Category</div>
</q-badge>
</div>
</template>
<template v-slot:header-add-goal="prop">
<div class="row items-center">
<q-btn
outline
icon="add"
label="Goal"
color="light-blue"
size="sm"
@click="openEditGoalDialog(prop.node)"
></q-btn>
</div>
</template>
<template v-slot:header-add-goal-list="prop">
<div class="row items-center">
<q-btn
outline
icon="add"
label="Goal List"
color="light-blue"
size="sm"
></q-btn>
</div>
</template>
<template v-slot:header-add-category="prop">
<div class="row items-center">
<q-btn
outline
icon="add"
label="Category"
color="light-blue"
size="sm"
></q-btn>
</div>
</template>
</q-tree>
<q-dialog v-model="edit_goal_dialog" persistent>
<EditGoalDialog
:data="edit_goal_data"
@cancel="edit_goal_dialog = false"
@delete-goal="delete_goal"
@emit-goal="emit_goal"
/>
</q-dialog> -->
</template>
<script setup lang="ts">
// interface Node {
// label: string;
// header?: string; // 'goal' | 'goal-list' | 'category' | 'add-goal' | 'add-goal-list' | 'add-category'
// children?: Node[];
// goal?: BingoGoal;
// parent?: BingoCategory | BingoGoalList;
// tickable?: boolean;
// noTick?: boolean;
// }
// import {
// getColorForString
// } from '@/js/lib/Util.ts';
// import BingoCategory from '@/js/lib/BingoCategory.ts';
// import type BingoGame from '@/js/lib/BingoGame.ts';
// import BingoGoal from '@/js/lib/BingoGoal.ts';
// import BingoGoalList from '@/js/lib/BingoGoalList.ts';
// import Parser from '@/js/lib/Parser.ts';
// const props = defineProps<{
// editMode: boolean;
// game: BingoGame;
// }>();
// const game = Parser.getCopy(props.game);
// const editMode = props.editMode;
// const selected = ref<Node[]>([]);
// const ticked = ref<Node[]>([]);
// const expanded = ref<Node[]>([]);
// /* Edit Goal Dialog */
// interface EditGoalData {
// goal?: BingoGoal;
// all_tags: string[];
// parent_group: BingoCategory | BingoGoalList | undefined;
// }
// const edit_goal_data = ref<EditGoalData>({
// all_tags: [] as string[],
// parent_group: undefined
// });
// const edit_goal_dialog = ref(false);
// const openEditGoalDialog = (node: Node) => {
// edit_goal_data.value.goal = node.goal;
// edit_goal_data.value.parent_group = node.parent;
// edit_goal_data.value.all_tags = game?.getAllTags() ?? [];
// edit_goal_dialog.value = true;
// };
// const delete_goal = (goal: BingoGoal) => {
// // We always assume parent group is a goal list. You can't add goals to categories.
// const goal_list = edit_goal_data.value.parent_group as BingoGoalList;
// const index = goal_list.goals.findIndex(g => g === goal);
// goal_list.goals.splice(index, 1);
// // Reset values
// edit_goal_data.value = {
// goal: undefined,
// all_tags: [],
// parent_group: undefined
// };
// edit_goal_dialog.value = false;
// };
// const emit_goal = (goal: BingoGoal) => {
// // We always assume parent group is a goal list. You can't add goals to categories.
// const goal_list = edit_goal_data.value.parent_group as BingoGoalList;
// // Add new goal
// if (!edit_goal_data.value.goal) {
// goal_list.goals.push(goal);
// }
// // Replace existing goal
// else {
// const index = goal_list.goals.findIndex(g => g === edit_goal_data.value.goal);
// goal_list.goals.splice(index, 1, goal);
// }
// // Reset values
// edit_goal_data.value = {
// goal: undefined,
// all_tags: [],
// parent_group: undefined
// };
// edit_goal_dialog.value = false;
// };
// // const getAllGoals = (node: Node): BingoGoal[] => {
// // const goals: BingoGoal[] = [];
// // if (node.children) {
// // for (const child of node.children) {
// // goals.push(...getAllGoals(child));
// // }
// // }
// // else if (node.goal) {
// // goals.push(node.goal);
// // }
// // return goals;
// // };
// // eslint-disable-next-line @typescript-eslint/no-unused-vars
// // const selectByTag = (tag: string) => {
// // ticked.value.length = 0;
// // ticked.value.push(...nodes.value.filter(node => {
// // return getAllGoals(node).map(goal => goal.tags.includes(tag));
// // }));
// // console.log(ticked.value);
// // };
// const nodes = computed(() => {
// const results: Node[] = [];
// if (!game) {
// return results;
// }
// results.push(...game.items.map(item => {
// const is_category = item instanceof BingoCategory;
// const is_goal_list = item instanceof BingoGoalList;
// const group_node: Node = {
// label: item.name,
// header: is_category
// ? 'category'
// : is_goal_list
// ? 'goal-list'
// : '',
// children: [],
// tickable: !editMode,
// noTick: editMode
// };
// if (is_category) {
// group_node.children = item.goal_lists.map(goal_list => {
// const goal_list_node: Node = {
// label: goal_list.name,
// header: 'goal-list',
// children: [],
// tickable: !editMode,
// noTick: editMode
// };
// goal_list_node.children?.push(...goal_list.goals.map(goal => {
// const goal_node: Node = {
// label: goal.name,
// header: 'goal',
// goal,
// parent: goal_list,
// tickable: !editMode,
// noTick: editMode
// };
// return goal_node;
// }));
// if (editMode) {
// goal_list_node.children?.push({
// label: 'Add Goal',
// header: 'add-goal',
// parent: goal_list,
// tickable: false,
// noTick: true
// });
// }
// return goal_list_node;
// });
// if (editMode) {
// group_node.children?.push({
// label: 'Add Goal List',
// header: 'add-goal-list',
// tickable: false,
// noTick: true
// });
// }
// }
// else if (is_goal_list) {
// group_node.children = item.goals.map(goal => {
// const goal_node: Node = {
// label: goal.name,
// header: 'goal',
// goal,
// parent: item,
// tickable: !editMode,
// noTick: editMode
// };
// return goal_node;
// });
// if (editMode) {
// group_node.children.push({
// label: 'Add Goal',
// header: 'add-goal',
// parent: item,
// tickable: false,
// noTick: true
// });
// }
// }
// return group_node;
// }));
// if (editMode) {
// results.push({
// label: 'Add Goal List',
// header: 'add-goal-list',
// tickable: false,
// noTick: true
// });
// results.push({
// label: 'Add Category',
// header: 'add-category',
// tickable: false,
// noTick: true
// });
// }
// return results;
// });
</script>

View File

@ -51,6 +51,24 @@ export default class BingoGame {
this.description = description; this.description = description;
} }
get reserved_names(): string[] {
const names: string[] = [];
this.items.map(group => {
const goal_lists = group instanceof BingoGoalList ? [ group ] : group.goal_lists;
goal_lists.map(subgroup => {
subgroup.goals.map(goal => {
if (goal.name) {
names.push(goal.name);
}
});
});
});
return names;
}
addItem(group: BingoCategoryOrGoalList): SuccessResponse { addItem(group: BingoCategoryOrGoalList): SuccessResponse {
const existingGroup = this.items.find(g => g === group); const existingGroup = this.items.find(g => g === group);
if (existingGroup) { if (existingGroup) {

View File

@ -7,6 +7,8 @@ import {
export default class BingoGoal { export default class BingoGoal {
name: string; name: string;
description: string = '';
tags: string[] = []; tags: string[] = [];
possible_spaces: number[] = []; possible_spaces: number[] = [];

View File

@ -17,9 +17,16 @@ export default class Parser {
return game; return game;
} }
static to_json(game: BingoGame): Record<string, any> { static to_json(game: BingoGame): JSON {
const json = instanceToPlain(game); const json = instanceToPlain(game);
return json; return json as JSON;
}
static getCopy(game: BingoGame): BingoGame {
const json: JSON = this.to_json(game);
const copy = this.from_json_raw(json);
return copy;
} }
} }

View File

@ -24,7 +24,6 @@ export const useLocalGamesStore = defineStore('localGames', () => {
games.value.length = 0; games.value.length = 0;
console.log(games_array);
games.value = games_array.map(game => { games.value = games_array.map(game => {
const bingo_game = Parser.from_json_raw(game); const bingo_game = Parser.from_json_raw(game);
@ -42,7 +41,6 @@ export const useLocalGamesStore = defineStore('localGames', () => {
// }); // });
console.info('--- Finished fetching games!'); console.info('--- Finished fetching games!');
console.log(games.value);
console.info(''); console.info('');
} }
@ -62,7 +60,7 @@ export const useLocalGamesStore = defineStore('localGames', () => {
return games.value.some(game => game.id === id); return games.value.some(game => game.id === id);
} }
function addGame(game: BingoGame) { async function addGame(game: BingoGame) {
if (hasGame(game.id)) { if (hasGame(game.id)) {
console.error(`Failed to clone game with ID ${ game.id } because it already exists`); console.error(`Failed to clone game with ID ${ game.id } because it already exists`);
@ -76,10 +74,27 @@ export const useLocalGamesStore = defineStore('localGames', () => {
cloned_game.is_local = true; cloned_game.is_local = true;
games.value.push(cloned_game); games.value.push(cloned_game);
saveGames();
await saveGames();
} }
function deleteGame(id: string) { async function updateGame(game: BingoGame) {
if (!hasGame(game.id)) {
console.error(`Failed to update game with ID ${ game.id } because it doesn't exist`);
return;
}
console.info(`--- Updating game with ID '${ game.id }' ...`);
const index = games.value.findIndex(g => g.id === game.id);
games.value[index] = game;
await saveGames();
}
async function deleteGame(id: string) {
if (hasGame(id)) { if (hasGame(id)) {
console.error(`Failed to delete game with ID ${ id } because it doesn't exist`); console.error(`Failed to delete game with ID ${ id } because it doesn't exist`);
@ -89,7 +104,8 @@ export const useLocalGamesStore = defineStore('localGames', () => {
console.info(`--- Deleting game with ID '${ id }' ...`); console.info(`--- Deleting game with ID '${ id }' ...`);
games.value = games.value.filter(game => game.id !== id); games.value = games.value.filter(game => game.id !== id);
saveGames();
await saveGames();
} }
return { return {
@ -100,6 +116,7 @@ export const useLocalGamesStore = defineStore('localGames', () => {
hasGame, hasGame,
addGame, addGame,
updateGame,
deleteGame deleteGame
}; };
}, { }, {

View File

@ -1,7 +1,7 @@
<template> <template>
<!-- <EditorComponent/> --> <EditorComponent/>
<GameList :game="yakuza" :edit-mode="true"/> <!-- <GameList :game="yakuza" :edit-mode="true"/> -->
<!-- <q-dialog v-model="dialog"> <!-- <q-dialog v-model="dialog">
@ -12,24 +12,24 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type BingoGame from '@/js/lib/BingoGame.ts'; // import type BingoGame from '@/js/lib/BingoGame.ts';
import { // import {
useLocalGamesStore // useLocalGamesStore
} from '@/stores/local-games.ts'; // } from '@/stores/local-games.ts';
const localGames = useLocalGamesStore(); // const localGames = useLocalGamesStore();
const yakuza = ref(); // const yakuza = ref();
onMounted(async() => { // onMounted(async() => {
await localGames.fetchGames(); // await localGames.fetchGames();
yakuza.value = localGames.games[0]; // yakuza.value = localGames.games[0];
console.log(yakuza.value); // console.log(yakuza.value);
}); // });
const dialog = ref<boolean>(false); // const dialog = ref<boolean>(false);
const selected_game = ref<BingoGame | undefined>(); // const selected_game = ref<BingoGame | undefined>();
</script> </script>