mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-08 03:20:12 +08:00
Merge pull request #24 from linux-odyssey/app-neil
feat: add Authentication Page
This commit is contained in:
13 files changed
+265
-73
No files matched your search
+4
-1
@@ -15,12 +15,15 @@ module.exports = {
|
|||||||
sourceType: 'module',
|
sourceType: 'module',
|
||||||
},
|
},
|
||||||
rules: {
|
rules: {
|
||||||
'prettier/prettier': ['error', { endOfLine: 'auto', semi: false }], // We added this
|
|
||||||
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
|
||||||
'import/extensions': 'off',
|
'import/extensions': 'off',
|
||||||
camelcase: ['error', { properties: 'never', ignoreDestructuring: true }],
|
camelcase: ['error', { properties: 'never', ignoreDestructuring: true }],
|
||||||
'no-param-reassign': ['error', { props: false }],
|
'no-param-reassign': ['error', { props: false }],
|
||||||
'no-underscore-dangle': ['error', { allow: ['_id'] }],
|
'no-underscore-dangle': ['error', { allow: ['_id'] }],
|
||||||
|
/* we added this */
|
||||||
|
'no-var': 'error',
|
||||||
|
'no-plusplus': 'off',
|
||||||
|
'prettier/prettier': ['error', { endOfLine: 'auto', semi: false }],
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"socket.io-client": "^4.7.1",
|
"socket.io-client": "^4.7.1",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
|
"vue-router": "^4.2.4",
|
||||||
"xterm": "^5.2.1",
|
"xterm": "^5.2.1",
|
||||||
"xterm-addon-attach": "^0.8.0",
|
"xterm-addon-attach": "^0.8.0",
|
||||||
"xterm-addon-fit": "^0.7.0"
|
"xterm-addon-fit": "^0.7.0"
|
||||||
|
|||||||
+7
-65
@@ -1,73 +1,15 @@
|
|||||||
<script setup>
|
|
||||||
import { onMounted } from 'vue'
|
|
||||||
import CommandlistPart from './components/CommandlistPart.vue'
|
|
||||||
import HeaderPart from './components/HeaderPart.vue'
|
|
||||||
import HintPart from './components/HintPart.vue'
|
|
||||||
import QuestPart from './components/QuestPart.vue'
|
|
||||||
import TerminalPart from './components/TerminalPart.vue'
|
|
||||||
import VisualizationPart from './components/VisualizationPart.vue'
|
|
||||||
import ControlPalette from './components/ControlPalette.vue'
|
|
||||||
import socket from './utils/socket'
|
|
||||||
import sessionManager from './utils/session'
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
await sessionManager.lastOrCreate()
|
|
||||||
socket.connect(sessionManager.getSession())
|
|
||||||
socket.on('connect', function open() {
|
|
||||||
console.log('Connected to the server.')
|
|
||||||
})
|
|
||||||
socket.on('disconnect', function close() {
|
|
||||||
console.log('Disconnected from the server.')
|
|
||||||
})
|
|
||||||
socket.on('message', console.log)
|
|
||||||
socket.on('graph', (event) => {
|
|
||||||
sessionManager.handleGraphUpdate(event)
|
|
||||||
})
|
|
||||||
socket.on('tasks', (tasks) => {
|
|
||||||
sessionManager.setTasks(tasks)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="bg-background-primary h-screen">
|
<div class="bg-background-primary h-screen">
|
||||||
<!--header-->
|
<!--header-->
|
||||||
<div id="header" class="bg-background-secondary content-center h-[6vh] p-1">
|
<div id="header" class="bg-background-secondary content-center h-[6vh] p-1">
|
||||||
<HeaderPart />
|
<HeaderPart />
|
||||||
</div>
|
</div>
|
||||||
<!--main-->
|
<!-- route outlet -->
|
||||||
<div id="main" class="h-[94vh] grid grid-cols-3 gap-3 p-3">
|
<!-- component matched by the route will render here -->
|
||||||
<!--Topic and Command List-->
|
<router-view></router-view>
|
||||||
<div class="bg-background-secondary h-full rounded-lg">
|
|
||||||
<section id="quest" class="h-2/3 m-3">
|
|
||||||
<QuestPart />
|
|
||||||
</section>
|
|
||||||
<hr class="border-text-line border" />
|
|
||||||
<section id="cmdlist" class="h-1/3">
|
|
||||||
<CommandlistPart />
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<!--Terminal and Hint-->
|
|
||||||
<div class="bg-background h-full rounded-lg">
|
|
||||||
<section id="terminal" class="h-3/5">
|
|
||||||
<TerminalPart />
|
|
||||||
</section>
|
|
||||||
<section id="hint" class="h-2/5">
|
|
||||||
<HintPart />
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<!--Visualization-->
|
|
||||||
<div class="h-full">
|
|
||||||
<div class="bg-border rounded-xl h-[90%] px-3 py-6">
|
|
||||||
<div class="bg-background rounded-xl h-full px-1 py-6">
|
|
||||||
<section id="visualization" class="bg-background-primary h-full">
|
|
||||||
<VisualizationPart />
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<ControlPalette />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script></script>
|
|
||||||
|
<script setup>
|
||||||
|
import HeaderPart from './components/HeaderPart.vue'
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<script setup></script>
|
||||||
|
<template>
|
||||||
|
<!--login page-->
|
||||||
|
<div
|
||||||
|
class="bg-background border-8 border-background-primary flex flex-1 flex-col items-center justify-center rounded-3xl p-10"
|
||||||
|
>
|
||||||
|
<h1 class="text-text-primary text-3xl font-black mb-8">Linux Odyssey</h1>
|
||||||
|
<form action="" class="w-full">
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="email" class="text-sm font-semibold text-text">Email</label
|
||||||
|
><input
|
||||||
|
type="email"
|
||||||
|
id="email"
|
||||||
|
class="my-4 bg-background-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-background-secondary"
|
||||||
|
placeholder="Enter your email address"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mb-6">
|
||||||
|
<label for="password" class="text-sm font-semibold leading-6 text-text"
|
||||||
|
>Password</label
|
||||||
|
><input
|
||||||
|
type="password"
|
||||||
|
id="password"
|
||||||
|
class="my-4 bg-background-primary text-text-primary bg- rounded-md block w-full px-3 h-10 shadow-sm focus:outline-none placeholder:text-text-line focus:ring-2 focus:ring-text-primary ring-1 ring-background-secondary"
|
||||||
|
placeholder="Enter your password"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<router-link
|
||||||
|
type="submit"
|
||||||
|
class="inline-flex justify-center rounded-lg text-sm font-black py-2 mt-3 bg-text-primary text-background w-full"
|
||||||
|
to="/game"
|
||||||
|
>
|
||||||
|
<span>Log in / Sign up</span>
|
||||||
|
</router-link>
|
||||||
|
<input type="hidden" name="remember" value="true" />
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
<template>
|
||||||
|
<div id="box">
|
||||||
|
<canvas ref="canvas"></canvas>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
|
||||||
|
const cw = ref(0)
|
||||||
|
const ch = ref(0)
|
||||||
|
const fallingCharArr = ref([])
|
||||||
|
const fontSize = ref(16)
|
||||||
|
const maxColums = ref(0)
|
||||||
|
const charArr = ['0', '1', '', '', '']
|
||||||
|
const ctx = ref(null)
|
||||||
|
|
||||||
|
const randomInt = (min, max) => {
|
||||||
|
return Math.floor(Math.random() * (max - min) + min)
|
||||||
|
}
|
||||||
|
|
||||||
|
const randomFloat = (min, max) => {
|
||||||
|
return Math.random() * (max - min) + min
|
||||||
|
}
|
||||||
|
|
||||||
|
const draw = (point) => {
|
||||||
|
point.value = charArr[randomInt(0, charArr.length)].toUpperCase()
|
||||||
|
if (point.y < 0) point.speed = randomFloat(10, 20)
|
||||||
|
|
||||||
|
ctx.value.fillStyle = '#0F0'
|
||||||
|
ctx.value.font = `${fontSize.value}px san-serif`
|
||||||
|
ctx.value.fillText(point.value, point.x, point.y)
|
||||||
|
|
||||||
|
point.y += point.speed
|
||||||
|
if (point.y > ch.value) {
|
||||||
|
point.y = randomFloat(-100, 0)
|
||||||
|
point.speed = randomFloat(2, 8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = () => {
|
||||||
|
ctx.value.fillStyle = 'rgba(0,0,0,0.05)'
|
||||||
|
ctx.value.fillRect(0, 0, cw.value, ch.value)
|
||||||
|
|
||||||
|
let i = fallingCharArr.value.length
|
||||||
|
while (i--) {
|
||||||
|
draw(fallingCharArr.value[i])
|
||||||
|
}
|
||||||
|
setTimeout(update, 25)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
cw.value = document.documentElement.clientWidth
|
||||||
|
ch.value = document.documentElement.clientHeight
|
||||||
|
maxColums.value = cw.value / fontSize.value
|
||||||
|
|
||||||
|
const canvas = document.querySelector('canvas')
|
||||||
|
ctx.value = canvas.getContext('2d')
|
||||||
|
|
||||||
|
canvas.width = cw.value
|
||||||
|
canvas.height = ch.value
|
||||||
|
|
||||||
|
for (let i = 0; i < maxColums.value; i++) {
|
||||||
|
fallingCharArr.value.push({
|
||||||
|
x: i * fontSize.value,
|
||||||
|
y: randomFloat(-500, 0),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
cw.value = document.documentElement.clientWidth
|
||||||
|
ch.value = document.documentElement.clientHeight
|
||||||
|
maxColums.value = cw.value / fontSize.value
|
||||||
|
canvas.width = cw.value
|
||||||
|
canvas.height = ch.value
|
||||||
|
})
|
||||||
|
|
||||||
|
update()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
#box {
|
||||||
|
position: relative;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
canvas {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
<button id="backward" class="flex items-center">
|
<button id="backward" class="flex items-center">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
:icon="['fas', 'arrow-left']"
|
:icon="['fas', 'arrow-left']"
|
||||||
class="text-text-line h-5 w-5"
|
class="text-text-tertiary h-5 w-5"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
<button>
|
<button>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ onMounted(async () => {
|
|||||||
<h1 id="topic" class="text-text" v-else-if="questData">
|
<h1 id="topic" class="text-text" v-else-if="questData">
|
||||||
{{ questData.title }}
|
{{ questData.title }}
|
||||||
</h1>
|
</h1>
|
||||||
<hr class="border-text-line border-2 my-2" />
|
<hr class="border-text-tertiary border-2 my-2" />
|
||||||
<div>
|
<div>
|
||||||
<div v-if="questData">
|
<div v-if="questData">
|
||||||
<p class="text-text">{{ questData.instruction }}</p>
|
<p class="text-text">{{ questData.instruction }}</p>
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
treeRef.value.removeEventListener('mousedown', mouseDownHandler)
|
treeRef?.value?.removeEventListener('mousedown', mouseDownHandler)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
+21
-1
@@ -1,4 +1,5 @@
|
|||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import './style.css'
|
import './style.css'
|
||||||
|
|
||||||
/* import the fontawesome core */
|
/* import the fontawesome core */
|
||||||
@@ -24,7 +25,10 @@ import {
|
|||||||
faCircleQuestion,
|
faCircleQuestion,
|
||||||
faCircleRight,
|
faCircleRight,
|
||||||
} from '@fortawesome/free-regular-svg-icons'
|
} from '@fortawesome/free-regular-svg-icons'
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
import GamePage from './views/GamePage.vue'
|
||||||
|
import AuthPage from './views/AuthPage.vue'
|
||||||
|
|
||||||
/* add icons to the library */
|
/* add icons to the library */
|
||||||
library.add(
|
library.add(
|
||||||
@@ -41,4 +45,20 @@ library.add(
|
|||||||
faTerminal
|
faTerminal
|
||||||
)
|
)
|
||||||
|
|
||||||
createApp(App).component('font-awesome-icon', FontAwesomeIcon).mount('#app')
|
const routes = [
|
||||||
|
{ path: '/', name: 'Home', component: AuthPage },
|
||||||
|
{ path: '/game', name: 'Game', component: GamePage },
|
||||||
|
]
|
||||||
|
|
||||||
|
const router = createRouter({
|
||||||
|
history: createWebHistory(),
|
||||||
|
routes,
|
||||||
|
})
|
||||||
|
|
||||||
|
const app = createApp(App)
|
||||||
|
|
||||||
|
app.component('font-awesome-icon', FontAwesomeIcon)
|
||||||
|
|
||||||
|
app.use(router)
|
||||||
|
|
||||||
|
app.mount('#app')
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<script setup>
|
||||||
|
import AuthenticationPart from '../components/AuthenticationPart.vue'
|
||||||
|
import Background from '../components/DynamicBackground.vue'
|
||||||
|
</script>
|
||||||
|
<template>
|
||||||
|
<div class="relative w-screen h-screen">
|
||||||
|
<Background class="w-full h-full" />
|
||||||
|
<div
|
||||||
|
class="h-screen w-screen absolute top-0 left-0 flex flex-wrap justify-center content-center"
|
||||||
|
>
|
||||||
|
<div class="h-2/3 w-1/3">
|
||||||
|
<AuthenticationPart />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
<script setup>
|
||||||
|
import { onMounted } from 'vue'
|
||||||
|
import socket from '../utils/socket'
|
||||||
|
import sessionManager from '../utils/session'
|
||||||
|
import CommandlistPart from '../components/CommandlistPart.vue'
|
||||||
|
import HintPart from '../components/HintPart.vue'
|
||||||
|
import QuestPart from '../components/QuestPart.vue'
|
||||||
|
import TerminalPart from '../components/TerminalPart.vue'
|
||||||
|
import VisualizationPart from '../components/VisualizationPart.vue'
|
||||||
|
import ControlPalette from '../components/ControlPalette.vue'
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await sessionManager.lastOrCreate()
|
||||||
|
socket.connect(sessionManager.getSession())
|
||||||
|
socket.on('connect', function open() {
|
||||||
|
console.log('Connected to the server.')
|
||||||
|
})
|
||||||
|
socket.on('disconnect', function close() {
|
||||||
|
console.log('Disconnected from the server.')
|
||||||
|
})
|
||||||
|
socket.on('graph', (event) => {
|
||||||
|
sessionManager.handleGraphUpdate(event)
|
||||||
|
})
|
||||||
|
socket.on('tasks', (tasks) => {
|
||||||
|
sessionManager.setTasks(tasks)
|
||||||
|
})
|
||||||
|
socket.on('message', console.log)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<!--main-->
|
||||||
|
<div id="main" class="h-[94vh] grid grid-cols-3 gap-3 p-3">
|
||||||
|
<!--Topic and Command List-->
|
||||||
|
<div class="bg-background-secondary h-full rounded-lg">
|
||||||
|
<section id="quest" class="h-2/3 m-3">
|
||||||
|
<QuestPart />
|
||||||
|
</section>
|
||||||
|
<hr class="border-text-tertiary border" />
|
||||||
|
<section id="cmdlist" class="h-1/3">
|
||||||
|
<CommandlistPart />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<!--Terminal and Hint-->
|
||||||
|
<div class="bg-background h-full rounded-lg">
|
||||||
|
<section id="terminal" class="h-3/5">
|
||||||
|
<TerminalPart />
|
||||||
|
</section>
|
||||||
|
<section id="hint" class="h-2/5">
|
||||||
|
<HintPart />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<!--Visualization-->
|
||||||
|
<div class="h-full">
|
||||||
|
<div class="bg-border rounded-xl h-[90%] px-3 py-6">
|
||||||
|
<div class="bg-background rounded-xl h-full px-1 py-6">
|
||||||
|
<section id="visualization" class="bg-background-primary h-full">
|
||||||
|
<VisualizationPart />
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ControlPalette />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script></script>
|
||||||
@@ -7,12 +7,12 @@ export default {
|
|||||||
background: {
|
background: {
|
||||||
primary: '#282A33',
|
primary: '#282A33',
|
||||||
secondary: '#131313',
|
secondary: '#131313',
|
||||||
DEFAULT: '#010103',
|
DEFAULT: '#000000',
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
primary: '#39FF14',
|
primary: '#00FF00',
|
||||||
secondary: '#ADADB5',
|
secondary: '#ADADB5',
|
||||||
line: '#4A4B63',
|
tertiary: '#4A4B63',
|
||||||
DEFAULT: '#FFFFFF',
|
DEFAULT: '#FFFFFF',
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
|
|||||||
@@ -975,6 +975,11 @@
|
|||||||
"@vue/compiler-dom" "3.3.4"
|
"@vue/compiler-dom" "3.3.4"
|
||||||
"@vue/shared" "3.3.4"
|
"@vue/shared" "3.3.4"
|
||||||
|
|
||||||
|
"@vue/devtools-api@^6.5.0":
|
||||||
|
version "6.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
|
||||||
|
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
||||||
|
|
||||||
"@vue/eslint-config-prettier@^7.1.0":
|
"@vue/eslint-config-prettier@^7.1.0":
|
||||||
version "7.1.0"
|
version "7.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@vue/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz#97936379c7fb1d982b9d2c6b122306e3c2e464c8"
|
resolved "https://registry.yarnpkg.com/@vue/eslint-config-prettier/-/eslint-config-prettier-7.1.0.tgz#97936379c7fb1d982b9d2c6b122306e3c2e464c8"
|
||||||
@@ -4829,6 +4834,13 @@ vue-eslint-parser@^9.3.0:
|
|||||||
lodash "^4.17.21"
|
lodash "^4.17.21"
|
||||||
semver "^7.3.6"
|
semver "^7.3.6"
|
||||||
|
|
||||||
|
vue-router@^4.2.4:
|
||||||
|
version "4.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/vue-router/-/vue-router-4.2.4.tgz#382467a7e2923e6a85f015d081e1508052c191b9"
|
||||||
|
integrity sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==
|
||||||
|
dependencies:
|
||||||
|
"@vue/devtools-api" "^6.5.0"
|
||||||
|
|
||||||
vue@^3.3.4:
|
vue@^3.3.4:
|
||||||
version "3.3.4"
|
version "3.3.4"
|
||||||
resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.4.tgz#8ed945d3873667df1d0fcf3b2463ada028f88bd6"
|
resolved "https://registry.yarnpkg.com/vue/-/vue-3.3.4.tgz#8ed945d3873667df1d0fcf3b2463ada028f88bd6"
|
||||||
|
|||||||
Reference in new issue
Block a user