diff options
| author | Leonardo Bishop <me@leonardobishop.com> | 2024-03-12 19:18:52 +0000 |
|---|---|---|
| committer | Leonardo Bishop <me@leonardobishop.com> | 2024-03-12 19:18:52 +0000 |
| commit | a4052ffee8bc7c6c8a69eba5120b5c6c2d951b0f (patch) | |
| tree | 970921e587c0972ed4bf8a82a18bbad8dee10458 | |
| parent | addf95bc7e1e694cd9ba7797c8b0847bfecaf54c (diff) | |
Add items
| -rw-r--r-- | components/base/ItemStack/ItemStackModal.vue | 19 | ||||
| -rw-r--r-- | components/base/ItemStack/ItemStackPicker.vue | 3 | ||||
| -rw-r--r-- | components/editor/EditorSidebar.vue | 16 | ||||
| -rw-r--r-- | components/editor/EditorSidebarItem.vue | 66 | ||||
| -rw-r--r-- | components/editor/task/EditorTaskConfigurationRow.vue | 2 | ||||
| -rw-r--r-- | components/header/PageHeader.vue | 56 | ||||
| -rw-r--r-- | data/questItemDefinitions.json | 52 | ||||
| -rw-r--r-- | data/testData.json | 9 | ||||
| -rw-r--r-- | layouts/editor.vue | 4 | ||||
| -rw-r--r-- | lib/questsLoader.ts | 12 | ||||
| -rw-r--r-- | pages/category/[id].vue | 53 | ||||
| -rw-r--r-- | pages/item/[id].vue | 52 | ||||
| -rw-r--r-- | pages/quest/[id].vue | 57 | ||||
| -rw-r--r-- | stores/session.ts | 57 |
14 files changed, 334 insertions, 124 deletions
diff --git a/components/base/ItemStack/ItemStackModal.vue b/components/base/ItemStack/ItemStackModal.vue index 7cf4db9..865c054 100644 --- a/components/base/ItemStack/ItemStackModal.vue +++ b/components/base/ItemStack/ItemStackModal.vue @@ -3,12 +3,11 @@ import { computed, ref } from 'vue'; import materials from '@/lib/materials'; const model = defineModel(); - const emit = defineEmits(['confirm']); - const props = defineProps<{ value: any }>(); +const session = useSessionStore(); //TODO unshitify const value = ref<any>(props.value); @@ -42,6 +41,17 @@ const selectedType = ref( const noTypeSelected = computed(() => selectedType.value === ''); const noValue = computed(() => !isQuestItem.value && !isItemStack.value && !isMaterial.value); +const selectedQuestItem = computed({ + get() { + return value.value?.['quest-item']; + }, + set(newValue: string) { + value.value = {} + value.value['quest-item'] = newValue; + } +}) +const knownQuestItems = computed(() => { return session.session.items.map((item) => item.id) }); + const setSelectedType = (type: string) => { if (type === 'questitem') { value.value = {}; @@ -98,6 +108,11 @@ const confirm = () => { <ItemStackForm v-model="value" /> </div> + <div id="quest-item" class="option-group" v-if="selectedType === 'questitem'"> + <label for="quest-item">Quest Item</label> + <multiselect v-model="selectedQuestItem" :options="knownQuestItems" :searchable="true" + placeholder="Enter quest item" /> + </div> <div id="confirm" class="control-group"> <Button :icon="['fas', 'times']" :label="'Cancel'" @click="model = false"></Button> diff --git a/components/base/ItemStack/ItemStackPicker.vue b/components/base/ItemStack/ItemStackPicker.vue index bb0b84d..2dc35d3 100644 --- a/components/base/ItemStack/ItemStackPicker.vue +++ b/components/base/ItemStack/ItemStackPicker.vue @@ -49,7 +49,8 @@ const update = (newValue: any) => { <template> <div class="itemstack" @click="showItemStackModal = true"> <span v-if="empty" class="empty">ItemStack...</span> - <span v-if="isQuestItem" class="item"><font-awesome-icon :icon="['fas', 'tag']" /> Quest Item</span> + <span v-if="isQuestItem" class="item"><font-awesome-icon :icon="['fas', 'tag']" /> Quest Item: {{ + value['quest-item'] }}</span> <span v-if="isItemStack" class="item"><font-awesome-icon :icon="['fas', 'cube']" /> ItemStack: {{ value.type || value.item || value.material }}</span> <span v-if="isMaterial" class="item"><font-awesome-icon :icon="['fas', 'apple-whole']" /> {{ value }}</span> diff --git a/components/editor/EditorSidebar.vue b/components/editor/EditorSidebar.vue index 992dd86..48e187f 100644 --- a/components/editor/EditorSidebar.vue +++ b/components/editor/EditorSidebar.vue @@ -24,7 +24,7 @@ const setSelectedType = (type: 'quests' | 'items') => { </span> <span class="option" @click="setSelectedType('items')" :class="{ selected: currentType === 'items' }"> <span> - <font-awesome-icon :icon="['fas', 'cube']" /> + <font-awesome-icon :icon="['fas', 'cubes']" /> Items </span> </span> @@ -34,6 +34,13 @@ const setSelectedType = (type: 'quests' | 'items') => { <EditorSidebarQuest v-for="quest in session.quests.filter((q) => (!session.categories.some((c) => c.id === q.options.category)))" :key="quest.id" :quest="quest" /> + <p id="count">{{ session.quests.length }} quest{{ session.quests.length === 1 ? '' : 's' }}, {{ + session.categories.length }} + categor{{ session.categories.length === 1 ? 'y' : 'ies' }}</p> + </div> + <div id="items" v-if="currentType === 'items'"> + <EditorSidebarItem v-for="item in session.items" :key="item.id" :item="item" /> + <p id="count">{{ session.items.length }} item{{ session.items.length === 1 ? '' : 's' }}</p> </div> <div id="configuration-container"> <EditorSidebarMainConfiguration /> @@ -99,5 +106,12 @@ const setSelectedType = (type: 'quests' | 'items') => { bottom: 0; width: 100% } + + #count { + margin: 0.5rem 0; + font-size: 0.7rem; + text-align: center; + color: var(--color-text-mute); + } } </style>
\ No newline at end of file diff --git a/components/editor/EditorSidebarItem.vue b/components/editor/EditorSidebarItem.vue new file mode 100644 index 0000000..4696bb1 --- /dev/null +++ b/components/editor/EditorSidebarItem.vue @@ -0,0 +1,66 @@ +<script setup lang="ts"> +import { computed, toRefs } from 'vue'; + +const props = defineProps<{ + item: EditorItem; +}>(); + +const { item } = toRefs(props); + +const route = useRoute(); + +const setSelectedItem = () => { + navigateTo({ path: `/item/${item.value.id}` }) +}; + +const selected = computed(() => { + return route.path.startsWith('/item') && route.params.id === item.value.id; +}); +</script> + +<template> + <div id="item-container" @click.stop="setSelectedItem" :class="{ selected: selected }"> + <span id="item-title"> + <font-awesome-icon class="item-icon" :icon="['fas', 'cube']" /> + <span id="item-name"> + <span id="item-display-id">{{ item.id }}</span> + <code id="item-display-type">{{ item.type }}</code> + </span> + </span> + </div> +</template> + +<style scoped> +#item-container { + cursor: pointer; + padding: 0.3rem 1rem; + transition: background-color 0.3s; + + #item-title { + display: flex; + align-items: center; + margin: 0; + gap: 0.6rem; + font-size: 0.8rem; + + #item-name { + display: flex; + flex-direction: column; + align-items: left; + + #item-display-type { + font-size: 0.6rem; + color: var(--color-text-mute); + } + } + } +} + +.selected { + background-color: var(--color-primary-mute) !important; +} + +#item-container:hover { + background-color: var(--color-hover); +} +</style>
\ No newline at end of file diff --git a/components/editor/task/EditorTaskConfigurationRow.vue b/components/editor/task/EditorTaskConfigurationRow.vue index 666669d..c00896f 100644 --- a/components/editor/task/EditorTaskConfigurationRow.vue +++ b/components/editor/task/EditorTaskConfigurationRow.vue @@ -41,7 +41,7 @@ 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 error = computed(() => currentValue.value === undefined || currentValue.value === null || currentValue.value === '' || (Array.isArray(currentValue.value) && currentValue.value.length === 0) || (typeof currentValue.value === 'object' && Object.keys(currentValue.value).length === 0)); const updateValue = (value: any) => { currentValue.value = value; }; diff --git a/components/header/PageHeader.vue b/components/header/PageHeader.vue new file mode 100644 index 0000000..3c616ca --- /dev/null +++ b/components/header/PageHeader.vue @@ -0,0 +1,56 @@ +<template> + <div id="header"> + <slot /> + </div> +</template> + +<style lang="scss"> +#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; + position: relative; + z-index: 0; + + #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); +} +</style>
\ No newline at end of file diff --git a/data/questItemDefinitions.json b/data/questItemDefinitions.json new file mode 100644 index 0000000..1174d51 --- /dev/null +++ b/data/questItemDefinitions.json @@ -0,0 +1,52 @@ +{ + "questItems": { + "raw": { + "description": "Items imported from in-game using /q a items import.", + "selectable": false + }, + "defined": { + "description": "Normal items from Minecraft.", + "defined": true + }, + "mmoitems": { + "description": "ItemStacks which belong to the MMOItems plugin.", + "configuration": { + "type": { + "type": "string", + "description": "The MMOItems type for this item." + }, + "id": { + "type": "string", + "description": "The MMOItems ID for this item." + } + } + }, + "slimefun": { + "description": "ItemStacks which belong to the Slimefun plugin.", + "configuration": { + "id": { + "type": "string", + "description": "The Slimefun item ID for this item." + } + } + }, + "executableitems": { + "description": "ItemStacks which belong to the ExecutableItems plugin.", + "configuration": { + "id": { + "type": "string", + "description": "The ExecutableItems ID for this item." + } + } + }, + "itemsadder": { + "description": "ItemStacks which belong to the ItemsAdder plugin.", + "configuration": { + "id": { + "type": "string", + "description": "The ItemsAdder ID for this item." + } + } + } + } +}
\ No newline at end of file diff --git a/data/testData.json b/data/testData.json index c01d6c3..268a780 100644 --- a/data/testData.json +++ b/data/testData.json @@ -380,5 +380,14 @@ }, "permission-required": true } + }, + "items": { + "coolsword": { + "type": "defined", + "configuration": { + "type": "DIAMOND_SWORD", + "name": "Super cool sword" + } + } } }
\ No newline at end of file diff --git a/layouts/editor.vue b/layouts/editor.vue index 884160f..7275651 100644 --- a/layouts/editor.vue +++ b/layouts/editor.vue @@ -1,6 +1,6 @@ <script setup lang="ts"> import { useSessionStore } from '@/stores/session'; -import { loadQuestsFromJson, loadCategoriesFromJson } from '@/lib/questsLoader'; +import { loadQuestsFromJson, loadCategoriesFromJson, loadItemsFromJson } from '@/lib/questsLoader'; import testData from '@/data/testData.json'; import taskDefinitions from '@/data/taskDefinitions.json'; @@ -8,9 +8,11 @@ const sessionStore = useSessionStore(); const quests = loadQuestsFromJson(testData.quests); const categories = loadCategoriesFromJson(testData.categories); +const items = loadItemsFromJson(testData.items); sessionStore.setQuests(quests); sessionStore.setCategories(categories); +sessionStore.setItems(items); sessionStore.setTaskDefinitions(taskDefinitions.taskTypes); // sessionStore.updateEditorCategories(); </script> diff --git a/lib/questsLoader.ts b/lib/questsLoader.ts index a4ea64f..296afc3 100644 --- a/lib/questsLoader.ts +++ b/lib/questsLoader.ts @@ -67,6 +67,18 @@ export function loadCategoriesFromJson(config: any): EditorCategory[] { }); } +export function loadItemsFromJson(config: any): EditorItem[] { + return Object.keys(config).map((itemid: any) => { + const item = config[itemid]; + + return { + id: itemid, + type: item.type, + config: item.config, + }; + }); +} + //TODO don't write fields if they're unchanged export function mapJsonQuestToYamlObject(quest: EditorQuest): any { return { diff --git a/pages/category/[id].vue b/pages/category/[id].vue index 3570a15..da68bb5 100644 --- a/pages/category/[id].vue +++ b/pages/category/[id].vue @@ -15,7 +15,7 @@ const categoryName = sessionStore.getCategoryById(categoryId)?.display.name; </script> <template> - <div id="header"> + <PageHeader> <span id="path"> <font-awesome-icon class="icon" :icon="['fas', 'fa-folder']" /> <span class="title" v-if="categoryName">{{ stripColorCodes(categoryName) }} </span> @@ -24,7 +24,7 @@ const categoryName = sessionStore.getCategoryById(categoryId)?.display.name; <span id="controls" class="control-group"> <Button type="solid" :disabled="true" :icon="['fas', 'fa-save']" :label="'Save'"></Button> </span> - </div> + </PageHeader> <div id="options-container"> <EditorCategoryOptionsPanel :categoryId="categoryId" /> @@ -33,55 +33,6 @@ const categoryName = sessionStore.getCategoryById(categoryId)?.display.name; </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; - position: relative; - z-index: 0; - - #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; diff --git a/pages/item/[id].vue b/pages/item/[id].vue new file mode 100644 index 0000000..f1dbb95 --- /dev/null +++ b/pages/item/[id].vue @@ -0,0 +1,52 @@ +<script setup lang="ts"> +import { useSessionStore } from '@/stores/session'; +import { stripColorCodes } from '@/lib/util'; + +definePageMeta({ + layout: 'editor' +}) + +const sessionStore = useSessionStore(); +const route = useRoute(); + +const itemId = route.params.id as string; + +const item = sessionStore.getItemById(itemId); +</script> + +<template> + <PageHeader> + <span id="path"> + <font-awesome-icon class="icon" :icon="['fas', 'cube']" /> + <span class="title">{{ itemId }} </span> + </span> + <span id="controls" class="control-group"> + <Button type="solid" :disabled="true" :icon="['fas', 'fa-save']" :label="'Save'"></Button> + </span> + </PageHeader> + + <div id="options-container"> + </div> +</template> + +<style scoped> +#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: auto; + max-height: calc(100% - 55px); +} + +header { + border-bottom: 1px solid var(--color-border); +} +</style>
\ No newline at end of file diff --git a/pages/quest/[id].vue b/pages/quest/[id].vue index a9b2074..080f232 100644 --- a/pages/quest/[id].vue +++ b/pages/quest/[id].vue @@ -2,7 +2,7 @@ import { useSessionStore } from '@/stores/session'; import { computed, ref } from 'vue'; import { navigateToEditorPane, stripColorCodes } from '@/lib/util'; -import type Yaml from '~/components/editor/quest/modal/Yaml.vue'; +import type EditorQuestModalYaml from '~/components/editor/quest/modal/EditorQuestModalYaml.vue'; definePageMeta({ layout: 'editor' @@ -24,7 +24,7 @@ const categoryFromSelectedQuest = computed(() => { } }); -const yamlModal = ref<InstanceType<typeof Yaml> | null>(null); +const yamlModal = ref<InstanceType<typeof EditorQuestModalYaml> | null>(null); const showDeleteModal = ref(false); const showRenameModal = ref(false); const showDuplicateModal = ref(false); @@ -53,7 +53,7 @@ const showYaml = () => { </script> <template> - <div id="header"> + <PageHeader> <span id="path"> <template v-if="categoryFromSelectedQuest"> <font-awesome-icon class="icon" :icon="['fas', 'fa-folder']" /> @@ -71,7 +71,7 @@ const showYaml = () => { <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> + </PageHeader> <div id="options-container"> <EditorQuestOptionsPanel :questId="questId" /> @@ -88,55 +88,6 @@ const showYaml = () => { </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; - position: relative; - z-index: 0; - - #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; diff --git a/stores/session.ts b/stores/session.ts index acd2c66..7822547 100644 --- a/stores/session.ts +++ b/stores/session.ts @@ -58,6 +58,12 @@ export interface EditorCategory { permissionRequired: string; } +export interface EditorItem { + id: string; + type: string; + config: any +} + export interface TaskDefinition { description: string; icon: { @@ -75,13 +81,27 @@ export interface TaskDefinition { } } +export interface QuestItemDefinition { + description: string; + selectable: boolean; + defined: boolean; + configuration: { + [key: string]: { + type: string | string[]; + description: string; + } + } +} + export const useSessionStore = defineStore('session', { state: () => ({ sessionType: '', session: { quests: [] as EditorQuest[], categories: [] as EditorCategory[], + items: [] as EditorItem[], taskDefinitions: {} as { [key: string]: TaskDefinition }, + questItemDefinitions: {} as { [key: string]: QuestItemDefinition } } }), getters: { @@ -94,6 +114,9 @@ export const useSessionStore = defineStore('session', { getCategories(): EditorCategory[] { return this.session.categories }, + getItems(): EditorItem[] { + return this.session.items + }, getQuestById: (state) => (id: string) => { if (!id) return null; return state.session.quests.find(quest => quest.id === id) @@ -102,6 +125,10 @@ export const useSessionStore = defineStore('session', { if (!id) return null; return state.session.categories.find(quest => quest.id === id) }, + getItemById: (state) => (id: string) => { + if (!id) return null; + return state.session.items.find(item => item.id === id); + }, getQuestsInCategory: (state) => (id: string) => { if (!id) return []; return state.session.quests.filter(quest => quest.options.category === id) @@ -114,10 +141,16 @@ export const useSessionStore = defineStore('session', { }, getKnownTaskTypes: (state) => () => { return Object.keys(state.session.taskDefinitions) + }, + getQuestItemDefintions: (state) => { + return state.session.questItemDefinitions + }, + getQuestItemDefinitionByTaskType: (state) => (type: string) => { + return state.session.questItemDefinitions[type] + }, + getKnownQuestItemTypes: (state) => () => { + return Object.keys(state.session.questItemDefinitions) } - // getEditorCategories: (state) => { - // return state.editor.categories - // } }, actions: { setSessionType(type: string) { @@ -129,18 +162,14 @@ export const useSessionStore = defineStore('session', { setCategories(categories: EditorCategory[]) { this.session.categories = categories }, - // updateEditorCategories() { - // const categories: { [key: string]: { quests: string[] } } = {} - // this.session.categories.forEach(category => { - // categories[category.id] = { quests: [] } - // }) - // this.session.quests.forEach(quest => { - // categories[quest.options.category].quests.push(quest.id) - // }) - // this.editor.categories = categories; - // }, + setItems(items: EditorItem[]) { + this.session.items = items + }, setTaskDefinitions(definitions: { [key: string]: TaskDefinition }) { - this.session.taskDefinitions = definitions + this.session.taskDefinitions = definitions; + }, + setQuestItemDefinitions(definitions: { [key: string]: QuestItemDefinition }) { + this.session.questItemDefinitions = definitions; }, changeQuestId(oldId: string, newId: string) { const quest = this.getQuestById(oldId); |
