aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--components/base/ItemStack/ItemStackModal.vue19
-rw-r--r--components/base/ItemStack/ItemStackPicker.vue3
-rw-r--r--components/editor/EditorSidebar.vue16
-rw-r--r--components/editor/EditorSidebarItem.vue66
-rw-r--r--components/editor/task/EditorTaskConfigurationRow.vue2
-rw-r--r--components/header/PageHeader.vue56
-rw-r--r--data/questItemDefinitions.json52
-rw-r--r--data/testData.json9
-rw-r--r--layouts/editor.vue4
-rw-r--r--lib/questsLoader.ts12
-rw-r--r--pages/category/[id].vue53
-rw-r--r--pages/item/[id].vue52
-rw-r--r--pages/quest/[id].vue57
-rw-r--r--stores/session.ts57
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);