mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
Merge branch 'ts-analytics' of https://github.com/linux-odyssey/linux-odyssey into ts-analytics
This commit is contained in:
commit
ca3d9b4236
23 files changed
+504
-180
No files matched your search
@@ -0,0 +1,28 @@
|
||||
<script setup>
|
||||
defineProps({
|
||||
url: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
icons: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
desciption: String,
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<a :href="url" target="_blank" class="h-10 w-10" :title="desciption">
|
||||
<font-awesome-icon
|
||||
:icon="icons"
|
||||
:alt="title"
|
||||
class="text-text-primary h-10 delay-130 hover:-translate-y-1 hover:scale-110"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
@@ -26,24 +26,6 @@ const handleLogout = async () => {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
const handleMap = async () => {
|
||||
try {
|
||||
router.push({ name: 'map' })
|
||||
} catch (err) {
|
||||
// toast.error('Failed to load map')
|
||||
toast.error('無法讀取地圖')
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
const handleLeaderBoard = async () => {
|
||||
try {
|
||||
router.push({ name: 'leaderboard' })
|
||||
} catch (err) {
|
||||
// toast.error('Failed to load map')
|
||||
toast.error('無法讀取排行榜')
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
|
||||
defineProps({
|
||||
title: {
|
||||
@@ -126,18 +108,18 @@ onMounted(async () => {
|
||||
class="text-text-primary h-full w-full"
|
||||
/>
|
||||
</a>
|
||||
<button title="LeaderBoard" @click="handleLeaderBoard" class="h-5 w-5">
|
||||
<RouterLink title="LeaderBoard" to="/leaderboard" class="h-5 w-5">
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'trophy']"
|
||||
class="text-text-primary h-full w-full"
|
||||
/>
|
||||
</button>
|
||||
<button title="Map" @click="handleMap" class="h-5 w-5">
|
||||
</RouterLink>
|
||||
<RouterLink title="Map" to="/map" class="h-5 w-5">
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'map']"
|
||||
class="text-text-primary h-full w-full"
|
||||
/>
|
||||
</button>
|
||||
</RouterLink>
|
||||
<a
|
||||
title="Bug Report"
|
||||
:href="bugReportUrl"
|
||||
|
||||
@@ -26,53 +26,65 @@ const right = () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="w-full flex items-center p-2">
|
||||
<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 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"
|
||||
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>
|
||||
<div id="hint" class="bg-bg flex flex-wrap p-8">
|
||||
<ul>
|
||||
<li
|
||||
v-for="hint in sessionStore.session.hints[current]"
|
||||
:key="hint"
|
||||
class="text-text font-xl whitespace-pre-wrap"
|
||||
>
|
||||
<MarkdownText :content="hint" />
|
||||
</li>
|
||||
</ul>
|
||||
<br />
|
||||
</div>
|
||||
</template>
|
||||
+2
-5
@@ -1,7 +1,4 @@
|
||||
export const surveyUrl = '/api/v1/survey'
|
||||
export const bugReportUrl = '/api/v1/bug-report'
|
||||
|
||||
export default {
|
||||
surveyUrl,
|
||||
bugReportUrl,
|
||||
}
|
||||
export const facebookUrl = 'https://www.facebook.com/LinuxOdyssey'
|
||||
export const githubUrl = 'https://github.com/linux-odyssey/linux-odyssey'
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M0 0H512V512H0V0Z" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_426_5" transform="scale(0.00195312)"/>
|
||||
</pattern>
|
||||
<image id="image0_426_5" width="512" height="512" xlink:href="data:image/png;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 32 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M0 0H512V512H0V0Z" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_424_19" transform="scale(0.00195312)"/>
|
||||
</pattern>
|
||||
<image id="image0_424_19" width="512" height="512" xlink:href="data:image/png;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 35 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="474" height="475" viewBox="0 0 474 475" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path d="M0 0H474V475H0V0Z" fill="url(#pattern0)"/>
|
||||
<defs>
|
||||
<pattern id="pattern0" patternContentUnits="objectBoundingBox" width="1" height="1">
|
||||
<use xlink:href="#image0_426_3" transform="matrix(0.00215972 0 0 0.00215517 -0.00105485 0)"/>
|
||||
</pattern>
|
||||
<image id="image0_426_3" width="464" height="464" xlink:href="data:image/png;base64,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 truncated
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 34 KiB |
+9
-2
@@ -18,13 +18,18 @@ import {
|
||||
faMap,
|
||||
faTrophy,
|
||||
faFileInvoice,
|
||||
faEnvelope,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faLightbulb,
|
||||
faCircleQuestion,
|
||||
faCircleRight,
|
||||
} from '@fortawesome/free-regular-svg-icons'
|
||||
import { faGithub, faGoogle } from '@fortawesome/free-brands-svg-icons'
|
||||
import {
|
||||
faFacebook,
|
||||
faGithub,
|
||||
faGoogle,
|
||||
} from '@fortawesome/free-brands-svg-icons'
|
||||
|
||||
/* add icons to the library */
|
||||
library.add(
|
||||
@@ -47,5 +52,7 @@ library.add(
|
||||
faCircleRight,
|
||||
faGithub,
|
||||
faGoogle,
|
||||
faFileInvoice
|
||||
faFileInvoice,
|
||||
faFacebook,
|
||||
faEnvelope
|
||||
)
|
||||
+3
-4
@@ -5,15 +5,14 @@ import RegisterPage from './views/RegisterPage.vue'
|
||||
import ChooseUsernamePage from './views/ChooseUsernamePage.vue'
|
||||
import QuestMapPage from './views/QuestMapPage.vue'
|
||||
import LeaderboardPage from './views/LeaderboardPage.vue'
|
||||
import LandingPage from './views/LandingPage.vue'
|
||||
import { isLoggedIn } from './utils/auth'
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
redirect: {
|
||||
name: 'register',
|
||||
},
|
||||
meta: { requiresGuest: true },
|
||||
name: 'landing-page',
|
||||
component: LandingPage,
|
||||
},
|
||||
{
|
||||
path: '/login',
|
||||
|
||||
+10
-18
@@ -20,6 +20,7 @@ function newSession() {
|
||||
pwd: '',
|
||||
hints: [],
|
||||
tasks: [],
|
||||
responses: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +42,7 @@ function setQuest(questId) {
|
||||
|
||||
async function setSession(session) {
|
||||
try {
|
||||
console.log('Setting session...', session)
|
||||
store.session = session
|
||||
store.session.graph = new FileGraph(session.graph)
|
||||
term.reset()
|
||||
@@ -85,16 +87,12 @@ function updateGraph(event) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateHints(hints) {
|
||||
store.session.hints.push(hints)
|
||||
}
|
||||
|
||||
function updateStatus(status) {
|
||||
store.session.status = status
|
||||
}
|
||||
|
||||
function setTasks(tasks) {
|
||||
store.session.tasks = tasks
|
||||
function newResponse(response) {
|
||||
console.log('newResponse', response, 'session', store.session)
|
||||
store.session.responses.push(response.responses)
|
||||
store.session.hints.push(response.hints)
|
||||
store.session.tasks = response.tasks
|
||||
store.session.status = response.status
|
||||
}
|
||||
|
||||
export function useTerminal() {
|
||||
@@ -135,14 +133,8 @@ function setup() {
|
||||
socket.on('graph', (event) => {
|
||||
updateGraph(event)
|
||||
})
|
||||
socket.on('hints', (event) => {
|
||||
updateHints(event)
|
||||
})
|
||||
socket.on('tasks', (tasks) => {
|
||||
setTasks(tasks)
|
||||
})
|
||||
socket.on('status', (event) => {
|
||||
updateStatus(event)
|
||||
socket.on('response', (response) => {
|
||||
newResponse(response)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ onMounted(async () => {
|
||||
<!-- main -->
|
||||
<div id="main" class="h-[94vh] w-full flex p-3 space-x-3">
|
||||
<!-- Topic and Command List -->
|
||||
<div class="bg-bg-secondary h-full w-1/3 rounded-lg">
|
||||
<div class="bg-bg-secondary h-full w-1/4 rounded-lg">
|
||||
<section id="quest" class="h-full p-3 overflow-auto">
|
||||
<QuestPart />
|
||||
</section>
|
||||
@@ -71,17 +71,17 @@ onMounted(async () => {
|
||||
</section> -->
|
||||
</div>
|
||||
<!-- Terminal and Hint -->
|
||||
<div class="bg-bg h-full w-1/3 rounded-lg">
|
||||
<section id="terminal" class="h-3/5 overflow-hidden">
|
||||
<TerminalPart />
|
||||
<div class="bg-bg h-full w-1/2 rounded-lg">
|
||||
<section id="hint" class="h-3/5">
|
||||
<HintPart />
|
||||
</section>
|
||||
<hr class="border-border border" />
|
||||
<section id="hint" class="h-2/5">
|
||||
<HintPart />
|
||||
<section id="terminal" class="h-2/5 overflow-hidden">
|
||||
<TerminalPart />
|
||||
</section>
|
||||
</div>
|
||||
<!-- Visualization -->
|
||||
<div class="h-full w-1/3">
|
||||
<div class="h-full w-1/4">
|
||||
<div class="bg-border rounded-xl h-[90%] p-2">
|
||||
<div class="bg-bg rounded-lg h-full p-2">
|
||||
<section id="visualization" class="bg-bg-primary h-full rounded-md">
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
<script setup>
|
||||
import ContactButton from '../components/ContactButton.vue'
|
||||
import DynamicBackground from '../components/DynamicBackground.vue'
|
||||
import { bugReportUrl, facebookUrl, githubUrl } from '../config'
|
||||
|
||||
const scrollToSection = (sectionId) => {
|
||||
const section = document.getElementById(sectionId)
|
||||
if (section) {
|
||||
const container = document.querySelector('.overflow-y-auto') // Replace '.overflow-y-auto' with the appropriate class or selector for your container
|
||||
if (container) {
|
||||
container.scrollTo({
|
||||
top: section.offsetTop - container.offsetTop,
|
||||
behavior: 'smooth', // Add smooth scrolling behavior if desired
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="w-screen h-screen bg-scroll bg-bg">
|
||||
<DynamicBackground class="w-full h-full" />
|
||||
<div class="h-screen w-screen absolute top-0 left-0">
|
||||
<!-- game header -->
|
||||
<div
|
||||
class="w-full h-[6vh] px-5 flex flex-row items-center justify-between bg-bg-secondary"
|
||||
>
|
||||
<div class="flex flex-row items-center gap-3">
|
||||
<img src="../img/icon_totem.svg" class="h-8 justify-self-start" />
|
||||
<button
|
||||
id="GameTitle"
|
||||
class="text-text-primary inline-block font-bold whitespace-nowrap pt-1"
|
||||
style="font-size: 2vh"
|
||||
>
|
||||
Linux Odyssey
|
||||
</button>
|
||||
</div>
|
||||
<div class="flex flex-row items-center gap-3">
|
||||
<button
|
||||
id="Intro"
|
||||
class="text-text inline-block font-bold whitespace-nowrap pt-1"
|
||||
@click="scrollToSection('intro')"
|
||||
style="font-size: 2vh"
|
||||
>
|
||||
Intro
|
||||
</button>
|
||||
<button
|
||||
id="Feature"
|
||||
class="text-text inline-block font-bold whitespace-nowrap pt-1"
|
||||
@click="scrollToSection('feature')"
|
||||
style="font-size: 2vh"
|
||||
>
|
||||
Feature</button
|
||||
><button
|
||||
id="Info"
|
||||
class="text-text inline-block font-bold whitespace-nowrap pt-1"
|
||||
@click="scrollToSection('about')"
|
||||
style="font-size: 2vh"
|
||||
>
|
||||
About
|
||||
</button>
|
||||
|
||||
<button
|
||||
id="Contact"
|
||||
class="text-text inline-block font-bold whitespace-nowrap pt-1"
|
||||
@click="scrollToSection('contact')"
|
||||
style="font-size: 2vh"
|
||||
>
|
||||
Contact Us
|
||||
</button>
|
||||
<RouterLink
|
||||
title="Sign In"
|
||||
id="SignIn"
|
||||
to="/register"
|
||||
class="text-text inline-block font-bold whitespace-nowrap pt-1"
|
||||
style="font-size: 2vh"
|
||||
>
|
||||
Sign In
|
||||
</RouterLink>
|
||||
<a
|
||||
title="Bug Report"
|
||||
:href="bugReportUrl"
|
||||
target="_blank"
|
||||
class="h-5 w-5"
|
||||
>
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'bug']"
|
||||
class="text-text-primary h-full w-full"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- main content -->
|
||||
<main class="w-full h-[94vh] overflow-y-auto">
|
||||
<div class="w-full flex flex-col items-center">
|
||||
<div
|
||||
class="flex flex-col md:flex-row mt-10 justify-around gap-5 items-center my-10"
|
||||
>
|
||||
<h1 class="text-text-primary text-7xl font-extrabold font-mono">
|
||||
Linux
|
||||
</h1>
|
||||
<img src="../img/icon_totem.svg" class="h-[30vh]" />
|
||||
<h1 class="text-text-primary text-7xl font-extrabold font-mono">
|
||||
Odyssey
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- intro -->
|
||||
<div class="w-full md:w-2/3 px-5">
|
||||
<section
|
||||
id="intro"
|
||||
class="text-text grid grid-cols-1 gap-3 justify-items-center"
|
||||
>
|
||||
<h1
|
||||
class="text-text inline-block bg-bg-primary p-5 rounded-xl opacity-90"
|
||||
style="font-size: 2.5vh"
|
||||
>
|
||||
程式設計不僅僅是寫程式碼,還涉及到如何有效地與你的電腦互動。
|
||||
程式教育卻往往忽略背後「開發環境」的重要性,
|
||||
使得許多學生在過程中充滿挫折。
|
||||
「開發環境」是寫程式、執行程式過程所需要的一切工具。
|
||||
而這些工具需要在終端機輸入指令來使用。
|
||||
大部分的程式要用來運行的環境,並非 Windows 或 Mac OS,而是 Linux
|
||||
作業系統。 Linux Odyssey 即是為此設計的線上 Linux 互動教學網站。
|
||||
</h1>
|
||||
</section>
|
||||
<section
|
||||
id="feature"
|
||||
class="text-text p-15 grid grid-rows-3 gap-10 items-center pt-10"
|
||||
>
|
||||
<div
|
||||
class="bg-bg-primary p-5 rounded-xl opacity-90 grid grid-cols-3 grid-rows-2 justify-items-center items-center"
|
||||
>
|
||||
<img
|
||||
src="../img/icon_shuttle.svg"
|
||||
class="h-[18vh] row-span-2"
|
||||
/>
|
||||
<h1
|
||||
class="text-text-primary p-5 font-extrabold font-mono col-span-2"
|
||||
style="font-size: 3.5vh"
|
||||
>
|
||||
初學者也能立即上手
|
||||
</h1>
|
||||
<h1 class="text-text p-2 col-span-2" style="font-size: 2.5vh">
|
||||
我們明白對於初學者來說,設定和使用開發工具可能是一大障礙。
|
||||
學習者無需安裝任何軟體,僅需通過瀏覽器即可連上一個真實的 Linux
|
||||
環境。
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="bg-bg-primary p-5 rounded-xl opacity-90 grid grid-cols-3 grid-rows-2 justify-items-center items-center"
|
||||
>
|
||||
<h1
|
||||
class="text-text-primary p-5 font-extrabold font-mono col-span-2"
|
||||
style="font-size: 3.5vh"
|
||||
>
|
||||
遊戲般的學習體驗
|
||||
</h1>
|
||||
<img src="../img/icon_sword.svg" class="h-[18vh] row-span-2" />
|
||||
<h1 class="text-text p-2 col-span-2" style="font-size: 2.5vh">
|
||||
Linux Odyssey 融合了遊戲的樂趣與學習的實用性。
|
||||
透過精心設計的遊戲關卡,學習者可以一邊接受挑戰、破解謎題,
|
||||
一邊掌握 Linux 指令的運用。
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
class="bg-bg-primary p-5 rounded-xl opacity-90 grid grid-cols-3 grid-rows-2 justify-items-center items-center"
|
||||
>
|
||||
<img
|
||||
src="../img/icon_settings.svg"
|
||||
class="h-[18vh] row-span-2"
|
||||
/>
|
||||
<h1
|
||||
class="text-text-primary p-5 font-extrabold font-mono col-span-2 pt-8"
|
||||
style="font-size: 3.5vh"
|
||||
>
|
||||
真實的 Linux 操作之旅
|
||||
</h1>
|
||||
<h1 class="text-text p-2 col-span-2" style="font-size: 2.5vh">
|
||||
Linux Odyssey 提供的不僅是理論知識,更是真實的操作體驗。
|
||||
每一個指令都是在真實的 Linux 環境中執行,
|
||||
讓學習者能夠真正深入軟體開發的世界。
|
||||
</h1>
|
||||
</div>
|
||||
</section>
|
||||
<section class="w-full py-10">
|
||||
<iframe
|
||||
src="https://www.youtube.com/embed/XzvfmUf1QP8?si=QxnKJMNjsm27fGGF"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
allowfullscreen
|
||||
class="w-full aspect-video"
|
||||
></iframe>
|
||||
</section>
|
||||
<section
|
||||
id="start"
|
||||
class="text-text bg-bg-secondary border-8 border-text-tertiary rounded-xl transition ease-in-out hover:scale-110 duration-300"
|
||||
>
|
||||
<RouterLink title="StartGame" id="StartGame" to="/register">
|
||||
<div class="text-text text-xl p-2 w-full text-center">
|
||||
準備好鍵盤,跟我們一起開啟你的 Linux 冒險吧!
|
||||
</div>
|
||||
</RouterLink>
|
||||
</section>
|
||||
<section
|
||||
id="about"
|
||||
class="text-text flex flex-col items-center mt-10 gap-10"
|
||||
>
|
||||
<div class="w-full flex flex-col items-center gap-2">
|
||||
<h1 class="text-text-primary text-3xl font-extrabold font-mono">
|
||||
團隊介紹
|
||||
</h1>
|
||||
<p class="text-lg">國立臺北科技大學 智慧自動化工程科 第二屆</p>
|
||||
<div class="flex flex-row gap-5 justify-center">
|
||||
<h1 class="font-bold">Wancat</h1>
|
||||
<h1 class="font-bold">Neil</h1>
|
||||
<h1 class="font-bold">A.H.</h1>
|
||||
<h1 class="font-bold">Ray Gur</h1>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full flex flex-col items-center gap-2">
|
||||
<h1 class="text-text-primary text-3xl font-extrabold font-mono">
|
||||
得獎肯定
|
||||
</h1>
|
||||
<h1 class="text-xl text-center">
|
||||
大專校院資訊應用服務創新競賽<br />
|
||||
鈦坦敏捷開發特別獎 第二名
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- <h1
|
||||
class="text-text-primary p-5 font-extrabold font-mono col-span-3 gap-3"
|
||||
style="font-size: 3.5vh"
|
||||
>
|
||||
玩家感想
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"很好玩很像益智遊戲"
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"我從來不知道自由軟體的存在,謝謝你們。"
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"還蠻好玩的,讓程式變好玩"
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"以前不喜歡電腦課現在覺得喜歡一點點"
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"程式真的對新手很友善,讓我學到了很多東西"
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"感覺做得很好很簡明易懂關卡設計很用心"
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"這個體驗我覺得非常有趣,可以體驗到沒辦法體驗的程式,學長也都熱心指導,非常感謝學長的用心的講解,希望以後還可以再體驗這種活動"
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"以前我覺得寫程式很難不過現在覺得很有趣"
|
||||
</h1>
|
||||
<h1 class="text-text p-3" style="font-size: 2.5vh">
|
||||
"以前覺得寫程式是我生活中碰不到而且很難的項目。但通過這堂課之後,覺得寫程式不再那麼難以親近"
|
||||
</h1> -->
|
||||
</section>
|
||||
</div>
|
||||
<section
|
||||
id="contact"
|
||||
class="text-text mt-10 flex flex-col items-center"
|
||||
>
|
||||
<h1 class="text-text-primary font-extrabold font-mono text-3xl">
|
||||
聯絡我們
|
||||
</h1>
|
||||
<div class="flex flex-row items-center justify-around mt-5 gap-5">
|
||||
<ContactButton
|
||||
:url="facebookUrl"
|
||||
:icons="['fab', 'facebook']"
|
||||
title="Facebook"
|
||||
desciption="Linux Odyssey"
|
||||
/>
|
||||
<ContactButton
|
||||
:url="githubUrl"
|
||||
:icons="['fab', 'github']"
|
||||
title="GitHub"
|
||||
desciption="linux-odyssey/linux-odyssey"
|
||||
/>
|
||||
<ContactButton
|
||||
url="mailto:team@linuxodyssey.xyz"
|
||||
:icons="['fas', 'envelope']"
|
||||
title="Email"
|
||||
desciption="team@linuxodyssey.xyz"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -1,5 +1,7 @@
|
||||
import { model, Schema, Types } from 'mongoose'
|
||||
|
||||
import { IResponse, responseSchema } from './response.js'
|
||||
|
||||
export interface INode {
|
||||
path: string
|
||||
type: string
|
||||
@@ -46,13 +48,14 @@ export interface ISession {
|
||||
user: Types.ObjectId
|
||||
quest: string
|
||||
containerId: string | null
|
||||
status: string
|
||||
status: 'active' | 'finished' | 'inactive'
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
finishedAt?: Date
|
||||
lastActivityAt: Date
|
||||
tasks: ITask[]
|
||||
hints: string[][]
|
||||
responses: IResponse[][]
|
||||
graph: INode
|
||||
}
|
||||
|
||||
@@ -85,6 +88,11 @@ export const Session = model<ISession>(
|
||||
},
|
||||
tasks: [taskSchema],
|
||||
hints: [[String]],
|
||||
responses: {
|
||||
type: [[responseSchema]],
|
||||
required: true,
|
||||
default: [],
|
||||
},
|
||||
graph: {
|
||||
type: nodeSchema,
|
||||
default: {
|
||||
|
||||
@@ -67,7 +67,7 @@ export function socialLoginHandler(req: Request, res: Response) {
|
||||
res.redirect('/choose-username')
|
||||
return
|
||||
}
|
||||
res.redirect('/')
|
||||
res.redirect('/map')
|
||||
}
|
||||
|
||||
export const registerFromSession = asyncHandler(
|
||||
|
||||
@@ -6,8 +6,6 @@ import { pushToSession } from '../socket.js'
|
||||
import { finishSession } from '../../models/sessionManager.js'
|
||||
import { asyncHandler } from '../../middleware/error.js'
|
||||
|
||||
const commandCompleteCallbacks = new Map()
|
||||
|
||||
export const newCommand = asyncHandler(async (req: Request, res: Response) => {
|
||||
const { command, pwd, output, error, params } = matchedData(req)
|
||||
|
||||
@@ -51,42 +49,14 @@ export const newCommand = asyncHandler(async (req: Request, res: Response) => {
|
||||
c.stage = response.stage
|
||||
await c.save()
|
||||
|
||||
if (response.stage === 'END') {
|
||||
session.responses.push(response.responses)
|
||||
|
||||
if ((session.status as string) === 'finished') {
|
||||
await finishSession(session)
|
||||
}
|
||||
commandCompleteCallbacks.set(session.id, response.callback)
|
||||
pushToSession(session.id, 'status', 'pending')
|
||||
res.status(201).json(response)
|
||||
} else {
|
||||
res.status(200).end()
|
||||
pushToSession(session.id, 'response', response)
|
||||
}
|
||||
|
||||
res.status(200).end()
|
||||
await session.save()
|
||||
})
|
||||
|
||||
export const completedCommand = asyncHandler(
|
||||
async (req: Request, res: Response) => {
|
||||
const { sessionId } = req.user as any
|
||||
|
||||
const session = await Session.findById(sessionId)
|
||||
|
||||
if (!session) {
|
||||
res.status(404).json({ message: 'session not found' })
|
||||
return
|
||||
}
|
||||
|
||||
if (session.status === 'inactive') {
|
||||
res.status(400).json({ message: 'session is not active' })
|
||||
return
|
||||
}
|
||||
|
||||
const callback = commandCompleteCallbacks.get(session.id)
|
||||
if (!callback) {
|
||||
res.status(200).json({ message: 'no callback found' })
|
||||
return
|
||||
}
|
||||
callback()
|
||||
commandCompleteCallbacks.delete(session.id)
|
||||
res.json({ message: 'ok' })
|
||||
}
|
||||
)
|
||||
@@ -25,6 +25,7 @@ function sessionDetail(session: ISession) {
|
||||
...sessionSummary(session),
|
||||
hints: session.hints,
|
||||
tasks: session.tasks,
|
||||
responses: session.responses,
|
||||
graph: session.graph,
|
||||
}
|
||||
}
|
||||
@@ -55,6 +56,7 @@ export const createSession = asyncHandler(
|
||||
export const getSessionById = asyncHandler(
|
||||
async (req: Request, res: Response) => {
|
||||
const { sessionId } = matchedData(req)
|
||||
console.log(sessionId)
|
||||
const session = await Session.findOne({
|
||||
_id: sessionId,
|
||||
user: (req.user as Express.ExistingUser).id,
|
||||
|
||||
@@ -5,6 +5,5 @@ import { newCommandValidator } from '../validators/commandValidator.js'
|
||||
const router = Router()
|
||||
|
||||
router.post('/', newCommandValidator, commandController.newCommand)
|
||||
router.post('/completed', commandController.completedCommand)
|
||||
|
||||
export default router
|
||||
@@ -22,6 +22,10 @@ router.post(
|
||||
activeSessionValidator,
|
||||
sessionController.getOrCreateSession
|
||||
)
|
||||
router.get('/:id', sessionDetailValidator, sessionController.getSessionById)
|
||||
router.get(
|
||||
'/:sessionId',
|
||||
sessionDetailValidator,
|
||||
sessionController.getSessionById
|
||||
)
|
||||
|
||||
export default router
|
||||
@@ -9,6 +9,6 @@ export const checkSessionId = () =>
|
||||
check('sessionId').notEmpty().isString().isMongoId()
|
||||
|
||||
export const sessionListValidator = [checkQuestId(), checkSessionStatus()]
|
||||
export const sessionDetailValidator = [checkSessionId(), noError]
|
||||
export const sessionDetailValidator = [checkSessionId()]
|
||||
export const createSessionValidator = [checkQuestId(), noError]
|
||||
export const activeSessionValidator = [checkQuestId(), noError]
|
||||
@@ -8,12 +8,12 @@ import type {
|
||||
IStage,
|
||||
ITask,
|
||||
} from '@linux-odyssey/models'
|
||||
import { pushToSession } from '../api/socket.js'
|
||||
|
||||
export interface ExecuteResult {
|
||||
responses: IResponse[]
|
||||
hints: string[]
|
||||
callback: () => void
|
||||
tasks?: ITask[]
|
||||
status: string
|
||||
stage?: string
|
||||
end?: boolean
|
||||
}
|
||||
@@ -79,15 +79,17 @@ export default class SessionHandler {
|
||||
|
||||
this.addNewTasks()
|
||||
|
||||
this.session.responses.push(stage.responses)
|
||||
|
||||
if (stage.id === 'END') {
|
||||
this.session.status = 'finished'
|
||||
}
|
||||
|
||||
return {
|
||||
responses: stage.responses,
|
||||
hints: stage.hints,
|
||||
callback: () => {
|
||||
pushToSession(this.session.id, 'tasks', this.session.tasks)
|
||||
if (stage.hints && stage.hints.length > 0)
|
||||
pushToSession(this.session.id, 'hints', stage.hints)
|
||||
pushToSession(this.session.id, 'status', this.session.status)
|
||||
},
|
||||
tasks: this.session.tasks,
|
||||
status: this.session.status,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,11 +100,7 @@ export default class SessionHandler {
|
||||
return {
|
||||
responses,
|
||||
hints,
|
||||
callback: () => {
|
||||
if (hints && hints.length > 0)
|
||||
pushToSession(this.session.id, 'hints', hints)
|
||||
pushToSession(this.session.id, 'status', this.session.status)
|
||||
},
|
||||
status: this.session.status,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,7 @@ describe('example helloworld app', () => {
|
||||
describe('Game Start Page UI', () => {
|
||||
const title = '終端機的基本知識'
|
||||
beforeEach(() => {
|
||||
cy.PrepareForGame()
|
||||
cy.PrepareForGame('get-started')
|
||||
})
|
||||
it('Check Header', () => {
|
||||
cy.get('#GameTitle').should('contain', 'Linux Odyssey').and('be.visible')
|
||||
@@ -124,7 +124,7 @@ describe('example helloworld app', () => {
|
||||
})
|
||||
describe('Game Play', () => {
|
||||
beforeEach(() => {
|
||||
cy.PrepareForGame()
|
||||
cy.PrepareForGame('get-started')
|
||||
})
|
||||
it('Typing in Terminal', () => {
|
||||
cy.typeInCommand('12345{enter}')
|
||||
@@ -136,7 +136,7 @@ describe('example helloworld app', () => {
|
||||
cy.checkTaskInit()
|
||||
cy.InitTerminal()
|
||||
cy.typeInCommand('echo start{enter}')
|
||||
cy.checkPending()
|
||||
// cy.checkPending()
|
||||
cy.waitUntilActive()
|
||||
cy.get('#Lbutton').should('be.visible').and('be.disabled')
|
||||
cy.get('#Rbutton').should('be.visible').and('be.disabled')
|
||||
@@ -144,7 +144,7 @@ describe('example helloworld app', () => {
|
||||
cy.getQuestInfo('✓ 輸入 echo start 來開始教程').should('be.visible')
|
||||
cy.getQuestInfo('➤ 在終端機輸入 echo hello').should('be.visible')
|
||||
cy.typeInCommand('echo hello{enter}')
|
||||
cy.checkPending()
|
||||
// cy.checkPending()
|
||||
cy.waitUntilActive()
|
||||
cy.get('#Lbutton').should('be.visible').and('be.enabled')
|
||||
cy.get('#Rbutton').should('be.visible').and('be.disabled')
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
describe('Complete quests with only commands', () => {
|
||||
beforeEach(() => {
|
||||
cy.PrepareForGame()
|
||||
})
|
||||
it('Complete quest : get-started', () => {
|
||||
cy.CompleteStageWithCommands('get-started')
|
||||
})
|
||||
|
||||
@@ -38,7 +38,7 @@ Cypress.Commands.add('LoginWithPassword', (username, password) => {
|
||||
cy.get('#password').type(password)
|
||||
cy.get('#LogInOrSignUp').click()
|
||||
})
|
||||
Cypress.Commands.add('PrepareForGame', () => {
|
||||
Cypress.Commands.add('PrepareForGame', (questId) => {
|
||||
cy.visit('/')
|
||||
// make sure the login page is loaded
|
||||
cy.url().should('satisfy', (elements: string) => {
|
||||
@@ -51,8 +51,9 @@ Cypress.Commands.add('PrepareForGame', () => {
|
||||
)
|
||||
// make sure the map page is loaded
|
||||
cy.url().should('include', '/map')
|
||||
cy.visit('/game/get-started')
|
||||
cy.url().should('include', '/game/get-started')
|
||||
const url = `/game/${questId}`
|
||||
cy.visit(url)
|
||||
cy.url().should('include', url)
|
||||
// check task first so the terminal has better possibility loaded
|
||||
cy.checkTaskInit()
|
||||
cy.get('.xterm-screen', { timeout: 10000 })
|
||||
@@ -114,8 +115,7 @@ Cypress.Commands.add('checkPending', () => {
|
||||
)
|
||||
})
|
||||
Cypress.Commands.add('CompleteStageWithCommands', (stagename) => {
|
||||
cy.visit(`/game/${stagename}`)
|
||||
cy.url().should('include', `/game/${stagename}`, { timeout: 50000 })
|
||||
cy.PrepareForGame(stagename)
|
||||
cy.log(`Completing stage :${stagename}`)
|
||||
cy.checkTaskInit()
|
||||
cy.CheckTextElement('#reset', '重來', 'Reset').click()
|
||||
@@ -123,19 +123,22 @@ Cypress.Commands.add('CompleteStageWithCommands', (stagename) => {
|
||||
cy.InitTerminal()
|
||||
cy.readFile(`../quests/${stagename}/answer.sh`, 'utf-8').then((answers) => {
|
||||
const answerarr = answers.split('\n')
|
||||
for (const element of answerarr) {
|
||||
answerarr.forEach((element, index) => {
|
||||
cy.typeInCommand(`${element}{enter}`)
|
||||
if (answerarr.indexOf(element) + 1 !== answerarr.length) {
|
||||
cy.checkPending()
|
||||
if (index < answerarr.length - 1) {
|
||||
cy.waitUntilActive()
|
||||
} else {
|
||||
cy.waitUntilActive(true).then(() => {
|
||||
cy.CheckTextElement(
|
||||
'#QuestCompleted',
|
||||
'關卡完成!',
|
||||
'Quest Completed!'
|
||||
)
|
||||
cy.get('div[class="modal"]').find('p').should('be.visible')
|
||||
cy.get('#BacktoMap').should('be.visible').and('contain', '回到地圖')
|
||||
})
|
||||
}
|
||||
if (answerarr.indexOf(element) + 1 === answerarr.length) {
|
||||
cy.waitUntilActive(true)
|
||||
cy.CheckTextElement('#QuestCompleted', '關卡完成!', 'Quest Completed!')
|
||||
cy.get('div[class="modal"]').find('p').should('be.visible')
|
||||
cy.get('#BacktoMap').should('be.visible').and('contain', '回到地圖')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
Cypress.Commands.add('CheckTextElement', (id, chText, enText) => {
|
||||
|
||||
Reference in new issue
Block a user