mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-08 03:20:12 +08:00
feat: 119-Quest-Intro (#154)
- Add an introduction for every quest - Not yet written documents for the introduction, so the contents are from the information in the game
This commit is contained in:
3 files changed
+192
-63
No files matched your search
@@ -0,0 +1,121 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
import { useToast } from 'vue-toastification'
|
||||||
|
import { ref, defineProps, onMounted } from 'vue'
|
||||||
|
import { getQuest } from '../utils/api'
|
||||||
|
import MarkdownText from './MarkdownText.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
questTitle: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
questId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
questUnlocked: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const emit = defineEmits(['closeIntro'])
|
||||||
|
const showIntro = ref(true) // Controls whether the modal is visible or not
|
||||||
|
const router = useRouter()
|
||||||
|
const toast = useToast()
|
||||||
|
const { questTitle, questId, questUnlocked } = props
|
||||||
|
const questInstruction = ref('')
|
||||||
|
const questColor = ref('')
|
||||||
|
const questTextColor = ref('')
|
||||||
|
const closeModal = () => {
|
||||||
|
showIntro.value = false
|
||||||
|
emit('closeIntro', true)
|
||||||
|
router.push({ name: 'map' })
|
||||||
|
}
|
||||||
|
const handleQuests = (id: string) => {
|
||||||
|
if (questUnlocked) {
|
||||||
|
router.push({ name: 'game', params: { questId: id } })
|
||||||
|
} else {
|
||||||
|
toast.warning('你還沒完成前一個關卡!')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const handleColour = () => {
|
||||||
|
if (questUnlocked) {
|
||||||
|
questColor.value = '#00ff00'
|
||||||
|
questTextColor.value = '#000000'
|
||||||
|
} else {
|
||||||
|
questColor.value = '#505050'
|
||||||
|
questTextColor.value = '#a0a0a0'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onMounted(async () => {
|
||||||
|
const quest = await getQuest(questId)
|
||||||
|
questInstruction.value = quest.instruction
|
||||||
|
handleColour()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div v-if="showIntro" class="modal">
|
||||||
|
<div class="modal-content bg-bg-secondary rounded-lg">
|
||||||
|
<span class="close text-text content-between" @click="closeModal"
|
||||||
|
>×</span
|
||||||
|
>
|
||||||
|
|
||||||
|
<h2
|
||||||
|
id="QuestTitle"
|
||||||
|
class="text-2xl text-text mb-5 w-full inline-flex justify-center"
|
||||||
|
>
|
||||||
|
{{ questTitle }}
|
||||||
|
</h2>
|
||||||
|
<div class="w-full h-3/5 p-8">
|
||||||
|
<div class="px-5 whitespace-pre-wrap">
|
||||||
|
<MarkdownText
|
||||||
|
class="text-text markdown-content"
|
||||||
|
:content="questInstruction"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<a
|
||||||
|
id="StartAdventure"
|
||||||
|
target="_blank"
|
||||||
|
@click="handleQuests(questId)"
|
||||||
|
:style="{ backgroundColor: questColor, color: questTextColor }"
|
||||||
|
class="inline-flex justify-center rounded-lg text-base font-black py-3 mt-5 w-full text-lg"
|
||||||
|
>開始冒險</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: 10% auto;
|
||||||
|
padding: 20px;
|
||||||
|
width: 60%;
|
||||||
|
height: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 10px;
|
||||||
|
font-size: 30px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.markdown-content {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,13 +1,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, watch, computed } from 'vue'
|
import { ref, onMounted, watch, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
|
||||||
import { useToast } from 'vue-toastification'
|
import { useToast } from 'vue-toastification'
|
||||||
import { DAG } from '@linux-odyssey/utils'
|
import { DAG } from '@linux-odyssey/utils'
|
||||||
import { getQuests } from '../utils/api'
|
import { getQuests } from '../utils/api'
|
||||||
import useUserProfile from '../store/userProfile'
|
import useUserProfile from '../store/userProfile'
|
||||||
|
import QuestIntro from './QuestIntro.vue'
|
||||||
|
|
||||||
const store = useUserProfile()
|
const store = useUserProfile()
|
||||||
const router = useRouter()
|
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
|
|
||||||
const svgWidth = ref(window.innerWidth)
|
const svgWidth = ref(window.innerWidth)
|
||||||
@@ -18,7 +17,7 @@ const marginY = 120
|
|||||||
|
|
||||||
const nodeWidth = 160
|
const nodeWidth = 160
|
||||||
const nodeHeight = 50
|
const nodeHeight = 50
|
||||||
|
const opened = ref<Node | null>(null)
|
||||||
type Node = {
|
type Node = {
|
||||||
id: string
|
id: string
|
||||||
title: string
|
title: string
|
||||||
@@ -101,12 +100,12 @@ async function computeGraphData() {
|
|||||||
toast.error('Failed to load quest data')
|
toast.error('Failed to load quest data')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function handleNodeClick(node: Node) {
|
||||||
function handleNodeClick(node: { id: string; unlocked: boolean }) {
|
opened.value = node
|
||||||
if (node.unlocked) {
|
}
|
||||||
router.push({ name: 'game', params: { questId: node.id } })
|
function closeIntro(close: boolean) {
|
||||||
} else {
|
if (close) {
|
||||||
toast.warning('你還沒完成前一個關卡!')
|
opened.value = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,63 +144,69 @@ const edgeStyle = computed(() => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="relative bg-black w-full h-full flex">
|
<div class="relative bg-black w-full h-full flex">
|
||||||
<img
|
<div
|
||||||
src="../img/catelogbg.svg"
|
class="flex place-content-center w-full bg-catelogbg bg-cover bg-scroll"
|
||||||
alt="bg"
|
|
||||||
class="p-2 stroke-2 scale-y-120 justify-center items-center"
|
|
||||||
/>
|
|
||||||
<h1
|
|
||||||
class="p-10 absolute w-fit z-2 font-mono flex flax-wrap text-xl"
|
|
||||||
style="width: 30%; height: 6%; font-size: 3vh; color: #00ff00"
|
|
||||||
>
|
>
|
||||||
踏上你的Linux冒險之旅吧!
|
<h1
|
||||||
</h1>
|
class="p-10 absolute w-fit z-2 font-mono flex flax-wrap text-xl"
|
||||||
<svg
|
style="width: 30%; height: 6%; font-size: 3vh; color: #00ff00"
|
||||||
:width="svgWidth"
|
>
|
||||||
:height="svgHeight"
|
踏上你的Linux冒險之旅吧!
|
||||||
class="absolute"
|
</h1>
|
||||||
@mousedown="startDrag"
|
<svg
|
||||||
@mousemove="drag"
|
:width="svgWidth"
|
||||||
@mouseup="endDrag"
|
:height="svgHeight"
|
||||||
@mouseleave="endDrag"
|
class="absolute"
|
||||||
>
|
@mousedown="startDrag"
|
||||||
<g :transform="`translate(${offsetX}, ${offsetY})`">
|
@mousemove="drag"
|
||||||
<g
|
@mouseup="endDrag"
|
||||||
v-for="edge in graphData.edges"
|
@mouseleave="endDrag"
|
||||||
:key="`${edge.source.id}-${edge.target.id}`"
|
>
|
||||||
>
|
<g :transform="`translate(${offsetX}, ${offsetY})`">
|
||||||
<path
|
<g
|
||||||
:d="curvedPath(edge.source, edge.target)"
|
v-for="edge in graphData.edges"
|
||||||
:class="['edge', edgeStyle(edge)]"
|
:key="`${edge.source.id}-${edge.target.id}`"
|
||||||
/>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
v-for="node in graphData.nodes"
|
|
||||||
:key="node.id"
|
|
||||||
@click="handleNodeClick(node)"
|
|
||||||
>
|
|
||||||
<rect
|
|
||||||
:x="node.x - nodeWidth / 2"
|
|
||||||
:y="node.y - nodeHeight / 2"
|
|
||||||
:width="nodeWidth"
|
|
||||||
:height="nodeHeight"
|
|
||||||
rx="10"
|
|
||||||
ry="10"
|
|
||||||
:class="['node', nodeStyle(node)]"
|
|
||||||
/>
|
|
||||||
<text
|
|
||||||
:x="node.x"
|
|
||||||
:y="node.y"
|
|
||||||
text-anchor="middle"
|
|
||||||
alignment-baseline="middle"
|
|
||||||
:class="['node-text', nodeStyle(node)]"
|
|
||||||
font-size="18"
|
|
||||||
>
|
>
|
||||||
{{ node.title }}
|
<path
|
||||||
</text>
|
:d="curvedPath(edge.source, edge.target)"
|
||||||
|
:class="['edge', edgeStyle(edge)]"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<g
|
||||||
|
v-for="node in graphData.nodes"
|
||||||
|
:key="node.id"
|
||||||
|
@click="handleNodeClick(node)"
|
||||||
|
>
|
||||||
|
<rect
|
||||||
|
:x="node.x - nodeWidth / 2"
|
||||||
|
:y="node.y - nodeHeight / 2"
|
||||||
|
:width="nodeWidth"
|
||||||
|
:height="nodeHeight"
|
||||||
|
rx="10"
|
||||||
|
ry="10"
|
||||||
|
:class="['node', nodeStyle(node)]"
|
||||||
|
/>
|
||||||
|
<text
|
||||||
|
:x="node.x"
|
||||||
|
:y="node.y"
|
||||||
|
text-anchor="middle"
|
||||||
|
alignment-baseline="middle"
|
||||||
|
:class="['node-text', nodeStyle(node)]"
|
||||||
|
font-size="18"
|
||||||
|
>
|
||||||
|
{{ node.title }}
|
||||||
|
</text>
|
||||||
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</svg>
|
||||||
</svg>
|
<QuestIntro
|
||||||
|
:questTitle="opened.title"
|
||||||
|
:questId="opened.id"
|
||||||
|
:questUnlocked="opened.unlocked"
|
||||||
|
v-if="opened"
|
||||||
|
@close-intro="closeIntro"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export default {
|
|||||||
DEFAULT: '#FF0000',
|
DEFAULT: '#FF0000',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
backgroundImage: {
|
||||||
|
catelogbg: "url('src/img/catelogbg.svg')",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
plugins: [],
|
plugins: [],
|
||||||
|
|||||||
Reference in new issue
Block a user