import { isTemplateV2GroupElement, isTemplateV2LayoutElement, type TemplateV2ToolbarElement, } from "@/components/slide-editor/layout/LayoutToolbar"; import { findFirstComponentLayoutElement } from "@/components/slide-editor/layout/layoutToolbarTarget"; import { rawChartToEditorChart } from "@/components/slide-editor/model/chart-model"; import { rawElementForEditorToolbar } from "@/components/slide-editor/model/model"; import type { ChartSlideElement, TableSlideElement, } from "@/components/slide-editor/state/state"; import type { SlideElement } from "@/components/slide-editor/types"; import type { TemplateV2ToolbarBox, TemplateV2ToolbarElementSelection, TemplateV2ToolbarSelection, } from "@/components/slide-editor/selection/toolbarTypes"; type RawRecord = Record; export type TemplateV2SelectionToolbarTarget = { selection: TemplateV2ToolbarElementSelection; element: TemplateV2ToolbarElement; box: TemplateV2ToolbarBox; }; export type TemplateV2ChartSelectionToolbarTarget = { selection: TemplateV2ToolbarElementSelection; element: ChartSlideElement; box: TemplateV2ToolbarBox; }; export type TemplateV2TableSelectionToolbarTarget = { selection: TemplateV2ToolbarElementSelection; element: TableSlideElement; box: TemplateV2ToolbarBox; }; export type TemplateV2EditorSelectionToolbarTarget = { selection: TemplateV2ToolbarElementSelection; element: SlideElement; box: TemplateV2ToolbarBox; }; type SelectionToolbarTargetOptions = { selection: TemplateV2ToolbarSelection; selectedBox: TemplateV2ToolbarBox | null; selectedComponent: RawRecord | null; selectedElement: RawRecord | null; absoluteBoxForSelection: ( selection: TemplateV2ToolbarSelection, ) => TemplateV2ToolbarBox | null; }; export function getTemplateV2SelectionToolbarTarget({ selection, selectedBox, selectedComponent, selectedElement, absoluteBoxForSelection, }: SelectionToolbarTargetOptions): TemplateV2SelectionToolbarTarget | null { if ( selection?.kind === "element" && selectedElement && selectedBox && (isTemplateV2LayoutElement(selectedElement) || isTemplateV2GroupElement(selectedElement)) ) { return { selection, element: selectedElement as TemplateV2ToolbarElement, box: selectedBox, }; } if (selection?.kind !== "component" || !selectedComponent) return null; const layoutRoot = findFirstComponentLayoutElement( readArray(selectedComponent.elements), ); if (!layoutRoot) return null; const elementSelection: TemplateV2ToolbarElementSelection = { kind: "element", componentIndex: selection.componentIndex, elementPath: layoutRoot.elementPath, }; const box = absoluteBoxForSelection(elementSelection); return box ? { selection: elementSelection, element: layoutRoot.element, box } : null; } export function getTemplateV2SelectionChartToolbarTarget({ selection, selectedBox, selectedComponent, selectedElement, absoluteBoxForSelection, }: SelectionToolbarTargetOptions): TemplateV2ChartSelectionToolbarTarget | null { if ( selection?.kind === "element" && selectedElement && selectedBox && isTemplateV2ChartElement(selectedElement) ) { return { selection, element: chartToolbarElement(selectedElement, selectedBox), box: selectedBox, }; } if (selection?.kind !== "component" || !selectedComponent) return null; const chartRoot = findFirstComponentChartElement( readArray(selectedComponent.elements), ); if (!chartRoot) return null; const elementSelection: TemplateV2ToolbarElementSelection = { kind: "element", componentIndex: selection.componentIndex, elementPath: chartRoot.elementPath, }; const box = absoluteBoxForSelection(elementSelection); return box ? { selection: elementSelection, element: chartToolbarElement(chartRoot.element, box), box, } : null; } export function getTemplateV2SelectionTableToolbarTarget({ selection, selectedBox, selectedComponent, selectedElement, absoluteBoxForSelection, }: SelectionToolbarTargetOptions): TemplateV2TableSelectionToolbarTarget | null { if ( selection?.kind === "element" && selectedElement && selectedBox && isTemplateV2TableElement(selectedElement) ) { return { selection, element: tableToolbarElement(selectedElement, selectedBox), box: selectedBox, }; } if (selection?.kind !== "component" || !selectedComponent) return null; const tableRoot = findFirstComponentTableElement( readArray(selectedComponent.elements), ); if (!tableRoot) return null; const elementSelection: TemplateV2ToolbarElementSelection = { kind: "element", componentIndex: selection.componentIndex, elementPath: tableRoot.elementPath, }; const box = absoluteBoxForSelection(elementSelection); return box ? { selection: elementSelection, element: tableToolbarElement(tableRoot.element, box), box, } : null; } export function getTemplateV2SelectionEditorToolbarTarget({ selection, selectedComponent, absoluteBoxForSelection, }: SelectionToolbarTargetOptions): TemplateV2EditorSelectionToolbarTarget | null { if (selection?.kind !== "component" || !selectedComponent) return null; const elements = readArray(selectedComponent.elements); if (elements.length !== 1) return null; const editorElement = asRecord(elements[0]); if (!isTemplateV2EditorToolbarElement(editorElement)) return null; const elementSelection: TemplateV2ToolbarElementSelection = { kind: "element", componentIndex: selection.componentIndex, elementPath: [0], }; const box = absoluteBoxForSelection(elementSelection); const toolbarElement = box ? rawElementForEditorToolbar(editorElement, box) : null; return box && toolbarElement ? { selection: elementSelection, element: toolbarElement, box } : null; } function readArray(value: unknown): unknown[] { return Array.isArray(value) ? value : []; } function asRecord(value: unknown): RawRecord | null { return value && typeof value === "object" && !Array.isArray(value) ? (value as RawRecord) : null; } function childElements(element: RawRecord): unknown[] { if (Array.isArray(element.children)) return element.children; if (Array.isArray(element.elements)) return element.elements; if (asRecord(element.child)) return [element.child]; if (asRecord(element.item)) return [element.item]; return []; } function isTemplateV2ChartElement( element: RawRecord | null | undefined, ): element is RawRecord { return element?.type === "chart"; } function isTemplateV2TableElement( element: RawRecord | null | undefined, ): element is RawRecord { return element?.type === "table"; } function isTemplateV2EditorToolbarElement( element: RawRecord | null | undefined, ): element is RawRecord { return ( element?.type === "text" || element?.type === "text-list" || element?.type === "rectangle" || element?.type === "ellipse" || element?.type === "line" ); } function chartToolbarElement( element: RawRecord, box: TemplateV2ToolbarBox, ): ChartSlideElement { return { ...rawChartToEditorChart(element), position: { x: box.x, y: box.y, }, size: { width: box.width, height: box.height, }, }; } function tableToolbarElement( element: RawRecord, box: TemplateV2ToolbarBox, ): TableSlideElement { return { ...element, type: "table", position: { x: box.x, y: box.y, }, size: { width: box.width, height: box.height, }, columns: readArray(element.columns) as TableSlideElement["columns"], rows: readArray(element.rows).map((row) => readArray(row)) as TableSlideElement["rows"], }; } function findFirstComponentChartElement( elements: unknown[], parentPath: number[] = [], ): { element: RawRecord; elementPath: number[] } | null { for (let index = 0; index < elements.length; index += 1) { const element = asRecord(elements[index]); if (!element) continue; const elementPath = [...parentPath, index]; if (isTemplateV2ChartElement(element)) { return { element, elementPath }; } const nested = findFirstComponentChartElement( childElements(element), elementPath, ); if (nested) return nested; } return null; } function findFirstComponentTableElement( elements: unknown[], parentPath: number[] = [], ): { element: RawRecord; elementPath: number[] } | null { for (let index = 0; index < elements.length; index += 1) { const element = asRecord(elements[index]); if (!element) continue; const elementPath = [...parentPath, index]; if (isTemplateV2TableElement(element)) { return { element, elementPath }; } const nested = findFirstComponentTableElement( childElements(element), elementPath, ); if (nested) return nested; } return null; }