mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-06 03:20:13 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b41e67ee0b | ||
|
|
50a0bbbab1 | ||
|
|
d712925d0f | ||
|
|
b78398680d | ||
|
|
c7f52b1003 | ||
|
|
f008fb9f60 | ||
|
|
76a39add29 | ||
|
|
eb9118d5b1 | ||
|
|
cfdf8730e8 | ||
|
|
6651071580 | ||
|
|
f30f6472f5 | ||
|
|
5ac3b3088d | ||
|
|
acc4d5acba | ||
|
|
1fb43b78e3 | ||
|
|
b27adc1f69 | ||
|
|
6f68107405 |
No files matched your search
@@ -90,7 +90,7 @@ const handleChange = () => {
|
||||
class="bg-bg border-8 border-bg-primary flex flex-1 flex-col items-center justify-center rounded-3xl p-10"
|
||||
>
|
||||
<h1 class="text-text-primary text-3xl font-black mb-2">{{ title }}</h1>
|
||||
<div class="text-error inline-flex justify-center items-center xl:hidden">
|
||||
<div class="text-error inline-flex justify-center items-center md:hidden">
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'circle-exclamation']"
|
||||
class="px-1 text-sm"
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
<template>
|
||||
<section class="h-[10%] p-3 grid grid-cols-3 gap-4 place-content-between">
|
||||
<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">
|
||||
<!-- Survey -->
|
||||
問卷
|
||||
</p>
|
||||
</a>
|
||||
</button>
|
||||
<button disabled id="solution" class="bg-bg-disabled rounded-lg p-2">
|
||||
<font-awesome-icon
|
||||
:icon="['far', 'circle-question']"
|
||||
@@ -41,6 +53,7 @@
|
||||
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 () => {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { defineProps, onMounted } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useToast } from 'vue-toastification'
|
||||
import { logout } from '../utils/auth'
|
||||
import { bugReportUrl } from '../config'
|
||||
import { bugReportUrl, surveyUrl } from '../config'
|
||||
import { reset } from '../store/session'
|
||||
import userProfileStore, {
|
||||
loadUserProfile,
|
||||
@@ -35,6 +35,15 @@ const handleMap = async () => {
|
||||
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: {
|
||||
@@ -110,6 +119,18 @@ onMounted(async () => {
|
||||
>
|
||||
{{ userProfileStore.username }}
|
||||
</p>
|
||||
<a title="Survey" :href="surveyUrl" target="_blank" class="h-5 w-5">
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'file-invoice']"
|
||||
class="text-text-primary h-full w-full"
|
||||
/>
|
||||
</a>
|
||||
<button title="LeaderBoard" @click="handleLeaderBoard" 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">
|
||||
<font-awesome-icon
|
||||
:icon="['fas', 'map']"
|
||||
|
||||
@@ -15,8 +15,8 @@ import { DAG } from '@linux-odyssey/utils'
|
||||
import api from '../utils/api'
|
||||
import { NodeImage } from '../img/svg.js'
|
||||
|
||||
const marginX = 200
|
||||
const marginY = 65
|
||||
const marginX = 500
|
||||
const marginY = 300
|
||||
const chartContainer = ref(null)
|
||||
let chartInstance = null
|
||||
const fullwidth = window.screen.width
|
||||
@@ -71,9 +71,10 @@ const genOption = (nodes, edges) => ({
|
||||
symbol: () => {
|
||||
return NodeImage
|
||||
},
|
||||
symbolSize: [fullwidth / 10, fullwidth / 25],
|
||||
symbolSize: [fullwidth / 13, fullwidth / 30],
|
||||
roam: 'move',
|
||||
zoom: 1,
|
||||
zoom: 1.5,
|
||||
center: ['150%', '50%'],
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: fullwidth / 90,
|
||||
@@ -103,7 +104,7 @@ const genOption = (nodes, edges) => ({
|
||||
if (unlocked) {
|
||||
return '#ADADB5'
|
||||
}
|
||||
return '454552'
|
||||
return '#505050'
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
+5
-1
@@ -16,6 +16,8 @@ import {
|
||||
faBug,
|
||||
faCircleExclamation,
|
||||
faMap,
|
||||
faTrophy,
|
||||
faFileInvoice,
|
||||
} from '@fortawesome/free-solid-svg-icons'
|
||||
import {
|
||||
faLightbulb,
|
||||
@@ -38,10 +40,12 @@ library.add(
|
||||
faArrowRight,
|
||||
faBug,
|
||||
faMap,
|
||||
faTrophy,
|
||||
faCircleExclamation,
|
||||
faLightbulb,
|
||||
faCircleQuestion,
|
||||
faCircleRight,
|
||||
faGithub,
|
||||
faGoogle
|
||||
faGoogle,
|
||||
faFileInvoice
|
||||
)
|
||||
@@ -159,7 +159,7 @@ stages:
|
||||
- ls-mkdir
|
||||
condition:
|
||||
pwd:
|
||||
- notes
|
||||
- ^/home/commander/notes$
|
||||
responses:
|
||||
- type: narrative
|
||||
content:
|
||||
|
||||
@@ -44,7 +44,7 @@ stages:
|
||||
- condition:
|
||||
not:
|
||||
pwd:
|
||||
- /home/commander
|
||||
- ^/home/commander$
|
||||
responses:
|
||||
- type: narrative
|
||||
content:
|
||||
@@ -78,11 +78,11 @@ stages:
|
||||
- ls
|
||||
condition:
|
||||
pwd:
|
||||
- /home/commander/folder1
|
||||
- ^/home/commander/folder1$
|
||||
exceptions:
|
||||
- condition:
|
||||
pwd:
|
||||
- /home/commander
|
||||
- ^/home/commander$
|
||||
responses:
|
||||
- type: narrative
|
||||
content:
|
||||
@@ -90,7 +90,7 @@ stages:
|
||||
- condition:
|
||||
not:
|
||||
pwd:
|
||||
- /home/commander
|
||||
- ^/home/commander$
|
||||
responses:
|
||||
- type: narrative
|
||||
content:
|
||||
@@ -113,7 +113,7 @@ stages:
|
||||
- cd-folder1
|
||||
condition:
|
||||
pwd:
|
||||
- /home/commander/folder1
|
||||
- ^/home/commander/folder1$
|
||||
command:
|
||||
- ls
|
||||
responses:
|
||||
@@ -131,7 +131,7 @@ stages:
|
||||
- ls-folder1
|
||||
condition:
|
||||
pwd:
|
||||
- /home/commander
|
||||
- ^/home/commander$
|
||||
responses:
|
||||
- content:
|
||||
- 你成功的回到上一層資料夾了。
|
||||
@@ -146,7 +146,7 @@ stages:
|
||||
- cd-..
|
||||
condition:
|
||||
pwd:
|
||||
- /home/commander/folder2
|
||||
- ^/home/commander/folder2$
|
||||
responses:
|
||||
- content:
|
||||
- 你成功的進入 `folder2` 資料夾了。
|
||||
@@ -160,7 +160,7 @@ stages:
|
||||
- cd-folder2
|
||||
condition:
|
||||
pwd:
|
||||
- /home/commander/folder2
|
||||
- ^/home/commander/folder2$
|
||||
command:
|
||||
- ls
|
||||
responses:
|
||||
@@ -176,7 +176,7 @@ stages:
|
||||
- ls-folder2
|
||||
condition:
|
||||
pwd:
|
||||
- /home/commander
|
||||
- ^/home/commander$
|
||||
responses:
|
||||
- content:
|
||||
- 你成功的回到原本的資料夾了。
|
||||
|
||||
@@ -17,6 +17,14 @@ stages:
|
||||
hints:
|
||||
- '!! 請勿使用全形空格 !!'
|
||||
|
||||
- id: warning-02
|
||||
repeatable: true
|
||||
condition:
|
||||
command:
|
||||
- '`'
|
||||
hints:
|
||||
- '!! 不需要輸入上下引號 !!'
|
||||
|
||||
- id: start
|
||||
task: 輸入 `echo start` 來開始教程
|
||||
condition:
|
||||
|
||||
@@ -64,14 +64,12 @@ stages:
|
||||
- ➤ 請輸入 `cd ../folder2` 來進入 `folder2` 資料夾。
|
||||
|
||||
- id: cd-folder2
|
||||
task: 用指定方式進入 `folder2` 資料夾
|
||||
task: 進入 `folder2` 資料夾
|
||||
requirements:
|
||||
- cd-folder1
|
||||
condition:
|
||||
pwd:
|
||||
- ^/home/commander/folder2$
|
||||
command:
|
||||
- ^cd \.\./folder2$
|
||||
exceptions:
|
||||
- condition:
|
||||
not:
|
||||
@@ -95,10 +93,8 @@ stages:
|
||||
requirements:
|
||||
- cd-folder2
|
||||
condition:
|
||||
pwd:
|
||||
- ^/home/commander/folder2$
|
||||
command:
|
||||
- ^ls \.\.\/folder1$
|
||||
output:
|
||||
- file1.txt
|
||||
exceptions:
|
||||
- condition:
|
||||
not:
|
||||
@@ -107,7 +103,7 @@ stages:
|
||||
responses:
|
||||
- type: narrative
|
||||
content:
|
||||
- 請先回到 `folder2` 資料夾,並用指定的方式查看 `folder1` 資料夾的內容。
|
||||
- 進到 `folder1` 資料夾並用 `ls` 查看內容。
|
||||
responses:
|
||||
- content:
|
||||
- 很好,你現在知道如何運用相對路徑了。
|
||||
|
||||
@@ -44,19 +44,19 @@ stages:
|
||||
responses:
|
||||
- content:
|
||||
- 這個資料夾中有一個檔案和一個資料夾。
|
||||
- 現在你可以使用 `cat` 指令來查看 `file.txt` 的內容。
|
||||
- 現在你可以使用 `cat` 指令來查看 `file1.txt` 的內容。
|
||||
hints:
|
||||
- ➤ 請輸入 `cat file.txt` 來查看檔案的內容。
|
||||
- ➤ 請輸入 `cat file1.txt` 來查看檔案的內容。
|
||||
|
||||
- id: cat-file
|
||||
task: 查看 `file.txt` 的內容
|
||||
- id: cat-file1
|
||||
task: 查看 `file1.txt` 的內容
|
||||
requirements:
|
||||
- ls-~
|
||||
condition:
|
||||
pwd:
|
||||
- ^/home/commander$
|
||||
command:
|
||||
- ^cat file.txt$
|
||||
- ^cat file1.txt$
|
||||
responses:
|
||||
- content:
|
||||
- 你應該能看到檔案的內容了。
|
||||
@@ -68,7 +68,7 @@ stages:
|
||||
- id: ls-folder
|
||||
task: 查看 `folder` 資料夾
|
||||
requirements:
|
||||
- cat-file
|
||||
- cat-file1
|
||||
condition:
|
||||
pwd:
|
||||
- ^/home/commander$
|
||||
|
||||
@@ -50,9 +50,18 @@ export default class CommandHandler extends SessionHandler {
|
||||
}
|
||||
|
||||
async discoverHandler() {
|
||||
const graph = new FileGraph(this.session.graph)
|
||||
graph.discover(this.params.discover)
|
||||
this.session.graph = graph
|
||||
try {
|
||||
const graph = new FileGraph(this.session.graph)
|
||||
graph.discover(this.params.discover)
|
||||
this.session.graph = graph
|
||||
} catch (e) {
|
||||
logger.error('Update file graph failed', {
|
||||
error: e,
|
||||
session: this.session.id,
|
||||
graph: this.session.graph,
|
||||
discover: this.params.discover,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async isMatch(condition) {
|
||||
@@ -111,37 +120,46 @@ export default class CommandHandler extends SessionHandler {
|
||||
}
|
||||
|
||||
async run() {
|
||||
this.quest = await Quest.findById(this.session.quest)
|
||||
const stages = this.getStages()
|
||||
if (stages.length === 0) {
|
||||
logger.warn('stage not found', this.session.tasks)
|
||||
return {}
|
||||
}
|
||||
|
||||
this.handleEvent()
|
||||
this.handleCommand()
|
||||
|
||||
const matches = await Promise.all(
|
||||
stages.map((stage) => this.isMatch(stage.condition))
|
||||
)
|
||||
|
||||
const stage = stages.find((_, i) => matches[i])
|
||||
if (!stage) {
|
||||
for (const s of stages) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const exception = await this.checkException(s)
|
||||
if (exception) {
|
||||
return this.executeException(exception)
|
||||
}
|
||||
try {
|
||||
this.quest = await Quest.findById(this.session.quest)
|
||||
const stages = this.getStages()
|
||||
if (stages.length === 0) {
|
||||
logger.warn('stage not found', this.session.tasks)
|
||||
return {}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
const response = this.execute(stage)
|
||||
|
||||
return {
|
||||
stage: stage.id,
|
||||
end: this.session.status === 'finished',
|
||||
...response,
|
||||
this.handleEvent()
|
||||
this.handleCommand()
|
||||
|
||||
const matches = await Promise.all(
|
||||
stages.map((stage) => this.isMatch(stage.condition))
|
||||
)
|
||||
|
||||
const stage = stages.find((_, i) => matches[i])
|
||||
if (!stage) {
|
||||
for (const s of stages) {
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
const exception = await this.checkException(s)
|
||||
if (exception) {
|
||||
return this.executeException(exception)
|
||||
}
|
||||
}
|
||||
return {}
|
||||
}
|
||||
const response = this.execute(stage)
|
||||
|
||||
return {
|
||||
stage: stage.id,
|
||||
end: this.session.status === 'finished',
|
||||
...response,
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error('CommandHandler error', {
|
||||
error: err,
|
||||
session: this.session.id,
|
||||
command: this.commandInput,
|
||||
})
|
||||
return {}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user