aboutsummaryrefslogtreecommitdiffstats
path: root/components/editor/quest
diff options
context:
space:
mode:
authorLeonardo Bishop <me@leonardobishop.com>2024-03-10 01:31:42 +0000
committerLeonardo Bishop <me@leonardobishop.com>2024-03-10 01:31:42 +0000
commit71e4ad2c71efea471923ea47f01bfda841387f81 (patch)
treef08293fdc56b4eb2e3d0e520b79b4d8aad78924c /components/editor/quest
parent4495c02c41b95ce6df0c34dbf6ac62f7addae7a3 (diff)
Use nuxt auto import magic
Diffstat (limited to 'components/editor/quest')
-rw-r--r--components/editor/quest/OptionsPanel.vue131
-rw-r--r--components/editor/quest/TasksOptionsPanel.vue84
-rw-r--r--components/editor/quest/modal/Delete.vue30
-rw-r--r--components/editor/quest/modal/Duplicate.vue58
-rw-r--r--components/editor/quest/modal/Rename.vue58
5 files changed, 361 insertions, 0 deletions
diff --git a/components/editor/quest/OptionsPanel.vue b/components/editor/quest/OptionsPanel.vue
new file mode 100644
index 0000000..de32abb
--- /dev/null
+++ b/components/editor/quest/OptionsPanel.vue
@@ -0,0 +1,131 @@
+<script setup lang="ts">
+import { useSessionStore, type EditorQuest } from '@/stores/session';
+import { computed, ref } from '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/components/editor/quest/TasksOptionsPanel.vue b/components/editor/quest/TasksOptionsPanel.vue
new file mode 100644
index 0000000..7742408
--- /dev/null
+++ b/components/editor/quest/TasksOptionsPanel.vue
@@ -0,0 +1,84 @@
+<script setup lang="ts">
+import { useSessionStore, type EditorQuest } from '@/stores/session';
+import { computed, ref } from '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>
+ <EditorTaskConfiguration 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/components/editor/quest/modal/Delete.vue b/components/editor/quest/modal/Delete.vue
new file mode 100644
index 0000000..47c6388
--- /dev/null
+++ b/components/editor/quest/modal/Delete.vue
@@ -0,0 +1,30 @@
+<script setup lang="ts">
+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/components/editor/quest/modal/Duplicate.vue b/components/editor/quest/modal/Duplicate.vue
new file mode 100644
index 0000000..e089222
--- /dev/null
+++ b/components/editor/quest/modal/Duplicate.vue
@@ -0,0 +1,58 @@
+<script setup lang="ts">
+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/components/editor/quest/modal/Rename.vue b/components/editor/quest/modal/Rename.vue
new file mode 100644
index 0000000..7339219
--- /dev/null
+++ b/components/editor/quest/modal/Rename.vue
@@ -0,0 +1,58 @@
+<script setup lang="ts">
+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