Compare commits
2 Commits
b92b40d9f1
...
1a97e3721c
| Author | SHA1 | Date | |
|---|---|---|---|
| 1a97e3721c | |||
| d5d64b88c6 |
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
import { projects } from "@/project"
|
import { projects } from "@/project"
|
||||||
import { sessionGet, sessionSet } from "@/session"
|
import { sessionGet, sessionSet } from "@/session"
|
||||||
|
import ls from "./ls"
|
||||||
|
|
||||||
export default function (project: string) {
|
export default function (project: string) {
|
||||||
const state = sessionGet()
|
const state = sessionGet()
|
||||||
|
|
@ -10,6 +11,7 @@ export default function (project: string) {
|
||||||
if (state && projects().includes(project)) {
|
if (state && projects().includes(project)) {
|
||||||
sessionSet("project", project)
|
sessionSet("project", project)
|
||||||
sessionSet("cwd", "")
|
sessionSet("cwd", "")
|
||||||
|
return ls()
|
||||||
} else {
|
} else {
|
||||||
return { error: `failed to load ${project}` }
|
return { error: `failed to load ${project}` }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -72,7 +72,6 @@
|
||||||
|
|
||||||
// Listen for navigation commands from parent
|
// Listen for navigation commands from parent
|
||||||
window.addEventListener('message', (event) => {
|
window.addEventListener('message', (event) => {
|
||||||
console.log(event)
|
|
||||||
if (event.data.type === 'NAV_COMMAND') {
|
if (event.data.type === 'NAV_COMMAND') {
|
||||||
switch (event.data.action) {
|
switch (event.data.action) {
|
||||||
case 'back': history.back(); break
|
case 'back': history.back(); break
|
||||||
|
|
|
||||||
|
|
@ -181,7 +181,8 @@
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
#statusbar.showing-msg .line-cwd {
|
#statusbar.showing-msg .line-cwd,
|
||||||
|
#statusbar.showing-msg .line-www {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ export const Terminal: FC = async () => (
|
||||||
|
|
||||||
<div id="statusbar">
|
<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-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 class="line-msg"><span id="statusbar-msg"></span></div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import { focusInput } from "./focus"
|
||||||
import { resize } from "./resize"
|
import { resize } from "./resize"
|
||||||
import { sessionId } from "./session"
|
import { sessionId } from "./session"
|
||||||
import { send } from "./websocket"
|
import { send } from "./websocket"
|
||||||
|
import { status } from "./statusbar"
|
||||||
|
|
||||||
export const commands: string[] = []
|
export const commands: string[] = []
|
||||||
|
|
||||||
|
|
@ -30,6 +31,7 @@ export const browserCommands: Record<string, (...args: string[]) => void | Promi
|
||||||
resize()
|
resize()
|
||||||
focusInput()
|
focusInput()
|
||||||
},
|
},
|
||||||
|
status: (msg: string) => status(msg),
|
||||||
reload: () => window.location.reload(),
|
reload: () => window.location.reload(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import type { Message } from "@/shared/types"
|
import type { Message } from "@/shared/types"
|
||||||
import { cacheCommands } from "./commands"
|
import { cacheCommands } from "./commands"
|
||||||
|
import { cacheApps } from "./webapp"
|
||||||
import { handleOutput } from "./scrollback"
|
import { handleOutput } from "./scrollback"
|
||||||
import { handleStreamStart, handleStreamAppend, handleStreamReplace, handleStreamEnd } from "./stream"
|
import { handleStreamStart, handleStreamAppend, handleStreamReplace, handleStreamEnd } from "./stream"
|
||||||
import { handleGameStart } from "./game"
|
import { handleGameStart } from "./game"
|
||||||
|
|
@ -14,7 +15,9 @@ export async function dispatchMessage(msg: Message) {
|
||||||
case "output":
|
case "output":
|
||||||
handleOutput(msg); break
|
handleOutput(msg); break
|
||||||
case "commands":
|
case "commands":
|
||||||
cacheCommands(msg.data as string[]); break
|
cacheCommands(msg.data); break
|
||||||
|
case "apps":
|
||||||
|
cacheApps(msg.data); break
|
||||||
case "error":
|
case "error":
|
||||||
console.error(msg.data); break
|
console.error(msg.data); break
|
||||||
case "stream:start":
|
case "stream:start":
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { initHyperlink } from "./hyperlink"
|
||||||
import { initInput } from "./input"
|
import { initInput } from "./input"
|
||||||
import { initResize } from "./resize"
|
import { initResize } from "./resize"
|
||||||
import { initScrollback } from "./scrollback"
|
import { initScrollback } from "./scrollback"
|
||||||
|
import { initSession } from "./session"
|
||||||
import { startVramCounter } from "./vram"
|
import { startVramCounter } from "./vram"
|
||||||
import { startConnection } from "./websocket"
|
import { startConnection } from "./websocket"
|
||||||
|
|
||||||
|
|
@ -25,6 +26,7 @@ initHyperlink()
|
||||||
initInput()
|
initInput()
|
||||||
initResize()
|
initResize()
|
||||||
initScrollback()
|
initScrollback()
|
||||||
|
initSession()
|
||||||
|
|
||||||
startConnection()
|
startConnection()
|
||||||
startVramCounter()
|
startVramCounter()
|
||||||
|
|
@ -5,15 +5,24 @@
|
||||||
import type { SessionStartMessage, SessionUpdateMessage } from "@/shared/types"
|
import type { SessionStartMessage, SessionUpdateMessage } from "@/shared/types"
|
||||||
import { browserCommands } from "./commands"
|
import { browserCommands } from "./commands"
|
||||||
import { randomId } from "../shared/utils"
|
import { randomId } from "../shared/utils"
|
||||||
|
import { apps } from "./webapp"
|
||||||
import { $ } from "./dom"
|
import { $ } from "./dom"
|
||||||
|
|
||||||
export const sessionId = randomId()
|
export const sessionId = randomId()
|
||||||
export const projectName = $("project-name") as HTMLAnchorElement
|
export const projectName = $("project-name") as HTMLAnchorElement
|
||||||
export const projectCwd = $("project-cwd") as HTMLAnchorElement
|
export const projectCwd = $("project-cwd") as HTMLAnchorElement
|
||||||
|
export const projectWww = $("project-www") as HTMLAnchorElement
|
||||||
export const sessionStore = new Map<string, string>()
|
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) {
|
export function handleSessionStart(msg: SessionStartMessage) {
|
||||||
sessionStore.set("NOSE_DIR", msg.data.NOSE_DIR)
|
sessionStore.set("NOSE_DIR", msg.data.NOSE_DIR)
|
||||||
|
sessionStore.set("hostname", msg.data.hostname)
|
||||||
updateProjectName(msg.data.project)
|
updateProjectName(msg.data.project)
|
||||||
updateCwd(msg.data.cwd)
|
updateCwd(msg.data.cwd)
|
||||||
browserCommands.mode?.(msg.data.mode)
|
browserCommands.mode?.(msg.data.mode)
|
||||||
|
|
@ -32,6 +41,7 @@ export function handleSessionUpdate(msg: SessionUpdateMessage) {
|
||||||
function updateProjectName(project: string) {
|
function updateProjectName(project: string) {
|
||||||
sessionStore.set("project", project)
|
sessionStore.set("project", project)
|
||||||
projectName.textContent = project
|
projectName.textContent = project
|
||||||
|
updateWww(project)
|
||||||
updateCwd("/")
|
updateCwd("/")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -41,6 +51,18 @@ function updateCwd(cwd: string) {
|
||||||
projectCwd.textContent = cwd
|
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 {
|
function displayProjectPath(path: string): string {
|
||||||
let prefix = sessionStore.get("NOSE_DIR") || ""
|
let prefix = sessionStore.get("NOSE_DIR") || ""
|
||||||
prefix += "/" + sessionStore.get("project")
|
prefix += "/" + sessionStore.get("project")
|
||||||
|
|
|
||||||
12
src/js/webapp.ts
Normal file
12
src/js/webapp.ts
Normal 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"))
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import color from "kleur"
|
||||||
import type { Message } from "./shared/types"
|
import type { Message } from "./shared/types"
|
||||||
import { NOSE_ICON, NOSE_BIN, NOSE_DATA, NOSE_DIR, NOSE_ROOT_BIN, DEFAULT_PROJECT } 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 { transpile, isFile, tilde, isDir } from "./utils"
|
||||||
import { serveApp } from "./webapp"
|
import { serveApp, apps, initWebapps } from "./webapp"
|
||||||
import { commands, commandPath, loadCommandModule } from "./commands"
|
import { commands, commandPath, loadCommandModule } from "./commands"
|
||||||
import { runCommandFn } from "./shell"
|
import { runCommandFn } from "./shell"
|
||||||
import { send, addWebsocket, removeWebsocket, closeWebsockets } from "./websocket"
|
import { send, addWebsocket, removeWebsocket, closeWebsockets } from "./websocket"
|
||||||
|
|
@ -164,11 +164,14 @@ app.get("/", c => c.html(<Layout><Terminal /></Layout>))
|
||||||
|
|
||||||
app.get("/ws", c => {
|
app.get("/ws", c => {
|
||||||
const _sessionId = c.req.query("session")
|
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, {
|
return upgradeWebSocket(c, {
|
||||||
async onOpen(_e, ws) {
|
async onOpen(_e, ws) {
|
||||||
addWebsocket(ws)
|
addWebsocket(ws)
|
||||||
send(ws, { type: "commands", data: await commands() })
|
send(ws, { type: "commands", data: await commands() })
|
||||||
|
send(ws, { type: "apps", data: apps() })
|
||||||
|
|
||||||
send(ws, {
|
send(ws, {
|
||||||
type: "session:start",
|
type: "session:start",
|
||||||
|
|
@ -176,7 +179,8 @@ app.get("/ws", c => {
|
||||||
NOSE_DIR: NOSE_DIR,
|
NOSE_DIR: NOSE_DIR,
|
||||||
project: DEFAULT_PROJECT,
|
project: DEFAULT_PROJECT,
|
||||||
cwd: "/",
|
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()
|
await initNoseDir()
|
||||||
initCommands()
|
initCommands()
|
||||||
|
initWebapps()
|
||||||
initSneakers()
|
initSneakers()
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ export type Message =
|
||||||
| GameStartMessage
|
| GameStartMessage
|
||||||
| StreamMessage
|
| StreamMessage
|
||||||
| CommandsMessage
|
| CommandsMessage
|
||||||
|
| AppsMessage
|
||||||
|
|
||||||
export type CommandOutput = string | string[]
|
export type CommandOutput = string | string[]
|
||||||
| { text: string, script?: string }
|
| { text: string, script?: string }
|
||||||
|
|
@ -30,6 +31,11 @@ export type CommandsMessage = {
|
||||||
data: string[]
|
data: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type AppsMessage = {
|
||||||
|
type: "apps"
|
||||||
|
data: string[]
|
||||||
|
}
|
||||||
|
|
||||||
export type OutputMessage = {
|
export type OutputMessage = {
|
||||||
type: "output"
|
type: "output"
|
||||||
id?: string
|
id?: string
|
||||||
|
|
@ -57,6 +63,7 @@ export type SessionStartMessage = {
|
||||||
project: string
|
project: string
|
||||||
cwd: string
|
cwd: string
|
||||||
mode: string
|
mode: string
|
||||||
|
hostname: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,14 +5,19 @@ import type { Child } from "hono/jsx"
|
||||||
import { type Context, Hono } from "hono"
|
import { type Context, Hono } from "hono"
|
||||||
import { renderToString } from "hono/jsx/dom/server"
|
import { renderToString } from "hono/jsx/dom/server"
|
||||||
import { join } from "path"
|
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 { NOSE_DIR } from "./config"
|
||||||
import { isFile, isDir } from "./utils"
|
import { isFile, isDir } from "./utils"
|
||||||
|
|
||||||
export type Handler = (r: Context) => string | Child | Response | Promise<Response>
|
export type Handler = (r: Context) => string | Child | Response | Promise<Response>
|
||||||
export type App = Hono | Handler
|
export type App = Hono | Handler
|
||||||
|
|
||||||
|
export function initWebapps() {
|
||||||
|
startWatcher()
|
||||||
|
}
|
||||||
|
|
||||||
export async function serveApp(c: Context, subdomain: string): Promise<Response> {
|
export async function serveApp(c: Context, subdomain: string): Promise<Response> {
|
||||||
const app = await findApp(subdomain)
|
const app = await findApp(subdomain)
|
||||||
const path = appDir(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() })
|
||||||
|
})
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user