Fix re-login error (#60)

* refactor: put all session ref into one reactive store

* fix relogin duplicate signal

* fix: put quest data into store, fix hint reactivity

* fix: duplicate after relogin

* only register listener once
This commit is contained in:
Lin, Hung-Shin authored and Annie Huang committed 2023-11-12 21:24:01 +08:00
1 parent 96cdd2b157
commit acff4a3f2c
10 files changed
+179 -168

No files matched your search

+2 -2
View File
@@ -25,10 +25,10 @@
</template> </template>
<script setup> <script setup>
import sessionManager from '../utils/session' import { createSession } from '../store/session'
const reset = async () => { const reset = async () => {
console.log('resetting...') console.log('resetting...')
await sessionManager.createSession() await createSession()
} }
</script> </script>
+8 -2
View File
@@ -4,12 +4,18 @@ import { useRouter } from 'vue-router'
import { logout } from '../utils/auth' import { logout } from '../utils/auth'
import { bugReportUrl } from '../config' import { bugReportUrl } from '../config'
import api from '../utils/api' import api from '../utils/api'
import { reset } from '../store/session'
const router = useRouter() const router = useRouter()
const handleLogout = async () => { const handleLogout = async () => {
await logout() try {
router.push({ name: 'login' }) await logout()
reset()
router.push({ name: 'login' })
} catch (err) {
console.error(err)
}
} }
const questData = ref(null) const questData = ref(null)
+12 -8
View File
@@ -1,11 +1,15 @@
<script setup> <script setup>
import { ref, watch } from 'vue' import { ref, watch } from 'vue'
import sessionManager from '../utils/session' import sessionStore from '../store/session'
const current = ref(-1) const current = ref(-1)
watch(sessionManager.hints, (hints) => { watch(
current.value = hints.length - 1 () => sessionStore.session.hints,
}) (hints) => {
current.value = hints.length - 1
},
{ deep: true }
)
const left = () => { const left = () => {
if (current.value > 0) { if (current.value > 0) {
@@ -14,7 +18,7 @@ const left = () => {
} }
const right = () => { const right = () => {
if (current.value < sessionManager.hints.value.length - 1) { if (current.value < sessionStore.session.hints.length - 1) {
current.value += 1 current.value += 1
} }
} }
@@ -43,13 +47,13 @@ const right = () => {
<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">{{ 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">/</p>
<p class="inline text-text font-xl w-1/14 p-2 m-1"> <p class="inline text-text font-xl w-1/14 p-2 m-1">
{{ sessionManager.hints.value.length }} {{ sessionStore.session.hints.length }}
</p> </p>
<button <button
class="p-2 m-1 w-1/8" class="p-2 m-1 w-1/8"
id="Rbutton" id="Rbutton"
@click="right" @click="right"
:disabled="current === sessionManager.hints.value.length - 1" :disabled="current === sessionStore.session.hints.length - 1"
> >
<font-awesome-icon :icon="['fas', 'arrow-right']" class="text-text" /> <font-awesome-icon :icon="['fas', 'arrow-right']" class="text-text" />
</button> </button>
@@ -57,7 +61,7 @@ const right = () => {
</div> </div>
<div id="hint" class="bg-bg flex flex-wrap p-8"> <div id="hint" class="bg-bg flex flex-wrap p-8">
<p class="text-text font-xl whitespace-pre-wrap"> <p class="text-text font-xl whitespace-pre-wrap">
{{ sessionManager.hints.value[current] }} {{ sessionStore.session.hints[current] }}
</p> </p>
<br /> <br />
</div> </div>
+7 -25
View File
@@ -1,37 +1,20 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import sessionStore from '../store/session'
import api from '../utils/api'
import sessionManager from '../utils/session'
const questData = ref(null)
const questErr = ref(null)
onMounted(async () => {
try {
const res = await api.get('/quests/helloworld')
questData.value = res.data
} catch (err) {
questErr.value = err
}
})
</script> </script>
<template> <template>
<div class="h-full w-full max-h-full"> <div class="h-full w-full max-h-full">
<h1 id="topic" class="text-red-500" v-if="questErr"> <h1 id="topic" class="text-text" v-if="sessionStore.quest">
Failed to get quest infomation {{ sessionStore.quest.title }}
</h1>
<h1 id="topic" class="text-text" v-else-if="questData">
{{ questData.title }}
</h1> </h1>
<hr class="border-text-tertiary border-2 my-2" /> <hr class="border-text-tertiary border-2 my-2" />
<div> <div>
<div v-if="questData" class="whitespace-pre-wrap"> <div v-if="sessionStore.quest" class="whitespace-pre-wrap">
<p class="text-text">{{ questData.instruction }}</p> <p class="text-text">{{ sessionStore.quest.instruction }}</p>
<br /> <br />
<p class="text-text">Tasks:</p> <p class="text-text">Tasks:</p>
<ul v-if="sessionManager.session.value"> <ul v-if="sessionStore.session">
<li v-for="task in sessionManager.session.value.tasks" :key="task.id"> <li v-for="task in sessionStore.session.tasks" :key="task.id">
<p v-if="task.completed" class="text-text-primary"> <p v-if="task.completed" class="text-text-primary">
<span class="">✓</span> <span class="">✓</span>
{{ task.name }} {{ task.name }}
@@ -43,7 +26,6 @@ onMounted(async () => {
</li> </li>
</ul> </ul>
</div> </div>
<p class="text-red-500" v-else-if="questErr">{{ questErr.message }}</p>
<p class="text-text" v-else>Loading...</p> <p class="text-text" v-else>Loading...</p>
</div> </div>
</div> </div>
+4 -3
View File
@@ -10,12 +10,13 @@
<script setup> <script setup>
import { onMounted, ref } from 'vue' import { onMounted, ref } from 'vue'
import sessionManager from '../utils/session' import { useTerminal } from '../store/session'
const terminal = ref(null) const terminal = ref(null)
onMounted(() => { onMounted(() => {
sessionManager.term.mount(terminal.value) const term = useTerminal()
window.addEventListener('resize', () => sessionManager.term.resizeScreen()) term.mount(terminal.value)
window.addEventListener('resize', () => term.resizeScreen())
}) })
</script> </script>
+3 -3
View File
@@ -1,6 +1,6 @@
<script setup> <script setup>
import FileNode from './FileNode.vue' import FileNode from './FileNode.vue'
import sessionManager from '../utils/session.js' import sessionStore from '../store/session.js'
</script> </script>
<template> <template>
@@ -8,8 +8,8 @@ import sessionManager from '../utils/session.js'
<ul> <ul>
<li> <li>
<FileNode <FileNode
:node="sessionManager.graph.value" :node="sessionStore.session.graph"
:pwd="sessionManager.pwd.value" :pwd="sessionStore.session.pwd"
/> />
</li> </li>
</ul> </ul>
+127
View File
@@ -0,0 +1,127 @@
import { reactive } from 'vue'
import { FileGraph } from '@linux-odyssey/file-graph'
import api from '../utils/api'
import Socket from '../utils/socket'
import SocketTerminal from '../utils/terminal'
function newSession() {
const graph = new FileGraph({
path: '/',
type: 'folder',
discovered: false,
})
return {
status: 'inactive',
graph,
pwd: '',
hints: [],
tasks: [],
}
}
const store = reactive({
session: newSession(),
questId: '',
quest: null,
})
const socket = new Socket()
const term = new SocketTerminal(40, 80)
function setQuest(questId) {
api.get(`/quests/${questId}`).then((res) => {
store.questId = res.data._id
store.quest = res.data
})
}
async function setSession(session) {
store.session = session
store.session.graph = new FileGraph(session.graph)
term.reset()
await socket.connect(session)
term.focus()
}
export async function createSession() {
console.log('Creating a new session...')
const res = await api.post('/sessions', { quest_id: store.questId })
const { data } = res
await setSession(data)
}
async function getActiveSession(questId) {
const res = await api.post('/sessions/active', {
quest_id: questId,
})
const session = res.data
try {
await setSession(session)
} catch (err) {
console.log(err)
await createSession().catch(console.error)
}
}
function updateGraph(event) {
if (event.discover) {
store.session.graph.discover(event.discover)
}
if (event.pwd) {
store.session.pwd = event.pwd
}
}
function updateHints(hints) {
store.session.hints.push(...hints)
}
function updateStatus(status) {
store.session.status = status
}
function setTasks(tasks) {
store.session.tasks = tasks
}
export function useTerminal() {
return term
}
export function reset() {
socket.reset()
term.reset()
store.session = newSession()
store.quest = null
}
export async function init(questId) {
reset()
await setQuest(questId)
await getActiveSession(questId)
}
function setup() {
socket.on('terminal', (data) => {
term.write(data)
})
term.onData((data) => {
socket.emit('terminal', data)
})
socket.on('graph', (event) => {
updateGraph(event)
})
socket.on('hints', (event) => {
updateHints(event)
})
socket.on('tasks', (tasks) => {
setTasks(tasks)
})
socket.on('status', (event) => {
updateStatus(event)
})
}
setup()
export default store
-107
View File
@@ -1,107 +0,0 @@
import { ref } from 'vue'
import { FileGraph } from '@linux-odyssey/file-graph'
import api from './api'
import Socket from './socket'
import SocketTerminal from './terminal'
class SessionManager {
constructor(questId) {
this.questId = questId
this.session = ref(null)
this.hints = ref([])
this.graph = ref(
new FileGraph({
path: '/',
type: 'folder',
discovered: false,
})
)
this.pwd = ref('')
this.status = ref('inactive')
this.socket = new Socket()
this.term = new SocketTerminal(40, 80)
}
init() {
this.socket.on('terminal', (data) => {
this.term.write(data)
})
this.term.onData((data) => {
this.socket.emit('terminal', data)
})
this.socket.on('graph', (event) => {
this.handleGraphUpdate(event)
})
this.socket.on('hints', (event) => {
this.handleHintUpdate(event)
})
this.socket.on('tasks', (tasks) => {
this.setTasks(tasks)
})
this.socket.on('status', (event) => {
this.handleStatusUpdate(event)
})
return this.getActiveSession()
}
getSession() {
return this.session.value
}
async setSession(session) {
this.session.value = session
this.graph.value = new FileGraph(session.graph)
this.hints.value = session.hints
this.status.value = session.status
this.term.reset()
await this.socket.connect(session)
this.term.focus()
}
handleGraphUpdate(event) {
if (event.discover) {
this.graph.value.discover(event.discover)
}
if (event.pwd) {
this.pwd.value = event.pwd
}
}
handleHintUpdate(hints) {
this.hints.value = [...this.hints.value, ...hints]
}
handleStatusUpdate(status) {
this.status.value = status
}
setTasks(tasks) {
this.session.value.tasks = tasks
}
async createSession() {
console.log('Creating a new session...')
const res = await api.post('/sessions', { quest_id: this.questId })
const { data } = res
console.log('Created Session ID:', data._id)
this.setSession(data)
}
async getActiveSession() {
const res = await api.post('/sessions/active', {
quest_id: this.questId,
})
const session = res.data
console.log(session)
try {
await this.setSession(session)
} catch (err) {
console.log(err)
this.createSession().catch(console.error)
}
}
}
const sessionManager = new SessionManager('helloworld')
export default sessionManager
+12 -14
View File
@@ -8,10 +8,7 @@ class SocketWrapper {
connect(session) { connect(session) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (this.socket) { this.disconnect()
this.socket.off()
this.socket.disconnect()
}
this.socket = io('', { this.socket = io('', {
query: { query: {
@@ -38,25 +35,26 @@ class SocketWrapper {
} }
} }
once(event, callback) {
if (this.socket) {
this.socket.once(event, callback)
}
}
bindListeners() { bindListeners() {
this.listeners.forEach(({ event, callback }) => { this.listeners.forEach(({ event, callback }) => {
this.socket.on(event, callback) this.socket.on(event, callback)
}) })
} }
send(data) {
this.socket.send(data)
}
emit(event, data) { emit(event, data) {
this.socket.emit(event, data) this.socket.emit(event, data)
} }
disconnect() {
if (this.socket) {
this.socket.off()
this.socket.disconnect()
}
}
reset() {
this.disconnect()
}
} }
export default SocketWrapper export default SocketWrapper
+4 -4
View File
@@ -1,6 +1,6 @@
<script setup> <script setup>
import { computed, onMounted } from 'vue' import { computed, onMounted } from 'vue'
import sessionManager from '../utils/session' import sessionStore, { init } from '../store/session'
import GameHeaderPart from '../components/GameHeaderPart.vue' import GameHeaderPart from '../components/GameHeaderPart.vue'
import CommandlistPart from '../components/CommandlistPart.vue' import CommandlistPart from '../components/CommandlistPart.vue'
import HintPart from '../components/HintPart.vue' import HintPart from '../components/HintPart.vue'
@@ -11,11 +11,11 @@ import ControlPalette from '../components/ControlPalette.vue'
import CompleteModal from '../components/CompleteModal.vue' import CompleteModal from '../components/CompleteModal.vue'
const completed = computed(() => { const completed = computed(() => {
return sessionManager.status.value === 'finished' return sessionStore.session.status === 'finished'
}) })
onMounted(async () => { onMounted(async () => {
await sessionManager.init() await init('helloworld')
}) })
</script> </script>
@@ -28,7 +28,7 @@ onMounted(async () => {
<input <input
type="hidden" type="hidden"
id="currentStatus" id="currentStatus"
:value="sessionManager.status.value" :value="sessionStore.session.status"
/> />
<!-- main --> <!-- main -->
<div id="main" class="h-[94vh] w-full flex p-3 space-x-3"> <div id="main" class="h-[94vh] w-full flex p-3 space-x-3">