1288 lines
46 KiB
Markdown
1288 lines
46 KiB
Markdown
# Template V2 Slide Rendering and Editor Documentation
|
|
|
|
This document explains the Template V2 slide editor for a developer who is new
|
|
to the project. It covers what features exist, which files own each feature,
|
|
how rendering works, how editing works, and where to make changes safely.
|
|
|
|
The code lives mainly in:
|
|
|
|
- `components/slide-editor`
|
|
- `app/(presentation-generator)/custom-template/components/EachSlide`
|
|
- `lib/template-v2-json-to-html.ts`
|
|
|
|
## Mental Model
|
|
|
|
Template V2 is a JSON-driven slide system. A slide has a `ui` layout object.
|
|
That layout is edited in the browser with a Konva canvas and is also rendered
|
|
to HTML for preview/export flows.
|
|
|
|
There are two important rendering paths:
|
|
|
|
1. Editor rendering: `surface/TemplateV2KonvaSlide.tsx` renders editable Konva
|
|
nodes through `surface/nodes.tsx`.
|
|
2. HTML rendering/export: `TemplateV2LayoutPreview.tsx` and
|
|
`lib/template-v2-json-to-html.ts` render the same slide data outside the
|
|
Konva editor.
|
|
|
|
The editor generally works like this:
|
|
|
|
1. A slide layout is loaded into `TemplateV2KonvaSlide`.
|
|
2. The slide is normalized and stored in local `uiDraft` state.
|
|
3. Konva nodes render components/elements from `uiDraft`.
|
|
4. Selection state decides which toolbar/editor is visible.
|
|
5. Toolbars produce partial element/component changes.
|
|
6. `commitUi()` writes the next layout to local state, undo history, and Redux.
|
|
7. Redux keeps the current slide `ui` in the presentation state.
|
|
|
|
## Important Terms
|
|
|
|
- Template V2 layout: The raw `ui` layout object for a slide. It can contain
|
|
`components`, root `elements`, `background`, and asset/font metadata.
|
|
- Component: A top-level movable group. Components have `position`, `size`, and
|
|
usually an `elements` array.
|
|
- Element: A renderable object. Examples are `text`, `text-list`, `image`,
|
|
`rectangle`, `ellipse`, `line`, `container`, `flex`, `grid`, `table`, and
|
|
`chart`.
|
|
- Root element: An element stored directly under slide `elements`, not inside a
|
|
component. The editor treats root elements as a virtual component with
|
|
`ROOT_ELEMENTS_COMPONENT_INDEX`.
|
|
- Selection: The active item in the editor. It can be a component, multiple
|
|
components, or a nested element path.
|
|
- Element path: An array of indexes that points to a nested element inside an
|
|
element tree. For example, `[2, 0, 1]` means component element 2, then child
|
|
0, then child 1.
|
|
- Editor units: Template V2 editor geometry is stored in `1280 x 720` canvas
|
|
pixels. Positions, sizes, padding, gaps, and border radius values are edited
|
|
as pixels.
|
|
- Raw element: A permissive `Record<string, any>` representation of Template V2
|
|
JSON used by the editor surface.
|
|
- Editor element: A typed `SlideElement` from `types.ts` used by
|
|
element toolbars.
|
|
|
|
## Folder Map
|
|
|
|
| Folder | Purpose |
|
|
| --- | --- |
|
|
| `surface/` | Main editable canvas, Konva nodes, font loading, image loading/export assets. |
|
|
| `model/` | Geometry, selection, raw UI updates, conversions between raw Template V2 elements and typed toolbar elements. |
|
|
| `types.ts` | Plain TypeScript contract for Template V2 editor data. |
|
|
| `importing/` | Template V2 import/adaptation code. |
|
|
| `selection/` | Floating selection toolbar, transformer boxes, layer ordering, toolbar positioning. |
|
|
| `toolbar/` | Element toolbar router, shared toolbar helpers/icons, design variable toolbar. |
|
|
| `text/` | Rich text rendering helpers, Tiptap inline editor, text and bullet toolbars. |
|
|
| `tables/` | Table Konva renderer, table toolbar, inline cell editor, selected cell state. |
|
|
| `charts/` | Chart renderer, chart toolbar, chart editor content, chart data conversion. |
|
|
| `images/` | Image toolbar, crop/focus UI, icon search/replacement editor. |
|
|
| `shapes/` | Rectangle/ellipse toolbar and line toolbar. |
|
|
| `layout/` | Container/flex/grid/line selection toolbar controls and layout algorithms. |
|
|
| `clipboard/` | Copy/paste payload creation, parsing, browser clipboard integration. |
|
|
| `events/` | Custom browser event names and payload types used to connect editor UI to other app panels. |
|
|
| `state/` | Small shared state hooks/types for inline editing and table cell selection. |
|
|
| `utils/` | Small editor utilities, currently color helpers. |
|
|
|
|
## Main Data Flow
|
|
|
|
### Types and Import
|
|
|
|
Primary files:
|
|
|
|
- `types.ts`
|
|
- `importing/template-v2-import.ts`
|
|
|
|
`types.ts` defines the editor data contract for supported slide elements:
|
|
|
|
- `TextElement`
|
|
- `ImageElement`
|
|
- `TextListElement`
|
|
- `TableElement`
|
|
- `RectangleElement`
|
|
- `EllipseElement`
|
|
- `LineElement`
|
|
- `ChartElement`
|
|
- `FlexElement`
|
|
- `GridElement`
|
|
- `GroupElement`
|
|
- `Slide`
|
|
- `Deck`
|
|
|
|
The exported `SlideElement` union is what element toolbars edit.
|
|
|
|
`template-v2-import.ts` adapts generated Template V2 payloads into editor
|
|
deck/slide data. Backend validation owns the JSON contract; the frontend
|
|
adapter owns scaling, asset URL normalization, fallback elements, and legacy
|
|
shape compatibility. Important functions:
|
|
|
|
- `adaptTemplateV2ResponseToDeck()`: Converts a template response into a deck.
|
|
- `adaptGeneratedTemplateV2PresentationToDeck()`: Converts a generated
|
|
presentation payload into deck slides.
|
|
- `extractTemplateV2Layouts()`: Reads layouts from different possible response
|
|
shapes.
|
|
- `adaptTemplateV2LayoutToSlide()`: Converts one Template V2 layout into one
|
|
typed slide.
|
|
- `normalizeTemplateV2Slide()`: Normalizes elements after import.
|
|
- `adaptTemplateV2ComponentToElement()`: Converts a merged component into a
|
|
group-like element.
|
|
- `withEqualTemplateV2FlowChildSizes()`: Normalizes flex/grid child sizing.
|
|
|
|
When adding a new element type, start by updating `types.ts`, then make sure
|
|
`template-v2-import.ts` can adapt old/raw data into that shape.
|
|
|
|
### Editor Surface State
|
|
|
|
Primary file:
|
|
|
|
- `surface/TemplateV2KonvaSlide.tsx`
|
|
|
|
This is the main editable slide component. It owns:
|
|
|
|
- `uiDraft`: The current local Template V2 layout.
|
|
- `currentUiRef`: Ref copy of the latest `uiDraft` for event handlers.
|
|
- `selection`: Current selection.
|
|
- `nodeRefs`: Map from selection keys to Konva nodes.
|
|
- `undoStackRef` and `redoStackRef`: Undo/redo history.
|
|
- `inlineEdit`: Text/table inline edit state.
|
|
- `iconEditorSelection`: The icon currently being edited.
|
|
- `isUploadingImage`: Upload overlay state.
|
|
|
|
Important functions in `TemplateV2KonvaSlide.tsx`:
|
|
|
|
- `setRootNode()`: Saves the surface DOM node and registers it for toolbar
|
|
position calculations.
|
|
- `setSelectionNodeRef()`: Registers Konva nodes by selection key.
|
|
- `activateSurface()`: Marks this slide surface as the active editor surface.
|
|
- `clearSurface()`: Clears active surface state for this slide.
|
|
- `clearEditorUiState()`: Clears selection, inline edit, table cell selection,
|
|
chart/icon editor state, and optionally active surface.
|
|
- `commitUi()`: Central write function. Updates local state, pushes undo
|
|
history, clears redo stack, and dispatches `updateSlideUi`.
|
|
- `undo()` and `redo()`: Move layouts between undo/redo stacks and call
|
|
`commitUi(previous, false)`.
|
|
- `select()`: Applies normal, multi-select, and toggle selection behavior.
|
|
- `updateComponent()`: Updates one component in the raw UI.
|
|
- `updateElement()`: Updates one nested element in the raw UI.
|
|
- `handleComponentDragStart()`, `handleComponentDragMove()`,
|
|
`handleComponentDragEnd()`: Drag one or multiple selected components.
|
|
- `deleteSelection()`: Deletes selected components or nested elements.
|
|
- `createClipboardPayload()`: Converts current selection into a clipboard
|
|
payload.
|
|
- `pasteClipboardPayload()`: Inserts clipboard payload into current UI.
|
|
- `duplicateSelection()`: Copy/paste without using the system clipboard.
|
|
- `openInlineEditor()`: Opens rich text editor for text or text-list elements.
|
|
- `closeInlineEditor()`: Commits or discards inline text edits.
|
|
- `commitInlineTextRuns()`: Receives live rich text runs from Tiptap without
|
|
pushing every keystroke to Redux.
|
|
- `applyToolbarElementChange()`: Merges typed toolbar edits back into the raw
|
|
selected element.
|
|
- `applyLayoutElementChange()`: Applies container/flex/grid line toolbar edits.
|
|
- `ungroupSelectedComponent()`: Converts a component's children into standalone
|
|
components/elements.
|
|
- `reorderSelectedComponentLayer()`: Bring forward/backward actions.
|
|
- `openImageUpload()` and `handleImageUploadChange()`: Replace image data.
|
|
- `openIconEditor()` and `handleIconChange()`: Replace SVG/icon data.
|
|
- `openChartEditor()`: Opens chart editor via custom events.
|
|
- `handleElementDoubleClick()`: Opens inline text/table/chart/icon/image edit
|
|
flows depending on element type.
|
|
|
|
The render body creates:
|
|
|
|
- A fixed-size editor surface.
|
|
- A hidden image file input for image replacement.
|
|
- A Konva `Stage` with background layer and editable content layer.
|
|
- `MemoizedRawElementNode` for root elements.
|
|
- `MemoizedRawComponentNode` for components.
|
|
- `TemplateV2SelectionTransformers` for resize/transform handles.
|
|
- `TemplateV2SelectionToolbar` for component/layout actions.
|
|
- `ElementToolbar` for type-specific element editing.
|
|
- `TableInlineEditor` for selected table cells.
|
|
- `TemplateV2InlineEditor` for text editing.
|
|
- `IconsEditor` for icon replacement.
|
|
|
|
### Model Helpers
|
|
|
|
Primary files:
|
|
|
|
- `model/core.ts`
|
|
- `model/model.ts`
|
|
- `model/inserted-content.ts`
|
|
- `model/chart-model.ts`
|
|
- `model/render-style.ts`
|
|
- `model/element-model.ts`
|
|
- `model/design-variables.ts`
|
|
- `model/template-v2-ungroup.ts`
|
|
|
|
`model/model.ts` is now the compatibility barrel plus the tightly coupled
|
|
editor operations for selection, geometry, layout, and toolbar merging.
|
|
Smaller pure helper groups live beside it:
|
|
|
|
- `model/core.ts`: stage constants, raw Template V2 types, raw readers,
|
|
clamps, id normalization, and editable-target checks.
|
|
- `model/inserted-content.ts`: conversion of inserted palette/block content
|
|
into raw top-level components.
|
|
- `model/chart-model.ts`: raw chart to editor chart conversion and the reverse.
|
|
- `model/render-style.ts`: color, opacity, shadow, and border-radius helpers
|
|
used by Konva rendering.
|
|
|
|
Important groups:
|
|
|
|
Raw UI updates:
|
|
|
|
- `updateComponentInUi()`
|
|
- `setComponentPositionsInUi()`
|
|
- `updateElementInUi()`
|
|
- `updateElementArray()`
|
|
- `deleteSelectionFromUi()`
|
|
|
|
Geometry and layout:
|
|
|
|
- `componentBox()`
|
|
- `elementBox()`
|
|
- `elementSize()`
|
|
- `absoluteBoxForSelection()`
|
|
- `absoluteInlineEditBox()`
|
|
- `absoluteElementLocalFrame()`
|
|
- `renderedLocalBoxForElementSelection()`
|
|
- `childrenBounds()`
|
|
- `layoutChildren()`
|
|
- `layoutContainerChildren()`
|
|
- `elementWithNormalizedLayoutChildren()`
|
|
|
|
Selection:
|
|
|
|
- `keyForSelection()`
|
|
- `keysForSelection()`
|
|
- `selectionFromKey()`
|
|
- `selectionWithComponentToggle()`
|
|
- `componentIndexesForSelection()`
|
|
- `selectionForComponentIndexes()`
|
|
- `selectionTouchesComponent()`
|
|
- `selectionTouchesElement()`
|
|
|
|
Clipboard helpers:
|
|
|
|
- `componentForClipboardSelection()`
|
|
- `rootElementClipboardComponent()`
|
|
|
|
Inserted content conversion:
|
|
|
|
- `appendInsertedContent()`
|
|
- `insertedComponentToRaw()`
|
|
- `insertedElementToComponent()`
|
|
- `rawElementFromInsertedElement()`
|
|
- `convertInsertedChildArrays()`
|
|
- `normalizeInsertedElementGeometry()`
|
|
- `normalizeInsertedTextCollections()`
|
|
- `normalizeInsertedTableRows()`
|
|
|
|
These functions are implemented in `model/inserted-content.ts` and re-exported
|
|
from `model/model.ts` for existing imports.
|
|
|
|
Toolbar conversion:
|
|
|
|
- `rawElementForEditorToolbar()`: Converts raw selected element into typed
|
|
`SlideElement` for `ElementToolbar`.
|
|
- `mergeEditorToolbarElement()`: Merges toolbar result back into raw element.
|
|
- `rawStrokeForEditor()` and `editorStrokeToRaw()`
|
|
- `rawBorderRadiusForEditor()` and `editorBorderRadiusToRaw()`
|
|
- `rawChartToEditorChart()` and `editorChartToRawChart()` from
|
|
`model/chart-model.ts`
|
|
|
|
Rendering primitives:
|
|
|
|
- `linePoints()`
|
|
- `fillColor()`, `fillOpacity()`
|
|
- `strokeColor()`, `strokeWidth()`, `strokeOpacity()`
|
|
- `colorWithOpacity()`
|
|
- `shadowProps()`
|
|
- `borderRadius()`
|
|
- `readPadding()`
|
|
|
|
Style helpers live in `model/render-style.ts`; raw readers such as
|
|
`readPadding()` live in `model/core.ts`.
|
|
|
|
`model/element-model.ts` contains typed helper functions used by toolbars:
|
|
|
|
- `elementBox()`
|
|
- `resizeElement()`
|
|
- `moveElement()`
|
|
- `elementFont()`
|
|
- `applyTextElementSelectionFont()`
|
|
- `tableRowsAsStrings()`
|
|
|
|
`model/design-variables.ts` implements design-variable option selection:
|
|
|
|
- `applyDesignVariableOption()`
|
|
- `selectedDesignVariableOptionIndex()`
|
|
- `designVariableNameLabel()`
|
|
|
|
`model/template-v2-ungroup.ts` owns component ungrouping:
|
|
|
|
- `ungroupTemplateV2ComponentInUi()`
|
|
|
|
## Rendering Features
|
|
|
|
### Konva Editor Renderer
|
|
|
|
Primary file:
|
|
|
|
- `surface/nodes.tsx`
|
|
|
|
Important functions/components:
|
|
|
|
- `RawComponentNode()`: Renders one top-level component as a Konva `Group`.
|
|
Handles component selection, drag callbacks, multi-select participation, and
|
|
nested element rendering.
|
|
- `RawElementNode()`: Renders one element inside a component or root element
|
|
tree. It handles element selection, double-click behavior, table cell
|
|
callbacks, and nested child rendering.
|
|
- `SelectionBoundsRect()`: Draws selection boundaries for selected nodes.
|
|
- `RawElementVisual()`: Switches by `element.type` and returns the correct
|
|
visual renderer.
|
|
- `RawRichTextElement()`: Renders `text` and `text-list` using Konva text token
|
|
layout.
|
|
- `RawImageElement()`: Loads and renders images, icon masks, fit modes, crop
|
|
focus, flip, and border radius clipping.
|
|
- `useLoadedKonvaImage()`: Async image loader hook.
|
|
- `imageCornerRadii()` and `drawRoundedImageClip()`: Rounded image clipping.
|
|
- `RawInfographicElement()`: Renders infographic-style elements.
|
|
|
|
Supported visual types in `RawElementVisual()`:
|
|
|
|
- `rectangle`
|
|
- `ellipse`
|
|
- `container`
|
|
- `flex`
|
|
- `grid`
|
|
- `group`
|
|
- `line`
|
|
- `text`
|
|
- `text-list`
|
|
- `image`
|
|
- `table`
|
|
- `chart`
|
|
- `infographic`
|
|
|
|
Rendering rules:
|
|
|
|
- The outer group in `RawElementNode` applies position, size, rotation, opacity,
|
|
and clipping.
|
|
- Shapes use `fillColor`, `fillOpacity`, `strokeColor`, `strokeOpacity`,
|
|
`strokeWidth`, `shadowProps`, and `borderRadius` helpers.
|
|
- Lines use `linePoints`, stroke color/width/opacity, optional `stroke.dash`,
|
|
shadow, and hit stroke width for easier selection.
|
|
- Text uses text layout helpers from `text/template-v2-text.ts`.
|
|
- Images load through `loadKonvaImage()` and are clipped manually for rounded
|
|
corners.
|
|
- Charts are delegated to `charts/TemplateV2ChartJsElement.tsx`, which renders
|
|
Chart.js output into the Konva scene as an image-backed element.
|
|
- Tables are delegated to `tables/TemplateV2TableElement.tsx`.
|
|
|
|
### HTML Preview Renderer
|
|
|
|
Primary file:
|
|
|
|
- `app/(presentation-generator)/custom-template/components/EachSlide/TemplateV2LayoutPreview.tsx`
|
|
|
|
This renderer is used outside the Konva editor. It renders Template V2 layout
|
|
data as normal React DOM. Key functions:
|
|
|
|
- `renderImage()`: Renders image DOM with `objectFit`, `objectPosition`, flip,
|
|
border radius, and icon mask color.
|
|
- `renderText()`: Renders text runs into spans.
|
|
- `renderTextList()`: Renders bullet/number lists.
|
|
- `renderTable()`: Renders table DOM.
|
|
- `renderContainer()`
|
|
- `renderFlex()`
|
|
- `renderGrid()`
|
|
- `renderGroup()`
|
|
- `renderChart()`
|
|
- `frameStyle()`
|
|
- `boxStyle()`
|
|
- `fontStyle()`
|
|
|
|
This path is useful for slide thumbnails or non-editable previews. If a visual
|
|
feature is added to the Konva renderer, check this file too.
|
|
|
|
### HTML Export Renderer
|
|
|
|
Primary file:
|
|
|
|
- `lib/template-v2-json-to-html.ts`
|
|
|
|
This produces HTML strings from Template V2 JSON. Important functions:
|
|
|
|
- `renderItem()`: Main switch by element type.
|
|
- `renderImage()`
|
|
- `renderText()`
|
|
- `renderTextList()`
|
|
- `renderTable()`
|
|
- `renderContainer()`
|
|
- `renderFlex()`
|
|
- `renderGrid()`
|
|
- `renderGroup()`
|
|
- `renderLine()`
|
|
- `renderChart()`
|
|
- `frameStyle()`
|
|
- `boxStyle()`
|
|
- `fontStyle()`
|
|
- `imageFocusStyle()`
|
|
- `chartConfig()`
|
|
|
|
Any feature that should survive export must be represented here. For example,
|
|
line dash support is already handled in `renderLine()` through
|
|
`stroke-dasharray`.
|
|
|
|
## Editor Feature Map
|
|
|
|
| Feature | Rendering owner | Editor owner | Model/helpers | Notes |
|
|
| --- | --- | --- | --- | --- |
|
|
| Slide surface and background | `TemplateV2KonvaSlide`, `backgroundColor()` | `TemplateV2KonvaSlide` | `model/render-style.ts`, `model/core.ts` | Surface is fixed `1280 x 720`. |
|
|
| Components | `RawComponentNode()` | Selection toolbar, drag handlers | `componentBox()`, `updateComponentInUi()` | Top-level movable groups. |
|
|
| Root elements | `MemoizedRawElementNode` in surface | `ElementToolbar` | `rootElementsComponent()` | Treated as virtual component index `-1`. |
|
|
| Selection | `SelectionBoundsRect`, transformers | `select()`, `SelectionToolbar` | `keyForSelection()`, `selectionWithComponentToggle()` | Supports component, multi-component, and element selections. |
|
|
| Resize/transform | `SelectionTransformers.tsx` | Konva Transformer callbacks | `resizeComponent()`, `positionFromNodeInParent()` | Updates raw geometry after transform. |
|
|
| Dragging | `RawComponentNode()` | `handleComponentDragStart/Move/End()` | `setComponentPositionsInUi()` | Multi-component drag updates all selected positions together. |
|
|
| Undo/redo | N/A | `commitUi()`, `undo()`, `redo()` | `MAX_HISTORY_ENTRIES` | Only `commitUi(..., true)` pushes history. |
|
|
| Copy/paste | N/A | `useTemplateV2Clipboard()` | `clipboard/clipboard.ts` | Supports custom MIME, plain text prefix, localStorage fallback. |
|
|
| Duplicate | N/A | `duplicateSelection()` | Clipboard payload helpers | Duplicate is local copy/paste. |
|
|
| Layer ordering | Render order in component array | `reorderSelectedComponentLayer()` | `selection/layering.ts` | Reorders component array. |
|
|
| Ungroup | Component/element rendering | Selection toolbar | `template-v2-ungroup.ts` | Converts grouped content to independent components/elements. |
|
|
| Insert elements | Rendered by normal renderer | Custom insert event listener | `insert/insert-elements.ts`, `model/inserted-content.ts` | Usually triggered from `PresentationActions`. |
|
|
| Text rendering | `RawRichTextElement()` | `TextToolbar`, inline editor | `template-v2-text.ts` | Uses text runs and custom layout. |
|
|
| Rich text editing | `TiptapInlineTextEditor` overlay | `TemplateV2InlineEditor` | `text-runs.ts` | Live runs are buffered to avoid typing lag. |
|
|
| Bullet/list editing | `RawRichTextElement()` with list runs | `BulletsToolbar` | `template-v2-text.ts`, `markdown-text.ts` | List markers only apply to `text-list`. |
|
|
| Images | `RawImageElement()` | `ImageToolbar`, upload flow | `loadKonvaImage()`, image helpers | Supports fit, crop focus, flip, opacity, radius. |
|
|
| Icon replacement | `RawImageElement()` with SVG URL/color | `IconsEditor` | `isRawIconElement()`, `rawIconQuery()` | Icons are image-like elements with `is_icon`. |
|
|
| Shapes | `RawElementVisual()` | `ShapeToolbar` | `model/render-style.ts` | Rectangle and ellipse share toolbar. |
|
|
| Lines | `RawElementVisual()` line branch | `LineToolbar` | `linePoints()`, `stroke.dash` | Supports style, border panel, transform, shadow, opacity. |
|
|
| Containers | `RawElementVisual()` and child rendering | `ContainerToolbarControls` | `layoutContainerChildren()` | Container has fill/stroke/radius/padding/shadow/alignment. |
|
|
| Flex layout | `layoutFlowChildren()` | `LayoutToolbar` flow controls | `flowLayout.ts`, `layoutResize.ts` | Calculates child boxes from direction, gap, grow/shrink. |
|
|
| Grid layout | `layoutFlowChildren()` | `LayoutToolbar` flow controls | `placeGridChildren()`, `layoutResize.ts` | Calculates grid cells, spans, and alignment. |
|
|
| Layout add/remove items | Layout render path | `ItemsControl` | `layoutItems.ts`, `layoutResize.ts` | Supports `children` and `elements` child arrays. |
|
|
| Tables | `TemplateV2TableElement` | `TableToolbar`, `TableInlineEditor` | `useTableCellSelection()` | Cell editing uses text toolbar plus Tiptap editor. |
|
|
| Charts | `TemplateV2ChartJsElement` | `ChartToolbar`, chart editor event | `chart-data.ts` | Chart editor is opened by event and updates selected chart. |
|
|
| Design variables | Normal element render | `DesignVariablesToolbar` | `design-variables.ts` | Overrides element properties from predefined options. |
|
|
| Fonts | Text/table/chart renderers | Text toolbar font picker | `google-fonts.ts`, `fontLoading.ts` | Surface waits for fonts before enabling layer. |
|
|
|
|
## Detailed Feature Explanations
|
|
|
|
### Selection and Toolbars
|
|
|
|
Primary files:
|
|
|
|
- `selection/SelectionToolbar.tsx`
|
|
- `selection/SelectionTransformers.tsx`
|
|
- `selection/toolbarTarget.ts`
|
|
- `selection/toolbarPosition.ts`
|
|
- `toolbar/ElementToolbar.tsx`
|
|
- `layout/LayoutToolbar.tsx`
|
|
|
|
There are two toolbar systems:
|
|
|
|
1. Selection/layout toolbar: `TemplateV2SelectionToolbar` renders
|
|
`TemplateV2LayoutToolbar`. It handles component actions and layout-specific
|
|
controls for container/flex/grid/component selections.
|
|
2. Element toolbar: `ElementToolbar` routes selected element types to
|
|
`TextToolbar`, `BulletsToolbar`, `ImageToolbar`, `ShapeToolbar`,
|
|
`LineToolbar`, `ChartToolbar`, or `TableToolbar`.
|
|
|
|
`toolbarTarget.ts` decides when a layout toolbar target exists. It currently
|
|
targets layout elements such as container/flex/grid. Line elements are handled
|
|
by `ElementToolbar` so the editor does not show duplicate line toolbars.
|
|
|
|
`toolbarPosition.ts` calculates the floating toolbar anchor and viewport
|
|
position. It considers the selected box, toolbar width, surface bounds, and
|
|
whether the toolbar can fit above or below the selection.
|
|
|
|
`SelectionTransformers.tsx` renders Konva Transformer handles for resizing and
|
|
transforming selected items. It relies on `nodeRefs` registered by rendered
|
|
nodes in `surface/nodes.tsx`.
|
|
|
|
### Component Selection, Multi-select, Dragging, Resize
|
|
|
|
Selection data lives in `TemplateV2KonvaSlide.tsx` and uses types from
|
|
`model/model.ts`.
|
|
|
|
Single component selection:
|
|
|
|
1. User clicks a component group in `RawComponentNode`.
|
|
2. `onSelect()` calls `select()` in the surface.
|
|
3. `selection` becomes `{ kind: "component", componentIndex }`.
|
|
4. Toolbar and transformer use the selected component's box.
|
|
|
|
Multi-select:
|
|
|
|
1. `selectionWithComponentToggle()` handles modifier keys.
|
|
2. Selected component indexes become `{ kind: "multi-component", componentIndexes }`.
|
|
3. `keysForSelection()` returns all selected keys.
|
|
4. Transformer/selection UI treats them as a group.
|
|
|
|
Dragging:
|
|
|
|
1. `RawComponentNode` starts a Konva drag.
|
|
2. `handleComponentDragStart()` records model and node positions.
|
|
3. `handleComponentDragMove()` updates sibling selected nodes visually during
|
|
a multi-component drag.
|
|
4. `handleComponentDragEnd()` commits final model positions with
|
|
`setComponentPositionsInUi()`.
|
|
|
|
Resize:
|
|
|
|
1. Transformer changes node size/scale.
|
|
2. Resize helpers convert Konva geometry back to raw Template V2 position/size.
|
|
3. `commitUi()` persists the new state.
|
|
|
|
### Undo and Redo
|
|
|
|
Primary owner:
|
|
|
|
- `TemplateV2KonvaSlide.tsx`
|
|
|
|
`commitUi(nextUi, pushHistory = true)` is the only function that should write
|
|
new slide UI state. If `pushHistory` is true, it pushes the current layout onto
|
|
`undoStackRef` and clears `redoStackRef`.
|
|
|
|
Undo:
|
|
|
|
1. `undo()` pops from `undoStackRef`.
|
|
2. Current layout is pushed to `redoStackRef`.
|
|
3. Previous layout is committed with `pushHistory = false`.
|
|
|
|
Redo:
|
|
|
|
1. `redo()` pops from `redoStackRef`.
|
|
2. Current layout is pushed to `undoStackRef`.
|
|
3. Next layout is committed with `pushHistory = false`.
|
|
|
|
Keyboard handling listens for Cmd/Ctrl+Z and Cmd/Ctrl+Y only on the active
|
|
surface. Editable inputs are ignored through `isEditableTarget()`.
|
|
|
|
### Copy, Paste, Duplicate
|
|
|
|
Primary files:
|
|
|
|
- `clipboard/clipboard.ts`
|
|
- `clipboard/useClipboard.ts`
|
|
- `TemplateV2KonvaSlide.tsx`
|
|
|
|
`clipboard/clipboard.ts` handles pure data operations:
|
|
|
|
- `createTemplateV2ClipboardPayload()`: Creates payload from selected
|
|
component(s) or root element converted into a component-like payload.
|
|
- `pasteTemplateV2ClipboardPayload()`: Inserts copied components with an offset,
|
|
unique ids, and a new selection.
|
|
|
|
`useClipboard.ts` handles browser integration:
|
|
|
|
- Uses custom MIME: `application/x-presenton-template-v2`.
|
|
- Also writes plain text with prefix `PRESENTON_TEMPLATE_V2:`.
|
|
- Stores fallback in `localStorage`.
|
|
- Handles browser paste events and keyboard shortcut fallback.
|
|
|
|
Duplicate uses the same data path as copy/paste, but it does not require the
|
|
system clipboard.
|
|
|
|
### Custom Events
|
|
|
|
Primary file:
|
|
|
|
- `events/events.ts`
|
|
|
|
Events connect editor surfaces to app UI that is not directly nested under the
|
|
surface.
|
|
|
|
Event constants:
|
|
|
|
- `TEMPLATE_V2_INSERT_ELEMENTS_EVENT`: Add blocks/components/elements to active
|
|
slide.
|
|
- `TEMPLATE_V2_SURFACE_SELECTED_EVENT`: Notify other UI which slide surface is
|
|
active and what selection/history state exists.
|
|
- `TEMPLATE_V2_ACTIVATE_SURFACE_EVENT`: Programmatically activate one slide
|
|
surface.
|
|
|
|
Why events are used:
|
|
|
|
- Toolbar/actions may live outside the slide surface React subtree.
|
|
- Multiple slide surfaces can exist in the page.
|
|
- The active surface needs to receive insert commands without prop drilling
|
|
through the whole app.
|
|
|
|
When adding a new external editor panel, define a typed event payload in
|
|
`events.ts`, listen in `TemplateV2KonvaSlide.tsx`, and include a `slideId` or
|
|
active-surface guard so the wrong slide does not handle the event.
|
|
|
|
### Text and Rich Text
|
|
|
|
Primary files:
|
|
|
|
- `text/template-v2-text.ts`
|
|
- `text/text-runs.ts`
|
|
- `text/text-line-height.ts`
|
|
- `text/TextToolbar.tsx`
|
|
- `text/BulletsToolbar.tsx`
|
|
- `text/TemplateV2InlineEditor.tsx`
|
|
- `text/TiptapInlineTextEditor.tsx`
|
|
- `text/markdown-text.ts`
|
|
|
|
Rendering:
|
|
|
|
- Raw text data is normalized by helpers in `template-v2-text.ts`.
|
|
- `RawRichTextElement()` in `surface/nodes.tsx` calls these helpers to convert
|
|
text runs into render tokens.
|
|
- Text rendering supports mixed runs, alignment, vertical alignment, line
|
|
height, wrapping, bold, italic, underline, color, letter spacing, and list
|
|
markers.
|
|
|
|
Important text helpers:
|
|
|
|
- `rawFont()`: Reads raw element font into a normalized render font.
|
|
- `rawTextContent()`: Reads plain text content.
|
|
- `rawTextRunsForEditor()`: Converts raw text to editor runs.
|
|
- `rawTextListRunsForEditor()`: Converts raw text-list to editor runs.
|
|
- `rawRenderTextRuns()`: Converts raw text into render runs.
|
|
- `rawTextListRenderTextRuns()`: Converts text-list items into render runs.
|
|
- `layoutRichText()`: Main rich text token layout function.
|
|
- `layoutRenderTextRuns()`: Breaks render runs into lines.
|
|
- `normalizeRawTextMarkdownElement()`: Removes markdown/list syntax where it
|
|
should not be persisted.
|
|
|
|
Inline editing:
|
|
|
|
1. Double-click a text or text-list element.
|
|
2. `openInlineEditor()` creates an inline edit state with current runs/style.
|
|
3. `TemplateV2InlineEditor` positions a DOM/Tiptap editor over the Konva text.
|
|
4. `TiptapInlineTextEditor` converts runs to Tiptap JSON.
|
|
5. On input, runs are scheduled via `requestAnimationFrame` and emitted without
|
|
committing Redux on every keystroke.
|
|
6. On close, `closeInlineEditor()` commits final runs to the raw element.
|
|
|
|
Toolbar editing:
|
|
|
|
- `TextToolbar` edits font family, size, styles, color, alignment, opacity,
|
|
letter spacing, line height, and list settings if present.
|
|
- `BulletsToolbar` wraps `TextToolbar` behavior for `text-list` elements and
|
|
marker settings.
|
|
|
|
Important performance detail:
|
|
|
|
- `commitInlineTextRuns()` only updates inline state while typing. The slide UI
|
|
is committed on close. This avoids typing lag caused by rerendering the whole
|
|
slide/Redux store on every keystroke.
|
|
|
|
### Images and Crop
|
|
|
|
Primary files:
|
|
|
|
- `images/ImageToolbar.tsx`
|
|
- `surface/nodes.tsx`
|
|
- `surface/exportAssets.ts`
|
|
- `utils/api.ts` for asset URL resolution outside this folder
|
|
|
|
Rendering:
|
|
|
|
- `RawImageElement()` loads images with `useLoadedKonvaImage()`.
|
|
- It supports `fit` values:
|
|
- `cover` shown as "Fill" in the toolbar.
|
|
- `contain`.
|
|
- `fill` shown as "Stretch".
|
|
- It supports crop focus through `focus_x` and `focus_y`, with optional
|
|
crop zoom through `crop_scale`.
|
|
- It supports `flip_h`, `flip_v`, `opacity`, `border_radius`, and icon color
|
|
masking.
|
|
|
|
Crop model:
|
|
|
|
- The persisted crop data is not a pixel crop rectangle.
|
|
- It is `fit: "cover"` plus `focus_x`, `focus_y`, and optional `crop_scale`.
|
|
- The toolbar shows a Canva-style fixed crop window with a draggable/scalable
|
|
image box outside it. It commits the final focus and zoom when the user
|
|
applies or releases the crop interaction.
|
|
|
|
`ImageToolbar.tsx` owns:
|
|
|
|
- Fit dropdown.
|
|
- Replace image action.
|
|
- Crop window and source image scaling overlay.
|
|
- Horizontal/vertical flip.
|
|
- Opacity.
|
|
- Border radius.
|
|
|
|
Important functions:
|
|
|
|
- `cropActionsPosition()`: Keeps crop actions inside the canvas and below
|
|
the main image toolbar.
|
|
- `normalizeCropDraft()`: Clamps focus to `0..100` and crop scale to bounds.
|
|
- `commitCrop()`: Writes `fit: "cover"`, `focus_x`, `focus_y`, and
|
|
`crop_scale`.
|
|
- `CropOverlay()`: Shows the fixed crop window, outside shade, draggable image,
|
|
and resize handles.
|
|
- `CropActions()`: Shows reset/apply/close.
|
|
|
|
Image replacement:
|
|
|
|
- `openImageUpload()` triggers a hidden file input.
|
|
- `handleImageUploadChange()` uploads using `ImagesApi.uploadImage()`.
|
|
- The returned asset path is normalized with `resolveBackendAssetSource()`.
|
|
- Selected image element gets new `data`.
|
|
|
|
### Icons
|
|
|
|
Primary files:
|
|
|
|
- `images/IconsEditor.tsx`
|
|
- `surface/nodes.tsx`
|
|
- `model/model.ts`
|
|
- `lib/svg-color.ts`
|
|
|
|
Icons are image-like elements, usually SVG assets with `is_icon` or static SVG
|
|
source paths.
|
|
|
|
Rendering:
|
|
|
|
- `RawImageElement()` detects static SVG icon sources.
|
|
- If the icon has a `color`, it builds an updated SVG URL with
|
|
`buildSvgUpdateUrl()`.
|
|
|
|
Editing:
|
|
|
|
- Double-click an icon-like image opens `IconsEditor`.
|
|
- `IconsEditor` searches icons with `PresentationGenerationApi.searchIcons()`.
|
|
- It can change icon weight by rewriting the icon URL path.
|
|
- It can optionally apply icon styles across the presentation.
|
|
- When an icon is chosen, `handleIconChange()` writes the new `data` URL.
|
|
|
|
Important helpers:
|
|
|
|
- `isRawIconElement()`
|
|
- `isStaticSvgIconSource()`
|
|
- `rawIconQuery()`
|
|
|
|
### Shapes: Rectangle and Ellipse
|
|
|
|
Primary files:
|
|
|
|
- `shapes/ShapeToolbar.tsx`
|
|
- `surface/nodes.tsx`
|
|
- `model/render-style.ts`
|
|
|
|
Rendering:
|
|
|
|
- `RawElementVisual()` handles `rectangle` and `ellipse`.
|
|
- Rectangles support fill, stroke, border radius, shadow, rotation, and opacity.
|
|
- Ellipses support fill, stroke, shadow, rotation, and opacity.
|
|
|
|
Toolbar:
|
|
|
|
- `ShapeToolbar` edits:
|
|
- Fill color and opacity.
|
|
- Border color, width, and opacity.
|
|
- Shape type rectangle/ellipse.
|
|
- Position and size.
|
|
- Rectangle border radius.
|
|
- Drop shadow toggle and settings.
|
|
- Shape opacity.
|
|
|
|
Shadow data:
|
|
|
|
```ts
|
|
{
|
|
color: "#000000",
|
|
blur: number,
|
|
opacity: number,
|
|
offset_x: number,
|
|
offset_y: number
|
|
}
|
|
```
|
|
|
|
Rendering uses `shadowProps()` in `model/render-style.ts`.
|
|
|
|
### Lines
|
|
|
|
Primary files:
|
|
|
|
- `shapes/LineToolbar.tsx`
|
|
- `layout/LineToolbarControls.tsx`
|
|
- `surface/nodes.tsx`
|
|
- `lib/template-v2-json-to-html.ts`
|
|
|
|
Rendering:
|
|
|
|
- Konva line rendering is in the `type === "line"` branch of
|
|
`RawElementVisual()`.
|
|
- Points are generated by `linePoints(width, height, strokeWidth)`.
|
|
- Stroke color, width, opacity, shadow, and `stroke.dash` are applied.
|
|
- Export HTML uses `renderLine()` and maps `stroke.dash` to
|
|
`stroke-dasharray`.
|
|
|
|
Toolbar:
|
|
|
|
- `LineToolbar` is the main selected-line toolbar.
|
|
- It supports:
|
|
- Style dropdown: solid, dashed, dotted.
|
|
- Border panel: color, width, opacity.
|
|
- Color quick control.
|
|
- Transform panel: x, y, width, height, rotation.
|
|
- Shadow panel.
|
|
- Line opacity.
|
|
|
|
`layout/LineToolbarControls.tsx` is a compact version used when a line is
|
|
inside a layout/component toolbar context. The selected line itself should use
|
|
`shapes/LineToolbar.tsx` through `ElementToolbar`.
|
|
|
|
### Containers
|
|
|
|
Primary files:
|
|
|
|
- `layout/ContainerToolbarControls.tsx`
|
|
- `layout/LayoutToolbar.tsx`
|
|
- `surface/nodes.tsx`
|
|
- `model/model.ts`
|
|
|
|
Containers are visual boxes with optional child content. They can have fill,
|
|
stroke, radius, padding, shadow, and alignment. Children are positioned by
|
|
`layoutContainerChildren()` in `model/model.ts`.
|
|
|
|
Toolbar:
|
|
|
|
- Fill.
|
|
- Stroke.
|
|
- Border radius.
|
|
- Padding.
|
|
- Shadow.
|
|
- Child alignment matrix.
|
|
|
|
Rendering:
|
|
|
|
- Container background/border is rendered first.
|
|
- Child content is rendered inside the container.
|
|
- `shouldClipElementChildren()` decides whether children are clipped by parent
|
|
bounds.
|
|
|
|
### Flex and Grid Layout
|
|
|
|
Primary files:
|
|
|
|
- `layout/flowLayout.ts`
|
|
- `layout/wrappedFlexLayout.ts`
|
|
- `layout/layoutResize.ts`
|
|
- `layout/layoutItems.ts`
|
|
- `layout/LayoutToolbar.tsx`
|
|
- `layout/layoutToolbarTarget.ts`
|
|
- `surface/nodes.tsx`
|
|
|
|
Rendering:
|
|
|
|
- `layoutChildren()` in `model/model.ts` delegates to `layoutFlowChildren()`
|
|
for `flex`, `grid`, `list-view`, and `grid-view`.
|
|
- `layoutFlowChildren()` chooses flex or grid through `flowLayoutKind()`.
|
|
|
|
Flex:
|
|
|
|
- `layoutFlexChildren()` reads direction, gap, padding, align, justify, grow,
|
|
shrink, basis, and wrap.
|
|
- `flexBasis()` calculates each child's main-axis size.
|
|
- `childCrossSize()` calculates the cross-axis size.
|
|
- Alignment offsets place children inside the parent content box.
|
|
|
|
Grid:
|
|
|
|
- `layoutGridChildren()` reads columns, rows, gaps, padding, align, justify.
|
|
- `placeGridChildren()` assigns each child to a grid cell and handles spans.
|
|
- Child boxes are calculated from grid cell dimensions.
|
|
|
|
Editing:
|
|
|
|
- `LayoutToolbar` displays gap and items controls for flex/grid.
|
|
- `layoutItems.ts` owns add/remove item changes:
|
|
- `layoutItemStats()`
|
|
- `addLayoutItemChanges()`
|
|
- `removeLastLayoutItemChanges()`
|
|
- It supports both `children` and `elements` arrays.
|
|
- `layoutResize.ts` adjusts parent/component sizes when item counts change:
|
|
- `updateComponentLayoutElement()`
|
|
- `layoutElementWithAdjustedItemSpace()`
|
|
- `adjustedFlexSize()`
|
|
- `adjustedGridSize()`
|
|
|
|
When debugging layout, inspect child arrays first. Some generated layouts use
|
|
`children`, others use `elements`.
|
|
|
|
### Tables
|
|
|
|
Primary files:
|
|
|
|
- `tables/TemplateV2TableElement.tsx`
|
|
- `tables/TableToolbar.tsx`
|
|
- `tables/TableInlineEditor.tsx`
|
|
- `tables/useTableCellSelection.ts`
|
|
- `model/element-model.ts`
|
|
|
|
Rendering:
|
|
|
|
- `TemplateV2TableElement` renders table cells in Konva.
|
|
- Cell text is rendered using rich text layout helpers.
|
|
- `SelectedTableCellOutline` draws active cell outline.
|
|
|
|
Toolbar:
|
|
|
|
- `TableToolbar` edits table-level and selected-cell properties.
|
|
- `TableInlineEditor` opens a text editor over the selected cell.
|
|
- `useTableCellSelection()` tracks selected and editing cells.
|
|
|
|
Cell edit flow:
|
|
|
|
1. User selects or double-clicks a table cell.
|
|
2. Surface calls `selectTableCell()` or `editTableCell()`.
|
|
3. `TableInlineEditor` creates a text-element-like object for the cell.
|
|
4. `TextToolbar` edits the cell font/style.
|
|
5. `TiptapInlineTextEditor` edits the cell runs.
|
|
6. `updateCell()` writes the changed cell back to `columns` or `rows`.
|
|
|
|
CSV-like helpers:
|
|
|
|
- `tableDraftFromElement()`
|
|
- `tableRowsFromDraft()`
|
|
|
|
These are useful for converting table data to/from text-like drafts.
|
|
|
|
### Charts
|
|
|
|
Primary files:
|
|
|
|
- `charts/TemplateV2ChartJsElement.tsx`
|
|
- `charts/ChartToolbar.tsx`
|
|
- `charts/ChartEditorContent.tsx`
|
|
- `charts/chart-data.ts`
|
|
- `charts/ChartColorPalette.tsx`
|
|
- `events/events.ts`
|
|
|
|
Rendering:
|
|
|
|
- `TemplateV2ChartJsElement` renders Chart.js output to an offscreen canvas and
|
|
places that canvas in the Konva scene.
|
|
- It supports chart variants such as bar, horizontal/stacked bar, line, area,
|
|
pie/donut, scatter, bubble, radar, and polar area.
|
|
- It normalizes raw chart categories and series before rendering.
|
|
|
|
Data helpers:
|
|
|
|
- `chart-data.ts` handles chart type and series/data normalization.
|
|
- `rawChartToEditorChart()` in `model/chart-model.ts` converts raw chart elements to
|
|
typed `ChartElement` for editing.
|
|
- `editorChartToRawChart()` converts edited chart data back to raw Template V2.
|
|
|
|
Editing:
|
|
|
|
- `ChartToolbar` provides chart type, edit-data, and color controls.
|
|
- The edit-data button and chart double-click open `ChartDataEditorPopover`
|
|
directly from `TemplateV2KonvaSlide`.
|
|
- The popover edits chart type, data, colors, title, axes, and X/Y grids, then
|
|
writes the saved chart back through the selected surface element.
|
|
|
|
### Design Variables
|
|
|
|
Primary files:
|
|
|
|
- `toolbar/DesignVariablesToolbar.tsx`
|
|
- `model/design-variables.ts`
|
|
|
|
Some elements define `design_variables`. If present, `ElementToolbar` shows
|
|
`DesignVariablesToolbar` instead of the normal type toolbar.
|
|
|
|
Flow:
|
|
|
|
1. Element has `design_variables`.
|
|
2. `ElementToolbar` detects the array.
|
|
3. `DesignVariablesToolbar` renders select controls.
|
|
4. User picks an option.
|
|
5. `applyDesignVariableOption()` applies the option to paths defined by the
|
|
variable's effects.
|
|
|
|
This lets template authors expose controlled design choices without requiring
|
|
the user to edit every raw property.
|
|
|
|
### Font Loading
|
|
|
|
Primary files:
|
|
|
|
- `surface/fontLoading.ts`
|
|
- `text/google-fonts.ts`
|
|
- `text/TextToolbar.tsx`
|
|
- `importing/template-v2-import.ts`
|
|
|
|
Fonts can come from template assets or Google font options. The editor surface
|
|
uses `useFontLoadState()` to wait for fonts before enabling the main layer.
|
|
This reduces layout flicker and text measurement mismatches.
|
|
|
|
`TextToolbar` can load/select fonts from:
|
|
|
|
- Fonts already included in the template.
|
|
- Google font options defined in `google-fonts.ts`.
|
|
|
|
### Image, SVG, and Asset Loading
|
|
|
|
Primary files:
|
|
|
|
- `surface/exportAssets.ts`
|
|
- `surface/nodes.tsx`
|
|
- `utils/api.ts`
|
|
- `lib/svg-color.ts`
|
|
|
|
`loadKonvaImage()` loads image assets into `HTMLImageElement` instances for
|
|
Konva. `resolveBackendAssetSource()` and related helpers normalize backend
|
|
asset URLs for browser/runtime differences.
|
|
|
|
SVG icon recoloring uses `buildSvgUpdateUrl()` from `lib/svg-color.ts`.
|
|
|
|
## File and Function Mapping by Feature
|
|
|
|
### Rendering
|
|
|
|
| Feature | File | Functions/components |
|
|
| --- | --- | --- |
|
|
| Main editor surface | `surface/TemplateV2KonvaSlide.tsx` | `TemplateV2KonvaSlideComponent` |
|
|
| Konva component renderer | `surface/nodes.tsx` | `RawComponentNode` |
|
|
| Konva element renderer | `surface/nodes.tsx` | `RawElementNode`, `RawElementVisual` |
|
|
| Text rendering | `surface/nodes.tsx`, `text/template-v2-text.ts` | `RawRichTextElement`, `layoutRichText`, `rawRenderTextRuns` |
|
|
| Image rendering | `surface/nodes.tsx` | `RawImageElement`, `useLoadedKonvaImage`, `imageCornerRadii` |
|
|
| Table rendering | `tables/TemplateV2TableElement.tsx` | `TemplateV2TableElement`, `TableCellText` |
|
|
| Chart rendering | `charts/TemplateV2ChartJsElement.tsx` | `TemplateV2ChartJsElement`, Chart.js canvas renderer |
|
|
| Flex/grid layout | `layout/flowLayout.ts` | `layoutFlowChildren`, `layoutFlexChildren`, `layoutGridChildren` |
|
|
| HTML preview | `TemplateV2LayoutPreview.tsx` | `renderImage`, `renderText`, `renderFlex`, `renderGrid`, `renderChart` |
|
|
| HTML export | `lib/template-v2-json-to-html.ts` | `renderItem`, `renderImage`, `renderLine`, `renderChart`, `boxStyle` |
|
|
|
|
### Editing
|
|
|
|
| Feature | File | Functions/components |
|
|
| --- | --- | --- |
|
|
| Commit state/history | `surface/TemplateV2KonvaSlide.tsx` | `commitUi`, `undo`, `redo` |
|
|
| Selection | `surface/TemplateV2KonvaSlide.tsx`, `model/model.ts` | `select`, `keyForSelection`, `selectionWithComponentToggle` |
|
|
| Resize handles | `selection/SelectionTransformers.tsx` | `TemplateV2SelectionTransformers` |
|
|
| Floating selection toolbar | `selection/SelectionToolbar.tsx` | `TemplateV2SelectionToolbar` |
|
|
| Toolbar routing | `toolbar/ElementToolbar.tsx` | `ElementToolbar`, `TOOLBAR_RENDERERS` |
|
|
| Text toolbar | `text/TextToolbar.tsx` | `TextToolbar` |
|
|
| Bullet toolbar | `text/BulletsToolbar.tsx` | `BulletsToolbar` |
|
|
| Inline text editor | `text/TemplateV2InlineEditor.tsx`, `text/TiptapInlineTextEditor.tsx` | `TemplateV2InlineEditor`, `TiptapInlineTextEditor` |
|
|
| Image toolbar | `images/ImageToolbar.tsx` | `ImageToolbar`, `CropOverlay`, `CropControls` |
|
|
| Icon editor | `images/IconsEditor.tsx` | `IconsEditor` |
|
|
| Shape toolbar | `shapes/ShapeToolbar.tsx` | `ShapeToolbar` |
|
|
| Line toolbar | `shapes/LineToolbar.tsx` | `LineToolbar` |
|
|
| Container toolbar | `layout/ContainerToolbarControls.tsx` | `TemplateV2ContainerToolbarControls` |
|
|
| Flex/grid toolbar | `layout/LayoutToolbar.tsx` | `FlowControls`, `GapControl`, `ItemsControl` |
|
|
| Table toolbar | `tables/TableToolbar.tsx` | `TableToolbar` |
|
|
| Table cell editor | `tables/TableInlineEditor.tsx` | `TableInlineEditor` |
|
|
| Chart toolbar/editor | `charts/ChartToolbar.tsx`, `charts/ChartEditorContent.tsx` | `ChartToolbar`, `ChartEditorContent` |
|
|
| Clipboard | `clipboard/useClipboard.ts`, `clipboard/clipboard.ts` | `useTemplateV2Clipboard`, `createTemplateV2ClipboardPayload`, `pasteTemplateV2ClipboardPayload` |
|
|
| Insert/chart events | `events/events.ts`, `surface/TemplateV2KonvaSlide.tsx` | event constants, insert/chart event handlers |
|
|
|
|
## How to Add a New Element Type
|
|
|
|
Use this checklist when adding a new renderable element type.
|
|
|
|
1. Types
|
|
- Add the TypeScript type in `types.ts`.
|
|
- Add it to the `SlideElement` union.
|
|
|
|
2. Import/adaptation
|
|
- Update `importing/template-v2-import.ts` so generated/raw payloads adapt
|
|
into the new element type.
|
|
- Normalize legacy aliases if the backend may emit multiple names.
|
|
|
|
3. Raw editor model
|
|
- Add conversion logic in `rawElementForEditorToolbar()` if the toolbar needs
|
|
typed data.
|
|
- Add merge logic in `mergeEditorToolbarElement()` if the toolbar writes
|
|
back to raw Template V2.
|
|
- Add geometry helpers if the element has unusual sizing.
|
|
|
|
4. Konva rendering
|
|
- Add a branch in `RawElementVisual()` in `surface/nodes.tsx`.
|
|
- Reuse `fillColor`, `strokeColor`, `shadowProps`, `borderRadius`, and text
|
|
helpers where possible.
|
|
|
|
5. Editor toolbar
|
|
- Add or update a toolbar component.
|
|
- Register it in `toolbar/ElementToolbar.tsx`.
|
|
- Make sure toolbar panels have `data-template-v2-floating-toolbar` or
|
|
`data-inline-edit-ignore` if they are portal/floating UI.
|
|
|
|
6. HTML preview/export
|
|
- Add DOM rendering to `TemplateV2LayoutPreview.tsx`.
|
|
- Add HTML string rendering to `lib/template-v2-json-to-html.ts`.
|
|
|
|
7. Clipboard/selection
|
|
- Usually no changes are needed if the element is normal JSON.
|
|
- If the element has external assets or ids, make sure duplicate/paste does
|
|
not create collisions.
|
|
|
|
8. Verification
|
|
- Run:
|
|
- `./node_modules/.bin/tsc --noEmit --incremental false`
|
|
- targeted `eslint` for changed files.
|
|
- Manually test editor render, toolbar edit, undo/redo, copy/paste, preview,
|
|
and export if affected.
|
|
|
|
## Common Debugging Guide
|
|
|
|
### Toolbar click closes selection or action does nothing
|
|
|
|
Likely cause:
|
|
|
|
- Floating UI is rendered outside the slide surface and is treated as an
|
|
outside click.
|
|
|
|
Fix:
|
|
|
|
- Add `data-template-v2-floating-toolbar="true"` or
|
|
`data-inline-edit-ignore="true"` to the floating panel/root.
|
|
- Check the document pointerdown handler in `TemplateV2KonvaSlide.tsx`.
|
|
|
|
### Editor shows two toolbars
|
|
|
|
Likely cause:
|
|
|
|
- The selected element is eligible for both `TemplateV2SelectionToolbar` and
|
|
`ElementToolbar`.
|
|
|
|
Fix:
|
|
|
|
- Check `selection/toolbarTarget.ts`.
|
|
- Check `toolbar/ElementToolbar.tsx`.
|
|
- Make one toolbar system the owner for that element type.
|
|
|
|
### Drag snaps back
|
|
|
|
Likely cause:
|
|
|
|
- Konva node position and raw model position are out of sync.
|
|
|
|
Check:
|
|
|
|
- `handleComponentDragStart()`
|
|
- `handleComponentDragMove()`
|
|
- `handleComponentDragEnd()`
|
|
- `setComponentPositionsInUi()`
|
|
|
|
### Text typing is slow
|
|
|
|
Likely cause:
|
|
|
|
- Every keystroke is committing to Redux or rerendering the whole slide.
|
|
|
|
Check:
|
|
|
|
- `TiptapInlineTextEditor` should schedule run emission.
|
|
- `commitInlineTextRuns()` should update inline state, not commit full UI.
|
|
- Full commit should happen in `closeInlineEditor()`.
|
|
|
|
### Bullets appear on normal text
|
|
|
|
Likely cause:
|
|
|
|
- Markdown/list normalization is applying list markers to `text` instead of
|
|
`text-list`.
|
|
|
|
Check:
|
|
|
|
- `normalizeRawTextMarkdownElement()`
|
|
- `normalizeMarkdownTextInUi()`
|
|
- `text/markdown-text.ts`
|
|
|
|
### Flex/grid controls do not update visible children
|
|
|
|
Likely cause:
|
|
|
|
- The layout uses `elements`, but code only edits `children`, or vice versa.
|
|
|
|
Check:
|
|
|
|
- `layout/layoutItems.ts`
|
|
- `layout/layoutResize.ts`
|
|
- `childArrayInfo()` helpers.
|
|
|
|
### Line style changes in toolbar but not canvas
|
|
|
|
Likely cause:
|
|
|
|
- Renderer is not reading `stroke.dash`.
|
|
|
|
Check:
|
|
|
|
- `surface/nodes.tsx` line branch.
|
|
- `lib/template-v2-json-to-html.ts` `renderLine()`.
|
|
|
|
### Preview/export differs from editor
|
|
|
|
Likely cause:
|
|
|
|
- Feature was added to Konva renderer only.
|
|
|
|
Check:
|
|
|
|
- `surface/nodes.tsx`
|
|
- `TemplateV2LayoutPreview.tsx`
|
|
- `lib/template-v2-json-to-html.ts`
|
|
|
|
### Undo/redo affects wrong slide or does nothing
|
|
|
|
Likely cause:
|
|
|
|
- Surface active state is wrong, or shortcut is captured by an input.
|
|
|
|
Check:
|
|
|
|
- `activateSurface()`
|
|
- `isSurfaceActive()`
|
|
- `TEMPLATE_V2_SURFACE_SELECTED_EVENT`
|
|
- undo/redo keydown handler in `TemplateV2KonvaSlide.tsx`
|
|
|
|
## Testing and Verification Commands
|
|
|
|
From `servers/nextjs`:
|
|
|
|
```bash
|
|
PATH=/home/badu/pinokio/bin/miniconda/bin:$PATH ./node_modules/.bin/tsc --noEmit --incremental false
|
|
```
|
|
|
|
Targeted lint examples:
|
|
|
|
```bash
|
|
PATH=/home/badu/pinokio/bin/miniconda/bin:$PATH ./node_modules/.bin/eslint components/slide-editor/surface components/slide-editor/model --max-warnings=0
|
|
PATH=/home/badu/pinokio/bin/miniconda/bin:$PATH ./node_modules/.bin/eslint components/slide-editor/text components/slide-editor/tables --max-warnings=0
|
|
PATH=/home/badu/pinokio/bin/miniconda/bin:$PATH ./node_modules/.bin/eslint components/slide-editor/images components/slide-editor/shapes components/slide-editor/charts --max-warnings=0
|
|
```
|
|
|
|
Manual smoke test checklist:
|
|
|
|
1. Select a component.
|
|
2. Drag it once and confirm it does not snap back.
|
|
3. Resize it and undo/redo.
|
|
4. Copy/paste a component and a multi-selection.
|
|
5. Edit text and confirm typing stays responsive.
|
|
6. Edit bullets and confirm list markers only affect text-list elements.
|
|
7. Replace/crop/flip an image.
|
|
8. Edit rectangle shadow and opacity.
|
|
9. Edit line style, border width/color/opacity, and shadow.
|
|
10. Add/remove flex/grid items.
|
|
11. Edit table cell text.
|
|
12. Open chart editor and apply chart changes.
|
|
13. Check non-edit preview/export for visual parity.
|
|
|
|
## Current Design Decisions
|
|
|
|
- `TemplateV2KonvaSlide.tsx` owns editing state because many features need to
|
|
coordinate: selection, toolbars, inline editing, chart/image/icon editors,
|
|
undo/redo, and Redux commits.
|
|
- Toolbars are split by feature folders so each element type owns its editor UI.
|
|
- `model/model.ts` stays as the compatibility entry point for existing imports,
|
|
but pure helper groups are split into `core.ts`, `inserted-content.ts`,
|
|
`chart-model.ts`, and `render-style.ts`.
|
|
- Text editing uses DOM/Tiptap instead of Konva text input because rich text
|
|
editing needs selection, marks, keyboard behavior, and IME behavior that Konva
|
|
does not provide.
|
|
- Image crop is stored as focus point percentages rather than a pixel crop rect
|
|
because the renderer/export already support `object-position` semantics.
|
|
- Flex/grid layouts must support both `children` and `elements` because
|
|
generated templates may contain either shape.
|
|
- Export parity matters. When a feature is visible in the editor, make sure the
|
|
HTML preview/export path also understands it.
|