Compare commits
5 Commits
15a8ff29e6
...
f828384dba
| Author | SHA1 | Date | |
|---|---|---|---|
| f828384dba | |||
| d99b4d53e1 | |||
| 877f888c96 | |||
| 40b5bb3df3 | |||
| a9ea29287e |
|
|
@ -52,10 +52,10 @@ https://wakamaifondue.com/
|
|||
|
||||
## Pluto Goals: Phase 2
|
||||
|
||||
- [ ] public tunnel for your NOSE webapps
|
||||
- [ ] public tunnel lives through reboots
|
||||
- [ ] self updating NOSE server
|
||||
- [ ] `pub/` static hosting in webapps
|
||||
- [x] public tunnel for your NOSE webapps
|
||||
- [x] public tunnel lives through reboots
|
||||
- [x] self updating NOSE server
|
||||
- [x] `pub/` static hosting in webapps
|
||||
- [ ] game/bin/www cartridges
|
||||
- [ ] upload files to projects
|
||||
- [ ] pico8-style games
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
export const game = true
|
||||
|
||||
import type { GameContext, InputState } from "@/shared/game"
|
||||
import { rng } from "@/shared/utils.ts"
|
||||
import { rng } from "@/shared/utils"
|
||||
|
||||
const WIDTH = 960
|
||||
const HEIGHT = 540
|
||||
|
|
|
|||
20
bin/games.tsx
Normal file
20
bin/games.tsx
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// List all the games installed on the system.
|
||||
|
||||
import { readdirSync } from "fs"
|
||||
import { join } from "path"
|
||||
import { NOSE_SYS_BIN } from "@/config"
|
||||
|
||||
export default async function () {
|
||||
let games = await Promise.all(readdirSync(NOSE_SYS_BIN, { withFileTypes: true }).map(async file => {
|
||||
if (!file.isFile()) return
|
||||
|
||||
const code = await Bun.file(join(NOSE_SYS_BIN, file.name)).text()
|
||||
|
||||
if (/^export const game\s*=\s*true\s*;?\s*$/m.test(code))
|
||||
return file.name.replace(".tsx", "").replace(".ts", "")
|
||||
})).then(games => games.filter(file => file))
|
||||
|
||||
return <>
|
||||
{games.map(game => <a href={`#${game}`}>{game}</a>)}
|
||||
</>
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@
|
|||
export const game = true
|
||||
|
||||
import type { GameContext, InputState } from "@/shared/game"
|
||||
import { rng } from "@/shared/utils.ts"
|
||||
import { rng } from "@/shared/utils"
|
||||
|
||||
const CELL = 20
|
||||
const WIDTH = 30
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
export const game = true
|
||||
|
||||
import type { GameContext, InputState } from "@/shared/game"
|
||||
import { randomElement, randomIndex } from "@/shared/utils.ts"
|
||||
import { randomElement, randomIndex } from "@/shared/utils"
|
||||
|
||||
const COLS = 10
|
||||
const ROWS = 20
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { initHistory } from "./history.js"
|
|||
import { initHyperlink } from "./hyperlink.js"
|
||||
import { initInput } from "./input.js"
|
||||
import { initResize } from "./resize.js"
|
||||
import { initScrollback } from "./scrollback.js"
|
||||
import { startVramCounter } from "./vram.js"
|
||||
import { startConnection } from "./websocket.js"
|
||||
|
||||
|
|
@ -19,6 +20,7 @@ initHistory()
|
|||
initHyperlink()
|
||||
initInput()
|
||||
initResize()
|
||||
initScrollback()
|
||||
|
||||
startConnection()
|
||||
startVramCounter()
|
||||
|
|
@ -2,12 +2,16 @@
|
|||
// The scrollback shows your history of interacting with the shell.
|
||||
// input, output, etc
|
||||
|
||||
import { scrollback, $$ } from "./dom.js"
|
||||
import { randomId } from "../shared/utils.js"
|
||||
import type { CommandOutput } from "../shared/types.js"
|
||||
import { scrollback, cmdInput, $$ } from "./dom.js"
|
||||
import { randomId } from "../shared/utils.js"
|
||||
|
||||
type InputStatus = "waiting" | "streaming" | "ok" | "error"
|
||||
|
||||
export function initScrollback() {
|
||||
window.addEventListener("click", handleInputClick)
|
||||
}
|
||||
|
||||
export function autoScroll() {
|
||||
// requestAnimationFrame(() => scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight)
|
||||
// scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight
|
||||
|
|
@ -63,10 +67,11 @@ export function addOutput(id: string, output: CommandOutput) {
|
|||
item.textContent = content
|
||||
|
||||
const input = document.querySelector(`[data-id="${id}"].input`)
|
||||
if (input instanceof HTMLLIElement)
|
||||
if (input instanceof HTMLLIElement) {
|
||||
input.parentNode!.insertBefore(item, input.nextSibling)
|
||||
else
|
||||
} else {
|
||||
insert(item)
|
||||
}
|
||||
|
||||
autoScroll()
|
||||
}
|
||||
|
|
@ -135,3 +140,13 @@ function processOutput(output: CommandOutput): ["html" | "text", string] {
|
|||
|
||||
return [html ? "html" : "text", content]
|
||||
}
|
||||
|
||||
function handleInputClick(e: MouseEvent) {
|
||||
const target = e.target
|
||||
|
||||
if (!(target instanceof HTMLElement)) return
|
||||
|
||||
if (target.matches(".input .content")) {
|
||||
cmdInput.value = target.textContent
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import { addErrorMessage } from "./scrollback.js"
|
|||
|
||||
const MAX_RETRIES = 5
|
||||
let retries = 0
|
||||
let connected = false
|
||||
let msgQueue: Message[] = []
|
||||
|
||||
let ws: WebSocket | null = null
|
||||
|
||||
|
|
@ -20,10 +22,21 @@ export function startConnection() {
|
|||
ws.onmessage = receive
|
||||
ws.onclose = retryConnection
|
||||
ws.onerror = () => ws?.close()
|
||||
ws.onopen = () => {
|
||||
connected = true
|
||||
msgQueue.forEach(msg => send(msg))
|
||||
msgQueue.length = 0
|
||||
}
|
||||
}
|
||||
|
||||
// send any message
|
||||
export function send(msg: Message) {
|
||||
if (!connected) {
|
||||
msgQueue.push(msg)
|
||||
startConnection()
|
||||
return
|
||||
}
|
||||
|
||||
if (!msg.session) msg.session = sessionID
|
||||
ws?.readyState === 1 && ws.send(JSON.stringify(msg))
|
||||
console.log("-> send", msg)
|
||||
|
|
@ -41,6 +54,8 @@ export function close() {
|
|||
}
|
||||
|
||||
function retryConnection() {
|
||||
connected = false
|
||||
|
||||
if (retries >= MAX_RETRIES) {
|
||||
addErrorMessage(`!! Failed to reconnect ${retries} times. Server is down.`)
|
||||
if (ws) ws.onclose = () => { }
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ app.use("*", async (c, next) => {
|
|||
})
|
||||
|
||||
app.on("GET", ["/js/:path{.+}", "/shared/:path{.+}"], async c => {
|
||||
const path = "./src/" + c.req.path.replace("..", ".")
|
||||
let path = "./src/" + c.req.path.replace("..", ".")
|
||||
|
||||
// path must end in .js or .ts
|
||||
if (!path.endsWith(".js") && !path.endsWith(".ts")) return c.text("File not found", 404)
|
||||
if (!path.endsWith(".js") && !path.endsWith(".ts")) path += ".ts"
|
||||
|
||||
const ts = path.replace(".js", ".ts")
|
||||
if (isFile(ts)) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user