import { Box, Text, useNativeRenderer, useUiHost } from "../../../ui"; import { DialogFrame, ListView, TextField } from "../../../components/ui"; import type { PromptContext } from "../../../ui/dialog"; import { colors } from "../../../theme/colors"; import type { ChartSpec } from "../../../time-series/types"; import { useSeriesEditorController } from "./editor/controller"; import { DesktopSeriesEditorActions, DesktopSeriesEditorFields, } from "./editor/desktop-view"; import { OpenTuiSeriesEditorActions, OpenTuiSeriesEditorFields, } from "./editor/opentui-view"; import type { SeriesEditorFocus } from "./editor/model"; export interface SeriesEditorDialogProps extends PromptContext { initialSpec: ChartSpec; } export function SeriesEditorDialog({ dialogId, resolve, initialSpec }: SeriesEditorDialogProps) { const isDesktop = useUiHost().kind === "desktop-web"; const nativeRenderer = useNativeRenderer(); const controller = useSeriesEditorController({ dialogId, resolve, initialSpec, isDesktop, }); const { actions, activateQuickAdd, addCatalogSuggestion, beginExpressionEdit, beginQuickAdd, commitExpression, deactivateQuickAdd, defaultInstrument, editingExpression, error, expression, expressionRef, fields, focusKeyboardTarget, items, keyboardFocus, moveKeyboardFocus, moveQuickAddSelection, quickAddActive, quickAddItems, quickAddLoading, quickAddQuery, quickAddRef, quickAddSelection, quickAddSuggestions, selected, selectedIndex, setEditingExpression, setExpression, setQuickAddQuery, setQuickAddSelection, setSelectedIndex, submitQuickAdd, updateKeyboardFocus, } = controller; const availableTuiWidth = nativeRenderer.terminalWidth > 0 ? nativeRenderer.terminalWidth - 8 : 76; const tuiContentWidth = Math.max(1, Math.min(76, availableTuiWidth)); const contentWidth = isDesktop ? "660px" : tuiContentWidth; const fieldLabel = (field: SeriesEditorFocus, label: string) => ( keyboardFocus === field ? `› ${label}` : label ); // The keys with no button of their own; the buttons show theirs. const seriesKeys = selected ? ["E source", "P panel", "L log scale"] : []; // A narrow terminal drops the navigation keys first: nothing else shows the action keys. const navigationKeys = ["Tab field", "←→ change", "↑↓ series"]; while (navigationKeys.length > 0 && [...navigationKeys, ...seriesKeys].join(" · ").length > tuiContentWidth) { navigationKeys.pop(); } const footer = isDesktop ? seriesKeys.join(" · ") || undefined : [...navigationKeys, ...seriesKeys].join(" · "); return ( beginQuickAdd()} onKeyDown={(event) => { // The terminal's dialog keys see arrows first; a desktop field keeps // them unless the suggestion highlight takes them here. if (event.defaultPrevented || !isDesktop || !quickAddActive || quickAddItems.length === 0) return; const direction = event.name === "up" ? -1 : event.name === "down" ? 1 : 0; if (!direction || event.shift) return; event.preventDefault(); event.stopPropagation(); moveQuickAddSelection(direction); }} onChange={(value) => { setQuickAddQuery(value); if (value.trim()) activateQuickAdd(); }} onSubmit={submitQuickAdd} onBlur={deactivateQuickAdd} /> {quickAddActive && quickAddQuery.trim() && ( quickAddItems.length > 0 ? ( (isDesktop ? 5 : 6)} rowGap={isDesktop ? 0 : undefined} selectOnHover onSelect={setQuickAddSelection} onActivate={(_, index) => { beginQuickAdd(); addCatalogSuggestion(quickAddSuggestions[index]); }} /> ) : ( {quickAddLoading ? "Searching instruments…" : "No matching security or metric."} ) )} {items.length > 0 ? ( (isDesktop ? 5 : 7)} rowGap={isDesktop ? 0 : undefined} onSelect={(index) => { if (!isDesktop) focusKeyboardTarget("series"); setSelectedIndex(index); }} onActivate={(_, index) => setSelectedIndex(index)} /> ) : ( No series yet. Add one to start the chart. )} {selected && ( { if (event.defaultPrevented) return; if (event.name === "escape") { event.stopPropagation(); event.preventDefault(); resolve(null); } else if (event.name === "enter" || event.name === "return") { event.stopPropagation(); event.preventDefault(); if (commitExpression()) moveKeyboardFocus(1); } // Tab is left to the dialog: the terminal's ring commits and moves, // and the desktop walks its controls, committing as the field blurs. }} onChange={(value) => { setExpression(value); // Tab reaches the field on the desktop without a click, so typing // is what marks it as being edited and committed on blur. if (isDesktop && !editingExpression) { updateKeyboardFocus("source"); setEditingExpression(true); } }} onSubmit={() => { commitExpression(); }} onBlur={() => { if (!editingExpression) return; const committed = commitExpression(); if (!committed && !isDesktop) { updateKeyboardFocus("source"); setEditingExpression(true); queueMicrotask(() => expressionRef.current?.focus?.()); } }} /> {isDesktop ? ( ) : ( )} )} {error && {error}} {isDesktop ? ( ) : ( )} ); }