partial updating
This commit is contained in:
parent
5d898ac485
commit
2c8fff85f4
|
|
@ -3,14 +3,13 @@ import { define, Styles } from 'forge'
|
|||
import type { App, AppState } from '../shared/types'
|
||||
import { theme } from './themes'
|
||||
import { Modal, initModal } from './tags/modal'
|
||||
import { initUpdate } from './update'
|
||||
import { openEmojiPicker } from './tags/emoji-picker'
|
||||
|
||||
// UI state (survives re-renders)
|
||||
let selectedApp: string | null = localStorage.getItem('selectedApp')
|
||||
let sidebarCollapsed: boolean = localStorage.getItem('sidebarCollapsed') === 'true'
|
||||
|
||||
const DEFAULT_EMOJI = '🖥️'
|
||||
|
||||
// Server state (from SSE)
|
||||
let apps: App[] = []
|
||||
|
||||
|
|
@ -382,7 +381,7 @@ const AppDetail = ({ app }: { app: App }) => (
|
|||
<>
|
||||
<MainHeader>
|
||||
<MainTitle>
|
||||
<OpenEmojiPicker app={app}>{app.icon ?? DEFAULT_EMOJI}</OpenEmojiPicker>
|
||||
<OpenEmojiPicker app={app}>{app.icon}</OpenEmojiPicker>
|
||||
|
||||
{app.name}
|
||||
</MainTitle>
|
||||
|
|
@ -510,10 +509,10 @@ const Dashboard = () => {
|
|||
title={sidebarCollapsed ? app.name : undefined}
|
||||
>
|
||||
{sidebarCollapsed ? (
|
||||
<span style={{ fontSize: 18 }}>{app.icon ?? DEFAULT_EMOJI}</span>
|
||||
<span style={{ fontSize: 18 }}>{app.icon}</span>
|
||||
) : (
|
||||
<>
|
||||
<span style={{ fontSize: 14 }}>{app.icon ?? DEFAULT_EMOJI}</span>
|
||||
<span style={{ fontSize: 14 }}>{app.icon}</span>
|
||||
{app.name}
|
||||
<StatusDot state={app.state} style={{ marginLeft: 'auto' }} />
|
||||
</>
|
||||
|
|
@ -543,8 +542,9 @@ const render = () => {
|
|||
renderApp(<Dashboard />, document.getElementById('app')!)
|
||||
}
|
||||
|
||||
// Initialize modal with render function
|
||||
// Initialize render functions
|
||||
initModal(render)
|
||||
initUpdate(render)
|
||||
|
||||
// Set theme based on system preference
|
||||
const setTheme = () => {
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
25
src/client/update.tsx
Normal file
25
src/client/update.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
import { render } from 'hono/jsx/dom'
|
||||
import type { Child } from 'hono/jsx'
|
||||
|
||||
let globalRenderFn: (() => void) | null = null
|
||||
|
||||
export const initUpdate = (renderFn: () => void) => {
|
||||
globalRenderFn = renderFn
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the UI from state.
|
||||
*
|
||||
* update() - redraw everything
|
||||
* update('#emoji-results', <Results />) - target specific element
|
||||
*/
|
||||
export function update(): void
|
||||
export function update(selector: string, component: Child): void
|
||||
export function update(selector?: string, component?: Child) {
|
||||
if (selector && component !== undefined) {
|
||||
const el = document.querySelector(selector) as HTMLElement | null
|
||||
if (el) render(component, el)
|
||||
} else {
|
||||
globalRenderFn?.()
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import type { App as SharedApp, AppState, LogLine } from '../shared/types'
|
|||
|
||||
export type { AppState } from '../shared/types'
|
||||
|
||||
const DEFAULT_EMOJI = '🖥️'
|
||||
const APPS_DIR = join(process.env.DATA_DIR ?? '.', 'apps')
|
||||
const MAX_LOGS = 100
|
||||
|
||||
|
|
@ -42,29 +43,20 @@ const allAppDirs = () => {
|
|||
.sort()
|
||||
}
|
||||
|
||||
/** Returns names of valid apps (those with scripts.toes in package.json) */
|
||||
export const appNames = () => allAppDirs().filter(isApp)
|
||||
|
||||
let NEXT_PORT = 3001
|
||||
const getPort = () => NEXT_PORT++
|
||||
|
||||
/** Discover all apps and set initial states */
|
||||
const discoverApps = () => {
|
||||
for (const dir of allAppDirs()) {
|
||||
const { pkg, error } = loadApp(dir)
|
||||
const state: AppState = error ? 'invalid' : 'stopped'
|
||||
const icon = pkg.toes?.icon
|
||||
const icon = pkg.toes?.icon ?? DEFAULT_EMOJI
|
||||
_apps.set(dir, { name: dir, state, icon, error })
|
||||
}
|
||||
}
|
||||
|
||||
/** Start all valid apps */
|
||||
export const runApps = () => {
|
||||
for (const dir of appNames()) {
|
||||
const port = getPort()
|
||||
runApp(dir, port)
|
||||
}
|
||||
}
|
||||
export const runApps = () =>
|
||||
allAppDirs().filter(isApp).forEach(startApp)
|
||||
|
||||
type LoadResult = { pkg: any; error?: string }
|
||||
|
||||
|
|
@ -197,6 +189,7 @@ export const getApp = (dir: string): App | undefined => _apps.get(dir)
|
|||
export const startApp = (dir: string) => {
|
||||
const app = _apps.get(dir)
|
||||
if (!app || app.state !== 'stopped') return
|
||||
if (!isApp(dir)) return
|
||||
runApp(dir, getPort())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,16 @@ app.get('/api/apps/stream', c => {
|
|||
})
|
||||
})
|
||||
|
||||
app.get('/api/apps', c => {
|
||||
const apps = allApps().map(app => {
|
||||
const clone = { ...app }
|
||||
delete clone.proc
|
||||
delete clone.logs
|
||||
return clone
|
||||
})
|
||||
return c.json(apps)
|
||||
})
|
||||
|
||||
app.post('/api/apps/:app/start', c => {
|
||||
const appName = c.req.param('app')
|
||||
if (!appName) return c.json({ error: 'App not found' }, 404)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ export type LogLine = {
|
|||
export type App = {
|
||||
name: string
|
||||
state: AppState
|
||||
icon?: string
|
||||
icon: string
|
||||
error?: string
|
||||
port?: number
|
||||
started?: number
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user