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.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197<script lang="ts"> import type { Board } from '../board.svelte'; import NameField from './NameField.svelte'; import TileView from './TileView.svelte'; import { tileTitle } from './title'; import { windows, type Tabs } from './tree';
// Tiles stacked behind a quiet strip of tabs, as zen's desk keeps them: the // shown one lit, a window's tab with its ×. Every tab's windows stay // mounted, so coming back to a page or a shell finds it as it was; the // hidden ones skip layout and paint. A tab is also a handle to carry what // it holds elsewhere, and a double-click names it. let { board, tabs }: { board: Board; tabs: Tabs } = $props();
let strip = $state<HTMLElement>();
// The wheel scrolls the strip sideways, and the shown tab stays in view. function sideways(node: HTMLElement) { const wheel = (event: WheelEvent) => { if (Math.abs(event.deltaY) <= Math.abs(event.deltaX) || node.scrollWidth <= node.clientWidth) return; event.preventDefault(); node.scrollLeft += event.deltaY; }; node.addEventListener('wheel', wheel, { passive: false }); return () => node.removeEventListener('wheel', wheel); }
$effect(() => { void tabs.active; const tab = strip?.querySelector<HTMLElement>('.tab.active'); if (!strip || !tab) return; const box = strip.getBoundingClientRect(); const at = tab.getBoundingClientRect(); if (at.left < box.left) strip.scrollLeft -= box.left - at.left; else if (at.right > box.right) strip.scrollLeft += at.right - box.right; });</script>
<div class="tabs" data-tile={tabs.id}> <div class="strip" role="tablist" aria-label="tabs" bind:this={strip} {@attach sideways}> {#each tabs.children as child (child.id)} {@const name = tileTitle(child, board.windows)} {@const active = child.id === tabs.active} {@const window = child.kind === 'leaf' ? child.window : undefined} <span class="tab" class:active> {#if board.naming === child.id} <NameField value={child.name ?? ''} placeholder={tileTitle({ ...child, name: undefined }, board.windows)} label="Tab name" ondone={(next) => { board.naming = null; if (next !== null) board.rename(child.id, next); }} /> {:else} <button role="tab" aria-selected={active} title={name} data-dock-window={window} data-dock-tile={window ? undefined : child.id} onclick={() => (board.activate(tabs.id, child.id), board.select(window ?? windows(child)[0]))} ondblclick={() => (board.naming = child.id)} > <span class="name">{name}</span> </button> {/if} {#if window} <button class="drop" aria-label="close {name}" title="close" onclick={() => board.remove(window)}> <svg viewBox="0 0 16 16" width="10" height="10" aria-hidden="true"><path d="m4.5 4.5 7 7M11.5 4.5l-7 7" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" /></svg> </button> {/if} </span> {/each} </div> <div class="view"> {#each tabs.children as child (child.id)} {@const shown = child.id === tabs.active} <div class="page" class:shown class:tiled={child.kind !== 'leaf'} inert={!shown}><TileView {board} tile={child} headed={false} /></div> {/each} </div></div>
<style> .tabs { display: flex; flex: 1; width: 100%; height: 100%; flex-direction: column; min-width: 0; min-height: 0; overflow: hidden; background: var(--well); border-radius: 14px; box-shadow: 0 0 0 1px color-mix(in oklab, var(--text) 7%, transparent); } .strip { display: flex; flex: none; gap: 2px; padding: 6px 6px 0; overflow-x: auto; scrollbar-width: none; } .tab { position: relative; display: flex; flex: 0 1 auto; align-items: center; min-width: 112px; max-width: 220px; color: var(--muted); border-radius: 9px 9px 0 0; transition: color 120ms ease, background-color 120ms ease; } .tab:hover { color: var(--text); } .tab.active { color: var(--text); background: var(--title-bar); } .tab > button[role='tab'] { display: flex; align-items: center; min-width: 0; height: 32px; padding: 0 4px 0 11px; color: inherit; font: inherit; font-size: calc(12px * var(--ui-scale)); background: none; border: 0; cursor: pointer; } .name { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .drop { display: grid; flex: none; place-items: center; width: 20px; height: 20px; margin-right: 6px; padding: 0; color: var(--faint); background: none; border: 0; border-radius: 6px; cursor: pointer; opacity: 0; transition: opacity 120ms ease; } .tab:hover .drop, .tab.active .drop, .drop:focus-visible { opacity: 1; } .drop:hover { color: var(--text); background: var(--hover); } /* The shown window joins its tab: square where they meet, and no shadow of its own inside the sheet. */ .view { position: relative; display: flex; flex: 1; min-height: 0; background: var(--title-bar); } .page { position: absolute; inset: 0; display: flex; } .page:not(.shown) { content-visibility: hidden; pointer-events: none; } /* A tiled tab keeps the gap tiles have around them. */ .page.tiled { padding: 6px; } .page > :global(.leaf .window) { box-shadow: none; border-radius: 0; } .page > :global(.leaf .window > header) { border-radius: 0; }</style>