aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeonardo Bishop <me@leonardobishop.com>2024-03-10 01:01:22 +0000
committerLeonardo Bishop <me@leonardobishop.com>2024-03-10 01:01:22 +0000
commit4495c02c41b95ce6df0c34dbf6ac62f7addae7a3 (patch)
tree39ebbc0e3f850bc602d3e90a1ab7bbbe9a6552c3
parent9a11e0f4a38297006b89cc7bb2a60734111582e0 (diff)
Move selected state out of store and into route
-rw-r--r--components/Editor/EditorSidebarCategory.vue22
-rw-r--r--components/Editor/EditorSidebarQuest.vue18
-rw-r--r--layouts/editor.vue43
-rw-r--r--pages/category/[id].vue105
-rw-r--r--pages/index.vue36
-rw-r--r--pages/quest/[id].vue156
-rw-r--r--stores/session.ts10
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
},