Compare commits

...

2 Commits

12 changed files with 80 additions and 7 deletions

View File

@ -2,6 +2,7 @@
import { projects } from "@/project"
import { sessionGet, sessionSet } from "@/session"
import ls from "./ls"
export default function (project: string) {
const state = sessionGet()
@ -10,6 +11,7 @@ export default function (project: string) {
if (state && projects().includes(project)) {
sessionSet("project", project)
sessionSet("cwd", "")
return ls()
} else {
return { error: `failed to load ${project}` }
}

View File

@ -72,7 +72,6 @@
// Listen for navigation commands from parent
window.addEventListener('message', (event) => {
console.log(event)
if (event.data.type === 'NAV_COMMAND') {
switch (event.data.action) {
case 'back': history.back(); break

View File

@ -181,7 +181,8 @@
display: none;
}
#statusbar.showing-msg .line-cwd {
#statusbar.showing-msg .line-cwd,
#statusbar.showing-msg .line-www {
display: none;
}

View File

@ -41,6 +41,7 @@ export const Terminal: FC = async () => (
<div id="statusbar">
<div class="line-cwd"><a href="#projects" id="project-name">root</a>: <a href="#ls" id="project-cwd">/</a></div>
<div class="line-www"><a id="project-www" href="#">www</a></div>
<div class="line-msg"><span id="statusbar-msg"></span></div>
</div>
</>

View File

@ -8,6 +8,7 @@ import { focusInput } from "./focus"
import { resize } from "./resize"
import { sessionId } from "./session"
import { send } from "./websocket"
import { status } from "./statusbar"
export const commands: string[] = []
@ -30,6 +31,7 @@ export const browserCommands: Record<string, (...args: string[]) => void | Promi
resize()
focusInput()
},
status: (msg: string) => status(msg),
reload: () => window.location.reload(),
}

View File

@ -3,6 +3,7 @@
import type { Message } from "@/shared/types"
import { cacheCommands } from "./commands"
import { cacheApps } from "./webapp"
import { handleOutput } from "./scrollback"
import { handleStreamStart, handleStreamAppend, handleStreamReplace, handleStreamEnd } from "./stream"
import { handleGameStart } from "./game"
@ -14,7 +15,9 @@ export async function dispatchMessage(msg: Message) {
case "output":
handleOutput(msg); break
case "commands":
cacheCommands(msg.data as string[]); break
cacheCommands(msg.data); break
case "apps":
cacheApps(msg.data); break
case "error":
console.error(msg.data); break
case "stream:start":

View File

@ -10,6 +10,7 @@ import { initHyperlink } from "./hyperlink"
import { initInput } from "./input"
import { initResize } from "./resize"
import { initScrollback } from "./scrollback"
import { initSession } from "./session"
import { startVramCounter } from "./vram"
import { startConnection } from "./websocket"
@ -25,6 +26,7 @@ initHyperlink()
initInput()
initResize()
initScrollback()
initSession()
startConnection()
startVramCounter()

View File

@ -5,15 +5,24 @@
import type { SessionStartMessage, SessionUpdateMessage } from "@/shared/types"
import { browserCommands } from "./commands"
import { randomId } from "../shared/utils"
import { apps } from "./webapp"
import { $ } from "./dom"
export const sessionId = randomId()
export const projectName = $("project-name") as HTMLAnchorElement
export const projectCwd = $("project-cwd") as HTMLAnchorElement
export const projectWww = $("project-www") as HTMLAnchorElement
export const sessionStore = new Map<string, string>()
export function initSession() {
window.addEventListener("apps:change", e =>
updateWww(sessionStore.get("project") || "root")
)
}
export function handleSessionStart(msg: SessionStartMessage) {
sessionStore.set("NOSE_DIR", msg.data.NOSE_DIR)
sessionStore.set("hostname", msg.data.hostname)
updateProjectName(msg.data.project)
updateCwd(msg.data.cwd)
browserCommands.mode?.(msg.data.mode)
@ -32,6 +41,7 @@ export function handleSessionUpdate(msg: SessionUpdateMessage) {
function updateProjectName(project: string) {
sessionStore.set("project", project)
projectName.textContent = project
updateWww(project)
updateCwd("/")
}
@ -41,6 +51,18 @@ function updateCwd(cwd: string) {
projectCwd.textContent = cwd
}
function updateWww(project: string) {
if (!apps.includes(project)) {
projectWww.style.display = "none"
return
}
projectWww.style.display = ""
const hostname = sessionStore.get("hostname") || "localhost"
const s = hostname.startsWith("localhost") ? "" : "s"
projectWww.href = `http${s}://${project}.${hostname}`
}
function displayProjectPath(path: string): string {
let prefix = sessionStore.get("NOSE_DIR") || ""
prefix += "/" + sessionStore.get("project")

12
src/js/webapp.ts Normal file
View File

@ -0,0 +1,12 @@
////
// NOSE webapps
export const apps: string[] = []
export function cacheApps(a: string[]) {
apps.length = 0
apps.unshift(...a)
apps.sort()
window.dispatchEvent(new CustomEvent("apps:change"))
}

View File

@ -9,7 +9,7 @@ import color from "kleur"
import type { Message } from "./shared/types"
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 { serveApp, apps, initWebapps } from "./webapp"
import { commands, commandPath, loadCommandModule } from "./commands"
import { runCommandFn } from "./shell"
import { send, addWebsocket, removeWebsocket, closeWebsockets } from "./websocket"
@ -164,11 +164,14 @@ app.get("/", c => c.html(<Layout><Terminal /></Layout>))
app.get("/ws", c => {
const _sessionId = c.req.query("session")
const url = new URL(c.req.url)
let hostname = url.hostname + (url.port === "80" ? "" : `:${url.port}`)
return upgradeWebSocket(c, {
async onOpen(_e, ws) {
addWebsocket(ws)
send(ws, { type: "commands", data: await commands() })
send(ws, { type: "apps", data: apps() })
send(ws, {
type: "session:start",
@ -176,7 +179,8 @@ app.get("/ws", c => {
NOSE_DIR: NOSE_DIR,
project: DEFAULT_PROJECT,
cwd: "/",
mode: getState("ui:mode") || "tall"
mode: getState("ui:mode") || "tall",
hostname
}
})
},
@ -242,6 +246,7 @@ console.log(color.blue("NOSE_ROOT_BIN:"), color.yellow(tilde(NOSE_ROOT_BIN)))
await initNoseDir()
initCommands()
initWebapps()
initSneakers()
export default {

View File

@ -8,6 +8,7 @@ export type Message =
| GameStartMessage
| StreamMessage
| CommandsMessage
| AppsMessage
export type CommandOutput = string | string[]
| { text: string, script?: string }
@ -30,6 +31,11 @@ export type CommandsMessage = {
data: string[]
}
export type AppsMessage = {
type: "apps"
data: string[]
}
export type OutputMessage = {
type: "output"
id?: string
@ -57,6 +63,7 @@ export type SessionStartMessage = {
project: string
cwd: string
mode: string
hostname: string
}
}

View File

@ -5,14 +5,19 @@ import type { Child } from "hono/jsx"
import { type Context, Hono } from "hono"
import { renderToString } from "hono/jsx/dom/server"
import { join } from "path"
import { readdirSync } from "fs"
import { readdirSync, watch } from "fs"
import { sendAll } from "./websocket"
import { expectDir } from "./utils"
import { NOSE_DIR } from "./config"
import { isFile, isDir } from "./utils"
export type Handler = (r: Context) => string | Child | Response | Promise<Response>
export type App = Hono | Handler
export function initWebapps() {
startWatcher()
}
export async function serveApp(c: Context, subdomain: string): Promise<Response> {
const app = await findApp(subdomain)
const path = appDir(subdomain)
@ -94,3 +99,15 @@ function serveStatic(path: string): Response {
}
})
}
let wwwWatcher
function startWatcher() {
if (!expectDir(NOSE_DIR)) return
wwwWatcher = watch(NOSE_DIR, { recursive: true }, async (event, filename) => {
if (!filename) return
if (/^.+\/index\.tsx?$/.test(filename))
sendAll({ type: "apps", data: apps() })
})
}