Something went wrong. Try again.
small gleam coding and persistent agent daemon with a detachable cli and a canvas ui forked from okami.mom/albedo
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119<script lang="ts"> import { mount, untrack, type Component } from 'svelte'; import type { Board, Window, WindowType } from '../board.svelte'; import AgentsNode from '../nodes/AgentsNode.svelte'; import ArtifactNode from '../nodes/ArtifactNode.svelte'; import ChipNode from '../nodes/ChipNode.svelte'; import NoteNode from '../nodes/NoteNode.svelte'; import PtyNode from '../nodes/PtyNode.svelte'; import ReviewNode from '../nodes/ReviewNode.svelte'; import SessionNode from '../nodes/SessionNode.svelte'; import TerminalNode from '../nodes/TerminalNode.svelte'; import TraceNode from '../nodes/TraceNode.svelte'; import { hosted } from './hosts';
// Where a window shows: a tile, a tab, a spot on a canvas. The window is // mounted once (hosts.ts) and carried from slot to slot, so moving it // keeps its scroll, its page and its shell. A window off a canvas filling // the board is shown in BoardView's own slot (`over`), its place on the // canvas left empty until it goes back. let { board, window, over = false }: { board: Board; window: string; over?: boolean } = $props();
const components: Record<WindowType, Component<any>> = { session: SessionNode, review: ReviewNode, terminal: TerminalNode, trace: TraceNode, agents: AgentsNode, artifact: ArtifactNode, chip: ChipNode, note: NoteNode, pty: PtyNode, };
const type = $derived(board.windows[window]?.type); /** Marked as the one keys act on, once there is more than one it could be; a note marks itself. */ const away = $derived(!over && board.expandedOver && board.expanded === window); const marked = $derived(board.selected === window && type !== 'note' && board.list.filter((other) => other.type !== 'note').length > 1);
function make(owner: Board, id: string) { return (target: HTMLElement) => untrack(() => { // A closed window stays mounted until its host is swept, a frame later, still showing what it last held. let data: Window['data'] = owner.windows[id].data; return mount(components[owner.windows[id].type], { target, props: { id, get data() { return (data = owner.windows[id]?.data ?? data); }, get selected() { return owner.selected === id; }, }, }); }); }</script>
{#if type} <div class="window-slot" class:picked={board.picked.has(window)} class:selected={marked} data-id={window} data-type={type} data-place={over ? 'tile' : board.placeOf(window)} onpointerdowncapture={() => board.select(window)} onfocusin={() => board.selected !== window && board.select(window)} {@attach away ? null : hosted(board.id, window, make(board, window))}></div>{/if}
<style> /* Its own stacking context, so nothing inside a window climbs over a floating note; a window with a menu open is lifted so the menu can hang over its neighbours and the notes. */ .window-slot { position: relative; z-index: var(--z-window); width: 100%; height: 100%; } .window-slot:has(:global([aria-haspopup][aria-expanded='true'])) { z-index: var(--z-window-menu); } /* Every mark on a window (selected, picked, a flash) is drawn on the slot, never on the window's frame: the window's own layers (its title bar, its menus) stay inside its host, so a mark lies over all of it. */ .window-slot > :global(.window-host) { isolation: isolate; } /* flash() in board.svelte.ts animates it in. */ .window-slot::before { position: absolute; inset: 0; z-index: 1; border: 2px solid var(--accent); border-radius: 14px; opacity: 0; content: ''; pointer-events: none; } /* The selected window: its title in the accent, a faint ring, calmer than a pick's outline. */ .selected > :global(.window-host header h2) { color: var(--accent); } .selected:not(.picked)::after { position: absolute; inset: 0; border-radius: 14px; box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 35%, transparent); content: ''; pointer-events: none; } :global([data-theme='dark']) .selected:not(.picked)::after { box-shadow: 0 0 0 1px color-mix(in oklab, var(--accent) 55%, transparent); } /* Picked to make a section of (BoardView.svelte). */ .picked::after { position: absolute; inset: 0; border: 2px solid var(--accent); border-radius: 14px; content: ''; pointer-events: none; }</style>