diff options
Diffstat (limited to 'components')
| -rw-r--r-- | components/base/ItemStack/ItemStackModal.vue | 5 | ||||
| -rw-r--r-- | components/base/Pulser.vue | 56 | ||||
| -rw-r--r-- | components/editor/EditorSidebarCategory.vue | 1 | ||||
| -rw-r--r-- | components/editor/EditorSidebarQuest.vue | 3 | ||||
| -rw-r--r-- | components/editor/quest/EditorQuestOptionsPanel.vue | 2 | ||||
| -rw-r--r-- | components/editor/task/EditorTaskConfiguration.vue | 2 | ||||
| -rw-r--r-- | components/editor/task/modal/EditorTaskModalCreate.vue | 1 | ||||
| -rw-r--r-- | components/header/SiteHeader.vue | 18 | ||||
| -rw-r--r-- | components/loader/LoaderFileSystemButton.vue | 40 | ||||
| -rw-r--r-- | components/loader/LoaderFileSystemModal.vue | 81 | ||||
| -rw-r--r-- | components/loader/LoaderTestDataButton.vue | 17 | ||||
| -rw-r--r-- | components/loader/LoaderTestDataModal.vue | 52 |
12 files changed, 271 insertions, 7 deletions
diff --git a/components/base/ItemStack/ItemStackModal.vue b/components/base/ItemStack/ItemStackModal.vue index 865c054..2a53926 100644 --- a/components/base/ItemStack/ItemStackModal.vue +++ b/components/base/ItemStack/ItemStackModal.vue @@ -47,7 +47,9 @@ const selectedQuestItem = computed({ }, set(newValue: string) { value.value = {} - value.value['quest-item'] = newValue; + if (newValue) { + value.value['quest-item'] = newValue; + } } }) const knownQuestItems = computed(() => { return session.session.items.map((item) => item.id) }); @@ -102,6 +104,7 @@ const confirm = () => { <div id="material" class="option-group" v-if="selectedType === 'material'"> <label for="material">Material</label> <multiselect v-model="value" :options="materials" :searchable="true" placeholder="Enter material name" /> + <p>Any items of this material will be matched.</p> </div> <div id="itemstack" class="option-group" v-if="selectedType === 'itemstack'"> diff --git a/components/base/Pulser.vue b/components/base/Pulser.vue new file mode 100644 index 0000000..796b3cc --- /dev/null +++ b/components/base/Pulser.vue @@ -0,0 +1,56 @@ +<template> + <div class="circles"> + <div class="circle1"></div> + <div class="circle2"></div> + <div class="circle3"></div> + </div> +</template> + +<style scoped> +.circles { + position: relative; + height: 100px; + width: 100px; + + >div { + animation: growAndFade 3s infinite ease-out; + background-color: var(--color-primary); + border-radius: 50%; + height: 100%; + opacity: 0; + position: absolute; + width: 100%; + } + + .circle1 { + animation-delay: 1s; + } + + .circle2 { + animation-delay: 2s; + } + + .circle3 { + animation-delay: 3s; + } +} + +@keyframes growAndFade { + 0% { + opacity: .25; + transform: scale(0); + } + + 100% { + opacity: 0; + transform: scale(1); + } +} + +body { + align-items: center; + display: flex; + justify-content: center; + margin: 0; +} +</style>
\ No newline at end of file diff --git a/components/editor/EditorSidebarCategory.vue b/components/editor/EditorSidebarCategory.vue index 9dedf33..cfabf39 100644 --- a/components/editor/EditorSidebarCategory.vue +++ b/components/editor/EditorSidebarCategory.vue @@ -1,7 +1,6 @@ <script setup lang="ts"> import { useSessionStore, type EditorCategory } from '@/stores/session'; import { computed, ref, toRefs } from 'vue'; -import { stripColorCodes } from '@/lib/util'; const props = defineProps<{ category: EditorCategory; diff --git a/components/editor/EditorSidebarQuest.vue b/components/editor/EditorSidebarQuest.vue index 422e8c6..85edb38 100644 --- a/components/editor/EditorSidebarQuest.vue +++ b/components/editor/EditorSidebarQuest.vue @@ -1,7 +1,6 @@ <script setup lang="ts"> -import { useSessionStore, type EditorQuest } from '@/stores/session'; +import type { EditorQuest } from '@/stores/session'; import { computed, toRefs } from 'vue'; -import { stripColorCodes } from '@/lib/util'; const props = defineProps<{ quest: EditorQuest; diff --git a/components/editor/quest/EditorQuestOptionsPanel.vue b/components/editor/quest/EditorQuestOptionsPanel.vue index 5aaff23..6c1c8b1 100644 --- a/components/editor/quest/EditorQuestOptionsPanel.vue +++ b/components/editor/quest/EditorQuestOptionsPanel.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { useSessionStore, type EditorQuest } from '@/stores/session'; -import { computed, ref } from 'vue'; +import { computed } from 'vue'; const props = defineProps<{ questId: string; diff --git a/components/editor/task/EditorTaskConfiguration.vue b/components/editor/task/EditorTaskConfiguration.vue index bda75f4..5313767 100644 --- a/components/editor/task/EditorTaskConfiguration.vue +++ b/components/editor/task/EditorTaskConfiguration.vue @@ -32,7 +32,7 @@ const requiredFields = computed(() => { // }); const remainingGivenFields = computed(() => { - return Object.keys(taskConfig.value).filter((fieldName) => !requiredFields.value.includes(fieldName)); + return Object.keys(taskConfig.value).filter((fieldName) => !requiredFields.value.includes(fieldName) && fieldName in taskDefintion.value.configuration); }); const configKeysOptions = computed(() => Object.keys(taskDefintion.value.configuration).filter((key) => !Object.keys(taskConfig.value).some((fieldName) => fieldName === key))); diff --git a/components/editor/task/modal/EditorTaskModalCreate.vue b/components/editor/task/modal/EditorTaskModalCreate.vue index 30e4cca..e5b2d7a 100644 --- a/components/editor/task/modal/EditorTaskModalCreate.vue +++ b/components/editor/task/modal/EditorTaskModalCreate.vue @@ -1,7 +1,6 @@ <script setup lang="ts"> import { computed, ref } from 'vue'; import { useSessionStore } from '@/stores/session'; -import { validateTaskId } from '@/lib/util'; const model = defineModel(); diff --git a/components/header/SiteHeader.vue b/components/header/SiteHeader.vue index 0773a03..75686d8 100644 --- a/components/header/SiteHeader.vue +++ b/components/header/SiteHeader.vue @@ -1,3 +1,6 @@ +<script setup land="ts"> +</script> + <template> <header> <div id="nav"> @@ -5,6 +8,11 @@ <h1>Quests Web Editor</h1> <code>Preview</code> </div> + + <div id="controls"> + <LoaderTestDataButton /> + <LoaderFileSystemButton /> + </div> </header> </template> @@ -36,11 +44,21 @@ } +#controls { + padding: 1rem; + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; +} + header { border-bottom: 1px solid var(--color-border); background-color: var(--color-header); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); position: relative; z-index: 1; + display: flex; + justify-content: space-between; } </style>
\ No newline at end of file diff --git a/components/loader/LoaderFileSystemButton.vue b/components/loader/LoaderFileSystemButton.vue new file mode 100644 index 0000000..0d02477 --- /dev/null +++ b/components/loader/LoaderFileSystemButton.vue @@ -0,0 +1,40 @@ +<script setup lang="ts"> +import type LoaderFileSystemModal from './LoaderFileSystemModal.vue'; + +const loaderStore = useLoaderStore(); + +const { canUseFsApi } = getBrowserCapabilities(); + +const fileSystemModal = ref<InstanceType<typeof LoaderFileSystemModal> | null>(null); + +const openFileSystemPrompt = async () => { + fileSystemModal.value?.open(); + loaderStore.setFileSystemLoaderStatus('pending'); + const dirHandle = await openFileSystem(); + if (!dirHandle) { + loaderStore.setFileSystemLoaderStatus('inactive'); + return; + } + loaderStore.setPath(dirHandle.name); + loaderStore.setFileSystemLoaderStatus('loaded'); + try { + const { categories, quests, items } = await enumerateQuestDirectory(dirHandle); + loaderStore.setCategories(categories); + loaderStore.setQuests(quests); + loaderStore.setItems(items); + loaderStore.setFileSystemLoaderStatus('valid'); + } catch (e) { + console.error(e); + loaderStore.setFileSystemLoaderStatus('invalid'); + } +} +</script> + +<template> + <ClientOnly> + <Button type="solid" :icon="['fas', 'folder-open']" label="Import from Filesystem" @click="openFileSystemPrompt" + :disabled="!canUseFsApi" /> + + <LoaderFileSystemModal ref="fileSystemModal" /> + </ClientOnly> +</template>
\ No newline at end of file diff --git a/components/loader/LoaderFileSystemModal.vue b/components/loader/LoaderFileSystemModal.vue new file mode 100644 index 0000000..27f4e12 --- /dev/null +++ b/components/loader/LoaderFileSystemModal.vue @@ -0,0 +1,81 @@ +<script setup lang="ts"> +const loader = useLoaderStore(); +const session = useSessionStore(); + +const showModal = ref(false); + +const open = () => { + showModal.value = true; +} + +const confirm = () => { + const categories = loader.getCategories(); + const quests = loader.getQuests(); + const items = loader.getItems(); + + session.setCategories(categories); + session.setQuests(quests); + session.setItems(items); + + showModal.value = false; +} + +const status = computed(() => loader.getFileSystemLoaderStatus()); +const questsCount = computed(() => loader.getQuests().length); +const categoriesCount = computed(() => loader.getCategories().length); +const itemsCount = computed(() => loader.getItems().length); +const path = computed(() => loader.getPath()); + +defineExpose({ + open +}) +</script> + +<template> + <Modal v-model="showModal"> + <template v-slot:header> + <h2>Import from Filesystem</h2> + </template> + + <div v-if="status === 'pending'"> + <p>Select the Quests plugin data directory.</p> + <p>Waiting for selection...</p> + </div> + + <div v-if="status === 'inactive'"> + <p>The request was aborted.</p> + </div> + + <div v-if="status === 'loaded'"> + <p>Parsing files in directory <code>{{ path }}</code>...</p> + </div> + + <div v-if="status === 'invalid'"> + <p>You have selected an invalid directory.</p> + </div> + + <div v-if="status === 'valid'"> + <p>Successfully parsed directory <code>{{ path }}</code>.</p> + <ul> + <li>{{ categoriesCount }} categories loaded</li> + <li>{{ questsCount }} quests loaded</li> + <li>{{ itemsCount }} items loaded</li> + </ul> + <p>You are about to replace your current workspace. Are you sure you want to continue?</p> + </div> + + <div id="controls" class="control-group"> + <Button :icon="['fas', 'xmark']" :label="'Cancel'" @click="showModal = false"></Button> + <Button v-if="status === 'valid'" type="solid" :icon="['fas', 'check']" :label="'Confirm'" + @click="confirm"></Button> + </div> + </Modal> +</template> + +<style scoped> +#controls { + display: flex; + justify-content: flex-end; + margin-top: 1rem; +} +</style>
\ No newline at end of file diff --git a/components/loader/LoaderTestDataButton.vue b/components/loader/LoaderTestDataButton.vue new file mode 100644 index 0000000..b01b0ee --- /dev/null +++ b/components/loader/LoaderTestDataButton.vue @@ -0,0 +1,17 @@ +<script setup lang="ts"> +import type LoaderTestDataModal from './LoaderTestDataModal.vue'; + +const testDataModal = ref<InstanceType<typeof LoaderTestDataModal> | null>(null); + +const openTestDataModal = async () => { + testDataModal.value?.open(); +} +</script> + +<template> + <ClientOnly> + <Button :icon="['fas', 'flask-vial']" label="Demo" @click="openTestDataModal" /> + + <LoaderTestDataModal ref="testDataModal" /> + </ClientOnly> +</template>
\ No newline at end of file diff --git a/components/loader/LoaderTestDataModal.vue b/components/loader/LoaderTestDataModal.vue new file mode 100644 index 0000000..36d2d6d --- /dev/null +++ b/components/loader/LoaderTestDataModal.vue @@ -0,0 +1,52 @@ +<script setup lang="ts"> +import { loadCategoriesFromJson, loadItemsFromJson, loadQuestsFromJson } from '~/lib/questsLoader'; +import testData from '@/data/testData.json'; + +const session = useSessionStore(); + +const showModal = ref(false); + +const open = () => { + showModal.value = true; +} + +const confirm = () => { + const quests = loadQuestsFromJson(testData.quests); + const categories = loadCategoriesFromJson(testData.categories); + const items = loadItemsFromJson(testData.items); + + session.setQuests(quests); + session.setCategories(categories); + session.setItems(items); + + showModal.value = false; +} + +defineExpose({ + open +}) +</script> + +<template> + <Modal v-model="showModal"> + <template v-slot:header> + <h2>Import test data</h2> + </template> + + <p>You can view a demo of the Quests editor by loading test data. This will replace your current workspace. + Do you want to continue?</p> + + <div id="controls" class="control-group"> + <Button :icon="['fas', 'xmark']" :label="'Cancel'" @click="showModal = false"></Button> + <Button type="solid" :icon="['fas', 'check']" :label="'Confirm'" @click="confirm"></Button> + </div> + </Modal> +</template> + +<style scoped> +#controls { + display: flex; + justify-content: flex-end; + margin-top: 1rem; +} +</style>
\ No newline at end of file |
