Compare commits
7 Commits
7ffffebd4a
...
907d8b5393
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
907d8b5393 | ||
|
|
7be77d5a69 | ||
|
|
e384bce2ee | ||
|
|
fb657c5bb1 | ||
|
|
93a9ce9c8c | ||
|
|
d8fd1ae771 | ||
|
|
3f9db13192 |
|
|
@ -6,12 +6,11 @@ import { join, extname } from "path"
|
|||
|
||||
import type { CommandOutput } from "@/shared/types"
|
||||
import { isBinaryFile } from "@/utils"
|
||||
import { projectName, projectDir } from "@/project"
|
||||
import { projectDir } from "@/project"
|
||||
import { sessionGet } from "@/session"
|
||||
import { highlight } from "../lib/highlight"
|
||||
|
||||
export default async function (path: string) {
|
||||
const project = projectName()
|
||||
const root = sessionGet("cwd") || projectDir()
|
||||
|
||||
let files: string[] = []
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import { projectName, projectDir } from "@/project"
|
|||
import { sessionGet } from "@/session"
|
||||
|
||||
export default async function (path: string) {
|
||||
const project = projectName()
|
||||
const root = sessionGet("cwd") || projectDir()
|
||||
|
||||
let files: string[] = []
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Look around.
|
||||
|
||||
import { readdirSync } from "fs"
|
||||
import { projectName, projectDir } from "@/project"
|
||||
import { projectDir } from "@/project"
|
||||
import { sessionGet } from "@/session"
|
||||
|
||||
export default function () {
|
||||
|
|
|
|||
|
|
@ -80,6 +80,16 @@ a:visited {
|
|||
color: var(--purple);
|
||||
}
|
||||
|
||||
/* hide scrollbars, always */
|
||||
* {
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
}
|
||||
|
||||
*::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: var(--font-family);
|
||||
|
|
|
|||
|
|
@ -160,4 +160,24 @@
|
|||
|
||||
#scrollback .output {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#statusline {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 2px 8px;
|
||||
|
||||
background: var(--c64-light-gray);
|
||||
color: var(--c64-dark-blue);
|
||||
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
#statusline a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
////
|
||||
// Dispatch Messages received via WebSocket
|
||||
// Dispatch client->server Messages received via WebSocket
|
||||
|
||||
import { basename } from "path"
|
||||
import type { Message } from "./shared/types"
|
||||
import type { Message, InputMessage, SaveFileMessage } from "./shared/types"
|
||||
import { runCommand } from "./shell"
|
||||
import { send } from "./websocket"
|
||||
import { setState } from "./state"
|
||||
|
|
@ -11,10 +11,10 @@ export async function dispatchMessage(ws: any, msg: Message) {
|
|||
console.log("<- receive", msg)
|
||||
switch (msg.type) {
|
||||
case "input":
|
||||
await inputMessage(ws, msg); break
|
||||
await inputMessage(ws, msg as InputMessage); break
|
||||
|
||||
case "save-file":
|
||||
await saveFileMessage(ws, msg); break
|
||||
await saveFileMessage(ws, msg as SaveFileMessage); break
|
||||
|
||||
case "ui:mode":
|
||||
setState("ui:mode", msg.data); break
|
||||
|
|
@ -24,8 +24,8 @@ export async function dispatchMessage(ws: any, msg: Message) {
|
|||
}
|
||||
}
|
||||
|
||||
async function inputMessage(ws: any, msg: Message) {
|
||||
const result = await runCommand(msg.session || "", msg.id || "", msg.data as string, ws)
|
||||
async function inputMessage(ws: any, msg: InputMessage) {
|
||||
const result = await runCommand(msg.session, msg.id, msg.data as string, ws)
|
||||
|
||||
if (typeof result.output === "object" && "game" in result.output) {
|
||||
send(ws, { id: msg.id, type: "game:start", data: result.output.game })
|
||||
|
|
@ -34,7 +34,7 @@ async function inputMessage(ws: any, msg: Message) {
|
|||
}
|
||||
}
|
||||
|
||||
async function saveFileMessage(ws: any, msg: Message) {
|
||||
async function saveFileMessage(ws: any, msg: SaveFileMessage) {
|
||||
if (msg.id && typeof msg.data === "string") {
|
||||
await Bun.write(msg.id.replace("..", ""), msg.data, { createPath: true })
|
||||
send(ws, { type: "output", data: { status: "ok", output: `saved ${basename(msg.id)}` } })
|
||||
|
|
|
|||
|
|
@ -34,5 +34,7 @@ export const Terminal: FC = async () => (
|
|||
<li class="center">**** NOSE PLUTO V{new Date().getMonth() + 1}.{new Date().getDate()} ****</li>
|
||||
<li class="center">VRAM <span id="vram-size">000KB</span></li>
|
||||
</ul>
|
||||
|
||||
<div id="statusline"><div><a href="#projects" id="project-name">root</a>: <a href="#ls" id="project-cwd">/</a></div></div>
|
||||
</>
|
||||
)
|
||||
|
|
@ -38,5 +38,4 @@ export function cacheCommands(cmds: string[]) {
|
|||
commands.push(...cmds)
|
||||
commands.push(...Object.keys(browserCommands))
|
||||
commands.sort()
|
||||
console.log("CMDS", commands)
|
||||
}
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
////
|
||||
// Dispatch server->client Messages received via WebSocket
|
||||
|
||||
import type { Message } from "@/shared/types"
|
||||
import { cacheCommands } from "./commands"
|
||||
import { handleOutput } from "./scrollback"
|
||||
import { handleStreamStart, handleStreamAppend, handleStreamReplace, handleStreamEnd } from "./stream"
|
||||
import { handleGameStart } from "./game"
|
||||
import { browserCommands } from "./commands"
|
||||
import { handleSessionStart, handleSessionUpdate } from "./session"
|
||||
|
||||
// message received from server
|
||||
export async function dispatchMessage(msg: Message) {
|
||||
|
|
@ -24,8 +27,10 @@ export async function dispatchMessage(msg: Message) {
|
|||
handleStreamReplace(msg); break
|
||||
case "game:start":
|
||||
await handleGameStart(msg); break
|
||||
case "ui:mode":
|
||||
browserCommands.mode?.(msg.data as string); break
|
||||
case "session:start":
|
||||
handleSessionStart(msg); break
|
||||
case "session:update":
|
||||
handleSessionUpdate(msg); break
|
||||
default:
|
||||
console.error("unknown message type", msg)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,47 @@
|
|||
// Each browser tab is a shell session. This means you can run multiple sessions
|
||||
// in the same browser.
|
||||
|
||||
import type { SessionStartMessage, SessionUpdateMessage } from "@/shared/types"
|
||||
import { browserCommands } from "./commands"
|
||||
import { randomId } from "../shared/utils"
|
||||
import { $ } from "./dom"
|
||||
|
||||
export const sessionId = randomId()
|
||||
export const sessionId = randomId()
|
||||
export const projectName = $("project-name") as HTMLAnchorElement
|
||||
export const projectCwd = $("project-cwd") as HTMLAnchorElement
|
||||
export const sessionStore = new Map<string, string>()
|
||||
|
||||
export function handleSessionStart(msg: SessionStartMessage) {
|
||||
sessionStore.set("NOSE_DIR", msg.data.NOSE_DIR)
|
||||
updateProjectName(msg.data.project)
|
||||
updateCwd(msg.data.cwd)
|
||||
browserCommands.mode?.(msg.data.mode)
|
||||
}
|
||||
|
||||
export function handleSessionUpdate(msg: SessionUpdateMessage) {
|
||||
const data = msg.data as Record<string, string>
|
||||
|
||||
if (data.project)
|
||||
updateProjectName(data.project)
|
||||
|
||||
if (data.cwd)
|
||||
updateCwd(data.cwd)
|
||||
}
|
||||
|
||||
function updateProjectName(project: string) {
|
||||
sessionStore.set("project", project)
|
||||
projectName.textContent = project
|
||||
}
|
||||
|
||||
function updateCwd(cwd: string) {
|
||||
cwd = displayProjectPath(cwd)
|
||||
sessionStore.set("cwd", cwd)
|
||||
projectCwd.textContent = cwd
|
||||
}
|
||||
|
||||
function displayProjectPath(path: string): string {
|
||||
let prefix = sessionStore.get("NOSE_DIR") || ""
|
||||
prefix += "/" + sessionStore.get("project")
|
||||
|
||||
return path.replace(prefix, "") || "/"
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ export function projectDir(name = projectName()): string {
|
|||
}
|
||||
|
||||
export function projectBin(name = projectName()): string {
|
||||
return join(projectDir(), "bin")
|
||||
return join(projectDir(name), "bin")
|
||||
}
|
||||
|
||||
export function projectFiles(name = projectName()): Dirent[] {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { prettyJSON } from "hono/pretty-json"
|
|||
import color from "kleur"
|
||||
|
||||
import type { Message } from "./shared/types"
|
||||
import { NOSE_ICON, NOSE_BIN, NOSE_DATA, NOSE_DIR, NOSE_ROOT_BIN } from "./config"
|
||||
import { NOSE_ICON, NOSE_BIN, NOSE_DATA, NOSE_DIR, NOSE_ROOT_BIN, DEFAULT_PROJECT } from "./config"
|
||||
import { transpile, isFile, tilde, isDir } from "./utils"
|
||||
import { serveApp } from "./webapp"
|
||||
import { commands, commandPath, loadCommandModule } from "./commands"
|
||||
|
|
@ -137,8 +137,15 @@ app.get("/ws", c => {
|
|||
addWebsocket(ws)
|
||||
send(ws, { type: "commands", data: await commands() })
|
||||
|
||||
const mode = getState("ui:mode") || "tall"
|
||||
send(ws, { type: "ui:mode", data: mode })
|
||||
send(ws, {
|
||||
type: "session:start",
|
||||
data: {
|
||||
NOSE_DIR: NOSE_DIR,
|
||||
project: DEFAULT_PROJECT,
|
||||
cwd: "/",
|
||||
mode: getState("ui:mode") || "tall"
|
||||
}
|
||||
})
|
||||
},
|
||||
async onMessage(event, ws) {
|
||||
let data: Message | undefined
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// Session storage. 1 browser tab = 1 session
|
||||
|
||||
import { AsyncLocalStorage } from "async_hooks"
|
||||
import { send } from "./websocket"
|
||||
|
||||
export type Session = {
|
||||
taskId?: string
|
||||
|
|
@ -35,6 +36,12 @@ export function sessionSet(key: keyof Session, value: any) {
|
|||
const store = ALS.getStore()
|
||||
if (!store) throw "sessionSet() called outside of ALS.run"
|
||||
store[key] = value
|
||||
|
||||
if (!store.ws) return
|
||||
send(store.ws, {
|
||||
type: "session:update",
|
||||
data: { [key]: value }
|
||||
})
|
||||
}
|
||||
|
||||
export function sessionStore(sessionId: string, taskId?: string, ws?: any): Session {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@ export type Message = {
|
|||
type: MessageType
|
||||
data?: CommandResult | CommandOutput
|
||||
}
|
||||
| InputMessage
|
||||
| SaveFileMessage
|
||||
| SessionStartMessage
|
||||
| SessionUpdateMessage
|
||||
|
||||
export type MessageType = "error" | "input" | "output" | "commands" | "save-file"
|
||||
| "game:start"
|
||||
|
|
@ -20,3 +24,32 @@ export type CommandResult = {
|
|||
status: "ok" | "error"
|
||||
output: CommandOutput
|
||||
}
|
||||
|
||||
export type InputMessage = {
|
||||
type: "input"
|
||||
id: string
|
||||
session: string
|
||||
data: CommandResult | CommandOutput
|
||||
}
|
||||
|
||||
export type SaveFileMessage = {
|
||||
type: "save-file"
|
||||
id: string
|
||||
session: string
|
||||
data: CommandResult | CommandOutput
|
||||
}
|
||||
|
||||
export type SessionStartMessage = {
|
||||
type: "session:start"
|
||||
data: {
|
||||
NOSE_DIR: string
|
||||
project: string
|
||||
cwd: string
|
||||
mode: string
|
||||
}
|
||||
}
|
||||
|
||||
export type SessionUpdateMessage = {
|
||||
type: "session:update"
|
||||
data: Record<string, string>
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user