diff options
Diffstat (limited to 'src/features/surface-host/ui')
| -rw-r--r-- | src/features/surface-host/ui/Button.svelte | 30 | ||||
| -rw-r--r-- | src/features/surface-host/ui/MessageQueueList.svelte | 30 | ||||
| -rw-r--r-- | src/features/surface-host/ui/Number.svelte | 72 | ||||
| -rw-r--r-- | src/features/surface-host/ui/Progress.svelte | 12 | ||||
| -rw-r--r-- | src/features/surface-host/ui/Selector.svelte | 50 | ||||
| -rw-r--r-- | src/features/surface-host/ui/StatTable.svelte | 30 | ||||
| -rw-r--r-- | src/features/surface-host/ui/SurfaceTable.svelte | 14 | ||||
| -rw-r--r-- | src/features/surface-host/ui/SurfaceView.svelte | 87 | ||||
| -rw-r--r-- | src/features/surface-host/ui/TodoList.svelte | 106 | ||||
| -rw-r--r-- | src/features/surface-host/ui/Toggle.svelte | 36 |
10 files changed, 232 insertions, 235 deletions
diff --git a/src/features/surface-host/ui/Button.svelte b/src/features/surface-host/ui/Button.svelte index 62d7acf..ee9097c 100644 --- a/src/features/surface-host/ui/Button.svelte +++ b/src/features/surface-host/ui/Button.svelte @@ -1,21 +1,21 @@ <script lang="ts"> - import type { InvokeMessage } from "@dispatch/ui-contract"; - import type { ButtonFieldView } from "../logic/types"; + import type { InvokeMessage } from "@dispatch/ui-contract"; + import type { ButtonFieldView } from "../logic/types"; - let { - field, - surfaceId, - onInvoke, - }: { field: ButtonFieldView; surfaceId: string; onInvoke: (msg: InvokeMessage) => void } = - $props(); + let { + field, + surfaceId, + onInvoke, + }: { field: ButtonFieldView; surfaceId: string; onInvoke: (msg: InvokeMessage) => void } = + $props(); - function handleClick() { - onInvoke({ - type: "invoke", - surfaceId, - actionId: field.action.actionId, - }); - } + function handleClick() { + onInvoke({ + type: "invoke", + surfaceId, + actionId: field.action.actionId, + }); + } </script> <button onclick={handleClick}>{field.label}</button> diff --git a/src/features/surface-host/ui/MessageQueueList.svelte b/src/features/surface-host/ui/MessageQueueList.svelte index 12de970..554fa02 100644 --- a/src/features/surface-host/ui/MessageQueueList.svelte +++ b/src/features/surface-host/ui/MessageQueueList.svelte @@ -1,22 +1,22 @@ <script lang="ts"> - import { parseMessageQueuePayload } from "../logic/message-queue"; + import { parseMessageQueuePayload } from "../logic/message-queue"; - let { payload }: { readonly payload: unknown } = $props(); + let { payload }: { readonly payload: unknown } = $props(); - // Parse defensively; an unparseable payload yields null → render nothing - // (graceful skip, per the custom-field contract). - const data = $derived(parseMessageQueuePayload(payload)); + // Parse defensively; an unparseable payload yields null → render nothing + // (graceful skip, per the custom-field contract). + const data = $derived(parseMessageQueuePayload(payload)); </script> {#if data !== null && data.messages.length > 0} - <ul class="flex flex-col gap-1 text-sm"> - {#each data.messages as msg (msg.id)} - <li class="rounded-box bg-base-200 px-3 py-2"> - <p class="whitespace-pre-wrap">{msg.text}</p> - <time class="text-xs opacity-50" datetime={new Date(msg.queuedAt).toISOString()}> - {new Date(msg.queuedAt).toLocaleTimeString()} - </time> - </li> - {/each} - </ul> + <ul class="flex flex-col gap-1 text-sm"> + {#each data.messages as msg (msg.id)} + <li class="rounded-box bg-base-200 px-3 py-2"> + <p class="whitespace-pre-wrap">{msg.text}</p> + <time class="text-xs opacity-50" datetime={new Date(msg.queuedAt).toISOString()}> + {new Date(msg.queuedAt).toLocaleTimeString()} + </time> + </li> + {/each} + </ul> {/if} diff --git a/src/features/surface-host/ui/Number.svelte b/src/features/surface-host/ui/Number.svelte index 0f3323d..5a67087 100644 --- a/src/features/surface-host/ui/Number.svelte +++ b/src/features/surface-host/ui/Number.svelte @@ -1,43 +1,43 @@ <script lang="ts"> - import type { InvokeMessage } from "@dispatch/ui-contract"; - import type { NumberFieldView } from "../logic/types"; + import type { InvokeMessage } from "@dispatch/ui-contract"; + import type { NumberFieldView } from "../logic/types"; - let { - field, - surfaceId, - onInvoke, - }: { field: NumberFieldView; surfaceId: string; onInvoke: (msg: InvokeMessage) => void } = - $props(); + let { + field, + surfaceId, + onInvoke, + }: { field: NumberFieldView; surfaceId: string; onInvoke: (msg: InvokeMessage) => void } = + $props(); - // Commit on change/Enter rather than every keystroke. Ignore empty/non-numeric - // input (the backend also floors/validates); send the new number as payload. - function commit(event: Event) { - const target = event.target as HTMLInputElement; - const next = target.valueAsNumber; - if (Number.isNaN(next)) return; - onInvoke({ - type: "invoke", - surfaceId, - actionId: field.action.actionId, - payload: next, - }); - } + // Commit on change/Enter rather than every keystroke. Ignore empty/non-numeric + // input (the backend also floors/validates); send the new number as payload. + function commit(event: Event) { + const target = event.target as HTMLInputElement; + const next = target.valueAsNumber; + if (Number.isNaN(next)) return; + onInvoke({ + type: "invoke", + surfaceId, + actionId: field.action.actionId, + payload: next, + }); + } </script> <label class="flex items-center justify-between gap-2 text-sm"> - <span>{field.label}</span> - <span class="flex items-center gap-1"> - <input - type="number" - class="input input-bordered input-sm w-24" - value={field.value} - min={field.min} - max={field.max} - step={field.step} - onchange={commit} - /> - {#if field.unit} - <span class="opacity-60">{field.unit}</span> - {/if} - </span> + <span>{field.label}</span> + <span class="flex items-center gap-1"> + <input + type="number" + class="input input-bordered input-sm w-24" + value={field.value} + min={field.min} + max={field.max} + step={field.step} + onchange={commit} + /> + {#if field.unit} + <span class="opacity-60">{field.unit}</span> + {/if} + </span> </label> diff --git a/src/features/surface-host/ui/Progress.svelte b/src/features/surface-host/ui/Progress.svelte index cba9e0f..e291c79 100644 --- a/src/features/surface-host/ui/Progress.svelte +++ b/src/features/surface-host/ui/Progress.svelte @@ -1,13 +1,13 @@ <script lang="ts"> - import type { ProgressFieldView } from "../logic/types"; + import type { ProgressFieldView } from "../logic/types"; - let { field }: { field: ProgressFieldView } = $props(); + let { field }: { field: ProgressFieldView } = $props(); - const percent = $derived(Math.round(field.value * 100)); + const percent = $derived(Math.round(field.value * 100)); </script> <div> - <span>{field.label}</span> - <progress max="100" value={percent}>{percent}%</progress> - <span>{percent}%</span> + <span>{field.label}</span> + <progress max="100" value={percent}>{percent}%</progress> + <span>{percent}%</span> </div> diff --git a/src/features/surface-host/ui/Selector.svelte b/src/features/surface-host/ui/Selector.svelte index 2da104f..4cb3536 100644 --- a/src/features/surface-host/ui/Selector.svelte +++ b/src/features/surface-host/ui/Selector.svelte @@ -1,32 +1,32 @@ <script lang="ts"> - import type { InvokeMessage } from "@dispatch/ui-contract"; - import type { SelectorFieldView } from "../logic/types"; + import type { InvokeMessage } from "@dispatch/ui-contract"; + import type { SelectorFieldView } from "../logic/types"; - let { - field, - surfaceId, - onInvoke, - }: { field: SelectorFieldView; surfaceId: string; onInvoke: (msg: InvokeMessage) => void } = - $props(); + let { + field, + surfaceId, + onInvoke, + }: { field: SelectorFieldView; surfaceId: string; onInvoke: (msg: InvokeMessage) => void } = + $props(); - function handleChange(event: Event) { - const target = event.target as HTMLSelectElement; - onInvoke({ - type: "invoke", - surfaceId, - actionId: field.action.actionId, - payload: target.value, - }); - } + function handleChange(event: Event) { + const target = event.target as HTMLSelectElement; + onInvoke({ + type: "invoke", + surfaceId, + actionId: field.action.actionId, + payload: target.value, + }); + } </script> <label> - {field.label} - <select onchange={handleChange}> - {#each field.options as option (option.value)} - <option value={option.value} selected={option.value === field.value}> - {option.label} - </option> - {/each} - </select> + {field.label} + <select onchange={handleChange}> + {#each field.options as option (option.value)} + <option value={option.value} selected={option.value === field.value}> + {option.label} + </option> + {/each} + </select> </label> diff --git a/src/features/surface-host/ui/StatTable.svelte b/src/features/surface-host/ui/StatTable.svelte index 415423f..c559352 100644 --- a/src/features/surface-host/ui/StatTable.svelte +++ b/src/features/surface-host/ui/StatTable.svelte @@ -1,21 +1,21 @@ <script lang="ts"> - import type { StatFieldView } from "../logic/types"; + import type { StatFieldView } from "../logic/types"; - // Renders a run of stat fields as one aligned label/value table. Headerless: - // the column semantics aren't known generically, but the two-column layout - // gives the tidy, aligned readout the stats deserve (e.g. extension → version). - let { stats }: { readonly stats: readonly StatFieldView[] } = $props(); + // Renders a run of stat fields as one aligned label/value table. Headerless: + // the column semantics aren't known generically, but the two-column layout + // gives the tidy, aligned readout the stats deserve (e.g. extension → version). + let { stats }: { readonly stats: readonly StatFieldView[] } = $props(); </script> <div class="overflow-x-auto"> - <table class="table table-sm"> - <tbody> - {#each stats as stat, i (i)} - <tr> - <th class="font-medium">{stat.label}</th> - <td class="text-right tabular-nums">{stat.value}</td> - </tr> - {/each} - </tbody> - </table> + <table class="table table-sm"> + <tbody> + {#each stats as stat, i (i)} + <tr> + <th class="font-medium">{stat.label}</th> + <td class="text-right tabular-nums">{stat.value}</td> + </tr> + {/each} + </tbody> + </table> </div> diff --git a/src/features/surface-host/ui/SurfaceTable.svelte b/src/features/surface-host/ui/SurfaceTable.svelte index 764cc36..e47c122 100644 --- a/src/features/surface-host/ui/SurfaceTable.svelte +++ b/src/features/surface-host/ui/SurfaceTable.svelte @@ -1,14 +1,14 @@ <script lang="ts"> - import Table from "../../../components/Table.svelte"; - import { parseTablePayload } from "../logic/table"; + import Table from "../../../components/Table.svelte"; + import { parseTablePayload } from "../logic/table"; - let { payload }: { readonly payload: unknown } = $props(); + let { payload }: { readonly payload: unknown } = $props(); - // Parse defensively; an unparseable payload yields null → render nothing - // (graceful skip, per the custom-field contract). - const data = $derived(parseTablePayload(payload)); + // Parse defensively; an unparseable payload yields null → render nothing + // (graceful skip, per the custom-field contract). + const data = $derived(parseTablePayload(payload)); </script> {#if data !== null} - <Table columns={data.columns} rows={data.rows} /> + <Table columns={data.columns} rows={data.rows} /> {/if} diff --git a/src/features/surface-host/ui/SurfaceView.svelte b/src/features/surface-host/ui/SurfaceView.svelte index 3f92e3b..aed8d03 100644 --- a/src/features/surface-host/ui/SurfaceView.svelte +++ b/src/features/surface-host/ui/SurfaceView.svelte @@ -1,52 +1,49 @@ <script lang="ts"> - import type { InvokeMessage, SurfaceSpec } from "@dispatch/ui-contract"; - import { groupRenderFields, planSurface } from "../logic/plan"; - import Button from "./Button.svelte"; - import MessageQueueList from "./MessageQueueList.svelte"; - import Number from "./Number.svelte"; - import Progress from "./Progress.svelte"; - import Selector from "./Selector.svelte"; - import StatTable from "./StatTable.svelte"; - import SurfaceTable from "./SurfaceTable.svelte"; - import TodoList from "./TodoList.svelte"; - import Toggle from "./Toggle.svelte"; + import type { InvokeMessage, SurfaceSpec } from "@dispatch/ui-contract"; + import { groupRenderFields, planSurface } from "../logic/plan"; + import Button from "./Button.svelte"; + import MessageQueueList from "./MessageQueueList.svelte"; + import Number from "./Number.svelte"; + import Progress from "./Progress.svelte"; + import Selector from "./Selector.svelte"; + import StatTable from "./StatTable.svelte"; + import SurfaceTable from "./SurfaceTable.svelte"; + import TodoList from "./TodoList.svelte"; + import Toggle from "./Toggle.svelte"; - let { - spec, - onInvoke, - }: { spec: SurfaceSpec; onInvoke: (msg: InvokeMessage) => void } = $props(); + let { spec, onInvoke }: { spec: SurfaceSpec; onInvoke: (msg: InvokeMessage) => void } = $props(); - const plan = $derived(planSurface(spec)); - // Consecutive stats render together as one aligned table; everything else is - // a standalone widget. Grouping keys on field KIND only — never the surface id. - const groups = $derived(groupRenderFields(plan.fields)); + const plan = $derived(planSurface(spec)); + // Consecutive stats render together as one aligned table; everything else is + // a standalone widget. Grouping keys on field KIND only — never the surface id. + const groups = $derived(groupRenderFields(plan.fields)); </script> <article> - <h2>{spec.title}</h2> - {#each groups as group, i (i)} - {#if group.type === "stats"} - <StatTable stats={group.stats} /> - {:else if group.field.kind === "toggle"} - <Toggle field={group.field} surfaceId={spec.id} {onInvoke} /> - {:else if group.field.kind === "progress"} - <Progress field={group.field} /> - {:else if group.field.kind === "selector"} - <Selector field={group.field} surfaceId={spec.id} {onInvoke} /> - {:else if group.field.kind === "number"} - <Number field={group.field} surfaceId={spec.id} {onInvoke} /> - {:else if group.field.kind === "button"} - <Button field={group.field} surfaceId={spec.id} {onInvoke} /> - {:else if group.field.kind === "custom"} - <!-- Dispatch on rendererId (a renderer KIND, never a surface id); - unknown ids gracefully render nothing. --> - {#if group.field.rendererId === "table"} - <SurfaceTable payload={group.field.payload} /> - {:else if group.field.rendererId === "message-queue"} - <MessageQueueList payload={group.field.payload} /> - {:else if group.field.rendererId === "todo"} - <TodoList payload={group.field.payload} /> - {/if} - {/if} - {/each} + <h2>{spec.title}</h2> + {#each groups as group, i (i)} + {#if group.type === "stats"} + <StatTable stats={group.stats} /> + {:else if group.field.kind === "toggle"} + <Toggle field={group.field} surfaceId={spec.id} {onInvoke} /> + {:else if group.field.kind === "progress"} + <Progress field={group.field} /> + {:else if group.field.kind === "selector"} + <Selector field={group.field} surfaceId={spec.id} {onInvoke} /> + {:else if group.field.kind === "number"} + <Number field={group.field} surfaceId={spec.id} {onInvoke} /> + {:else if group.field.kind === "button"} + <Button field={group.field} surfaceId={spec.id} {onInvoke} /> + {:else if group.field.kind === "custom"} + <!-- Dispatch on rendererId (a renderer KIND, never a surface id); + unknown ids gracefully render nothing. --> + {#if group.field.rendererId === "table"} + <SurfaceTable payload={group.field.payload} /> + {:else if group.field.rendererId === "message-queue"} + <MessageQueueList payload={group.field.payload} /> + {:else if group.field.rendererId === "todo"} + <TodoList payload={group.field.payload} /> + {/if} + {/if} + {/each} </article> diff --git a/src/features/surface-host/ui/TodoList.svelte b/src/features/surface-host/ui/TodoList.svelte index b7b2183..e9b1b58 100644 --- a/src/features/surface-host/ui/TodoList.svelte +++ b/src/features/surface-host/ui/TodoList.svelte @@ -1,61 +1,61 @@ <script lang="ts"> - import { parseTodoPayload } from "../logic/todo"; + import { parseTodoPayload } from "../logic/todo"; - let { payload }: { readonly payload: unknown } = $props(); + let { payload }: { readonly payload: unknown } = $props(); - const data = $derived(parseTodoPayload(payload)); + const data = $derived(parseTodoPayload(payload)); </script> {#if data !== null && data.todos.length > 0} - <ul class="flex flex-col gap-1"> - {#each data.todos as todo, i (i)} - <li class="flex items-start gap-2 rounded-box bg-base-200 px-3 py-2 text-sm"> - <!-- Status indicator --> - <span class="mt-0.5 shrink-0"> - {#if todo.status === "in_progress"} - <span class="block h-4 w-4 rounded-full bg-primary"></span> - {:else if todo.status === "completed"} - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - stroke-width="3" - stroke-linecap="round" - stroke-linejoin="round" - class="h-4 w-4 text-success" - > - <polyline points="20 6 9 17 4 12"></polyline> - </svg> - {:else if todo.status === "cancelled"} - <svg - xmlns="http://www.w3.org/2000/svg" - viewBox="0 0 24 24" - fill="none" - stroke="currentColor" - stroke-width="3" - stroke-linecap="round" - stroke-linejoin="round" - class="h-4 w-4 text-base-content/40" - > - <line x1="18" y1="6" x2="6" y2="18"></line> - <line x1="6" y1="6" x2="18" y2="18"></line> - </svg> - {:else} - <!-- pending: empty circle --> - <span class="block h-4 w-4 rounded-full border-2 border-base-content/30"></span> - {/if} - </span> + <ul class="flex flex-col gap-1"> + {#each data.todos as todo, i (i)} + <li class="flex items-start gap-2 rounded-box bg-base-200 px-3 py-2 text-sm"> + <!-- Status indicator --> + <span class="mt-0.5 shrink-0"> + {#if todo.status === "in_progress"} + <span class="block h-4 w-4 rounded-full bg-primary"></span> + {:else if todo.status === "completed"} + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="3" + stroke-linecap="round" + stroke-linejoin="round" + class="h-4 w-4 text-success" + > + <polyline points="20 6 9 17 4 12"></polyline> + </svg> + {:else if todo.status === "cancelled"} + <svg + xmlns="http://www.w3.org/2000/svg" + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + stroke-width="3" + stroke-linecap="round" + stroke-linejoin="round" + class="h-4 w-4 text-base-content/40" + > + <line x1="18" y1="6" x2="6" y2="18"></line> + <line x1="6" y1="6" x2="18" y2="18"></line> + </svg> + {:else} + <!-- pending: empty circle --> + <span class="block h-4 w-4 rounded-full border-2 border-base-content/30"></span> + {/if} + </span> - <!-- Content --> - <span - class:flex-1={true} - class:line-through={todo.status === "completed" || todo.status === "cancelled"} - class:opacity-50={todo.status === "completed" || todo.status === "cancelled"} - > - {todo.content} - </span> - </li> - {/each} - </ul> + <!-- Content --> + <span + class:flex-1={true} + class:line-through={todo.status === "completed" || todo.status === "cancelled"} + class:opacity-50={todo.status === "completed" || todo.status === "cancelled"} + > + {todo.content} + </span> + </li> + {/each} + </ul> {/if} diff --git a/src/features/surface-host/ui/Toggle.svelte b/src/features/surface-host/ui/Toggle.svelte index aec8f4e..0326851 100644 --- a/src/features/surface-host/ui/Toggle.svelte +++ b/src/features/surface-host/ui/Toggle.svelte @@ -1,25 +1,25 @@ <script lang="ts"> - import type { InvokeMessage } from "@dispatch/ui-contract"; - import type { ToggleFieldView } from "../logic/types"; + import type { InvokeMessage } from "@dispatch/ui-contract"; + import type { ToggleFieldView } from "../logic/types"; - let { - field, - surfaceId, - onInvoke, - }: { field: ToggleFieldView; surfaceId: string; onInvoke: (msg: InvokeMessage) => void } = - $props(); + let { + field, + surfaceId, + onInvoke, + }: { field: ToggleFieldView; surfaceId: string; onInvoke: (msg: InvokeMessage) => void } = + $props(); - function handleChange() { - onInvoke({ - type: "invoke", - surfaceId, - actionId: field.action.actionId, - payload: !field.value, - }); - } + function handleChange() { + onInvoke({ + type: "invoke", + surfaceId, + actionId: field.action.actionId, + payload: !field.value, + }); + } </script> <label> - <input type="checkbox" checked={field.value} onchange={handleChange} /> - {field.label} + <input type="checkbox" checked={field.value} onchange={handleChange} /> + {field.label} </label> |
