diff options
| -rw-r--r-- | components/Editor/EditorSidebarCategory.vue | 22 | ||||
| -rw-r--r-- | components/Editor/EditorSidebarQuest.vue | 18 | ||||
| -rw-r--r-- | layouts/editor.vue | 43 | ||||
| -rw-r--r-- | pages/category/[id].vue | 105 | ||||
| -rw-r--r-- | pages/index.vue | 36 | ||||
| -rw-r--r-- | pages/quest/[id].vue | 156 | ||||
| -rw-r--r-- | stores/session.ts | 10 |
7 files changed, 333 insertions, 57 deletions
diff --git a/components/Editor/EditorSidebarCategory.vue b/components/Editor/EditorSidebarCategory.vue index 7153b92..071ebc8 100644 --- a/components/Editor/EditorSidebarCategory.vue +++ b/components/Editor/EditorSidebarCategory.vue @@ -13,6 +13,7 @@ const { category } = toRefs(props); const expanded = ref(true); const sessionStore = useSessionStore(); +const route = useRoute(); const questsInCategory = computed(() => { return sessionStore.getQuestsInCategory(category.value.id); @@ -23,21 +24,22 @@ const expandCategory = () => { }; const setSelectedCategory = () => { - sessionStore.setEditorSelected('Category', category.value.id); + navigateTo({ path: `/category/${category.value.id}` }) }; const selected = computed(() => { - return sessionStore.editor.selected.type === 'Category' && sessionStore.editor.selected.id === category.value.id; + return route.path.startsWith('/category') && route.params.id === category.value.id; }); </script> <template> - <div id="category-container" :class="{selected: selected}"> + <div id="category-container" :class="{ selected: selected }"> <span id="category-title" @click="setSelectedCategory"> - <font-awesome-icon @click.stop="expandCategory" class="category-icon" :icon="expanded ? ['fas', 'fa-caret-down'] : ['fas', 'fa-caret-up']"/> + <font-awesome-icon @click.stop="expandCategory" class="category-icon" + :icon="expanded ? ['fas', 'fa-caret-down'] : ['fas', 'fa-caret-up']" /> <span id="category-name"> - <span id="category-display-name">{{ stripColorCodes(category.display.name) }}</span> - <code id="category-display-id">{{ category.id }}</code> + <span id="category-display-name">{{ stripColorCodes(category.display.name) }}</span> + <code id="category-display-id">{{ category.id }}</code> </span> </span> </div> @@ -52,19 +54,19 @@ const selected = computed(() => { padding: 0.5rem 1rem; transition: background-color 0.3s; border-bottom: 1px solid var(--color-border-soft); - + #category-title { display: flex; align-items: center; margin: 0; gap: 1rem; font-size: 1.1rem; - + #category-name { display: flex; flex-direction: column; align-items: left; - + #category-display-id { font-size: 0.8rem; color: var(--color-text-mute); @@ -76,7 +78,7 @@ const selected = computed(() => { .selected { background-color: var(--color-primary-mute); } - + #quests { background-color: var(--color-background-mute); border-bottom: 1px solid var(--color-border-soft); diff --git a/components/Editor/EditorSidebarQuest.vue b/components/Editor/EditorSidebarQuest.vue index baf06f1..a7b3e3f 100644 --- a/components/Editor/EditorSidebarQuest.vue +++ b/components/Editor/EditorSidebarQuest.vue @@ -9,24 +9,24 @@ const props = defineProps<{ const { quest } = toRefs(props); -const sessionStore = useSessionStore(); +const route = useRoute(); const setSelectedQuest = () => { - sessionStore.setEditorSelected('Quest', quest.value.id); + navigateTo({ path: `/quest/${quest.value.id}` }) }; const selected = computed(() => { - return sessionStore.editor.selected.type === 'Quest' && sessionStore.editor.selected.id === quest.value.id; + return route.path.startsWith('/quest') && route.params.id === quest.value.id; }); </script> <template> - <div id="quest-container" @click.stop="setSelectedQuest" :class="{selected: selected}"> + <div id="quest-container" @click.stop="setSelectedQuest" :class="{ selected: selected }"> <span id="quest-title"> - <font-awesome-icon class="quest-icon" :icon="['far', 'fa-compass']"/> + <font-awesome-icon class="quest-icon" :icon="['far', 'fa-compass']" /> <span id="quest-name"> - <span id="quest-display-name">{{ stripColorCodes(quest.display.name) }}</span> - <code id="quest-display-id">{{ quest.id }}</code> + <span id="quest-display-name">{{ stripColorCodes(quest.display.name) }}</span> + <code id="quest-display-id">{{ quest.id }}</code> </span> </span> </div> @@ -44,12 +44,12 @@ const selected = computed(() => { margin: 0; gap: 0.5rem; font-size: 0.8rem; - + #quest-name { display: flex; flex-direction: column; align-items: left; - + #quest-display-id { font-size: 0.6rem; color: var(--color-text-mute); diff --git a/layouts/editor.vue b/layouts/editor.vue new file mode 100644 index 0000000..873e55c --- /dev/null +++ b/layouts/editor.vue @@ -0,0 +1,43 @@ +<script setup lang="ts"> +import { useSessionStore } from '@/stores/session'; +import { loadQuestsFromJson, loadCategoriesFromJson } from '@/lib/questsLoader'; +import SiteHeader from '@/components/Header/SiteHeader.vue'; +import EditorSidebar from '@/components/Editor/EditorSidebar.vue'; +import testData from '@/data/testData.json'; +import taskDefinitions from '@/data/taskDefinitions.json'; + +const sessionStore = useSessionStore(); + +const quests = loadQuestsFromJson(testData.quests); +const categories = loadCategoriesFromJson(testData.categories); + +sessionStore.setQuests(quests); +sessionStore.setCategories(categories); +sessionStore.setTaskDefinitions(taskDefinitions.taskTypes); +// sessionStore.updateEditorCategories(); +</script> + +<template> + <SiteHeader /> + + <div id="editor-container"> + <EditorSidebar /> + + <div id="editor-pane"> + <slot /> + </div> + </div> +</template> + +<style lang="scss" scoped> +#editor-container { + display: flex; + background-color: var(--color-background-soft); + max-height: calc(100vh - 73px); + flex-direction: row; +} + +#editor-pane { + width: 100%; +} +</style>
\ No newline at end of file diff --git a/pages/category/[id].vue b/pages/category/[id].vue new file mode 100644 index 0000000..66ad26c --- /dev/null +++ b/pages/category/[id].vue @@ -0,0 +1,105 @@ +<script setup lang="ts"> +import { useSessionStore } from '@/stores/session'; +import { stripColorCodes } from '@/lib/util'; +import CategoryOptionsPanel from '@/components/Editor/Category/CategoryOptionsPanel.vue'; +import CategoryChildrenOptionsPanel from '@/components/Editor/Category/CategoryChildrenOptionsPanel.vue'; +import Button from '@/components/Control/Button.vue'; + +definePageMeta({ + layout: 'editor' +}) + +const sessionStore = useSessionStore(); +const route = useRoute(); + +const categoryId = route.params.id as string; + +const categoryName = sessionStore.getCategoryById(categoryId)?.display.name; +</script> + +<template> + <div id="header"> + <span id="path"> + <font-awesome-icon class="icon" :icon="['fas', 'fa-folder']" /> + <span class="title" v-if="categoryName">{{ stripColorCodes(categoryName) }} </span> + <code>({{ categoryId }})</code> + </span> + <span id="controls" class="control-group"> + <Button type="solid" :disabled="true" :icon="['fas', 'fa-save']" :label="'Save'"></Button> + </span> + </div> + + <div id="options-container"> + <CategoryOptionsPanel :categoryId="categoryId" /> + <CategoryChildrenOptionsPanel :categoryId="categoryId" /> + </div> +</template> + +<style scoped> +#header { + padding: 1rem 1rem 0.5rem 1rem; + background-color: var(--color-background); + border-bottom: 1px solid var(--color-border); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width: 100%; + height: 55px; + display: flex; + align-items: left; + justify-content: space-between; + gap: 1rem; + + #path { + font-size: 1.2rem; + display: flex; + gap: 0.5rem; + align-items: center; + + .icon { + font-size: 0.8rem; + } + + .chevron { + font-size: 0.8rem; + color: var(--color-text-mute); + } + + .title { + font-weight: 700; + } + + code { + font-size: 0.8rem; + color: var(--color-text-mute); + } + } +} + +.none-selected { + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + font-size: 1.2rem; + color: var(--color-text-mute); +} + +#pane-container { + width: 100%; + flex-grow: 1; + height: calc(100vh - 73px); + max-height: calc(100vh - 73px); +} + +#options-container { + width: 100%; + display: flex; + gap: 1rem; + padding: 1rem; + overflow: scroll; + max-height: calc(100% - 55px); +} + +header { + border-bottom: 1px solid var(--color-border); +} +</style>
\ No newline at end of file diff --git a/pages/index.vue b/pages/index.vue index b94f998..dd46b31 100644 --- a/pages/index.vue +++ b/pages/index.vue @@ -1,37 +1,17 @@ <script setup lang="ts"> -import { useSessionStore } from '@/stores/session'; -import { loadQuestsFromJson, loadCategoriesFromJson } from '@/lib/questsLoader'; -import EditorSidebar from '@/components/Editor/EditorSidebar.vue'; -import testData from '@/data/testData.json'; -import taskDefinitions from '@/data/taskDefinitions.json'; -import EditorPane from '@/components/Editor/EditorPane.vue'; - -const sessionStore = useSessionStore(); - -const quests = loadQuestsFromJson(testData.quests); -const categories = loadCategoriesFromJson(testData.categories); - -sessionStore.setQuests(quests); -sessionStore.setCategories(categories); -sessionStore.setTaskDefinitions(taskDefinitions.taskTypes); -// sessionStore.updateEditorCategories(); +definePageMeta({ + layout: 'editor' +}) </script> <template> - <main> - <div id="editor-container"> - <EditorSidebar /> - - <EditorPane /> - </div> - </main> + <div id="title"> + <h1>Select a quest or category to continue</h1> + </div> </template> <style lang="scss" scoped> -#editor-container { - display: flex; - background-color: var(--color-background-soft); - max-height: calc(100vh - 73px); - flex-direction: row; +#title { + text-align: center; } </style>
\ No newline at end of file diff --git a/pages/quest/[id].vue b/pages/quest/[id].vue new file mode 100644 index 0000000..18d2d04 --- /dev/null +++ b/pages/quest/[id].vue @@ -0,0 +1,156 @@ +<script setup lang="ts"> +import { useSessionStore } from '@/stores/session'; +import { computed, ref } from 'vue'; +import { stripColorCodes } from '@/lib/util'; +import QuestOptionsPanel from '@/components/Editor/Quest/QuestOptionsPanel.vue'; +import QuestTasksOptionsPanel from '@/components/Editor/Quest/QuestTasksOptionsPanel.vue'; +import Button from '@/components/Control/Button.vue'; +import DeleteQuestModal from '@/components/Editor/Quest/Modal/DeleteQuestModal.vue'; +import RenameQuestModal from '@/components/Editor/Quest/Modal/RenameQuestModal.vue'; +import DuplicateQuestModal from '@/components/Editor/Quest/Modal/DuplicateQuestModal.vue'; + +definePageMeta({ + layout: 'editor' +}) + +const sessionStore = useSessionStore(); +const route = useRoute(); + +const questId = route.params.id as string; +const quest = sessionStore.getQuestById(questId); + + +const categoryFromSelectedQuest = computed(() => { + const quest = sessionStore.getQuestById(questId); + if (quest) { + return sessionStore.getCategoryById(quest.options.category) || null; + } else { + return null; + } +}); + +const showDeleteModal = ref(false); +const showRenameModal = ref(false); +const showDuplicateModal = ref(false); + +const renameQuest = (oldId: string, newId: string) => { + sessionStore.changeQuestId(oldId, newId); + sessionStore.editor.selected.id = newId; + showRenameModal.value = false; +}; + +const deleteQuest = (questId: string) => { + sessionStore.deleteQuest(questId); + sessionStore.setEditorSelected(null, null); + showDeleteModal.value = false; +}; + +const duplicateQuest = (oldId: string, newId: string) => { + sessionStore.duplicateQuest(oldId, newId); + sessionStore.editor.selected.id = newId; + showDuplicateModal.value = false; +}; +</script> + +<template> + <div id="header"> + <span id="path"> + <template v-if="categoryFromSelectedQuest"> + <font-awesome-icon class="icon" :icon="['fas', 'fa-folder']" /> + {{ stripColorCodes(categoryFromSelectedQuest?.display.name) }} + <font-awesome-icon class="chevron" :icon="['fas', 'fa-chevron-right']" /> + </template> + <font-awesome-icon class="icon" :icon="['far', 'fa-compass']" /> + <span class="title" v-if="quest">{{ stripColorCodes(quest.display.name) }} </span> + <code>({{ questId }})</code> + </span> + <span id="controls" class="control-group"> + <Button :icon="['fas', 'fa-code']" :label="'YAML'"></Button> + <Button :icon="['fas', 'fa-copy']" :label="'Duplicate'" @click="showDuplicateModal = true"></Button> + <Button :icon="['fas', 'fa-pen']" :label="'Rename'" @click="showRenameModal = true"></Button> + <Button :icon="['fas', 'fa-trash']" :label="'Delete'" @click="showDeleteModal = true"></Button> + <Button type="solid" :disabled="true" :icon="['fas', 'fa-save']" :label="'Save'"></Button> + </span> + </div> + + <div id="options-container"> + <QuestOptionsPanel :questId="questId" /> + <QuestTasksOptionsPanel :questId="questId" /> + </div> + + <DeleteQuestModal v-model="showDeleteModal" :key="`delete-quest-${questId}`" :questId="questId" + @delete="() => questId && deleteQuest(questId)" /> + <RenameQuestModal v-model="showRenameModal" :key="`rename-quest-${questId}`" :questId="questId" + @update="newId => questId && renameQuest(questId, newId)" /> + <DuplicateQuestModal v-model="showDuplicateModal" :key="`duplicate-quest-${questId}`" :questId="questId" + @duplicate="newId => questId && duplicateQuest(questId, newId)" /> +</template> + +<style scoped> +#header { + padding: 1rem 1rem 0.5rem 1rem; + background-color: var(--color-background); + border-bottom: 1px solid var(--color-border); + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); + width: 100%; + height: 55px; + display: flex; + align-items: left; + justify-content: space-between; + gap: 1rem; + + #path { + font-size: 1.2rem; + display: flex; + gap: 0.5rem; + align-items: center; + + .icon { + font-size: 0.8rem; + } + + .chevron { + font-size: 0.8rem; + color: var(--color-text-mute); + } + + .title { + font-weight: 700; + } + + code { + font-size: 0.8rem; + color: var(--color-text-mute); + } + } +} + +.none-selected { + display: flex; + align-items: center; + justify-content: center; + padding: 1rem; + font-size: 1.2rem; + color: var(--color-text-mute); +} + +#pane-container { + width: 100%; + flex-grow: 1; + height: calc(100vh - 73px); + max-height: calc(100vh - 73px); +} + +#options-container { + width: 100%; + display: flex; + gap: 1rem; + padding: 1rem; + overflow: scroll; + max-height: calc(100% - 55px); +} + +header { + border-bottom: 1px solid var(--color-border); +} +</style>
\ No newline at end of file diff --git a/stores/session.ts b/stores/session.ts index 1cc8dc7..1938b63 100644 --- a/stores/session.ts +++ b/stores/session.ts @@ -78,12 +78,6 @@ export const useSessionStore = defineStore('session', { quests: [] as EditorQuest[], categories: [] as EditorCategory[], taskDefinitions: {} as { [key: string]: TaskDefinition }, - }, - editor: { - selected: { - type: '' as 'Quest' | 'Category' | null, - id: '' as string | null, - } } }), getters: { @@ -141,10 +135,6 @@ export const useSessionStore = defineStore('session', { // }) // this.editor.categories = categories; // }, - setEditorSelected(type: 'Quest' | 'Category' | null, id: string | null) { - this.editor.selected.type = type - this.editor.selected.id = id - }, setTaskDefinitions(definitions: { [key: string]: TaskDefinition }) { this.session.taskDefinitions = definitions }, |
