Add category editing dialog

This commit is contained in:
Lordmau5 2023-11-28 18:16:46 +01:00
parent b5add9b8a0
commit f149e9d702
4 changed files with 265 additions and 15 deletions

6
components.d.ts vendored
View File

@ -7,17 +7,13 @@ export {}
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
copy: typeof import('./src/composables/EditGoalDialog copy.vue')['default'] EditCategoryDialog: typeof import('./src/composables/EditCategoryDialog.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'] 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'] GameEditorDialog: typeof import('./src/composables/GameEditorDialog.vue')['default']
GameList: typeof import('./src/composables/GameList.vue')['default']
GameListDialog__UNUSED_NEEDS_EDITS: typeof import('./src/composables/GameListDialog__UNUSED_NEEDS_EDITS.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']
MarkdownRenderer: typeof import('./src/composables/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']

View File

@ -0,0 +1,198 @@
<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="category_name"
:error="!!name_error.length"
:error-message="name_error"
square
filled
counter
clearable
maxlength="40"
label="Name"
type="text"
></q-input>
<q-input
class="col-10"
v-model="category_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-card-section>
<q-separator />
<q-card-actions>
<q-btn
v-if="data.category"
flat
color="red"
icon="delete"
:label="delete_label"
@click="deleteCategory()"
:disabled="!delete_actually_enabled"
>
<q-tooltip v-if="has_goal_lists">
Cannot delete category with goal lists
</q-tooltip>
</q-btn>
<q-space/>
<q-btn
flat
color="red"
icon="cancel"
label="Cancel"
@click="cancel()"
/>
<q-btn
flat
color="green"
icon="save"
label="Save"
@click="saveCategory()"
:disabled="!canSave()"
/>
</q-card-actions>
</q-card>
<q-dialog v-model="markdown_preview">
<q-card
flat
bordered
>
<q-card-section>
<MarkdownRenderer :text="category_description"/>
</q-card-section>
</q-card>
</q-dialog>
</template>
<script setup lang="ts">
import BingoCategory from '@/js/lib/BingoCategory.ts';
import {
stringCompare
} from '@/js/lib/Util.ts';
const emit = defineEmits([
'cancel',
'createCategory',
'updateCategory',
'deleteCategory'
]);
const {
data
} = defineProps<{
data: {
reserved_names: string[],
category?: BingoCategory,
},
}>();
const markdown_preview = ref(false);
const delete_label = ref('Delete (5)');
const delete_enabled = ref(false);
const has_goal_lists = computed(() => {
return data.category?.goal_lists?.length;
});
onMounted(() => {
if (has_goal_lists) {
delete_label.value = 'Delete';
return;
}
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 delete_actually_enabled = computed(() => {
return delete_enabled.value && !has_goal_lists;
});
const category_name = ref('');
const category_description = ref('');
category_name.value = data.category?.name ?? '';
category_description.value = data.category?.description ?? '';
const name_error = computed(() => {
if (isNameReserved(category_name.value)) {
return 'Name is reserved';
}
return '';
});
function isNameReserved(name: string) {
return name.length && data.reserved_names.some(reserved_name =>
reserved_name !== data.category?.name
&& stringCompare(reserved_name, name));
}
function canSave() {
return !isNameReserved(category_name.value);
}
function cancel() {
emit('cancel');
}
function saveCategory() {
const category = new BingoCategory(category_name.value);
category.description = category_description.value;
// Create new category
if (!data.category) {
emit('createCategory', category);
}
// Update existing category
else {
category.goal_lists = data.category.goal_lists;
emit('updateCategory', data.category, category);
}
}
function deleteCategory() {
emit('deleteCategory', data.category);
}
</script>

View File

@ -145,6 +145,14 @@
@click="event => openEditCategoryDialog(prop.node, event)" @click="event => openEditCategoryDialog(prop.node, event)"
/> />
<div>{{ prop.node.item?.name || prop.node.label }}</div> <div>{{ prop.node.item?.name || prop.node.label }}</div>
<q-btn
v-if="prop.node.item?.description?.length"
icon="help_outline"
flat
round
size="sm"
@click="event => openInfoDialog(prop.node.item?.description, event)"
></q-btn>
<q-badge <q-badge
outline outline
color="blue" color="blue"
@ -312,6 +320,16 @@
@delete-goal-list="delete_goal_list" @delete-goal-list="delete_goal_list"
/> />
</q-dialog> </q-dialog>
<q-dialog v-model="edit_category_dialog" persistent>
<EditCategoryDialog
:data="edit_category_data"
@cancel="edit_category_dialog = false"
@create-category="create_category"
@update-category="update_category"
@delete-category="delete_category"
/>
</q-dialog>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -573,10 +591,12 @@ const delete_goal_list = (goal_list: BingoGoalList) => {
/* Edit Category Dialog */ /* Edit Category Dialog */
interface EditCategoryData { interface EditCategoryData {
reserved_names: string[];
category?: BingoCategory; category?: BingoCategory;
} }
const edit_category_data = ref<EditCategoryData>({ const edit_category_data = ref<EditCategoryData>({
reserved_names: [] as string[]
}); });
const edit_category_dialog = ref(false); const edit_category_dialog = ref(false);
@ -589,19 +609,23 @@ function openEditCategoryDialog(node: Node, event: Event) {
edit_category_dialog.value = true; edit_category_dialog.value = true;
} }
const emit_category = (category: BingoCategory) => { const create_category = (category: BingoCategory) => {
// Add new category game.value?.items.push(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 // Reset values
edit_category_data.value = { edit_category_data.value = {
reserved_names: []
};
edit_category_dialog.value = false;
};
const update_category = (old_category: BingoCategory, category: BingoCategory) => {
const index = game.value?.items.findIndex(g => g === old_category);
game.value?.items.splice(index, 1, category);
// Reset values
edit_category_data.value = {
reserved_names: []
}; };
edit_category_dialog.value = false; edit_category_dialog.value = false;
}; };
@ -612,10 +636,40 @@ const delete_category = (category: BingoCategory) => {
// Reset values // Reset values
edit_category_data.value = { edit_category_data.value = {
reserved_names: []
}; };
edit_category_dialog.value = false; edit_category_dialog.value = false;
}; };
// 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 = {
// reserved_names: []
// };
// 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 = {
// reserved_names: []
// };
// edit_category_dialog.value = false;
// };
/* End Edit Category Dialog */ /* End Edit Category Dialog */
const nodes = computed(() => { const nodes = computed(() => {

View File

@ -8,6 +8,8 @@ import {
export default class BingoCategory { export default class BingoCategory {
name: string; name: string;
description: string = '';
@Type(() => BingoGoalList) @Type(() => BingoGoalList)
goal_lists: BingoGoalList[] = []; goal_lists: BingoGoalList[] = [];