import { annotationNoun, RemoveAnnotationCommand, SetAnnotationColorCommand, SetAnnotationTextCommand, } from '../../core/commands/annotationCommands'; import type { Command } from '../../core/commands/Command'; import { type AnnotationKind, findAnnotation, type GraphAnnotation } from '../../core/graph'; import { findGraphAsset, type Project } from '../../core/model/Project'; import { type PropertyValue, textOf } from '../../core/model/Properties'; import type { PropertyDef } from '../../core/properties/PropertySchema'; import { Button, Theme } from '../../ui'; import { createFieldRow } from './fieldFactory'; import { addGap, addNote, addTitle, type InspectorHost } from './host'; /** The key the colour field is registered under, so a live refresh finds it again. */ const COLOR_KEY = 'annotation-color'; const TEXT_KEY = 'annotation-text'; /** * Structure of a group/comment selection: its kind decides the wording, and whether it carries a * colour of its own decides whether the reset button is there. */ export function annotationSignature(project: Project, assetId: string, annotationId: string): string { const found = current(project, assetId, annotationId); if (!found) return 'missing'; return `${found.kind}:${found.annotation.color === undefined ? 'default' : 'own'}`; } /** Title (or text), colour, and the way to delete the box without touching what it frames. */ export function buildAnnotationSection(host: InspectorHost, assetId: string, annotationId: string): void { const found = current(host.state.project, assetId, annotationId); if (!found) return; const { kind, annotation } = found; const noun = annotationNoun(kind); addTitle(host, noun.toUpperCase()); addRow( host, { key: TEXT_KEY, label: kind === 'group' ? 'Title' : 'Text', kind: 'text', default: '' }, annotation.text, (value) => new SetAnnotationTextCommand(assetId, kind, annotationId, textOf(value)), ); addRow( host, { key: COLOR_KEY, label: 'Color', kind: 'color', default: Theme.colors.annotation }, annotation.color ?? Theme.colors.annotation, (value) => new SetAnnotationColorCommand(assetId, kind, annotationId, Number(value)), ); addNote( host, 'The text is drawn black or white, whichever reads better on this color – so any color stays legible.', 'small', ); if (annotation.color !== undefined) host.body.add( new Button('Use the default color', { onClick: () => host.state.execute(new SetAnnotationColorCommand(assetId, kind, annotationId, null)), }), ); addGap(host); addNote(host, describe(kind)); addGap(host); host.body.add( new Button(`Delete ${noun}`, { variant: 'danger', onClick: () => host.state.execute(new RemoveAnnotationCommand(assetId, kind, annotationId)), }), ); } /** Pushes the current text and colour into the rows. False when the box is gone (rebuild). */ export function refreshAnnotationSection( host: InspectorHost, assetId: string, annotationId: string, ): boolean { const found = current(host.state.project, assetId, annotationId); if (!found) return false; host.fields.get(TEXT_KEY)?.setValue(found.annotation.text); host.fields.get(COLOR_KEY)?.setValue(found.annotation.color ?? Theme.colors.annotation); return true; } /** * One row, committed on change only: a colour dragged in the picker fires continuously, and an * undo entry per pixel of the hue bar would bury everything that came before it. */ function addRow( host: InspectorHost, def: PropertyDef, value: PropertyValue, commit: (v: PropertyValue) => Command, ): void { const row = createFieldRow(host, def, value, { onInput: () => {}, onChange: (next) => void host.state.execute(commit(next)), }); host.fields.set(def.key, row); host.body.add(row.row); } function describe(kind: AnnotationKind): string { return kind === 'group' ? 'Nodes whose center lies in the box move with it. Deleting the group leaves them where they are.' : 'A comment is read, not run: it frames nothing and moves alone.'; } /** * Looked up inside the selected graph rather than by id alone, so a selection left over from a * deleted asset cannot edit whichever box later inherits the id. */ function current( project: Project, assetId: string, annotationId: string, ): { kind: AnnotationKind; annotation: GraphAnnotation } | null { const graph = findGraphAsset(project, assetId)?.graph; return graph ? findAnnotation(graph, annotationId) : null; }