categorise components

This commit is contained in:
Annie Huang committed 2024-04-06 22:06:29 +08:00
1 parent 9a4f9b958a
commit b5e49547ea
14 files changed
+12 -12

No files matched your search

@@ -0,0 +1,7 @@
<template>
<font-awesome-icon :icon="['fas', 'list']" class="text-text-primary p-1" />
<h1 id="CommandBook" class="inline text-text-primary font-xl">指令之書</h1>
<div id="cheatsheets">
<p class="text-text-secondary">--指令列表--</p>
</div>
</template>
+111
View File
@@ -0,0 +1,111 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useToast } from 'vue-toastification'
import { ref } from 'vue'
import { Vue3Lottie } from 'vue3-lottie'
// import { surveyUrl } from '../config'
import CompleteAnimation from '../img/complete-animation.json'
const showModal = ref(true) // Controls whether the modal is visible or not
const router = useRouter()
const toast = useToast()
const closeModal = () => {
showModal.value = false
}
const defineComponent = () => ({
components: {
Vue3Lottie,
},
setup() {
return {
CompleteAnimation,
}
},
})
const backtoMap = async () => {
try {
router.push({ name: 'map' })
} catch (err) {
// useToast().error('Failed to load map')
toast.error('無法讀取地圖')
console.error(err)
}
}
// Simulate completing a quest to show the modal
setTimeout(() => {
showModal.value = true
}, 5000) // Show modal after 5 seconds
</script>
<template>
<div v-if="showModal" class="modal">
<div class="modal-content bg-bg-secondary rounded-lg">
<span class="close text-text" @click="closeModal">&times;</span>
<h2
id="QuestCompleted"
class="text-2xl text-text mb-5 w-full inline-flex justify-center"
>
關卡完成!
</h2>
<div class="z-10 right-1/2 top-1/2" @animationend="defineComponent">
<Vue3Lottie
:animationData="CompleteAnimation"
:height="200"
:width="200"
/>
</div>
<p class="text-base text-text text-center">恭喜你完成這一關!</p>
<p class="text-base text-text text-center">
接下來回到地圖,迎接新的挑戰吧!
</p>
<!-- <p class="text-base text-text">
您已成功完成挑戰,真的非常感謝您的參與!<br />
如果您能撥冗填寫這份問卷,我們將不勝感激,這對我們非常重要!
</p> -->
<!-- <a
id="SurveyButton"
:href="surveyUrl"
target="_blank"
class="inline-flex justify-center rounded-lg text-base font-black py-2 mt-3 bg-text-primary w-full"
>填寫問卷</a
> -->
<a
id="BacktoMap"
target="_blank"
@click="backtoMap"
class="inline-flex justify-center rounded-lg text-base font-black py-2 mt-3 bg-text-primary w-full"
>回到地圖</a
>
</div>
</div>
</template>
<style scoped>
.modal {
display: block;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.7);
z-index: 1;
}
.modal-content {
position: relative;
margin: 15% auto;
padding: 20px;
width: 30%;
}
.close {
position: absolute;
top: 0;
right: 10px;
font-size: 30px;
cursor: pointer;
}
</style>
@@ -0,0 +1,59 @@
<script setup lang="ts">
import { useRouter } from 'vue-router'
import { useToast } from 'vue-toastification'
import { createSession } from '../store/session'
import { surveyUrl } from '../config'
const router = useRouter()
const reset = async () => {
console.log('resetting...')
await createSession()
}
const continuePlay = async () => {
try {
router.push({ name: 'map' })
} catch (err) {
useToast().error('無法讀取地圖')
console.error(err)
}
}
</script>
<template>
<section class="h-[10%] p-3 grid grid-cols-4 gap-4 place-content-between">
<button id="survey" class="bg-bg-secondary rounded-lg p-2">
<a title="Survey" :href="surveyUrl" target="_blank" class="h-5 w-5">
<font-awesome-icon
:icon="['fas', 'file-invoice']"
class="text-text-primary"
/>
<p class="text-text-primary inline ml-1">問卷</p>
</a>
</button>
<button disabled id="solution" class="bg-bg-disabled rounded-lg p-2">
<font-awesome-icon
:icon="['far', 'circle-question']"
class="text-text-disabled"
/>
<p class="text-text-disabled inline ml-1">解答</p>
</button>
<button id="reset" class="bg-bg-secondary rounded-lg p-2" @click="reset">
<font-awesome-icon
:icon="['fas', 'arrow-rotate-left']"
class="text-text-primary"
/>
<p class="text-text-primary inline ml-1">重來</p>
</button>
<button
id="continue"
class="bg-bg-secondary rounded-lg p-2"
@click="continuePlay"
>
<font-awesome-icon
:icon="['far', 'circle-right']"
class="text-text-primary"
/>
<p class="text-text-primary inline ml-1">繼續</p>
</button>
</section>
</template>
+90
View File
@@ -0,0 +1,90 @@
<script setup lang="ts">
import { ref, watch } from 'vue'
import sessionStore from '../store/session'
import MarkdownText from './MarkdownText.vue'
const current = ref(-1)
watch(
() => sessionStore.session.hints,
(hints) => {
current.value = hints.length - 1
},
{ deep: true }
)
const left = () => {
if (current.value > 0) {
current.value -= 1
}
}
const right = () => {
if (current.value < sessionStore.session.hints.length - 1) {
current.value += 1
}
}
</script>
<template>
<div class="h-full flex flex-col">
<!-- Hint Header -->
<div class="flex items-center p-2 h-10">
<font-awesome-icon
:icon="['far', 'lightbulb']"
class="text-yellow-200 p-2 content-center"
/>
<h1 class="inline text-text w-1/2 font-xl p-2 m-1">
<!-- Hint -->
提示
</h1>
<div v-if="current !== -1" class="flex w-full justify-end items-end">
<button
class="p-2 m-1 w-1/8"
id="Lbutton"
@click="left"
:disabled="current <= 0"
>
<font-awesome-icon
:icon="['fas', 'arrow-right']"
class="text-text"
rotation="180"
/>
</button>
<p class="inline text-text font-xl w-1/14 p-2 m-1">{{ current + 1 }}</p>
<p class="inline text-text font-xl w-1/14 p-2 m-1">/</p>
<p class="inline text-text font-xl w-1/14 p-2 m-1">
{{ sessionStore.session.hints.length }}
</p>
<button
class="p-2 m-1 w-1/8"
id="Rbutton"
@click="right"
:disabled="current === sessionStore.session.hints.length - 1"
>
<font-awesome-icon :icon="['fas', 'arrow-right']" class="text-text" />
</button>
</div>
</div>
<div id="hint" class="bg-bg p-8 overflow-y-auto">
<ul>
<li
v-for="response in sessionStore.session.responses[current]"
:key="response.type"
class="text-text font-xl whitespace-pre-wrap"
>
<div v-for="content in response.content" :key="content">
<MarkdownText :content="content" />
</div>
</li>
<li
v-for="hint in sessionStore.session.hints[current]"
:key="hint"
class="text-text-primary font-xl whitespace-pre-wrap"
>
<MarkdownText :content="hint" />
</li>
</ul>
<br />
</div>
</div>
</template>
+43
View File
@@ -0,0 +1,43 @@
<script setup lang="ts">
import { computed } from 'vue'
import MarkdownText from './MarkdownText.vue'
import sessionStore from '../store/session'
import { Task } from '../types'
const tasks = computed(() => {
return sessionStore.session.tasks.map((task: Task) => {
const prefix = task.completed ? '✓' : '➤'
const color = task.completed ? 'text-text-primary' : 'text-text'
return {
...task,
class: `${color} markdown-content`,
content: `${prefix} ${task.name}`,
}
})
})
</script>
<template>
<div class="h-full w-full max-h-full">
<p id="topic" class="text-text font-black" v-if="sessionStore.quest">
{{ sessionStore.quest.title }}
</p>
<br />
<div>
<div v-if="sessionStore.quest" class="whitespace-pre-wrap">
<MarkdownText
class="text-text markdown-content"
:content="sessionStore.quest.instruction"
/>
<br />
<p id="tasks" class="text-text">任務:</p>
<ul v-if="sessionStore.session">
<li v-for="task in tasks" :key="task.id">
<MarkdownText :class="task.class" :content="task.content" />
</li>
</ul>
</div>
<p class="text-text" v-else>Loading...</p>
</div>
</div>
</template>
+23
View File
@@ -0,0 +1,23 @@
<script setup lang="ts">
import { onMounted, ref } from 'vue'
import { useTerminal } from '../store/session'
const terminal = ref<HTMLElement | null>(null)
onMounted(() => {
const term = useTerminal()
if (!terminal.value) return
term.mount(terminal.value)
window.addEventListener('resize', () => term.resizeScreen())
})
</script>
<template>
<font-awesome-icon :icon="['fas', 'terminal']" class="text-text p-1" />
<button id="Terminal" class="text-text h-[5%]">終端機</button>
<div
id="terminal"
ref="terminal"
class="bg-bg-secondary h-[calc(100%-5%)] p-0.5"
></div>
</template>
+126
View File
@@ -0,0 +1,126 @@
<script setup lang="ts">
import FileNode from './FileNode.vue'
import sessionStore from '../store/session'
</script>
<template>
<div class="tree w-max h-max">
<ul>
<li>
<FileNode
:node="sessionStore.session.graph"
:pwd="sessionStore.session.pwd"
/>
</li>
</ul>
</div>
</template>
<style>
.tree ul {
padding-top: 20px;
position: relative;
transition: all 0.5s;
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
}
.tree li {
display: inline-block;
text-align: center;
list-style-type: none;
position: relative;
padding: 20px 5px 0 5px;
vertical-align: top;
transition: all 0.5s;
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
}
.tree li::before,
.tree li::after {
content: '';
position: absolute;
top: 0;
right: 50%;
border-top: 1px solid #00ff00;
width: 50%;
height: 20px;
}
.tree li::after {
right: auto;
left: 50%;
border-left: 1px solid #00ff00;
}
.tree li:only-child::after,
.tree li:only-child::before {
display: none;
}
.tree li:only-child {
padding-top: 0;
}
.tree li:first-child::before,
.tree li:last-child::after {
border: 0 none;
}
.tree li:last-child::before {
border-right: 1px solid #00ff00;
border-radius: 0 5px 0 0;
-webkit-border-radius: 0 5px 0 0;
-moz-border-radius: 0 5px 0 0;
}
.tree li:first-child::after {
border-radius: 5px 0 0 0;
-webkit-border-radius: 5px 0 0 0;
-moz-border-radius: 5px 0 0 0;
}
.tree ul ul::before {
content: '';
position: absolute;
top: 0;
left: 50%;
border-left: 1px solid #00ff00;
width: 0;
height: 20px;
}
.tree li a {
border: 1px solid #00ff00;
padding: 5px 10px;
text-decoration: none;
color: #ffffff;
font-family: arial, verdana, tahoma;
font-size: 11px;
display: inline-block;
border-radius: 5px;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
transition: all 0.5s;
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
}
a.discovered {
background: #526b4e;
color: #000;
border: 1px solid #94a0b4;
}
ul.discovered,
li.discovered {
border-color: #94a0b4;
}
a.pwd {
box-shadow: 0 0 40px rgba(255, 255, 0, 20);
}
</style>
@@ -0,0 +1,61 @@
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import TreeChart from './TreeChart.vue'
const treeRef = ref<HTMLElement | null>(null)
let pos = { top: 0, left: 0, x: 0, y: 0 }
function mouseMoveHandler(e: MouseEvent) {
const dx = e.clientX - pos.x
const dy = e.clientY - pos.y
if (treeRef.value === null) return
treeRef.value.scrollTop = pos.top - dy
treeRef.value.scrollLeft = pos.left - dx
}
function mouseUpHandler() {
if (treeRef.value === null) return
treeRef.value.style.cursor = 'grab'
treeRef.value.style.removeProperty('user-select')
document.removeEventListener('mousemove', mouseMoveHandler)
document.removeEventListener('mouseup', mouseUpHandler)
}
function mouseDownHandler(e: MouseEvent) {
if (treeRef.value === null) return
treeRef.value.style.cursor = 'grabbing'
treeRef.value.style.userSelect = 'none'
pos = {
left: treeRef.value.scrollLeft,
top: treeRef.value.scrollTop,
x: e.clientX,
y: e.clientY,
}
document.addEventListener('mousemove', mouseMoveHandler)
document.addEventListener('mouseup', mouseUpHandler)
}
onMounted(() => {
if (treeRef.value === null) return
treeRef.value.style.cursor = 'grab'
treeRef.value.addEventListener('mousedown', mouseDownHandler)
})
onUnmounted(() => {
treeRef?.value?.removeEventListener('mousedown', mouseDownHandler)
})
</script>
<template>
<div
id="tree"
ref="treeRef"
class="h-full w-full overflow-auto cursor-grabbing"
>
<TreeChart />
</div>
</template>