diff options
| author | Leonardo Bishop <me@leonardobishop.com> | 2024-03-10 00:13:25 +0000 |
|---|---|---|
| committer | Leonardo Bishop <me@leonardobishop.com> | 2024-03-10 00:13:25 +0000 |
| commit | 9a11e0f4a38297006b89cc7bb2a60734111582e0 (patch) | |
| tree | 5ebddde79e67b659714b5dbdbfcea289f06a4ae5 /src/components/Editor | |
| parent | 817478f3cf357fc09778d9dc3cf67a667e21f042 (diff) | |
Migrate to nuxt
Diffstat (limited to 'src/components/Editor')
16 files changed, 0 insertions, 1525 deletions
diff --git a/src/components/Editor/Category/CategoryChildrenOptionsPanel.vue b/src/components/Editor/Category/CategoryChildrenOptionsPanel.vue deleted file mode 100644 index 6e96f64..0000000 --- a/src/components/Editor/Category/CategoryChildrenOptionsPanel.vue +++ /dev/null @@ -1,53 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore, type EditorCategory } from '@/stores/session'; -import { computed } from 'vue'; -import EditorOptionsPanel from '../EditorOptionsPanel.vue'; - -const props = defineProps<{ - categoryId: string; -}>(); - -const sessionStore = useSessionStore(); - -const category = computed(() => { - return sessionStore.getCategoryById(props.categoryId) as EditorCategory; -}); -</script> - -<template> - <EditorOptionsPanel v-if="category"> - <div id="options"> - <h2>Quests in this category</h2> - <p>Drag to reorder.</p> - <div class="option-group"> - </div> - </div> - </EditorOptionsPanel> -</template> - -<style> -#options { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.option-group { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.description { - font-size: 0.8em; -} - -label { - font-weight: 700; -} - -h2 { - border-bottom: 1px solid var(--color-border); -} -</style> - diff --git a/src/components/Editor/Category/CategoryOptionsPanel.vue b/src/components/Editor/Category/CategoryOptionsPanel.vue deleted file mode 100644 index f7d548c..0000000 --- a/src/components/Editor/Category/CategoryOptionsPanel.vue +++ /dev/null @@ -1,54 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore, type EditorCategory } from '@/stores/session'; -import { computed } from 'vue'; -import EditorOptionsPanel from '../EditorOptionsPanel.vue'; -import Checkbox from '@/components/Control/Checkbox.vue'; - -const props = defineProps<{ - categoryId: string; -}>(); - -const sessionStore = useSessionStore(); - -const category = computed(() => { - return sessionStore.getCategoryById(props.categoryId) as EditorCategory; -}); -</script> - -<template> - <EditorOptionsPanel v-if="category"> - <div id="options"> - <div class="option-group"> - <Checkbox id="category-permissionrequired" label="Require permission for category" - description="Players must have permission to open and start quests in this category." v-model="category.permissionRequired" /> - </div> - </div> - </EditorOptionsPanel> -</template> - -<style> -#options { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.option-group { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.description { - font-size: 0.8em; -} - -label { - font-weight: 700; -} - -h2 { - border-bottom: 1px solid var(--color-border); -} -</style> - diff --git a/src/components/Editor/EditorOptionsPanel.vue b/src/components/Editor/EditorOptionsPanel.vue deleted file mode 100644 index 1415d84..0000000 --- a/src/components/Editor/EditorOptionsPanel.vue +++ /dev/null @@ -1,18 +0,0 @@ -<script setup lang="ts"> -</script> - -<template> - <div id="options-panel"> - <slot /> - </div> -</template> - -<style scoped> -#options-panel { - width: 100%; - background-color: var(--color-background); - border: 1px solid var(--color-border); - padding: 1rem; - height: 100%; -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/EditorPane.vue b/src/components/Editor/EditorPane.vue deleted file mode 100644 index bf9532a..0000000 --- a/src/components/Editor/EditorPane.vue +++ /dev/null @@ -1,216 +0,0 @@ -<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 CategoryOptionsPanel from '@/components/Editor/Category/CategoryOptionsPanel.vue'; -import CategoryChildrenOptionsPanel from '@/components/Editor/Category/CategoryChildrenOptionsPanel.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'; - -const sessionStore = useSessionStore(); - -const selectedType = computed(() => sessionStore.editor.selected.type); -const selectedId = computed(() => sessionStore.editor.selected.id); - -const selectedName = computed(() => { - if (selectedType.value === 'Quest' && selectedId.value) { - return sessionStore.getQuestById(selectedId.value)?.display.name; - } else if (selectedType.value === 'Category' && selectedId.value) { - return sessionStore.getCategoryById(selectedId.value)?.display.name; - } else { - return ''; - } -}); - -const categoryFromSelectedQuest = computed(() => { - if (!selectedId.value || selectedType.value !== 'Quest') return null; - - const quest = sessionStore.getQuestById(selectedId.value); - 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="pane-container" v-if="!selectedId || !selectedType"> - <h1 class="none-selected">Select a quest or category from the sidebar to continue</h1> - </div> - <div id="pane-container" v-if="selectedId && selectedType"> - <div id="header"> - <span id="path"> - <template v-if="selectedType === 'Quest'"> - <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">{{ stripColorCodes(selectedName!) }} </span> - <code>({{ selectedId }})</code> - </template> - <template v-if="selectedType === 'Category'"> - <font-awesome-icon class="icon" :icon="['fas', 'fa-folder']"/> - <span class="title">{{ stripColorCodes(selectedName!) }} </span> - <code>({{ selectedId }})</code> - </template> - </span> - <span id="controls" class="control-group"> - <Button - v-if="selectedType === 'Quest'" - :icon="['fas', 'fa-code']" - :label="'YAML'" - ></Button> - <Button - v-if="selectedType === 'Quest'" - :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 v-if="selectedType === 'Quest'" :questId="selectedId" /> - <QuestTasksOptionsPanel v-if="selectedType === 'Quest'" :questId="selectedId" /> - - <CategoryOptionsPanel v-if="selectedType === 'Category'" :categoryId="selectedId" /> - <CategoryChildrenOptionsPanel v-if="selectedType === 'Category'" :categoryId="selectedId" /> - </div> - </div> - - <DeleteQuestModal - v-if="selectedType === 'Quest' && selectedId" - v-model="showDeleteModal" - :key="`delete-quest-${selectedId}`" - :questId="selectedId" - @delete="() => selectedId && deleteQuest(selectedId)" - /> - <RenameQuestModal - v-if="selectedType === 'Quest' && selectedId" - v-model="showRenameModal" - :key="`rename-quest-${selectedId}`" - :questId="selectedId" - @update="newId => selectedId && renameQuest(selectedId, newId)" - /> - <DuplicateQuestModal - v-if="selectedType === 'Quest' && selectedId" - v-model="showDuplicateModal" - :key="`duplicate-quest-${selectedId}`" - :questId="selectedId" - @duplicate="newId => selectedId && duplicateQuest(selectedId, 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/src/components/Editor/EditorSidebar.vue b/src/components/Editor/EditorSidebar.vue deleted file mode 100644 index c9539fa..0000000 --- a/src/components/Editor/EditorSidebar.vue +++ /dev/null @@ -1,28 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore } from '@/stores/session'; -import { storeToRefs } from 'pinia'; -import EditorSidebarCategory from '@/components/Editor/EditorSidebarCategory.vue'; -import EditorSidebarQuest from '@/components/Editor/EditorSidebarQuest.vue'; - -const sessionStore = useSessionStore(); - -const { session } = storeToRefs(sessionStore); -</script> - -<template> - <div id="sidebar-container"> - <EditorSidebarCategory v-for="category in session.categories" :key="category.id" :category="category" /> - <EditorSidebarQuest v-for="quest in session.quests.filter((q) => (!session.categories.some((c) => c.id === q.options.category)))" :key="quest.id" :quest="quest" /> - </div> -</template> - -<style scoped> -#sidebar-container { - width: 20rem; - border-right: 1px solid var(--color-border); - height: calc(100vh - 73px); - max-height: calc(100vh - 73px); - background-color: var(--color-background); - user-select: none; -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/EditorSidebarCategory.vue b/src/components/Editor/EditorSidebarCategory.vue deleted file mode 100644 index 7153b92..0000000 --- a/src/components/Editor/EditorSidebarCategory.vue +++ /dev/null @@ -1,94 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore, type EditorCategory } from '@/stores/session'; -import { computed, ref, toRefs } from 'vue'; -import { stripColorCodes } from '@/lib/util'; -import EditorSidebarQuest from '@/components/Editor/EditorSidebarQuest.vue'; - -const props = defineProps<{ - category: EditorCategory; -}>(); - -const { category } = toRefs(props); - -const expanded = ref(true); - -const sessionStore = useSessionStore(); - -const questsInCategory = computed(() => { - return sessionStore.getQuestsInCategory(category.value.id); -}); - -const expandCategory = () => { - expanded.value = !expanded.value; -}; - -const setSelectedCategory = () => { - sessionStore.setEditorSelected('Category', category.value.id); -}; - -const selected = computed(() => { - return sessionStore.editor.selected.type === 'Category' && sessionStore.editor.selected.id === category.value.id; -}); -</script> - -<template> - <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']"/> - <span id="category-name"> - <span id="category-display-name">{{ stripColorCodes(category.display.name) }}</span> - <code id="category-display-id">{{ category.id }}</code> - </span> - </span> - </div> - <div v-if="expanded" id="quests"> - <EditorSidebarQuest class="quest" v-for="quest in questsInCategory" :key="quest.id" :quest="quest" /> - </div> -</template> - -<style scoped> -#category-container { - cursor: pointer; - 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); - } - } - } -} - -.selected { - background-color: var(--color-primary-mute); -} - -#quests { - background-color: var(--color-background-mute); - border-bottom: 1px solid var(--color-border-soft); -} - -.quest { - margin: 0 0 0 1.4rem; - background-color: var(--color-background); - border-left: 1px solid var(--color-border); -} - -#category-container:hover { - background-color: var(--color-hover); -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/EditorSidebarQuest.vue b/src/components/Editor/EditorSidebarQuest.vue deleted file mode 100644 index baf06f1..0000000 --- a/src/components/Editor/EditorSidebarQuest.vue +++ /dev/null @@ -1,68 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore, type EditorQuest } from '@/stores/session'; -import { computed, toRefs } from 'vue'; -import { stripColorCodes } from '@/lib/util'; - -const props = defineProps<{ - quest: EditorQuest; -}>(); - -const { quest } = toRefs(props); - -const sessionStore = useSessionStore(); - -const setSelectedQuest = () => { - sessionStore.setEditorSelected('Quest', quest.value.id); -}; - -const selected = computed(() => { - return sessionStore.editor.selected.type === 'Quest' && sessionStore.editor.selected.id === quest.value.id; -}); -</script> - -<template> - <div id="quest-container" @click.stop="setSelectedQuest" :class="{selected: selected}"> - <span id="quest-title"> - <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> - </span> - </div> -</template> - -<style scoped> -#quest-container { - cursor: pointer; - padding: 0.3rem 1rem; - transition: background-color 0.3s; - - #quest-title { - display: flex; - align-items: center; - 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); - } - } - } -} - -.selected { - background-color: var(--color-primary-mute) !important; -} - -#quest-container:hover { - background-color: var(--color-hover); -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/Quest/Modal/DeleteQuestModal.vue b/src/components/Editor/Quest/Modal/DeleteQuestModal.vue deleted file mode 100644 index d0b0c5a..0000000 --- a/src/components/Editor/Quest/Modal/DeleteQuestModal.vue +++ /dev/null @@ -1,42 +0,0 @@ -<script setup lang="ts"> -import Modal from '@/components/Control/Modal.vue'; -import Button from '@/components/Control/Button.vue'; - -const model = defineModel(); - -const emit = defineEmits(['delete']); - -defineProps({ - questId: String, -}); -</script> - -<template> - <Modal v-model="model"> - <template v-slot:header> - <h2>Really delete quest '{{ questId }}'?</h2> - </template> - <p>Are you sure you want to delete this quest? The quests editor does not have undo functionality (yet)! </p> - <div id="confirm" class="control-group"> - <Button - :icon="['fas', 'fa-times']" - :label="'Cancel'" - @click="model = false" - ></Button> - <Button - type="solid" - :icon="['fas', 'fa-trash']" - :label="'Delete'" - @click="emit('delete')" - ></Button> - </div> - </Modal> -</template> - -<style scoped> -#confirm { - display: flex; - justify-content: flex-end; - margin-top: 1rem; -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/Quest/Modal/DuplicateQuestModal.vue b/src/components/Editor/Quest/Modal/DuplicateQuestModal.vue deleted file mode 100644 index bcd3782..0000000 --- a/src/components/Editor/Quest/Modal/DuplicateQuestModal.vue +++ /dev/null @@ -1,69 +0,0 @@ -<script setup lang="ts"> -import Modal from '@/components/Control/Modal.vue'; -import Button from '@/components/Control/Button.vue'; -import { computed, ref } from 'vue'; -import { useSessionStore } from '@/stores/session'; - -const model = defineModel(); - -const emit = defineEmits(['duplicate']); - -const props = defineProps({ - questId: String, -}); - -const session = useSessionStore(); - -const newQuestId = ref(props.questId); - -const isDuplicate = computed(() => { - return session.getQuestById(newQuestId.value!) !== undefined; -}); - -</script> - -<template> - <Modal v-model="model"> - <template v-slot:header> - <h2>Duplicate '{{ questId }}'</h2> - </template> - - <template v-slot:body> - <div id="body"> - <div class="option-group"> - <label for="new-type">ID of new quest</label> - <input id="new-type" name="new-type" type="text" v-model="newQuestId" /> - </div> - <p v-if="isDuplicate" class="error-text">Name is not unique.</p> - <p>A Quest ID must be unique, alphanumeric, and not contain any spaces.</p> - <div id="confirm" class="control-group"> - <Button - :icon="['fas', 'fa-times']" - :label="'Cancel'" - @click="model = false" - ></Button> - <Button - type="solid" - :icon="['fas', 'fa-check']" - :label="'Duplicate'" - :disabled="isDuplicate" - @click="emit('duplicate', newQuestId)" - ></Button> - </div> - </div> - </template> - </Modal> -</template> - -<style scoped> -#confirm { - display: flex; - justify-content: flex-end; -} - -#body { - display: flex; - flex-direction: column; - gap: 0.5rem; -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/Quest/Modal/RenameQuestModal.vue b/src/components/Editor/Quest/Modal/RenameQuestModal.vue deleted file mode 100644 index 2ad1481..0000000 --- a/src/components/Editor/Quest/Modal/RenameQuestModal.vue +++ /dev/null @@ -1,69 +0,0 @@ -<script setup lang="ts"> -import Modal from '@/components/Control/Modal.vue'; -import Button from '@/components/Control/Button.vue'; -import { computed, ref } from 'vue'; -import { useSessionStore } from '@/stores/session'; - -const model = defineModel(); - -const emit = defineEmits(['update']); - -const props = defineProps({ - questId: String, -}); - -const session = useSessionStore(); - -const newQuestId = ref(props.questId); - -const isDuplicate = computed(() => { - return session.getQuestById(newQuestId.value!) !== undefined; -}); - -</script> - -<template> - <Modal v-model="model"> - <template v-slot:header> - <h2>Rename quest '{{ questId }}'</h2> - </template> - - <template v-slot:body> - <div id="body"> - <div class="option-group"> - <label for="new-type">New quest ID</label> - <input id="new-type" name="new-type" type="text" v-model="newQuestId" /> - </div> - <p v-if="isDuplicate" class="error-text">Name is not unique.</p> - <p>A Quest ID must be unique, alphanumeric, and not contain any spaces.</p> - <div id="confirm" class="control-group"> - <Button - :icon="['fas', 'fa-times']" - :label="'Cancel'" - @click="model = false" - ></Button> - <Button - type="solid" - :icon="['fas', 'fa-check']" - :label="'Rename'" - :disabled="isDuplicate" - @click="emit('update', newQuestId)" - ></Button> - </div> - </div> - </template> - </Modal> -</template> - -<style scoped> -#confirm { - display: flex; - justify-content: flex-end; -} - -#body { - display: flex; - flex-direction: column; - gap: 0.5rem; -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/Quest/QuestOptionsPanel.vue b/src/components/Editor/Quest/QuestOptionsPanel.vue deleted file mode 100644 index a462126..0000000 --- a/src/components/Editor/Quest/QuestOptionsPanel.vue +++ /dev/null @@ -1,145 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore, type EditorQuest } from '@/stores/session'; -import { computed, ref } from 'vue'; -import EditorOptionsPanel from '@/components/Editor/EditorOptionsPanel.vue'; -import Checkbox from '@/components/Control/Checkbox.vue'; - -const props = defineProps<{ - questId: string; -}>(); - -const sessionStore = useSessionStore(); - -const quest = computed(() => { - return sessionStore.getQuestById(props.questId) as EditorQuest; -}); -const knownCategories = computed(() => { - return sessionStore.session.categories.map((category) => category.id); -}); -const knownQuests = computed(() => { - return sessionStore.session.quests.map((quest) => quest.id); -}); - -</script> - -<template> - <EditorOptionsPanel v-if="quest"> - <div id="options"> - <div class="option-group"> - <label for="quest-category">Category</label> - <multiselect - id="quest-category" - v-model="quest.options.category" - :options="knownCategories" - :searchable="true" - placeholder="No category"></multiselect> - </div> - - <div class="option-group"> - <label for="quest-requirements">Requirements</label> - <multiselect - id="quest-requirements" - v-model="quest.options.requirements" - :options="knownQuests" - :searchable="true" - :taggable="true" - :multiple="true" - placeholder="Add requirement"></multiselect> - <p class="description"> - This quest will only be available if the player has completed all of the quests listed above. - </p> - </div> - - <h2>Quest options</h2> - - <div class="option-group"> - <Checkbox id="quest-permissionrequired" label="Require permission to start quest" - description="Players must have permission to start the quest." v-model="quest.options.permissionRequired" /> - </div> - - <div class="option-group"> - <Checkbox id="quest-cancellable" label="Allow players to cancel quest" - description="Players can cancel the quest after they have started it." v-model="quest.options.cancellable" /> - </div> - - <div class="option-group"> - <Checkbox id="quest-countstowardslimit" label="Count towards quest limit" - description="Quest will count towards the player's quest started limit." - v-model="quest.options.countsTowardsLimit" /> - </div> - - <div class="option-group"> - <Checkbox id="quest-repeatable" label="Allow players to repeat quest" - description="Quest can be completed again after it has been completed once." - v-model="quest.options.repeatable" /> - </div> - - <div class="option-group"> - <Checkbox id="quest-autostart" label="Automatically start quest" - description="Quest will start automatically when the player has unlocked it." - v-model="quest.options.autostart" /> - </div> - - - <h2>Cooldown</h2> - - <div class="option-group"> - <Checkbox id="quest-cooldown" label="Enable cooldown" - description="Players will have to wait a certain amount of time before they can start the quest again." - v-model="quest.options.cooldown.enabled" /> - </div> - - <div class="option-group"> - <label for="quest-cooldown-time"> - Cooldown (in seconds) - </label> - <input id="quest-cooldown-time" type="number" v-model="quest.options.cooldown.time" - :disabled="!quest.options.cooldown.enabled" /> - <p class="description"> - Common values are: <code>3600</code> (1 hour), <code>86400</code> (24 hours), <code>604800</code> (7 days), - <code>2592000</code> (30 days) - </p> - </div> - - <h2>Time limit</h2> - - <div class="option-group"> - <Checkbox id="quest-timelimit" label="Enable time limit" - description="Players will be required to complete the quest within a certain amount of time, otherwise it will be automatically cancelled." - v-model="quest.options.timeLimit.enabled" /> - </div> - - <div class="option-group"> - <label for="quest-timelimit-time"> - Time limit (in seconds) - </label> - <input id="quest-timelimit-time" type="number" v-model="quest.options.timeLimit.time" - :disabled="!quest.options.timeLimit.enabled" /> - <p class="description"> - Common values are: <code>3600</code> (1 hour), <code>86400</code> (24 hours), <code>604800</code> (7 days), - <code>2592000</code> (30 days) - </p> - </div> - </div> - </EditorOptionsPanel> -</template> - -<style src="vue-multiselect/dist/vue-multiselect.css" /> - -<style> -#options { - display: flex; - flex-direction: column; - gap: 1rem; -} - -.description { - font-size: 0.8em; -} - -h2 { - border-bottom: 1px solid var(--color-border); -} - -</style> - diff --git a/src/components/Editor/Quest/QuestTasksOptionsPanel.vue b/src/components/Editor/Quest/QuestTasksOptionsPanel.vue deleted file mode 100644 index a79e636..0000000 --- a/src/components/Editor/Quest/QuestTasksOptionsPanel.vue +++ /dev/null @@ -1,98 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore, type EditorQuest } from '@/stores/session'; -import { computed, ref } from 'vue'; -import EditorOptionsPanel from '@/components/Editor/EditorOptionsPanel.vue'; -import TaskConfiguration from '@/components/Editor/Quest/Task/TaskConfiguration.vue'; -import Button from '@/components/Control/Button.vue'; -import AddTaskModal from './Task/Modal/AddTaskModal.vue'; - -const props = defineProps<{ - questId: string; -}>(); - -const sessionStore = useSessionStore(); - -const quest = computed(() => { - return sessionStore.getQuestById(props.questId) as EditorQuest; -}); - -const showAddTaskModal = ref(false); - -const addTask = (newId: string, newType: string) => { - sessionStore.getQuestById(props.questId)!.tasks[newId] = { - id: newId, - config: { - type: newType, - }, - }; - - showAddTaskModal.value = false; -}; -</script> - -<template> - <EditorOptionsPanel v-if="quest"> - <div id="options"> - <h2>Tasks <code>({{ Object.keys(quest.tasks).length }})</code></h2> - - <p v-if="Object.keys(quest.tasks).length === 0" class="error-text">This quest does not have any tasks.</p> - <TaskConfiguration v-for="(task, taskId) in quest.tasks" :key="taskId" :taskId="String(taskId)" :quest="quest" /> - - <div id="controls"> - <Button - id="add-task" - :icon="['fas', 'fa-plus']" - type="solid" - label="Add task" - @click="showAddTaskModal = true" - /> - </div> - </div> - </EditorOptionsPanel> - - <AddTaskModal - v-if="quest" - v-model="showAddTaskModal" - :questId="questId" - @add="addTask" - /> -</template> - - -<style scoped> -#options { - display: flex; - flex-direction: column; - gap: 1rem; - - #controls { - display: flex; - justify-content: flex-end; - gap: 1rem; - } -} - -.option-group { - display: flex; - flex-direction: column; - gap: 0.5rem; -} - -.description { - font-size: 0.8em; -} - -label { - font-weight: 700; -} - -h2 { - border-bottom: 1px solid var(--color-border); - - code { - font-size: 0.8em; - color: var(--color-text-mute); - } -} -</style> - diff --git a/src/components/Editor/Quest/Task/Modal/AddTaskModal.vue b/src/components/Editor/Quest/Task/Modal/AddTaskModal.vue deleted file mode 100644 index 57139bb..0000000 --- a/src/components/Editor/Quest/Task/Modal/AddTaskModal.vue +++ /dev/null @@ -1,90 +0,0 @@ -<script setup lang="ts"> -import Modal from '@/components/Control/Modal.vue'; -import Button from '@/components/Control/Button.vue'; -import { computed, ref } from 'vue'; -import { useSessionStore } from '@/stores/session'; -import { validateTaskId } from '@/lib/util'; - -const model = defineModel(); - -const emit = defineEmits(['add']); - -const session = useSessionStore(); - -const props = defineProps({ - questId: { - type: String, - required: true, - }, -}); - -const knownTasks = computed(() => session.getQuestById(props.questId)!.tasks); -const knownTaskTypes = computed(() => session.getKnownTaskTypes()); - -const newId = ref(''); -const newType = ref(''); -const unknownTaskType = computed(() => !knownTaskTypes.value.includes(newType.value)); -const invalidTaskId = computed(() => !validateTaskId(newId.value)); -const duplicateTaskId = computed(() => knownTasks.value[newId.value] !== undefined); - -const newTypeDescription = computed(() => session.getTaskDefinitionByTaskType(newType.value)?.description); -</script> - -<template> - <Modal v-model="model"> - <template v-slot:header> - <h2>Add new task</h2> - </template> - - <template v-slot:body> - <div id="body"> - <div class="option-group"> - <label for="new-type">Task ID</label> - <input id="new-id" name="new-id" type="text" v-model="newId" /> - <p v-if="invalidTaskId" class="error-text">Invalid task ID.</p> - <p v-if="duplicateTaskId" class="error-text">Task ID already exists.</p> - </div> - <div class="option-group"> - <label for="new-type">Task type</label> - <multiselect - id="new-type" - v-model="newType" - :options="knownTaskTypes" - :searchable="true" - placeholder="Select a new type" - ></multiselect> - <p v-if="unknownTaskType" class="error-text">Invalid task type.</p> - </div> - <p v-if="newTypeDescription">{{ newTypeDescription }}</p> - <p>A task ID must be unique, alphanumeric, and not contain any spaces.</p> - <div id="confirm" class="control-group"> - <Button - :icon="['fas', 'fa-times']" - :label="'Cancel'" - @click="model = false" - ></Button> - <Button - type="solid" - :icon="['fas', 'fa-check']" - :label="'Confirm'" - :disabled="unknownTaskType || invalidTaskId || duplicateTaskId" - @click="emit('add', newId, newType)" - ></Button> - </div> - </div> - </template> - </Modal> -</template> - -<style scoped> -#confirm { - display: flex; - justify-content: flex-end; -} - -#body { - display: flex; - flex-direction: column; - gap: 0.5rem; -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/Quest/Task/Modal/ChangeTaskModal.vue b/src/components/Editor/Quest/Task/Modal/ChangeTaskModal.vue deleted file mode 100644 index c6b5921..0000000 --- a/src/components/Editor/Quest/Task/Modal/ChangeTaskModal.vue +++ /dev/null @@ -1,77 +0,0 @@ -<script setup lang="ts"> -import Modal from '@/components/Control/Modal.vue'; -import Button from '@/components/Control/Button.vue'; -import { computed, ref } from 'vue'; -import { useSessionStore } from '@/stores/session'; - -const model = defineModel(); - -const emit = defineEmits(['update']); - -const session = useSessionStore(); - -const props = defineProps({ - taskId: String, - currentTaskType: String, -}); - -const knownTaskTypes = computed(() => session.getKnownTaskTypes()); - -const newType = ref(''); -const unknownTaskType = computed(() => !knownTaskTypes.value.includes(newType.value)); -const noChange = computed(() => newType.value === props.currentTaskType); -const newTypeDescription = computed(() => session.getTaskDefinitionByTaskType(newType.value)?.description); -</script> - -<template> - <Modal v-model="model"> - <template v-slot:header> - <h2>Change the task type of '{{ taskId }}'</h2> - </template> - - <template v-slot:body> - <div id="body"> - <div class="option-group"> - <label for="new-type">New type</label> - <multiselect - id="new-type" - v-model="newType" - :options="knownTaskTypes" - :searchable="true" - placeholder="Select a new type" - ></multiselect> - </div> - <p v-if="unknownTaskType" class="error-text">Invalid task type.</p> - <p v-if="newTypeDescription">{{ newTypeDescription }}</p> - <p>Any configured options for this task will be overwritten.</p> - <div id="confirm" class="control-group"> - <Button - :icon="['fas', 'fa-times']" - :label="'Cancel'" - @click="model = false" - ></Button> - <Button - type="solid" - :icon="['fas', 'fa-check']" - :label="'Change'" - :disabled="unknownTaskType || noChange" - @click="emit('update', newType)" - ></Button> - </div> - </div> - </template> - </Modal> -</template> - -<style scoped> -#confirm { - display: flex; - justify-content: flex-end; -} - -#body { - display: flex; - flex-direction: column; - gap: 0.5rem; -} -</style>
\ No newline at end of file diff --git a/src/components/Editor/Quest/Task/TaskConfiguration.vue b/src/components/Editor/Quest/Task/TaskConfiguration.vue deleted file mode 100644 index 0646ad4..0000000 --- a/src/components/Editor/Quest/Task/TaskConfiguration.vue +++ /dev/null @@ -1,209 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore, type EditorQuest } from '@/stores/session'; -import { computed, ref } from 'vue'; -import Button from '@/components/Control/Button.vue'; -import TaskConfigurationRow from '@/components/Editor/Quest/Task/TaskConfigurationRow.vue'; -import ChangeTaskModal from './Modal/ChangeTaskModal.vue'; - -const props = defineProps<{ - taskId: string; - quest: EditorQuest; -}>(); - -const sessionStore = useSessionStore(); - -const taskType = computed(() => props.quest.tasks[props.taskId].config.type); -const taskDefintion = computed(() => sessionStore.getTaskDefinitionByTaskType(taskType.value)); - -const taskConfig = computed(() => { - return Object.keys(props.quest.tasks[props.taskId].config).filter((fieldName) => fieldName !== 'type').reduce((acc, fieldName) => { - acc[fieldName] = props.quest.tasks[props.taskId].config[fieldName]; - return acc; - }, {} as { [key: string]: any }); -}); - -const requiredFields = computed(() => { - return Object.keys(taskDefintion.value.configuration).filter((fieldName) => taskDefintion.value.configuration[fieldName].required); -}); - -const givenRequiredFields = computed(() => { - return requiredFields.value.filter((fieldName) => taskConfig.value[fieldName]); -}); - -const missingFields = computed(() => { - return requiredFields.value.filter((fieldName) => !props.quest.tasks[props.taskId].config[fieldName]); -}); - -const remainingGivenFields = computed(() => { - return Object.keys(taskConfig.value).filter((fieldName) => !requiredFields.value.includes(fieldName)); -}); - -const configKeysOptions = computed(() => Object.keys(taskDefintion.value.configuration).filter((key) => !Object.keys(taskConfig.value).some((fieldName) => fieldName === key))); -// const configKeysOptions = computed(() => { -// const keys = Object.keys(taskDefintion.value.configuration).filter((key) => !Object.keys(taskConfig.value).some((fieldName) => fieldName === key)); -// -// return keys.map((key) => { -// return { -// value: key, -// description: taskDefintion.value.configuration[key].description, -// note: taskDefintion.value.configuration[key].note, -// }; -// }); -// }); - -const onAddOption = (option: any) => { - sessionStore.getQuestById(props.quest.id)!.tasks[props.taskId].config[option] = taskDefintion.value.configuration[option].default || null; -}; - -const updateValue = (fieldName: string, value: any) => { - sessionStore.getQuestById(props.quest.id)!.tasks[props.taskId].config[fieldName] = value; -}; - -const deleteValue = (fieldName: string) => { - delete sessionStore.getQuestById(props.quest.id)!.tasks[props.taskId].config[fieldName]; -}; - -const showChangeModal = ref(false); - -const updateTaskType = (newType: string) => { - sessionStore.getQuestById(props.quest.id)!.tasks[props.taskId].config = { - type: newType - }; - showChangeModal.value = false; -} - -const deleteTaskType = (taskId: string) => { - delete sessionStore.getQuestById(props.quest.id)!.tasks[taskId]; -} -</script> - -<template> - <div id="task-configuration-table"> - <div id="task-header"> - <p id="task-id"> - <span id="task-name"> - {{ props.taskId }} - </span> - <code> - ({{ taskType }}) - </code> - </p> - <div id="task-controls" class="control-group"> - <Button - :icon="['fas', 'fa-pen']" - :label="'Change'" - @click="showChangeModal = true" - ></Button> - <Button - :icon="['fas', 'fa-trash']" - :label="'Delete'" - @click="deleteTaskType(props.taskId)" - ></Button> - </div> - </div> - <div id="task-configuration"> - <div v-if="!taskDefintion" class="error"> - <font-awesome-icon id="error-icon" :icon="['fas', 'fa-triangle-exclamation']"/> - <p id="error-message"> - Unable to edit task <code>{{ props.taskId }}</code>. - </p> - <p id="error-description"> - The quests web editor does not know how to configure task - type <code>{{ taskType }}</code> as it has no task definition. - </p> - </div> - - <div v-if="taskDefintion"> - <TaskConfigurationRow - v-for="fieldName in [...givenRequiredFields, ...missingFields, ...remainingGivenFields]" - :key="`${quest.id}-${props.taskId}-${taskType}-${fieldName}`" - :required="requiredFields.includes(fieldName)" - :configKey="fieldName" - :initialValue="taskConfig[fieldName]" - :taskType="taskType" - :type="(taskDefintion.configuration[fieldName].type as string)" - @update="newValue => updateValue(fieldName, newValue)" - @delete="() => deleteValue(fieldName)" - /> - <div id="add-option"> - <multiselect - class="multiselect" - :options="configKeysOptions" - :searchable="true" - @select="onAddOption" - placeholder="Add option..."> - </multiselect> - </div> - </div> - </div> - </div> - - <ChangeTaskModal - v-model="showChangeModal" - :taskId="props.taskId" - :currentTaskType="taskType" - :key="`change-task-${props.taskId}`" - @update="updateTaskType" - /> -</template> - -<style scoped> -.error { - padding: 0.5rem 0.5rem 0.5rem calc(0.5rem + 20px); - - #error-icon { - float: left; - margin: 5px 0 0 -20px; - } - - #error-message { - font-weight: 700; - } - -} - -#task-configuration-table { - display: flex; - flex-direction: column; - border: 1px solid var(--color-border); - - #task-header { - display: flex; - justify-content: space-between; - border-bottom: 1px solid var(--color-border); - background-color: var(--color-background-soft); - padding: 0.5rem; - - #task-id { - font-size: 1.2em; - - #task-name { - font-weight: 700; - } - - code { - font-size: 0.8em; - color: var(--color-text-mute); - } - } - } -} - -#add-option { - width: 100%; - border-right: 1px solid var(--color-border); - border-top: 1px solid var(--color-border); -} - -.multiselect::v-deep .multiselect__tags { - border: none !important; - border-radius: 0px !important; - background: transparent !important; -} - -.multiselect::v-deep .multiselect__select { - background: transparent !important; -} - -</style> - diff --git a/src/components/Editor/Quest/Task/TaskConfigurationRow.vue b/src/components/Editor/Quest/Task/TaskConfigurationRow.vue deleted file mode 100644 index f68ce97..0000000 --- a/src/components/Editor/Quest/Task/TaskConfigurationRow.vue +++ /dev/null @@ -1,195 +0,0 @@ -<script setup lang="ts"> -import { useSessionStore } from '@/stores/session'; -import { computed, ref, toRefs, watch } from 'vue'; -import TrueFalseSwitch from '@/components/Control/TrueFalseSwitch.vue'; -import ItemStackPicker from '@/components/Control/ItemStackPicker.vue'; -import materials from '@/lib/materials'; - -const props = defineProps({ - taskType: { - type: String, - required: true, - }, - configKey: { - type: String, - required: true, - }, - initialValue: null, - type: String, - required: Boolean, -}); -const emit = defineEmits(['update', 'delete']); - -const sessionStore = useSessionStore(); - -const definition = computed(() => { - const def = sessionStore.getTaskDefinitionByTaskType(props.taskType).configuration[props.configKey]; - return { description: def.description, note: def.note }; -}); - -const { description, note } = toRefs(definition.value); -const showDescription = ref(false); -const currentValue = ref(props.initialValue || - (props.type === 'boolean' - ? false - : (props.type === 'material-list' || props.type === 'string-list' - ? [] - : props.type === 'itemstack' - ? null - : '' - ))); - -if (props.initialValue !== currentValue.value) { - emit('update', currentValue.value); -} - -const error = computed(() => currentValue.value === undefined || currentValue.value === null || currentValue.value === '' || (Array.isArray(currentValue.value) && currentValue.value.length === 0)); -const updateValue = (value: any) => { - currentValue.value = value; -}; - -watch(currentValue, () => { - emit('update', currentValue.value); -}); - -const addValue = (searchQuery: any) => { - currentValue.value.push(searchQuery); -}; - -</script> - -<template> - <div id="task-configuration-row"> - <div id="key"> - <div id="delete" @click="emit('delete')" v-if="!props.required" class="delete"> - <font-awesome-icon :icon="['fas', 'fa-xmark']" /> - </div> - <p id="name" @click="showDescription = !showDescription">{{ props.configKey }}</p> - </div> - <div id="value"> - <div id="value-container"> - <!-- Data type 'string' --> - <input v-if="props.type === 'string'" v-model="currentValue" /> - - <!-- Data type 'number' --> - <input v-else-if="props.type === 'number'" type="number" v-model="currentValue" /> - - <!-- Data type 'boolean' --> - <TrueFalseSwitch v-else-if="props.type === 'boolean'" :value="!!currentValue" @update="updateValue" /> - - <!-- Data type 'material-list' --> - <multiselect v-else-if="props.type === 'material-list'" v-model="currentValue" - :options="materials" :multiple="true" :taggable="true" :searchable="true" placeholder="Enter material name" /> - - <!-- Data type 'string-list' --> - <multiselect v-else-if="props.type === 'string-list'" v-model="currentValue" :options="[]" @tag="addValue" - :multiple="true" :taggable="true" :searchable="true" placeholder="Enter string" /> - - <!-- Data type 'itemstack' --> - <ItemStackPicker v-else-if="props.type === 'itemstack'" :value="currentValue" @update="updateValue" /> - - <div v-if="showDescription || error" id="task-configuration-row-info"> - <p v-if="error" class="error">A value is required.</p> - <p>{{ description }} <i>{{ note }}</i></p> - </div> - </div> - </div> - </div> -</template> - -<style scoped> -#task-configuration-row { - display: flex; - flex-direction: row; - transition: background-color 0.3s; - border-bottom: 1px solid var(--color-border); - - #key { - width: 25%; - background-color: var(--color-background); - display: flex; - flex-direction: row; - align-items: center; - user-select: none; - - #delete { - display: flex; - align-items: center; - justify-content: center; - width: 20px; - height: 100%; - cursor: pointer; - color: var(--color-text-mute); - border-right: 1px solid var(--color-border); - background-color: var(--color-background-soft); - transition: color 0.3s; - - &:hover { - color: var(--color-false); - } - } - - #name { - display: flex; - align-items: center; - font-size: 0.8rem; - padding: 0.5rem; - width: 100%; - height: 100%; - font-family: monospace; - cursor: pointer; - transition: background-color 0.3s; - - &:hover { - background-color: var(--color-hover); - } - } - } - - #value { - width: 75%; - background-color: var(--color-background); - border-left: 1px solid var(--color-border); - - #value-container { - display: flex; - flex-direction: column; - height: 100%; - } - } -} - -#task-configuration-row:hover { - background-color: var(--color-hover); -} - -#task-configuration-row-info { - padding: 0.25rem 0.5rem; - font-size: 0.8em; - background-color: var(--color-background); - border-top: 1px solid var(--color-border); -} - -input { - width: 100%; - padding: 0.5rem; - border-radius: 0; - border: none; - font-family: monospace; - font-size: 0.8rem; - height: 40px; -} - -.error { - color: var(--color-false); -} - -.multiselect::v-deep .multiselect__tags { - border: unset !important; - border-radius: 0px !important; -} - -.multiselect::v-deep .multiselect__select { - background: unset !important; -} -</style>
\ No newline at end of file |
