mirror of
https://github.com/linux-odyssey/linux-odyssey.git
synced 2026-11-05 19:20:12 +08:00
wip: integrate filegraph into session
This commit is contained in:
1 parent
cdd22634e0
commit
2d95ca382f
13 files changed
+104
-41
No files matched your search
+12
-18
@@ -1,6 +1,7 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { FileGraph, FileObject } from '@linux-odyssey/file-graph'
|
import { FileGraph, FileGraphUpdateEvent } from '@linux-odyssey/file-graph'
|
||||||
import { IResponse, ITask } from '@linux-odyssey/game'
|
import { IResponse, ITask } from '@linux-odyssey/game'
|
||||||
|
import type { SessionDetail } from 'server/src/routers/sessionRouter'
|
||||||
import Socket from '../utils/socket'
|
import Socket from '../utils/socket'
|
||||||
import SocketTerminal from '../utils/terminal'
|
import SocketTerminal from '../utils/terminal'
|
||||||
import { Session } from '../types'
|
import { Session } from '../types'
|
||||||
@@ -21,6 +22,7 @@ interface SessionUpdate {
|
|||||||
status: string
|
status: string
|
||||||
responses: IResponse[]
|
responses: IResponse[]
|
||||||
tasks: ITask[]
|
tasks: ITask[]
|
||||||
|
graphUpdate: FileGraphUpdateEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Store {
|
interface Store {
|
||||||
@@ -40,12 +42,14 @@ const useSession = defineStore('session', {
|
|||||||
this.quest = await trpc.quests.getQuestDetail.query(questId)
|
this.quest = await trpc.quests.getQuestDetail.query(questId)
|
||||||
this.questId = questId
|
this.questId = questId
|
||||||
},
|
},
|
||||||
async setSession(session: Session) {
|
async setSession(session: SessionDetail) {
|
||||||
console.log('set session', session)
|
console.log('set session', session)
|
||||||
this.session = session
|
this.session = {
|
||||||
// this.session.graph = new FileGraph(session.graph)
|
...session,
|
||||||
|
graph: new FileGraph(session.graph),
|
||||||
|
}
|
||||||
term.reset()
|
term.reset()
|
||||||
await socket.connect(session)
|
await socket.connect(this.session)
|
||||||
term.focus()
|
term.focus()
|
||||||
},
|
},
|
||||||
async createSession() {
|
async createSession() {
|
||||||
@@ -63,24 +67,14 @@ const useSession = defineStore('session', {
|
|||||||
this.setSession(session)
|
this.setSession(session)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// updateGraph(event: { discover?: FileObject[]; pwd?: string }) {
|
|
||||||
// if (!this.session) return
|
|
||||||
// if (event.discover) {
|
|
||||||
// this.session.graph.discover(event.discover)
|
|
||||||
// }
|
|
||||||
// if (event.pwd) {
|
|
||||||
// this.session.pwd = event.pwd
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
newUpdate(update: SessionUpdate) {
|
newUpdate(update: SessionUpdate) {
|
||||||
if (!this.session) return
|
if (!this.session) return
|
||||||
this.session.responses = update.responses
|
this.session.responses = update.responses
|
||||||
this.session.tasks = update.tasks
|
this.session.tasks = update.tasks
|
||||||
this.session.status = update.status
|
this.session.status = update.status
|
||||||
// this.session.hints.push(response.hints)
|
if (update.graphUpdate) {
|
||||||
// if (response.tasks) {
|
this.session.graph.handleEvent(update.graphUpdate)
|
||||||
// this.session.tasks = response.tasks
|
}
|
||||||
// }
|
|
||||||
// if (
|
// if (
|
||||||
// response.status === 'finished' &&
|
// response.status === 'finished' &&
|
||||||
// this.session.status !== 'finished'
|
// this.session.status !== 'finished'
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ export interface StageResponse {
|
|||||||
export interface Session {
|
export interface Session {
|
||||||
_id: string
|
_id: string
|
||||||
status: string
|
status: string
|
||||||
// graph: FileGraph
|
graph: FileGraph
|
||||||
// pwd: string
|
// pwd: string
|
||||||
// hints: string[][]
|
// hints: string[][]
|
||||||
tasks: ITask[]
|
tasks: ITask[]
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "@linux-odyssey/file-graph",
|
"name": "@linux-odyssey/file-graph",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
"main": "./dist/cjs/index.js",
|
"main": "./dist/cjs/index.js",
|
||||||
"types": "./dist/types/index.d.ts",
|
"types": "./dist/types/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"require": "./dist/cjs/index.js",
|
"require": "./dist/cjs/index.js",
|
||||||
|
"import": "./dist/esm/index.js",
|
||||||
"types": "./dist/types/index.d.ts"
|
"types": "./dist/types/index.d.ts"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc -p tsconfig.cjs.json",
|
"build": "tsc -p tsconfig.json && tsc -p tsconfig.cjs.json",
|
||||||
"dev": "tsc -w",
|
"dev": "tsc -w",
|
||||||
"test": "yarn build && jest"
|
"test": "yarn build && jest"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { FileObject } from './file.js'
|
import { FileObject } from './file.js'
|
||||||
import FileNode from './fileNode.js'
|
import FileNode from './fileNode.js'
|
||||||
|
|
||||||
export interface Event {
|
export interface FileGraphUpdateEvent {
|
||||||
add?: FileObject[]
|
add?: FileObject[]
|
||||||
remove?: FileObject[]
|
remove?: FileObject[]
|
||||||
discover?: FileObject[]
|
discover?: FileObject[]
|
||||||
@@ -42,7 +42,7 @@ export default class FileGraph extends FileNode {
|
|||||||
this.merge(newNode)
|
this.merge(newNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleEvent(event: Event) {
|
handleEvent(event: FileGraphUpdateEvent) {
|
||||||
// eslint-disable-next-line default-case
|
// eslint-disable-next-line default-case
|
||||||
switch (true) {
|
switch (true) {
|
||||||
case !!event.add:
|
case !!event.add:
|
||||||
|
|||||||
@@ -6,10 +6,17 @@ import {
|
|||||||
} from './errors.js'
|
} from './errors.js'
|
||||||
import { dirname } from './utils.js'
|
import { dirname } from './utils.js'
|
||||||
|
|
||||||
export default class FileNode extends File {
|
export interface IFileNode {
|
||||||
|
path: string
|
||||||
|
type: 'file' | 'directory'
|
||||||
|
discovered: boolean
|
||||||
|
children?: IFileNode[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FileNode extends File implements IFileNode {
|
||||||
children: FileNode[]
|
children: FileNode[]
|
||||||
|
|
||||||
constructor(file: FileObject | FileNode) {
|
constructor(file: FileObject | IFileNode) {
|
||||||
super(file)
|
super(file)
|
||||||
this.children = []
|
this.children = []
|
||||||
if ((<FileNode>file).children)
|
if ((<FileNode>file).children)
|
||||||
|
|||||||
@@ -1,10 +1,3 @@
|
|||||||
export { default as File, FileObject } from './file.js'
|
export { default as File, FileObject } from './file.js'
|
||||||
export { default as FileNode } from './fileNode.js'
|
export { default as FileNode, IFileNode } from './fileNode.js'
|
||||||
export { default as FileGraph, Event } from './fileGraph.js'
|
export { default as FileGraph, FileGraphUpdateEvent } from './fileGraph.js'
|
||||||
|
|
||||||
export interface IFileNode {
|
|
||||||
path: string
|
|
||||||
type: string
|
|
||||||
discovered: boolean
|
|
||||||
children?: IFileNode[]
|
|
||||||
}
|
|
||||||
@@ -4,8 +4,11 @@
|
|||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"composite": true,
|
"composite": true,
|
||||||
"module": "NodeNext",
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
"outDir": "./dist/esm",
|
"outDir": "./dist/esm",
|
||||||
"declarationDir": "./dist/types"
|
"declarationDir": "./dist/types",
|
||||||
|
"target": "ES2020",
|
||||||
|
"useDefineForClassFields": true
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
IFileExistenceChecker,
|
IFileExistenceChecker,
|
||||||
IResponse,
|
IResponse,
|
||||||
ITask,
|
ITask,
|
||||||
|
ICommandParams,
|
||||||
} from './schema'
|
} from './schema'
|
||||||
|
|
||||||
export class GameSession {
|
export class GameSession {
|
||||||
@@ -22,7 +23,7 @@ export class GameSession {
|
|||||||
this.completed = completedEvents || []
|
this.completed = completedEvents || []
|
||||||
this.quest = new Quest(quest, checker)
|
this.quest = new Quest(quest, checker)
|
||||||
this.graph = new FileGraph(
|
this.graph = new FileGraph(
|
||||||
graph ?? new FileNode({ path: '/', type: 'root', discovered: false })
|
graph ?? { path: '/', type: 'directory', discovered: false }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,6 +40,9 @@ export class GameSession {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async runCommand(command: ICommand) {
|
async runCommand(command: ICommand) {
|
||||||
|
if (command.params) {
|
||||||
|
this.graph.handleEvent(command.params)
|
||||||
|
}
|
||||||
const stageId = await this.quest.findSatisfiedEvent(command, this.completed)
|
const stageId = await this.quest.findSatisfiedEvent(command, this.completed)
|
||||||
if (stageId) {
|
if (stageId) {
|
||||||
this.complete(stageId)
|
this.complete(stageId)
|
||||||
|
|||||||
@@ -168,4 +168,47 @@ describe('Session', () => {
|
|||||||
{ id: 'stage4', name: 'Stage 4', completed: false },
|
{ id: 'stage4', name: 'Stage 4', completed: false },
|
||||||
])
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should update graph', () => {
|
||||||
|
const session = new GameSession({}, quest, new MockFileChecker())
|
||||||
|
|
||||||
|
expect(session.getGraph()).toMatchObject({
|
||||||
|
path: '/',
|
||||||
|
type: 'directory',
|
||||||
|
discovered: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
session.runCommand({
|
||||||
|
command: 'ls',
|
||||||
|
params: {
|
||||||
|
discover: [
|
||||||
|
{
|
||||||
|
path: '/home/user',
|
||||||
|
type: 'directory',
|
||||||
|
discovered: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(session.getGraph()).toMatchObject({
|
||||||
|
path: '/',
|
||||||
|
type: 'directory',
|
||||||
|
discovered: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/home',
|
||||||
|
type: 'directory',
|
||||||
|
discovered: false,
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '/home/user',
|
||||||
|
type: 'directory',
|
||||||
|
discovered: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
@@ -2,14 +2,17 @@ import { model, Schema, Types } from 'mongoose'
|
|||||||
|
|
||||||
export interface INode {
|
export interface INode {
|
||||||
path: string
|
path: string
|
||||||
type: string
|
type: 'file' | 'directory'
|
||||||
discovered: boolean
|
discovered: boolean
|
||||||
children?: INode[]
|
children?: INode[]
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodeSchema = new Schema<INode>({
|
const nodeSchema = new Schema<INode>({
|
||||||
path: String,
|
path: String,
|
||||||
|
type: {
|
||||||
type: String,
|
type: String,
|
||||||
|
enum: ['file', 'directory'],
|
||||||
|
},
|
||||||
discovered: {
|
discovered: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ export const newCommand = asyncHandler(async (req: Request, res: Response) => {
|
|||||||
await c.save()
|
await c.save()
|
||||||
if (event) {
|
if (event) {
|
||||||
session.completedEvents = gameSession.completedEvents
|
session.completedEvents = gameSession.completedEvents
|
||||||
|
session.graph = gameSession.getGraph()
|
||||||
|
|
||||||
if (gameSession.isFinished()) {
|
if (gameSession.isFinished()) {
|
||||||
await finishSession(session)
|
await finishSession(session)
|
||||||
@@ -64,6 +65,7 @@ export const newCommand = asyncHandler(async (req: Request, res: Response) => {
|
|||||||
responses: gameSession.getResponses(),
|
responses: gameSession.getResponses(),
|
||||||
tasks: gameSession.getTasks(),
|
tasks: gameSession.getTasks(),
|
||||||
status: session.status,
|
status: session.status,
|
||||||
|
graphUpdate: command.params,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import config from '../config.js'
|
|||||||
import { protectedProcedure, router } from '../trpc.js'
|
import { protectedProcedure, router } from '../trpc.js'
|
||||||
import { questManager } from '../models/quest.js'
|
import { questManager } from '../models/quest.js'
|
||||||
|
|
||||||
|
export type SessionDetail = Awaited<ReturnType<typeof sessionDetail>>
|
||||||
|
|
||||||
function sessionDetail(session: ISession) {
|
function sessionDetail(session: ISession) {
|
||||||
const quest = questManager.get(session.quest)
|
const quest = questManager.get(session.quest)
|
||||||
if (!quest) {
|
if (!quest) {
|
||||||
@@ -22,14 +24,15 @@ function sessionDetail(session: ISession) {
|
|||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
_id: session._id.toString(),
|
_id: session._id.toString(),
|
||||||
user: session.user,
|
user: session.user.toString(),
|
||||||
quest: session.quest,
|
quest: session.quest,
|
||||||
status: session.status,
|
status: session.status,
|
||||||
createdAt: session.createdAt,
|
createdAt: session.createdAt.toISOString(),
|
||||||
lastActivityAt: session.lastActivityAt,
|
lastActivityAt: session.lastActivityAt.toISOString(),
|
||||||
completedEvents: session.completedEvents,
|
completedEvents: session.completedEvents,
|
||||||
responses: gameSession.getResponses(),
|
responses: gameSession.getResponses(),
|
||||||
tasks: gameSession.getTasks(),
|
tasks: gameSession.getTasks(),
|
||||||
|
graph: gameSession.getGraph(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+11
-2
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext", // 要編譯成的 JS 目標版本
|
"target": "ESNext", // 要編譯成的 JS 目標版本
|
||||||
"module": "NodeNext", // 要編譯成的 module
|
"module": "ESNext", // 要編譯成的 module
|
||||||
"moduleResolution": "NodeNext", // module 解析方式, default "classic"
|
"moduleResolution": "NodeNext", // module 解析方式, default "classic"
|
||||||
"esModuleInterop": true, // module 兼容, 會影響 build 方式
|
"esModuleInterop": true, // module 兼容, 會影響 build 方式
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
@@ -9,6 +9,15 @@
|
|||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"declarationMap": true
|
"declarationMap": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["*"],
|
||||||
|
"@app/*": ["app/*"],
|
||||||
|
"@server/*": ["server/*"],
|
||||||
|
"@models/*": ["packages/models/src/*"],
|
||||||
|
"@game/*": ["packages/game/src/*"],
|
||||||
|
"@file-graph/*": ["packages/file-graph/src/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user