From 38bd4239febff929035cf3281b20d0ce2be67cba Mon Sep 17 00:00:00 2001 From: Lloyd Date: Thu, 11 Sep 2025 02:51:47 +1000 Subject: [PATCH] =?UTF-8?q?=F0=9F=99=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/src/lib/components/grid/cell.svelte | 104 ++++++++++++------ frontend/src/lib/components/grid/cell.ts | 0 frontend/src/lib/components/grid/grid.svelte | 19 +++- .../src/lib/components/grid/grid.svelte.ts | 83 ++++++++++---- frontend/src/lib/components/grid/messages.ts | 8 +- .../src/lib/components/ui/popover/index.ts | 17 +++ .../ui/popover/popover-content.svelte | 29 +++++ .../ui/popover/popover-trigger.svelte | 17 +++ .../ui/tooltip/tooltip-content.svelte | 18 +-- 9 files changed, 223 insertions(+), 72 deletions(-) delete mode 100644 frontend/src/lib/components/grid/cell.ts create mode 100644 frontend/src/lib/components/ui/popover/index.ts create mode 100644 frontend/src/lib/components/ui/popover/popover-content.svelte create mode 100644 frontend/src/lib/components/ui/popover/popover-trigger.svelte diff --git a/frontend/src/lib/components/grid/cell.svelte b/frontend/src/lib/components/grid/cell.svelte index 28e61b0..4bd5369 100644 --- a/frontend/src/lib/components/grid/cell.svelte +++ b/frontend/src/lib/components/grid/cell.svelte @@ -1,8 +1,9 @@ {#if editing} -
- { - return cell?.raw_val ?? ''; - }, - (v) => { - cell = { - val: cell?.val, - raw_val: v - }; +
+ {#if showPreview} + + {/if} + +
+ cell?.temp_raw ?? '', + (v) => (cell = { eval: cell?.eval, raw: cell?.raw ?? '', temp_raw: v }) } - } - onblur={(e) => { - // cell = { - // val: cell?.val, - // raw_val: (e.target as HTMLInputElement).value - // }; - stopediting(); - }} - /> + onblur={stopediting} + /> +
-{:else if cell && isErr(cell.val)} +{:else if cell && isErr(cell.eval)} {@render InnerCell()}

- {getErrTitle(cell.val)} + {getErrTitle(cell.eval)}

- {getErrDesc(cell.val)} + {getErrDesc(cell.eval)}
{:else} @@ -101,12 +106,12 @@ style:height class={clsx('placeholder bg-background p-1', { active }, cla)} > - {#if cell && (cell.raw_val !== '' || getEvalLiteral(cell.val) !== '')} - - {#if cell.val && !externalediting} - {getEvalLiteral(cell.val)} + {#if cell && (cell.raw !== '' || getEvalLiteral(cell.eval) !== '')} + + {#if cell.eval && !externalediting} + {getEvalLiteral(cell.eval)} {:else} - {cell.raw_val} + {cell.raw} {/if} {/if} @@ -144,4 +149,37 @@ border-top: 12px solid red; /* size & color of the triangle */ border-left: 12px solid transparent; } + + .bubble { + z-index: 500; + background: var(--color-popover); + border: 1px solid var(--color-border, rgba(0, 0, 0, 0.12)); + border-radius: 10px; + color: var(--color-popover-foreground); + padding: 0.35rem 0.6rem; + box-shadow: 0 2px 18px rgba(0, 0, 0, 0.08); + max-width: min(15rem, 20vw); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + line-height: 1.2; + } + + /* (optional) subtle appear animation */ + @media (prefers-reduced-motion: no-preference) { + .bubble { + transform-origin: bottom left; + animation: bubble-in 120ms ease-out both; + } + @keyframes bubble-in { + from { + opacity: 0; + transform: translateY(2px) scale(0.98); + } + to { + opacity: 1; + transform: translateY(0) scale(1); + } + } + } diff --git a/frontend/src/lib/components/grid/cell.ts b/frontend/src/lib/components/grid/cell.ts deleted file mode 100644 index e69de29..0000000 diff --git a/frontend/src/lib/components/grid/grid.svelte b/frontend/src/lib/components/grid/grid.svelte index 5a6c21e..ff81c28 100644 --- a/frontend/src/lib/components/grid/grid.svelte +++ b/frontend/src/lib/components/grid/grid.svelte @@ -32,8 +32,8 @@ }; const grid = new Grid(socket); - let rows = 100; - let cols = 40; + let rows = 10; + let cols = 10; function handleCellInteraction(i: number, j: number, e: MouseEvent) { let pos = new Position(i, j); @@ -104,9 +104,12 @@ /> grid.setExternalEdit(grid.getActivePos())} - onblur={() => grid.setExternalEdit(null)} + onblur={() => grid.setCell(grid.getActivePos())} bind:value={ - () => grid.getActiveCell()?.raw_val ?? '', (raw) => grid.quickEval(grid.getActivePos(), raw) + () => grid.getActiveCell()?.temp_raw ?? '', + (v) => { + grid.setCellTemp(grid.getActivePos(), v); + } } class="relative w-[200px] pl-9" > @@ -114,7 +117,10 @@
@@ -163,7 +169,8 @@ handleCellInteraction(i, j, e); }} bind:cell={ - () => grid.getCell(new Position(i, j)), (v) => grid.setCell(new Position(i, j), v) + () => grid.getCell(new Position(i, j)), + (v) => grid.setCellTemp(new Position(i, j), v?.temp_raw) } active={grid.isActive(new Position(i, j))} /> diff --git a/frontend/src/lib/components/grid/grid.svelte.ts b/frontend/src/lib/components/grid/grid.svelte.ts index 9ef9ea9..5bfd921 100644 --- a/frontend/src/lib/components/grid/grid.svelte.ts +++ b/frontend/src/lib/components/grid/grid.svelte.ts @@ -1,5 +1,5 @@ import { toast } from 'svelte-sonner'; -import type { CellRef, CellT, LeadMsg } from './messages'; +import type { CellRef, CellT, Eval, LeadMsg } from './messages'; class Position { public row: number; @@ -41,7 +41,7 @@ class Grid { active_cell: Position | null = $state(null); editing_cell: Position | null = $state(null); external_editing_cell: Position | null = $state(null); - editing_preview = $state(null); + editing_preview: [Eval, boolean] | null = $state(null); // [Eval, dirty] constructor(socket: WebSocket, default_col_width = '80px', default_row_height = '30px') { this.socket = socket; @@ -49,30 +49,47 @@ class Grid { this.default_row_height = default_row_height; } - public getCell(pos: Position): CellT { + public getCell(pos: Position): CellT | undefined { return this.data[pos.key()]; } - public setCell(pos: Position, v: CellT) { - if (v?.raw_val == null || v.raw_val === '') { + public setCell(pos: Position | null) { + if (pos === null) return; + let data = this.data[pos.key()]; + if (data === undefined) return; + + if (data.temp_raw === '') { delete this.data[pos.key()]; return; } - this.data[pos.key()] = { - raw_val: v?.raw_val, - val: v.val - }; + data.raw = data.temp_raw; + data.eval = data.temp_eval; let msg: LeadMsg = { msg_type: 'set', cell: pos.ref(), - raw: v.raw_val + raw: data.temp_raw }; this.socket.send(JSON.stringify(msg)); } + public setCellTemp(pos: Position | null, raw: string | undefined) { + if (pos === null || raw === undefined) return; + + let x = this.data[pos.key()]; + + this.data[pos.key()] = { + raw: x?.raw ?? '', + temp_raw: raw, + eval: x?.eval ?? undefined, + temp_eval: x?.temp_eval ?? undefined + }; + + this.quickEval(pos, raw); + } + public getRowHeight(row: number) { return this.row_heights[row] ?? this.default_row_height; } @@ -107,16 +124,19 @@ class Grid { public startEditing(pos: Position) { this.active_cell = pos; this.editing_cell = pos; + + let cell = this.getCell(pos); + if (!cell) return; + cell.temp_eval = undefined; } public stopEditing(pos: Position) { this.editing_cell = null; - this.setCell(pos, this.getCell(pos)); + this.setCell(pos); } public stopEditingActive() { if (this.active_cell == null) return; - this.stopEditing(this.active_cell); } @@ -138,11 +158,12 @@ class Grid { this.external_editing_cell = pos; } - public getActiveCell(): CellT { + public getActiveCell(): CellT | undefined { if (this.active_cell === null) return { - raw_val: '', - val: undefined + raw: '', + temp_raw: '', + eval: undefined }; return this.getCell(this.active_cell); @@ -179,16 +200,22 @@ class Grid { } case 'set': { if (msg.cell === undefined) { - console.error('Expected cell ref for SET msgponse from server.'); + console.error('Expected cell ref for SET msg from server.'); return; } else if (msg.eval === undefined) { - console.error('Expected cell value for SET msgponse from server.'); + console.error('Expected cell value for SET msg from server.'); return; } - this.data[Position.key(msg.cell.row, msg.cell.col)] = { - raw_val: msg.raw ?? '', - val: msg.eval + let pos = new Position(msg.cell.row, msg.cell.col); + + let x = this.data[pos.key()]; + + this.data[pos.key()] = { + raw: msg.raw ?? '', + eval: msg.eval, + temp_raw: x?.temp_raw ?? '', + temp_eval: x?.temp_eval ?? undefined }; break; @@ -200,9 +227,23 @@ class Grid { } for (const m of msg.bulk_msgs) this.handle_msg(m); + break; } case 'eval': { - // TODO + if (msg.cell === undefined) { + console.error('Expected cell ref for EVAL msg from server.'); + return; + } else if (msg.eval === undefined) { + console.error('Expected cell value for EVAL msg from server.'); + return; + } + + let pos = new Position(msg.cell.row, msg.cell.col); + if (this.data[pos.key()] === undefined) return; + + this.data[pos.key()].temp_eval = msg.eval; + + break; } } } diff --git a/frontend/src/lib/components/grid/messages.ts b/frontend/src/lib/components/grid/messages.ts index 38d63ae..6fb9fb2 100644 --- a/frontend/src/lib/components/grid/messages.ts +++ b/frontend/src/lib/components/grid/messages.ts @@ -45,9 +45,11 @@ type Eval = | { err: LeadErr } | 'unset'; - interface CellT { - raw_val: string; - val?: Eval; +interface CellT { + raw: string; + temp_raw: string; + temp_eval?: Eval; + eval?: Eval; } export type { Eval, LeadMsg, LeadErr, Literal, CellRef, LiteralValue, CellT }; diff --git a/frontend/src/lib/components/ui/popover/index.ts b/frontend/src/lib/components/ui/popover/index.ts new file mode 100644 index 0000000..9f30922 --- /dev/null +++ b/frontend/src/lib/components/ui/popover/index.ts @@ -0,0 +1,17 @@ +import { Popover as PopoverPrimitive } from "bits-ui"; +import Content from "./popover-content.svelte"; +import Trigger from "./popover-trigger.svelte"; +const Root = PopoverPrimitive.Root; +const Close = PopoverPrimitive.Close; + +export { + Root, + Content, + Trigger, + Close, + // + Root as Popover, + Content as PopoverContent, + Trigger as PopoverTrigger, + Close as PopoverClose, +}; diff --git a/frontend/src/lib/components/ui/popover/popover-content.svelte b/frontend/src/lib/components/ui/popover/popover-content.svelte new file mode 100644 index 0000000..9bced7a --- /dev/null +++ b/frontend/src/lib/components/ui/popover/popover-content.svelte @@ -0,0 +1,29 @@ + + + + + diff --git a/frontend/src/lib/components/ui/popover/popover-trigger.svelte b/frontend/src/lib/components/ui/popover/popover-trigger.svelte new file mode 100644 index 0000000..586323c --- /dev/null +++ b/frontend/src/lib/components/ui/popover/popover-trigger.svelte @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte b/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte index e495efe..00ed69d 100644 --- a/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte +++ b/frontend/src/lib/components/ui/tooltip/tooltip-content.svelte @@ -1,12 +1,12 @@