@umodeapporg/ui 0.30.1 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/assets/umode-ui.css +32 -0
  2. package/dist/components/UI/BulkActionBar.js +1 -1
  3. package/dist/components/UI/BulkActionBar.js.map +1 -1
  4. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.d.ts +6 -0
  5. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.js +65 -0
  6. package/dist/components/UI/Flow/FlowAnnotationDraftPreview.js.map +1 -0
  7. package/dist/components/UI/Flow/FlowAnnotationNode.d.ts +34 -0
  8. package/dist/components/UI/Flow/FlowAnnotationNode.js +231 -0
  9. package/dist/components/UI/Flow/FlowAnnotationNode.js.map +1 -0
  10. package/dist/components/UI/Flow/FlowAnnotationToolbar.d.ts +32 -0
  11. package/dist/components/UI/Flow/FlowAnnotationToolbar.js +298 -0
  12. package/dist/components/UI/Flow/FlowAnnotationToolbar.js.map +1 -0
  13. package/dist/components/UI/Flow/FlowCanvas.d.ts +2 -0
  14. package/dist/components/UI/Flow/FlowCanvas.js +278 -20
  15. package/dist/components/UI/Flow/FlowCanvas.js.map +1 -1
  16. package/dist/components/UI/Flow/FlowInspector.js +6 -5
  17. package/dist/components/UI/Flow/FlowInspector.js.map +1 -1
  18. package/dist/components/UI/Flow/FlowSelectionBar.d.ts +10 -0
  19. package/dist/components/UI/Flow/FlowSelectionBar.js +56 -0
  20. package/dist/components/UI/Flow/FlowSelectionBar.js.map +1 -0
  21. package/dist/components/UI/Flow/flowAnnotations.d.ts +38 -0
  22. package/dist/components/UI/Flow/flowAnnotations.js +123 -0
  23. package/dist/components/UI/Flow/flowAnnotations.js.map +1 -0
  24. package/dist/components/UI/Flow/flowEditorStore.d.ts +29 -5
  25. package/dist/components/UI/Flow/flowEditorStore.js +123 -23
  26. package/dist/components/UI/Flow/flowEditorStore.js.map +1 -1
  27. package/dist/components/UI/Flow/flowTypes.d.ts +30 -0
  28. package/dist/components/UI/Flow/index.d.ts +2 -1
  29. package/dist/components/UI/Flow/index.js +5 -1
  30. package/dist/components/UI/Flow/index.js.map +1 -1
  31. package/dist/components/UI/Flow/useFlowAnnotationDrawing.d.ts +25 -0
  32. package/dist/components/UI/Flow/useFlowAnnotationDrawing.js +117 -0
  33. package/dist/components/UI/Flow/useFlowAnnotationDrawing.js.map +1 -0
  34. package/dist/locales/en/ui.json.d.ts +41 -0
  35. package/dist/locales/en/ui.json.js +1 -1
  36. package/dist/locales/index.d.ts +82 -0
  37. package/dist/locales/pt/ui.json.d.ts +41 -0
  38. package/dist/locales/pt/ui.json.js +1 -1
  39. package/package.json +1 -1
  40. package/src/assets/fonts/LICENSE-Virgil.txt +45 -0
  41. package/src/assets/fonts/virgil.woff2 +0 -0
  42. package/src/assets/styles/base.css +10 -0
@@ -1 +1 @@
1
- {"version":3,"file":"flowEditorStore.js","sources":["../../../../src/components/UI/Flow/flowEditorStore.ts"],"sourcesContent":["import { createStore, type StoreApi } from 'zustand/vanilla';\n\nimport { generateUUID } from '@/helpers/utilityHelpers';\n\nimport { flowOutputsOf, type IFlowBlockRegistry } from './flowBlockRegistry';\nimport {\n canConnect,\n FLOW_LEVEL_GAP,\n FLOW_SIBLING_GAP,\n isFlowNodeRemovable,\n layoutFlowByLevels,\n reachableFrom,\n} from './flowGraph';\nimport type { IFlowEdge, IFlowGraph, IFlowNode, IFlowPosition, TFlowPortId } from './flowTypes';\n\nconst HISTORY_LIMIT = 50;\nconst PASTE_OFFSET = 32;\n/** Edits to the same field within this window share one undo step (typing a name is one change). */\nconst COALESCE_WINDOW_MS = 1000;\n\nexport interface IFlowEditorState {\n graph: IFlowGraph;\n selectedNodeIds: Array<string>;\n selectedEdgeIds: Array<string>;\n past: Array<IFlowGraph>;\n future: Array<IFlowGraph>;\n clipboard: Array<IFlowNode>;\n isDirty: boolean;\n\n replaceGraph: (graph: IFlowGraph, options?: { keepHistory?: boolean }) => void;\n markClean: () => void;\n /** Snapshot before a gesture that changes the graph in many small steps (a drag). */\n recordHistory: () => void;\n addNode: (type: string) => string | null;\n /** Adds a block right of `sourceNodeId` and wires `sourcePort` to it in one undo step. */\n addNodeFromPort: (type: string, sourceNodeId: string, sourcePort: TFlowPortId) => string | null;\n /**\n * Puts a block in the middle of an edge: the edge now ends at the new block, whose first output\n * leads to the old target. The old target and everything after it move one column right, so the\n * new block takes the place it left. One undo step.\n */\n insertNodeOnEdge: (type: string, edgeId: string) => string | null;\n updateNodeConfig: (nodeId: string, config: unknown) => void;\n updateNodeLabel: (nodeId: string, label: string) => void;\n moveNodes: (positions: Record<string, IFlowPosition>) => void;\n connect: (source: string, sourcePort: TFlowPortId, target: string) => boolean;\n removeSelected: () => void;\n duplicateSelected: () => void;\n copySelected: () => void;\n paste: () => void;\n setSelection: (selection: { nodeIds?: Array<string>; edgeIds?: Array<string> }) => void;\n undo: () => void;\n redo: () => void;\n /** The host may rebuild its registry (labels after a language switch); gestures always read the latest. */\n setRegistry: (registry: IFlowBlockRegistry) => void;\n}\n\nexport type TFlowEditorStore = StoreApi<IFlowEditorState>;\n\nconst isPosition = (position: unknown): position is IFlowPosition =>\n typeof (position as IFlowPosition | undefined)?.x === 'number' &&\n typeof (position as IFlowPosition | undefined)?.y === 'number';\n\n/** Coerces a stored graph; a block saved without a position is placed by the automatic layout. */\nconst normalizeGraph = (graph: IFlowGraph | null | undefined, registry: IFlowBlockRegistry): IFlowGraph => {\n const normalized: IFlowGraph = {\n nodes: Array.isArray(graph?.nodes) ? graph.nodes : [],\n edges: Array.isArray(graph?.edges) ? graph.edges : [],\n };\n if (normalized.nodes.every((node) => isPosition(node.position))) return normalized;\n\n const layout = layoutFlowByLevels(normalized, registry);\n return {\n ...normalized,\n nodes: normalized.nodes.map((node) =>\n isPosition(node.position) ? node : { ...node, position: layout[node.nodeId] ?? { x: 0, y: 0 } },\n ),\n };\n};\n\nconst withoutNodes = (graph: IFlowGraph, nodeIds: Set<string>): IFlowGraph => ({\n nodes: graph.nodes.filter((node) => !nodeIds.has(node.nodeId)),\n edges: graph.edges.filter((edge) => !nodeIds.has(edge.source) && !nodeIds.has(edge.target)),\n});\n\nconst isPositionTaken = (graph: IFlowGraph, position: IFlowPosition): boolean =>\n graph.nodes.some(\n (node) =>\n Math.abs(node.position.x - position.x) < FLOW_LEVEL_GAP &&\n Math.abs(node.position.y - position.y) < FLOW_SIBLING_GAP,\n );\n\n/** Right of the selected block (or the rightmost one), stepping down the column until the slot is free. */\nconst nextFreePosition = (graph: IFlowGraph, selectedNodeIds: Array<string>): IFlowPosition => {\n if (graph.nodes.length === 0) return { x: 0, y: 0 };\n const anchor =\n graph.nodes.find((node) => node.nodeId === selectedNodeIds[0]) ??\n graph.nodes.reduce((current, node) => (node.position.x > current.position.x ? node : current));\n let candidate = { x: anchor.position.x + FLOW_LEVEL_GAP, y: anchor.position.y };\n while (isPositionTaken(graph, candidate)) candidate = { x: candidate.x, y: candidate.y + FLOW_SIBLING_GAP };\n return candidate;\n};\n\n/**\n * One store per editor instance (two canvases on a page never share state). The graph\n * is the source of truth; the canvas only renders it and reports gestures back.\n */\nexport const createFlowEditorStore = (initialGraph: IFlowGraph, registry: IFlowBlockRegistry): TFlowEditorStore =>\n createStore<IFlowEditorState>()((set, get) => {\n let currentRegistry = registry;\n let lastCoalesce: { key: string; at: number } | null = null;\n\n const commit = (\n next: IFlowGraph,\n selection?: { nodeIds?: Array<string>; edgeIds?: Array<string> },\n coalesceKey?: string,\n ) => {\n const { graph, past } = get();\n const now = Date.now();\n const extendsLastStep =\n coalesceKey !== undefined && lastCoalesce?.key === coalesceKey && now - lastCoalesce.at < COALESCE_WINDOW_MS;\n lastCoalesce = coalesceKey === undefined ? null : { key: coalesceKey, at: now };\n set({\n graph: next,\n past: extendsLastStep ? past : [...past, graph].slice(-HISTORY_LIMIT),\n future: [],\n isDirty: true,\n ...(selection?.nodeIds ? { selectedNodeIds: selection.nodeIds } : {}),\n ...(selection?.edgeIds ? { selectedEdgeIds: selection.edgeIds } : {}),\n });\n };\n\n const cloneNodes = (nodes: Array<IFlowNode>, offset: number): Array<IFlowNode> => {\n const instancesByType = new Map<string, number>();\n get().graph.nodes.forEach((node) => instancesByType.set(node.type, (instancesByType.get(node.type) ?? 0) + 1));\n return nodes\n .filter((node) => {\n const definition = currentRegistry.get(node.type);\n if (!definition || definition.category === 'trigger') return false;\n const instances = instancesByType.get(node.type) ?? 0;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return false;\n instancesByType.set(node.type, instances + 1);\n return true;\n })\n .map((node) => ({\n ...node,\n nodeId: generateUUID(),\n config: structuredClone(node.config),\n position: { x: node.position.x + offset, y: node.position.y + offset },\n }));\n };\n\n return {\n graph: normalizeGraph(initialGraph, registry),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n past: [],\n future: [],\n clipboard: [],\n isDirty: false,\n\n replaceGraph: (graph, options) => {\n set((state) => ({\n graph: normalizeGraph(graph, currentRegistry),\n past: options?.keepHistory ? [...state.past, state.graph].slice(-HISTORY_LIMIT) : [],\n future: [],\n selectedNodeIds: [],\n selectedEdgeIds: [],\n isDirty: Boolean(options?.keepHistory),\n }));\n },\n\n markClean: () => set({ isDirty: false }),\n\n recordHistory: () => {\n const { graph, past } = get();\n set({ past: [...past, graph].slice(-HISTORY_LIMIT), future: [] });\n },\n\n addNode: (type) => {\n const definition = currentRegistry.get(type);\n if (!definition) return null;\n\n const { graph, selectedNodeIds } = get();\n const instances = graph.nodes.filter((node) => node.type === type).length;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return null;\n\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config: definition.createDefaultConfig(),\n position: nextFreePosition(graph, selectedNodeIds),\n };\n commit({ ...graph, nodes: [...graph.nodes, node] }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n addNodeFromPort: (type, sourceNodeId, sourcePort) => {\n const definition = currentRegistry.get(type);\n const { graph } = get();\n const source = graph.nodes.find((node) => node.nodeId === sourceNodeId);\n if (!definition || !source) return null;\n\n const instances = graph.nodes.filter((node) => node.type === type).length;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return null;\n if (graph.edges.some((edge) => edge.source === sourceNodeId && edge.sourcePort === sourcePort)) return null;\n\n const outputs = flowOutputsOf(currentRegistry.get(source.type), source.config);\n if (!outputs.includes(sourcePort)) return null;\n const portIndex = outputs.indexOf(sourcePort);\n let position = {\n x: source.position.x + FLOW_LEVEL_GAP,\n y: source.position.y + (portIndex - (outputs.length - 1) / 2) * FLOW_SIBLING_GAP,\n };\n while (isPositionTaken(graph, position)) position = { x: position.x, y: position.y + FLOW_SIBLING_GAP };\n\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config: definition.createDefaultConfig(),\n position,\n };\n const nodes = [...graph.nodes, node];\n const wires = canConnect({ ...graph, nodes }, currentRegistry, {\n source: sourceNodeId,\n sourcePort,\n target: node.nodeId,\n });\n const edges = wires\n ? [...graph.edges, { edgeId: generateUUID(), source: sourceNodeId, sourcePort, target: node.nodeId }]\n : graph.edges;\n commit({ nodes, edges }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n insertNodeOnEdge: (type, edgeId) => {\n const definition = currentRegistry.get(type);\n const { graph } = get();\n const edge = graph.edges.find((candidate) => candidate.edgeId === edgeId);\n const target = edge ? graph.nodes.find((node) => node.nodeId === edge.target) : undefined;\n const config = definition?.createDefaultConfig();\n const continuationPort = definition ? flowOutputsOf(definition, config)[0] : undefined;\n if (!definition || !edge || !target || !continuationPort || definition.category === 'trigger') return null;\n\n const instances = graph.nodes.filter((node) => node.type === type).length;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return null;\n\n const shifted = reachableFrom(graph, target.nodeId);\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config,\n position: { ...target.position },\n };\n const nodes = [\n ...graph.nodes.map((current) =>\n shifted.has(current.nodeId)\n ? { ...current, position: { x: current.position.x + FLOW_LEVEL_GAP, y: current.position.y } }\n : current,\n ),\n node,\n ];\n const withoutEdge = graph.edges.filter((current) => current.edgeId !== edgeId);\n const incoming: IFlowEdge = { ...edge, target: node.nodeId };\n const outgoing: IFlowEdge = {\n edgeId: generateUUID(),\n source: node.nodeId,\n sourcePort: continuationPort,\n target: target.nodeId,\n };\n\n const wiresIn = canConnect({ nodes, edges: withoutEdge }, currentRegistry, incoming);\n const wiresOut = canConnect({ nodes, edges: [...withoutEdge, incoming] }, currentRegistry, outgoing);\n if (!wiresIn || !wiresOut) return null;\n\n commit({ nodes, edges: [...withoutEdge, incoming, outgoing] }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n updateNodeConfig: (nodeId, config) => {\n const { graph } = get();\n const node = graph.nodes.find((candidate) => candidate.nodeId === nodeId);\n const outputs = node ? flowOutputsOf(currentRegistry.get(node.type), config) : [];\n commit(\n {\n nodes: graph.nodes.map((current) => (current.nodeId === nodeId ? { ...current, config } : current)),\n edges: graph.edges.filter((edge) => edge.source !== nodeId || outputs.includes(edge.sourcePort)),\n },\n undefined,\n `config:${nodeId}`,\n );\n },\n\n updateNodeLabel: (nodeId, label) => {\n const { graph } = get();\n const hasText = label.trim().length > 0;\n commit(\n {\n ...graph,\n nodes: graph.nodes.map((node) =>\n node.nodeId === nodeId ? { ...node, label: hasText ? label : undefined } : node,\n ),\n },\n undefined,\n `label:${nodeId}`,\n );\n },\n\n moveNodes: (positions) => {\n const { graph } = get();\n set({\n graph: {\n ...graph,\n nodes: graph.nodes.map((node) =>\n positions[node.nodeId] ? { ...node, position: positions[node.nodeId] } : node,\n ),\n },\n isDirty: true,\n });\n },\n\n connect: (source, sourcePort, target) => {\n const { graph } = get();\n if (!canConnect(graph, currentRegistry, { source, sourcePort, target })) return false;\n const edge: IFlowEdge = { edgeId: generateUUID(), source, sourcePort, target };\n commit({ ...graph, edges: [...graph.edges, edge] });\n return true;\n },\n\n removeSelected: () => {\n const { graph, selectedNodeIds, selectedEdgeIds } = get();\n const removableNodeIds = graph.nodes\n .filter((node) => selectedNodeIds.includes(node.nodeId) && isFlowNodeRemovable(node, currentRegistry))\n .map((node) => node.nodeId);\n if (removableNodeIds.length === 0 && selectedEdgeIds.length === 0) return;\n const removedEdges = new Set(selectedEdgeIds);\n const next = withoutNodes(\n { ...graph, edges: graph.edges.filter((edge) => !removedEdges.has(edge.edgeId)) },\n new Set(removableNodeIds),\n );\n commit(next, { nodeIds: [], edgeIds: [] });\n },\n\n duplicateSelected: () => {\n const { graph, selectedNodeIds } = get();\n const selected = graph.nodes.filter((node) => selectedNodeIds.includes(node.nodeId));\n const copies = cloneNodes(selected, PASTE_OFFSET);\n if (copies.length === 0) return;\n commit(\n { ...graph, nodes: [...graph.nodes, ...copies] },\n { nodeIds: copies.map((node) => node.nodeId), edgeIds: [] },\n );\n },\n\n copySelected: () => {\n const { graph, selectedNodeIds } = get();\n set({ clipboard: graph.nodes.filter((node) => selectedNodeIds.includes(node.nodeId)) });\n },\n\n paste: () => {\n const { graph, clipboard } = get();\n const copies = cloneNodes(clipboard, PASTE_OFFSET);\n if (copies.length === 0) return;\n commit(\n { ...graph, nodes: [...graph.nodes, ...copies] },\n { nodeIds: copies.map((node) => node.nodeId), edgeIds: [] },\n );\n set({ clipboard: copies });\n },\n\n setSelection: ({ nodeIds, edgeIds }) => {\n set((state) => ({\n selectedNodeIds: nodeIds ?? state.selectedNodeIds,\n selectedEdgeIds: edgeIds ?? state.selectedEdgeIds,\n }));\n },\n\n undo: () => {\n const { graph, past, future } = get();\n const previous = past[past.length - 1];\n if (!previous) return;\n set({\n graph: previous,\n past: past.slice(0, -1),\n future: [graph, ...future].slice(0, HISTORY_LIMIT),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n isDirty: true,\n });\n },\n\n redo: () => {\n const { graph, past, future } = get();\n const following = future[0];\n if (!following) return;\n set({\n graph: following,\n past: [...past, graph].slice(-HISTORY_LIMIT),\n future: future.slice(1),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n isDirty: true,\n });\n },\n\n setRegistry: (nextRegistry) => {\n currentRegistry = nextRegistry;\n },\n };\n });\n"],"names":["node"],"mappings":"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"}
1
+ {"version":3,"file":"flowEditorStore.js","sources":["../../../../src/components/UI/Flow/flowEditorStore.ts"],"sourcesContent":["import { createStore, type StoreApi } from 'zustand/vanilla';\n\nimport { generateUUID } from '@/helpers/utilityHelpers';\n\nimport { cloneFlowAnnotations, normalizeFlowAnnotations } from './flowAnnotations';\nimport { flowOutputsOf, type IFlowBlockRegistry } from './flowBlockRegistry';\nimport {\n canConnect,\n FLOW_LEVEL_GAP,\n FLOW_SIBLING_GAP,\n isFlowNodeRemovable,\n layoutFlowByLevels,\n reachableFrom,\n} from './flowGraph';\nimport type {\n IFlowAnnotation,\n IFlowEdge,\n IFlowGraph,\n IFlowNode,\n IFlowPosition,\n IFlowSize,\n TFlowPortId,\n} from './flowTypes';\n\nconst HISTORY_LIMIT = 50;\nconst PASTE_OFFSET = 32;\n/** Edits to the same field within this window share one undo step (typing a name is one change). */\nconst COALESCE_WINDOW_MS = 1000;\n\nexport interface IFlowSelection {\n nodeIds?: Array<string>;\n edgeIds?: Array<string>;\n annotationIds?: Array<string>;\n}\n\nexport interface IFlowAnnotationGeometry {\n position?: IFlowPosition;\n size?: IFlowSize;\n points?: Array<IFlowPosition>;\n}\n\nexport interface IFlowEditorState {\n graph: IFlowGraph;\n selectedNodeIds: Array<string>;\n selectedEdgeIds: Array<string>;\n selectedAnnotationIds: Array<string>;\n past: Array<IFlowGraph>;\n future: Array<IFlowGraph>;\n clipboard: Array<IFlowNode>;\n annotationClipboard: Array<IFlowAnnotation>;\n isDirty: boolean;\n /** A marquee is being drawn: the selection is still changing, so nothing should react to it yet. */\n isAreaSelecting: boolean;\n\n replaceGraph: (graph: IFlowGraph, options?: { keepHistory?: boolean }) => void;\n markClean: () => void;\n /** Snapshot before a gesture that changes the graph in many small steps (a drag). */\n recordHistory: () => void;\n addNode: (type: string) => string | null;\n /** Adds a block right of `sourceNodeId` and wires `sourcePort` to it in one undo step. */\n addNodeFromPort: (type: string, sourceNodeId: string, sourcePort: TFlowPortId) => string | null;\n /**\n * Puts a block in the middle of an edge: the edge now ends at the new block, whose first output\n * leads to the old target. The old target and everything after it move one column right, so the\n * new block takes the place it left. One undo step.\n */\n insertNodeOnEdge: (type: string, edgeId: string) => string | null;\n updateNodeConfig: (nodeId: string, config: unknown) => void;\n updateNodeLabel: (nodeId: string, label: string) => void;\n moveNodes: (positions: Record<string, IFlowPosition>) => void;\n /** Adds an annotation and selects it, in one undo step. */\n addAnnotation: (annotation: Omit<IFlowAnnotation, 'annotationId'>) => string;\n /** Text and colour changes. Typing in the same note within a second is one undo step. */\n updateAnnotations: (\n annotationIds: Array<string>,\n patch: Partial<Pick<IFlowAnnotation, 'text' | 'tone' | 'stroke'>>,\n ) => void;\n /** Live geometry while dragging or resizing: no undo step, like `moveNodes`. */\n setAnnotationGeometry: (geometry: Record<string, IFlowAnnotationGeometry>) => void;\n removeAnnotations: (annotationIds: Array<string>) => void;\n connect: (source: string, sourcePort: TFlowPortId, target: string) => boolean;\n removeSelected: () => void;\n duplicateSelected: () => void;\n copySelected: () => void;\n paste: () => void;\n setSelection: (selection: IFlowSelection) => void;\n setAreaSelecting: (active: boolean) => void;\n undo: () => void;\n redo: () => void;\n /** The host may rebuild its registry (labels after a language switch); gestures always read the latest. */\n setRegistry: (registry: IFlowBlockRegistry) => void;\n}\n\nexport type TFlowEditorStore = StoreApi<IFlowEditorState>;\n\nconst isPosition = (position: unknown): position is IFlowPosition =>\n typeof (position as IFlowPosition | undefined)?.x === 'number' &&\n typeof (position as IFlowPosition | undefined)?.y === 'number';\n\n/** Coerces a stored graph; a block saved without a position is placed by the automatic layout. */\nconst normalizeGraph = (graph: IFlowGraph | null | undefined, registry: IFlowBlockRegistry): IFlowGraph => {\n const normalized: IFlowGraph = {\n nodes: Array.isArray(graph?.nodes) ? graph.nodes : [],\n edges: Array.isArray(graph?.edges) ? graph.edges : [],\n annotations: normalizeFlowAnnotations(graph?.annotations),\n };\n if (normalized.nodes.every((node) => isPosition(node.position))) return normalized;\n\n const layout = layoutFlowByLevels(normalized, registry);\n return {\n ...normalized,\n nodes: normalized.nodes.map((node) =>\n isPosition(node.position) ? node : { ...node, position: layout[node.nodeId] ?? { x: 0, y: 0 } },\n ),\n };\n};\n\nconst withoutNodes = (graph: IFlowGraph, nodeIds: Set<string>): IFlowGraph => ({\n ...graph,\n nodes: graph.nodes.filter((node) => !nodeIds.has(node.nodeId)),\n edges: graph.edges.filter((edge) => !nodeIds.has(edge.source) && !nodeIds.has(edge.target)),\n});\n\nconst isPositionTaken = (graph: IFlowGraph, position: IFlowPosition): boolean =>\n graph.nodes.some(\n (node) =>\n Math.abs(node.position.x - position.x) < FLOW_LEVEL_GAP &&\n Math.abs(node.position.y - position.y) < FLOW_SIBLING_GAP,\n );\n\n/** Right of the selected block (or the rightmost one), stepping down the column until the slot is free. */\nconst nextFreePosition = (graph: IFlowGraph, selectedNodeIds: Array<string>): IFlowPosition => {\n if (graph.nodes.length === 0) return { x: 0, y: 0 };\n const anchor =\n graph.nodes.find((node) => node.nodeId === selectedNodeIds[0]) ??\n graph.nodes.reduce((current, node) => (node.position.x > current.position.x ? node : current));\n let candidate = { x: anchor.position.x + FLOW_LEVEL_GAP, y: anchor.position.y };\n while (isPositionTaken(graph, candidate)) candidate = { x: candidate.x, y: candidate.y + FLOW_SIBLING_GAP };\n return candidate;\n};\n\n/**\n * One store per editor instance (two canvases on a page never share state). The graph\n * is the source of truth; the canvas only renders it and reports gestures back.\n */\nexport const createFlowEditorStore = (initialGraph: IFlowGraph, registry: IFlowBlockRegistry): TFlowEditorStore =>\n createStore<IFlowEditorState>()((set, get) => {\n let currentRegistry = registry;\n let lastCoalesce: { key: string; at: number } | null = null;\n\n const commit = (next: IFlowGraph, selection?: IFlowSelection, coalesceKey?: string) => {\n const { graph, past } = get();\n // Block edits rebuild `{ nodes, edges }` and never mean to touch the annotations.\n const nextGraph = next.annotations === undefined ? { ...next, annotations: graph.annotations ?? [] } : next;\n const now = Date.now();\n const extendsLastStep =\n coalesceKey !== undefined && lastCoalesce?.key === coalesceKey && now - lastCoalesce.at < COALESCE_WINDOW_MS;\n lastCoalesce = coalesceKey === undefined ? null : { key: coalesceKey, at: now };\n set({\n graph: nextGraph,\n past: extendsLastStep ? past : [...past, graph].slice(-HISTORY_LIMIT),\n future: [],\n isDirty: true,\n ...(selection?.nodeIds ? { selectedNodeIds: selection.nodeIds } : {}),\n ...(selection?.edgeIds ? { selectedEdgeIds: selection.edgeIds } : {}),\n ...(selection?.annotationIds ? { selectedAnnotationIds: selection.annotationIds } : {}),\n });\n };\n\n const annotationsOf = (graph: IFlowGraph): Array<IFlowAnnotation> => graph.annotations ?? [];\n\n const cloneNodes = (nodes: Array<IFlowNode>, offset: number): Array<IFlowNode> => {\n const instancesByType = new Map<string, number>();\n get().graph.nodes.forEach((node) => instancesByType.set(node.type, (instancesByType.get(node.type) ?? 0) + 1));\n return nodes\n .filter((node) => {\n const definition = currentRegistry.get(node.type);\n if (!definition || definition.category === 'trigger') return false;\n const instances = instancesByType.get(node.type) ?? 0;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return false;\n instancesByType.set(node.type, instances + 1);\n return true;\n })\n .map((node) => ({\n ...node,\n nodeId: generateUUID(),\n config: structuredClone(node.config),\n position: { x: node.position.x + offset, y: node.position.y + offset },\n }));\n };\n\n return {\n graph: normalizeGraph(initialGraph, registry),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n selectedAnnotationIds: [],\n past: [],\n future: [],\n clipboard: [],\n annotationClipboard: [],\n isDirty: false,\n isAreaSelecting: false,\n\n replaceGraph: (graph, options) => {\n set((state) => ({\n graph: normalizeGraph(graph, currentRegistry),\n past: options?.keepHistory ? [...state.past, state.graph].slice(-HISTORY_LIMIT) : [],\n future: [],\n selectedNodeIds: [],\n selectedEdgeIds: [],\n selectedAnnotationIds: [],\n isDirty: Boolean(options?.keepHistory),\n }));\n },\n\n markClean: () => set({ isDirty: false }),\n\n recordHistory: () => {\n const { graph, past } = get();\n set({ past: [...past, graph].slice(-HISTORY_LIMIT), future: [] });\n },\n\n addNode: (type) => {\n const definition = currentRegistry.get(type);\n if (!definition) return null;\n\n const { graph, selectedNodeIds } = get();\n const instances = graph.nodes.filter((node) => node.type === type).length;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return null;\n\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config: definition.createDefaultConfig(),\n position: nextFreePosition(graph, selectedNodeIds),\n };\n commit({ ...graph, nodes: [...graph.nodes, node] }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n addNodeFromPort: (type, sourceNodeId, sourcePort) => {\n const definition = currentRegistry.get(type);\n const { graph } = get();\n const source = graph.nodes.find((node) => node.nodeId === sourceNodeId);\n if (!definition || !source) return null;\n\n const instances = graph.nodes.filter((node) => node.type === type).length;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return null;\n if (graph.edges.some((edge) => edge.source === sourceNodeId && edge.sourcePort === sourcePort)) return null;\n\n const outputs = flowOutputsOf(currentRegistry.get(source.type), source.config);\n if (!outputs.includes(sourcePort)) return null;\n const portIndex = outputs.indexOf(sourcePort);\n let position = {\n x: source.position.x + FLOW_LEVEL_GAP,\n y: source.position.y + (portIndex - (outputs.length - 1) / 2) * FLOW_SIBLING_GAP,\n };\n while (isPositionTaken(graph, position)) position = { x: position.x, y: position.y + FLOW_SIBLING_GAP };\n\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config: definition.createDefaultConfig(),\n position,\n };\n const nodes = [...graph.nodes, node];\n const wires = canConnect({ ...graph, nodes }, currentRegistry, {\n source: sourceNodeId,\n sourcePort,\n target: node.nodeId,\n });\n const edges = wires\n ? [...graph.edges, { edgeId: generateUUID(), source: sourceNodeId, sourcePort, target: node.nodeId }]\n : graph.edges;\n commit({ nodes, edges }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n insertNodeOnEdge: (type, edgeId) => {\n const definition = currentRegistry.get(type);\n const { graph } = get();\n const edge = graph.edges.find((candidate) => candidate.edgeId === edgeId);\n const target = edge ? graph.nodes.find((node) => node.nodeId === edge.target) : undefined;\n const config = definition?.createDefaultConfig();\n const continuationPort = definition ? flowOutputsOf(definition, config)[0] : undefined;\n if (!definition || !edge || !target || !continuationPort || definition.category === 'trigger') return null;\n\n const instances = graph.nodes.filter((node) => node.type === type).length;\n if (definition.maxInstances !== undefined && instances >= definition.maxInstances) return null;\n\n const shifted = reachableFrom(graph, target.nodeId);\n const node: IFlowNode = {\n nodeId: generateUUID(),\n type,\n typeVersion: definition.typeVersion,\n config,\n position: { ...target.position },\n };\n const nodes = [\n ...graph.nodes.map((current) =>\n shifted.has(current.nodeId)\n ? { ...current, position: { x: current.position.x + FLOW_LEVEL_GAP, y: current.position.y } }\n : current,\n ),\n node,\n ];\n const withoutEdge = graph.edges.filter((current) => current.edgeId !== edgeId);\n const incoming: IFlowEdge = { ...edge, target: node.nodeId };\n const outgoing: IFlowEdge = {\n edgeId: generateUUID(),\n source: node.nodeId,\n sourcePort: continuationPort,\n target: target.nodeId,\n };\n\n const wiresIn = canConnect({ nodes, edges: withoutEdge }, currentRegistry, incoming);\n const wiresOut = canConnect({ nodes, edges: [...withoutEdge, incoming] }, currentRegistry, outgoing);\n if (!wiresIn || !wiresOut) return null;\n\n commit({ nodes, edges: [...withoutEdge, incoming, outgoing] }, { nodeIds: [node.nodeId], edgeIds: [] });\n return node.nodeId;\n },\n\n updateNodeConfig: (nodeId, config) => {\n const { graph } = get();\n const node = graph.nodes.find((candidate) => candidate.nodeId === nodeId);\n const outputs = node ? flowOutputsOf(currentRegistry.get(node.type), config) : [];\n commit(\n {\n nodes: graph.nodes.map((current) => (current.nodeId === nodeId ? { ...current, config } : current)),\n edges: graph.edges.filter((edge) => edge.source !== nodeId || outputs.includes(edge.sourcePort)),\n },\n undefined,\n `config:${nodeId}`,\n );\n },\n\n updateNodeLabel: (nodeId, label) => {\n const { graph } = get();\n const hasText = label.trim().length > 0;\n commit(\n {\n ...graph,\n nodes: graph.nodes.map((node) =>\n node.nodeId === nodeId ? { ...node, label: hasText ? label : undefined } : node,\n ),\n },\n undefined,\n `label:${nodeId}`,\n );\n },\n\n moveNodes: (positions) => {\n const { graph } = get();\n set({\n graph: {\n ...graph,\n nodes: graph.nodes.map((node) =>\n positions[node.nodeId] ? { ...node, position: positions[node.nodeId] } : node,\n ),\n },\n isDirty: true,\n });\n },\n\n addAnnotation: (annotation) => {\n const { graph } = get();\n const created: IFlowAnnotation = { ...annotation, annotationId: generateUUID() };\n commit(\n { ...graph, annotations: [...annotationsOf(graph), created] },\n { nodeIds: [], edgeIds: [], annotationIds: [created.annotationId] },\n );\n return created.annotationId;\n },\n\n updateAnnotations: (annotationIds, patch) => {\n const { graph } = get();\n const targets = new Set(annotationIds);\n if (targets.size === 0) return;\n commit(\n {\n ...graph,\n annotations: annotationsOf(graph).map((annotation) =>\n targets.has(annotation.annotationId) ? { ...annotation, ...patch } : annotation,\n ),\n },\n undefined,\n patch.text !== undefined ? `annotation-text:${annotationIds.join(',')}` : undefined,\n );\n },\n\n setAnnotationGeometry: (geometry) => {\n const { graph } = get();\n set({\n graph: {\n ...graph,\n annotations: annotationsOf(graph).map((annotation) =>\n geometry[annotation.annotationId] ? { ...annotation, ...geometry[annotation.annotationId] } : annotation,\n ),\n },\n isDirty: true,\n });\n },\n\n removeAnnotations: (annotationIds) => {\n const { graph, selectedAnnotationIds } = get();\n const removed = new Set(annotationIds);\n if (!annotationsOf(graph).some((annotation) => removed.has(annotation.annotationId))) return;\n commit(\n { ...graph, annotations: annotationsOf(graph).filter((annotation) => !removed.has(annotation.annotationId)) },\n { annotationIds: selectedAnnotationIds.filter((id) => !removed.has(id)) },\n );\n },\n\n connect: (source, sourcePort, target) => {\n const { graph } = get();\n if (!canConnect(graph, currentRegistry, { source, sourcePort, target })) return false;\n const edge: IFlowEdge = { edgeId: generateUUID(), source, sourcePort, target };\n commit({ ...graph, edges: [...graph.edges, edge] });\n return true;\n },\n\n removeSelected: () => {\n const { graph, selectedNodeIds, selectedEdgeIds, selectedAnnotationIds } = get();\n const removableNodeIds = graph.nodes\n .filter((node) => selectedNodeIds.includes(node.nodeId) && isFlowNodeRemovable(node, currentRegistry))\n .map((node) => node.nodeId);\n const removedNodes = new Set(removableNodeIds);\n // A marquee also selects the edges of every block it catches, the trigger's included. An edge\n // touching a block that stays only goes when it was picked on its own.\n const selectedNodes = new Set(selectedNodeIds);\n const removedEdges = new Set(\n graph.edges\n .filter((edge) => selectedEdgeIds.includes(edge.edgeId))\n .filter(\n (edge) =>\n removedNodes.has(edge.source) ||\n removedNodes.has(edge.target) ||\n (!selectedNodes.has(edge.source) && !selectedNodes.has(edge.target)),\n )\n .map((edge) => edge.edgeId),\n );\n const removedAnnotations = new Set(selectedAnnotationIds);\n const annotations = annotationsOf(graph).filter(\n (annotation) => !removedAnnotations.has(annotation.annotationId),\n );\n const removesAnnotations = annotations.length !== annotationsOf(graph).length;\n if (removedNodes.size === 0 && removedEdges.size === 0 && !removesAnnotations) return;\n const next = withoutNodes(\n { ...graph, edges: graph.edges.filter((edge) => !removedEdges.has(edge.edgeId)), annotations },\n removedNodes,\n );\n commit(next, { nodeIds: [], edgeIds: [], annotationIds: [] });\n },\n\n duplicateSelected: () => {\n const { graph, selectedNodeIds, selectedAnnotationIds } = get();\n const selected = graph.nodes.filter((node) => selectedNodeIds.includes(node.nodeId));\n const copies = cloneNodes(selected, PASTE_OFFSET);\n const annotationCopies = cloneFlowAnnotations(\n annotationsOf(graph).filter((annotation) => selectedAnnotationIds.includes(annotation.annotationId)),\n PASTE_OFFSET,\n );\n if (copies.length === 0 && annotationCopies.length === 0) return;\n commit(\n {\n ...graph,\n nodes: [...graph.nodes, ...copies],\n annotations: [...annotationsOf(graph), ...annotationCopies],\n },\n {\n nodeIds: copies.map((node) => node.nodeId),\n edgeIds: [],\n annotationIds: annotationCopies.map((annotation) => annotation.annotationId),\n },\n );\n },\n\n copySelected: () => {\n const { graph, selectedNodeIds, selectedAnnotationIds } = get();\n set({\n clipboard: graph.nodes.filter((node) => selectedNodeIds.includes(node.nodeId)),\n annotationClipboard: annotationsOf(graph).filter((annotation) =>\n selectedAnnotationIds.includes(annotation.annotationId),\n ),\n });\n },\n\n paste: () => {\n const { graph, clipboard, annotationClipboard } = get();\n const copies = cloneNodes(clipboard, PASTE_OFFSET);\n const annotationCopies = cloneFlowAnnotations(annotationClipboard, PASTE_OFFSET);\n if (copies.length === 0 && annotationCopies.length === 0) return;\n commit(\n {\n ...graph,\n nodes: [...graph.nodes, ...copies],\n annotations: [...annotationsOf(graph), ...annotationCopies],\n },\n {\n nodeIds: copies.map((node) => node.nodeId),\n edgeIds: [],\n annotationIds: annotationCopies.map((annotation) => annotation.annotationId),\n },\n );\n set({ clipboard: copies, annotationClipboard: annotationCopies });\n },\n\n setSelection: ({ nodeIds, edgeIds, annotationIds }) => {\n set((state) => ({\n selectedNodeIds: nodeIds ?? state.selectedNodeIds,\n selectedEdgeIds: edgeIds ?? state.selectedEdgeIds,\n selectedAnnotationIds: annotationIds ?? state.selectedAnnotationIds,\n }));\n },\n\n setAreaSelecting: (active) => {\n if (get().isAreaSelecting !== active) set({ isAreaSelecting: active });\n },\n\n undo: () => {\n const { graph, past, future } = get();\n const previous = past[past.length - 1];\n if (!previous) return;\n set({\n graph: previous,\n past: past.slice(0, -1),\n future: [graph, ...future].slice(0, HISTORY_LIMIT),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n selectedAnnotationIds: [],\n isDirty: true,\n });\n },\n\n redo: () => {\n const { graph, past, future } = get();\n const following = future[0];\n if (!following) return;\n set({\n graph: following,\n past: [...past, graph].slice(-HISTORY_LIMIT),\n future: future.slice(1),\n selectedNodeIds: [],\n selectedEdgeIds: [],\n selectedAnnotationIds: [],\n isDirty: true,\n });\n },\n\n setRegistry: (nextRegistry) => {\n currentRegistry = nextRegistry;\n },\n };\n });\n\n/**\n * The block the side panel edits: exactly one block selected, nothing else, and no marquee still\n * being drawn. `null` otherwise. Hosts that dock the panel themselves gate it on this.\n */\nexport const selectFlowInspectedNodeId = (state: IFlowEditorState): string | null =>\n state.selectedNodeIds.length === 1 && state.selectedAnnotationIds.length === 0 && !state.isAreaSelecting\n ? (state.selectedNodeIds[0] ?? null)\n : null;\n"],"names":["node"],"mappings":"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"}
@@ -21,9 +21,39 @@ export interface IFlowEdge {
21
21
  sourcePort: TFlowPortId;
22
22
  target: string;
23
23
  }
24
+ export type TFlowAnnotationKind = 'text' | 'rectangle' | 'ellipse' | 'line' | 'arrow' | 'step' | 'drawing';
25
+ export type TFlowAnnotationStroke = 'thin' | 'medium' | 'thick';
26
+ export type TFlowAnnotationTone = 'neutral' | 'brand' | 'success' | 'warning' | 'danger' | 'info';
27
+ /** What a press on the empty canvas does: select (marquee) or draw one kind of annotation. */
28
+ export type TFlowCanvasTool = 'select' | TFlowAnnotationKind;
29
+ export type TFlowShapeTool = Extract<TFlowAnnotationKind, 'line' | 'arrow' | 'rectangle' | 'ellipse' | 'step'>;
30
+ export interface IFlowSize {
31
+ width: number;
32
+ height: number;
33
+ }
34
+ /**
35
+ * A note drawn on the canvas for the people reading the flow. It never runs: the engine reads
36
+ * `nodes` and `edges` only, and graph validation ignores it.
37
+ */
38
+ export interface IFlowAnnotation {
39
+ annotationId: string;
40
+ kind: TFlowAnnotationKind;
41
+ /** Top-left corner of its box, in canvas coordinates. */
42
+ position: IFlowPosition;
43
+ size: IFlowSize;
44
+ tone: TFlowAnnotationTone;
45
+ /** Line weight of shapes and strokes, and the size of a text. Absent reads as `medium`. */
46
+ stroke?: TFlowAnnotationStroke;
47
+ text?: string;
48
+ /** `step`: the number it shows. */
49
+ step?: number;
50
+ /** `line` and `arrow`: start and end; `drawing`: the stroke. Relative to `position`. */
51
+ points?: Array<IFlowPosition>;
52
+ }
24
53
  export interface IFlowGraph {
25
54
  nodes: Array<IFlowNode>;
26
55
  edges: Array<IFlowEdge>;
56
+ annotations?: Array<IFlowAnnotation>;
27
57
  }
28
58
  export type TFlowIssueSeverity = 'error' | 'warning';
29
59
  export type TFlowGraphIssueCode = 'missingTrigger' | 'multipleTriggers' | 'unknownBlock' | 'unreachableNode' | 'multipleInputs' | 'portFanOut' | 'cycle' | 'missingTrueBranch';
@@ -1,7 +1,8 @@
1
+ export { FLOW_ANNOTATION_TONES, normalizeFlowAnnotations } from './flowAnnotations';
1
2
  export { createFlowBlockRegistry, defineFlowBlock, flowOutputsOf, type IFlowBlockRegistry } from './flowBlockRegistry';
2
3
  export { default as FlowCanvas, type IFlowCanvasProps } from './FlowCanvas';
3
4
  export { default as FlowEditorProvider, useFlowBlockRegistry, useFlowEditor, useFlowEditorStore, } from './FlowEditorProvider';
4
- export { createFlowEditorStore, type IFlowEditorState, type TFlowEditorStore } from './flowEditorStore';
5
+ export { createFlowEditorStore, type IFlowAnnotationGeometry, type IFlowEditorState, type IFlowSelection, selectFlowInspectedNodeId, type TFlowEditorStore, } from './flowEditorStore';
5
6
  export { canConnect, findCycle, FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, hasBlockingIssues, isFlowNodeRemovable, layoutFlowByLevels, reachableFrom, triggerNodesOf, validateFlowGraph, } from './flowGraph';
6
7
  export { default as FlowInspector } from './FlowInspector';
7
8
  export { default as FlowIssueList } from './FlowIssueList';
@@ -1,7 +1,8 @@
1
+ import { FLOW_ANNOTATION_TONES, normalizeFlowAnnotations } from "./flowAnnotations.js";
1
2
  import { createFlowBlockRegistry, defineFlowBlock, flowOutputsOf } from "./flowBlockRegistry.js";
2
3
  import { default as default2 } from "./FlowCanvas.js";
3
4
  import { default as default3, useFlowBlockRegistry, useFlowEditor, useFlowEditorStore } from "./FlowEditorProvider.js";
4
- import { createFlowEditorStore } from "./flowEditorStore.js";
5
+ import { createFlowEditorStore, selectFlowInspectedNodeId } from "./flowEditorStore.js";
5
6
  import { FLOW_LEVEL_GAP, FLOW_SIBLING_GAP, canConnect, findCycle, hasBlockingIssues, isFlowNodeRemovable, layoutFlowByLevels, reachableFrom, triggerNodesOf, validateFlowGraph } from "./flowGraph.js";
6
7
  import { default as default4 } from "./FlowInspector.js";
7
8
  import { default as default5 } from "./FlowIssueList.js";
@@ -10,6 +11,7 @@ import { default as default7 } from "./FlowNodeShell.js";
10
11
  import { default as default8 } from "./FlowResizablePanel.js";
11
12
  import { FLOW_RUN_STATUS_TONE, useFlowLabels } from "./useFlowLabels.js";
12
13
  export {
14
+ FLOW_ANNOTATION_TONES,
13
15
  FLOW_LEVEL_GAP,
14
16
  FLOW_RUN_STATUS_TONE,
15
17
  FLOW_SIBLING_GAP,
@@ -29,7 +31,9 @@ export {
29
31
  hasBlockingIssues,
30
32
  isFlowNodeRemovable,
31
33
  layoutFlowByLevels,
34
+ normalizeFlowAnnotations,
32
35
  reachableFrom,
36
+ selectFlowInspectedNodeId,
33
37
  triggerNodesOf,
34
38
  useFlowBlockRegistry,
35
39
  useFlowEditor,
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;"}
@@ -0,0 +1,25 @@
1
+ import { RefObject } from 'react';
2
+ import { IFlowPosition, TFlowAnnotationKind, TFlowAnnotationStroke, TFlowAnnotationTone, TFlowCanvasTool } from './flowTypes';
3
+ export interface IFlowAnnotationDraft {
4
+ kind: TFlowAnnotationKind;
5
+ tone: TFlowAnnotationTone;
6
+ stroke: TFlowAnnotationStroke;
7
+ start: IFlowPosition;
8
+ current: IFlowPosition;
9
+ points: Array<IFlowPosition>;
10
+ }
11
+ interface IUseFlowAnnotationDrawingOptions {
12
+ containerRef: RefObject<HTMLDivElement>;
13
+ tool: TFlowCanvasTool;
14
+ tone: TFlowAnnotationTone;
15
+ stroke: TFlowAnnotationStroke;
16
+ enabled: boolean;
17
+ onCreated: (annotationId: string, kind: TFlowAnnotationKind) => void;
18
+ }
19
+ /**
20
+ * While a drawing tool is active, a press anywhere on the canvas draws, over blocks and annotations
21
+ * too, as in Figma. Wheel and pinch keep panning and zooming, because nothing is laid over the canvas:
22
+ * the press is heard on the pane itself. Returns the shape being drawn, for the preview.
23
+ */
24
+ export declare const useFlowAnnotationDrawing: ({ containerRef, tool, tone, stroke, enabled, onCreated, }: IUseFlowAnnotationDrawingOptions) => IFlowAnnotationDraft | null;
25
+ export {};
@@ -0,0 +1,117 @@
1
+ import { useState, useEffect } from "react";
2
+ import { useReactFlow } from "@xyflow/react";
3
+ import { FLOW_DRAWING_MAX_POINTS, nextFlowStepNumber, FLOW_ANNOTATION_MIN_SIZE, FLOW_TEXT_CARET_OFFSET, boxBetween, boxAroundPoints } from "./flowAnnotations.js";
4
+ import { useFlowEditorStore } from "./FlowEditorProvider.js";
5
+ const CLICK_TRAVEL_PX = 4;
6
+ const DEFAULT_SHAPE_SIZE = { width: 160, height: 96 };
7
+ const DEFAULT_ARROW_LENGTH = 160;
8
+ const STEP_SIZE = 32;
9
+ const STROKE_PADDING = 6;
10
+ const ARROW_PADDING = 12;
11
+ const DRAWING_STEP_PX = 2;
12
+ const FREE_POSITION = { snapToGrid: false };
13
+ const distance = (from, to) => Math.hypot(to.x - from.x, to.y - from.y);
14
+ const annotationFromDraft = (draft, isClick, nextStep) => {
15
+ const { kind, tone, stroke, start, current } = draft;
16
+ const style = { kind, tone, stroke };
17
+ if (kind === "text")
18
+ return {
19
+ ...style,
20
+ position: { x: start.x, y: start.y - FLOW_TEXT_CARET_OFFSET },
21
+ size: { width: FLOW_ANNOTATION_MIN_SIZE, height: FLOW_ANNOTATION_MIN_SIZE },
22
+ text: ""
23
+ };
24
+ if (kind === "step")
25
+ return {
26
+ ...style,
27
+ position: { x: start.x - STEP_SIZE / 2, y: start.y - STEP_SIZE / 2 },
28
+ size: { width: STEP_SIZE, height: STEP_SIZE },
29
+ step: nextStep
30
+ };
31
+ if (kind === "rectangle" || kind === "ellipse")
32
+ return isClick ? { ...style, position: start, size: DEFAULT_SHAPE_SIZE } : { ...style, ...boxBetween(start, current) };
33
+ if (kind === "arrow" || kind === "line") {
34
+ const end = isClick ? { x: start.x + DEFAULT_ARROW_LENGTH, y: start.y } : current;
35
+ return { ...style, ...boxAroundPoints([start, end], ARROW_PADDING) };
36
+ }
37
+ if (draft.points.length < 2) return null;
38
+ return { ...style, ...boxAroundPoints(draft.points, STROKE_PADDING) };
39
+ };
40
+ const useFlowAnnotationDrawing = ({
41
+ containerRef,
42
+ tool,
43
+ tone,
44
+ stroke,
45
+ enabled,
46
+ onCreated
47
+ }) => {
48
+ const store = useFlowEditorStore();
49
+ const { screenToFlowPosition } = useReactFlow();
50
+ const [draft, setDraft] = useState(null);
51
+ useEffect(() => {
52
+ if (!enabled || tool === "select") return void 0;
53
+ const pane = containerRef.current?.querySelector(".react-flow__pane");
54
+ if (!pane) return void 0;
55
+ let detachGesture = null;
56
+ const handlePointerDown = (event) => {
57
+ if (event.button !== 0 || !(event.target instanceof Node) || !pane.contains(event.target)) return;
58
+ event.preventDefault();
59
+ event.stopPropagation();
60
+ const kind = tool;
61
+ const startScreen = { x: event.clientX, y: event.clientY };
62
+ const start = screenToFlowPosition(startScreen, FREE_POSITION);
63
+ let current = { kind, tone, stroke, start, current: start, points: [start] };
64
+ let lastKeptScreen = startScreen;
65
+ setDraft(current);
66
+ const handlePointerMove = (moveEvent) => {
67
+ const screen = { x: moveEvent.clientX, y: moveEvent.clientY };
68
+ const point = screenToFlowPosition(screen, FREE_POSITION);
69
+ const keepsPoint = kind === "drawing" && distance(lastKeptScreen, screen) >= DRAWING_STEP_PX && current.points.length < FLOW_DRAWING_MAX_POINTS;
70
+ if (keepsPoint) lastKeptScreen = screen;
71
+ current = { ...current, current: point, points: keepsPoint ? [...current.points, point] : current.points };
72
+ setDraft(current);
73
+ };
74
+ const detach = () => {
75
+ window.removeEventListener("pointermove", handlePointerMove);
76
+ window.removeEventListener("pointerup", handlePointerUp);
77
+ window.removeEventListener("pointercancel", handlePointerCancel);
78
+ detachGesture = null;
79
+ };
80
+ const handlePointerUp = (upEvent) => {
81
+ detach();
82
+ setDraft(null);
83
+ const isClick = distance(startScreen, { x: upEvent.clientX, y: upEvent.clientY }) < CLICK_TRAVEL_PX;
84
+ const annotation = annotationFromDraft(
85
+ { ...current, current: screenToFlowPosition({ x: upEvent.clientX, y: upEvent.clientY }, FREE_POSITION) },
86
+ isClick,
87
+ nextFlowStepNumber(store.getState().graph.annotations ?? [])
88
+ );
89
+ if (!annotation) return;
90
+ const swallowClick = (clickEvent) => clickEvent.stopPropagation();
91
+ pane.addEventListener("click", swallowClick, { capture: true, once: true });
92
+ window.setTimeout(() => pane.removeEventListener("click", swallowClick, { capture: true }), 0);
93
+ const annotationId = store.getState().addAnnotation(annotation);
94
+ onCreated(annotationId, kind);
95
+ };
96
+ const handlePointerCancel = () => {
97
+ detach();
98
+ setDraft(null);
99
+ };
100
+ window.addEventListener("pointermove", handlePointerMove);
101
+ window.addEventListener("pointerup", handlePointerUp);
102
+ window.addEventListener("pointercancel", handlePointerCancel);
103
+ detachGesture = detach;
104
+ };
105
+ pane.addEventListener("pointerdown", handlePointerDown, true);
106
+ return () => {
107
+ pane.removeEventListener("pointerdown", handlePointerDown, true);
108
+ detachGesture?.();
109
+ setDraft(null);
110
+ };
111
+ }, [containerRef, enabled, tool, tone, stroke, screenToFlowPosition, store, onCreated]);
112
+ return draft;
113
+ };
114
+ export {
115
+ useFlowAnnotationDrawing
116
+ };
117
+ //# sourceMappingURL=useFlowAnnotationDrawing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useFlowAnnotationDrawing.js","sources":["../../../../src/components/UI/Flow/useFlowAnnotationDrawing.ts"],"sourcesContent":["import { type RefObject, useEffect, useState } from 'react';\n\nimport { useReactFlow } from '@xyflow/react';\n\nimport {\n boxAroundPoints,\n boxBetween,\n FLOW_ANNOTATION_MIN_SIZE,\n FLOW_DRAWING_MAX_POINTS,\n FLOW_TEXT_CARET_OFFSET,\n nextFlowStepNumber,\n} from './flowAnnotations';\nimport { useFlowEditorStore } from './FlowEditorProvider';\nimport type {\n IFlowAnnotation,\n IFlowPosition,\n TFlowAnnotationKind,\n TFlowAnnotationStroke,\n TFlowAnnotationTone,\n TFlowCanvasTool,\n} from './flowTypes';\n\n/** Below this travel (screen px) a press is a click: shapes and arrows get a default size. */\nconst CLICK_TRAVEL_PX = 4;\nconst DEFAULT_SHAPE_SIZE = { width: 160, height: 96 };\nconst DEFAULT_ARROW_LENGTH = 160;\nconst STEP_SIZE = 32;\nconst STROKE_PADDING = 6;\nconst ARROW_PADDING = 12;\n/** Between two kept points of a stroke, in screen px: denser adds weight, not detail. */\nconst DRAWING_STEP_PX = 2;\n/** xyflow snaps converted points to the block grid by default; a hand-drawn stroke must stay free. */\nconst FREE_POSITION = { snapToGrid: false };\n\nexport interface IFlowAnnotationDraft {\n kind: TFlowAnnotationKind;\n tone: TFlowAnnotationTone;\n stroke: TFlowAnnotationStroke;\n start: IFlowPosition;\n current: IFlowPosition;\n points: Array<IFlowPosition>;\n}\n\ninterface IUseFlowAnnotationDrawingOptions {\n containerRef: RefObject<HTMLDivElement>;\n tool: TFlowCanvasTool;\n tone: TFlowAnnotationTone;\n stroke: TFlowAnnotationStroke;\n enabled: boolean;\n onCreated: (annotationId: string, kind: TFlowAnnotationKind) => void;\n}\n\nconst distance = (from: { x: number; y: number }, to: { x: number; y: number }): number =>\n Math.hypot(to.x - from.x, to.y - from.y);\n\nconst annotationFromDraft = (\n draft: IFlowAnnotationDraft,\n isClick: boolean,\n nextStep: number,\n): Omit<IFlowAnnotation, 'annotationId'> | null => {\n const { kind, tone, stroke, start, current } = draft;\n const style = { kind, tone, stroke };\n if (kind === 'text')\n return {\n ...style,\n position: { x: start.x, y: start.y - FLOW_TEXT_CARET_OFFSET },\n size: { width: FLOW_ANNOTATION_MIN_SIZE, height: FLOW_ANNOTATION_MIN_SIZE },\n text: '',\n };\n if (kind === 'step')\n return {\n ...style,\n position: { x: start.x - STEP_SIZE / 2, y: start.y - STEP_SIZE / 2 },\n size: { width: STEP_SIZE, height: STEP_SIZE },\n step: nextStep,\n };\n if (kind === 'rectangle' || kind === 'ellipse')\n return isClick\n ? { ...style, position: start, size: DEFAULT_SHAPE_SIZE }\n : { ...style, ...boxBetween(start, current) };\n if (kind === 'arrow' || kind === 'line') {\n const end = isClick ? { x: start.x + DEFAULT_ARROW_LENGTH, y: start.y } : current;\n return { ...style, ...boxAroundPoints([start, end], ARROW_PADDING) };\n }\n if (draft.points.length < 2) return null;\n return { ...style, ...boxAroundPoints(draft.points, STROKE_PADDING) };\n};\n\n/**\n * While a drawing tool is active, a press anywhere on the canvas draws, over blocks and annotations\n * too, as in Figma. Wheel and pinch keep panning and zooming, because nothing is laid over the canvas:\n * the press is heard on the pane itself. Returns the shape being drawn, for the preview.\n */\nexport const useFlowAnnotationDrawing = ({\n containerRef,\n tool,\n tone,\n stroke,\n enabled,\n onCreated,\n}: IUseFlowAnnotationDrawingOptions): IFlowAnnotationDraft | null => {\n const store = useFlowEditorStore();\n const { screenToFlowPosition } = useReactFlow();\n const [draft, setDraft] = useState<IFlowAnnotationDraft | null>(null);\n\n useEffect(() => {\n if (!enabled || tool === 'select') return undefined;\n const pane = containerRef.current?.querySelector<HTMLElement>('.react-flow__pane');\n if (!pane) return undefined;\n let detachGesture: (() => void) | null = null;\n\n const handlePointerDown = (event: PointerEvent) => {\n if (event.button !== 0 || !(event.target instanceof Node) || !pane.contains(event.target)) return;\n // Cancelling the press also cancels the mousedown that would start dragging the block under it.\n event.preventDefault();\n event.stopPropagation();\n const kind = tool;\n const startScreen = { x: event.clientX, y: event.clientY };\n const start = screenToFlowPosition(startScreen, FREE_POSITION);\n let current: IFlowAnnotationDraft = { kind, tone, stroke, start, current: start, points: [start] };\n let lastKeptScreen = startScreen;\n setDraft(current);\n\n const handlePointerMove = (moveEvent: PointerEvent) => {\n const screen = { x: moveEvent.clientX, y: moveEvent.clientY };\n const point = screenToFlowPosition(screen, FREE_POSITION);\n const keepsPoint =\n kind === 'drawing' &&\n distance(lastKeptScreen, screen) >= DRAWING_STEP_PX &&\n current.points.length < FLOW_DRAWING_MAX_POINTS;\n if (keepsPoint) lastKeptScreen = screen;\n current = { ...current, current: point, points: keepsPoint ? [...current.points, point] : current.points };\n setDraft(current);\n };\n\n const detach = () => {\n window.removeEventListener('pointermove', handlePointerMove);\n window.removeEventListener('pointerup', handlePointerUp);\n window.removeEventListener('pointercancel', handlePointerCancel);\n detachGesture = null;\n };\n\n const handlePointerUp = (upEvent: PointerEvent) => {\n detach();\n setDraft(null);\n const isClick = distance(startScreen, { x: upEvent.clientX, y: upEvent.clientY }) < CLICK_TRAVEL_PX;\n const annotation = annotationFromDraft(\n { ...current, current: screenToFlowPosition({ x: upEvent.clientX, y: upEvent.clientY }, FREE_POSITION) },\n isClick,\n nextFlowStepNumber(store.getState().graph.annotations ?? []),\n );\n if (!annotation) return;\n // The click that follows this release would clear the new selection, or select what is under it.\n const swallowClick = (clickEvent: MouseEvent) => clickEvent.stopPropagation();\n pane.addEventListener('click', swallowClick, { capture: true, once: true });\n window.setTimeout(() => pane.removeEventListener('click', swallowClick, { capture: true }), 0);\n const annotationId = store.getState().addAnnotation(annotation);\n onCreated(annotationId, kind);\n };\n\n const handlePointerCancel = () => {\n detach();\n setDraft(null);\n };\n\n window.addEventListener('pointermove', handlePointerMove);\n window.addEventListener('pointerup', handlePointerUp);\n window.addEventListener('pointercancel', handlePointerCancel);\n detachGesture = detach;\n };\n\n pane.addEventListener('pointerdown', handlePointerDown, true);\n return () => {\n pane.removeEventListener('pointerdown', handlePointerDown, true);\n detachGesture?.();\n setDraft(null);\n };\n }, [containerRef, enabled, tool, tone, stroke, screenToFlowPosition, store, onCreated]);\n\n return draft;\n};\n"],"names":[],"mappings":"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"}
@@ -283,6 +283,47 @@ declare const _default: {
283
283
  },
284
284
  "laneMap": {
285
285
  "label": "Flow map"
286
+ },
287
+ "selection": {
288
+ "count_one": "{{count}} selected",
289
+ "count_other": "{{count}} selected",
290
+ "duplicate": "Duplicate",
291
+ "delete": "Delete",
292
+ "clear": "Clear selection"
293
+ },
294
+ "annotations": {
295
+ "toolbar": "Annotation tools",
296
+ "textPlaceholder": "Write a note",
297
+ "tools": {
298
+ "select": "Select",
299
+ "drawing": "Pencil",
300
+ "rectangle": "Rectangle",
301
+ "ellipse": "Ellipse",
302
+ "arrow": "Arrow",
303
+ "text": "Text",
304
+ "shapes": "Shapes",
305
+ "withShortcut": "{{label}} ({{shortcut}})",
306
+ "line": "Line",
307
+ "step": "Step"
308
+ },
309
+ "tones": {
310
+ "neutral": "Grey",
311
+ "brand": "Purple",
312
+ "info": "Blue",
313
+ "success": "Green",
314
+ "warning": "Yellow",
315
+ "danger": "Red"
316
+ },
317
+ "open": "Annotate",
318
+ "close": "Close annotations",
319
+ "color": "Colour",
320
+ "stroke": "Thickness",
321
+ "deleteSelection": "Delete selection",
322
+ "strokes": {
323
+ "thin": "Thin",
324
+ "medium": "Medium",
325
+ "thick": "Thick"
326
+ }
286
327
  }
287
328
  },
288
329
  "followUp": {
@@ -12,7 +12,7 @@ const detailsTable = { "size": "Size", "quantity": "Qty" };
12
12
  const errorPage = { "message": "This screen could not load", "headline": "Something unexpected happened while building the page. Refreshing usually fixes it. If it happens again, contact support and tell them what you were doing.", "backToStart": "Back to start" };
13
13
  const filters = { "activeFilters": "Applied filters", "appliedCount_one": "{{count}} filter applied", "appliedCount_other": "{{count}} filters applied", "clearAll": "Clear all", "noFields": "No filters available for this list.", "noOptions": "No options found.", "optionsError": "Could not load the options for {{field}}.", "rangeMax": "Maximum", "rangeMin": "Minimum", "rangeSeparator": "to", "searchOptions": "Search options" };
14
14
  const filtersDrawer = { "selectedFilters": "Selected filters:", "pendingChanges": "Pending changes", "discardChangesTitle": "Discard changes?", "discardChangesMessage": "You have unapplied filter changes. Discard them?", "keepEditing": "Keep editing", "discard": "Discard" };
15
- const flow = { "canvas": "Flow editor", "resizePanel": "Drag to adjust the width. Double click resets", "ports": { "next": "Next", "true": "Yes", "false": "No", "error": "Error" }, "categories": { "trigger": "Trigger", "condition": "Condition", "action": "Action" }, "inspector": { "title": "Block settings", "empty": "Select a block to configure it", "label": "Block name", "delete": "Delete block", "duplicate": "Duplicate block", "unknownBlock": "This block type no longer exists", "close": "Close" }, "issues": { "title": "Issues", "none": "No issues", "missingTrigger": "The flow needs a trigger", "multipleTriggers": "A flow can have only one trigger", "unknownBlock": "Block of a type that no longer exists", "unreachableNode": "Loose block: no path from the trigger reaches it", "multipleInputs": "Block with more than one input", "portFanOut": "An output can connect to one block only", "cycle": "The blocks form a cycle", "missingTrueBranch": "The condition has no yes path" }, "run": { "passed": "Passed", "failed": "Did not pass", "skipped": "Skipped", "wouldRun": "Would run", "error": "Error", "blocked": "Blocked" }, "controls": { "zoomIn": "Zoom in", "zoomOut": "Zoom out", "fitView": "Fit view", "undo": "Undo", "redo": "Redo", "layout": "Arrange blocks" }, "edge": { "insertBlock": "Insert a block here" }, "node": { "disabled": "Disabled", "addBlock": "Add the next block", "issueCount_one": "{{count}} issue", "issueCount_other": "{{count}} issues" }, "laneMap": { "label": "Flow map" } };
15
+ const flow = { "canvas": "Flow editor", "resizePanel": "Drag to adjust the width. Double click resets", "ports": { "next": "Next", "true": "Yes", "false": "No", "error": "Error" }, "categories": { "trigger": "Trigger", "condition": "Condition", "action": "Action" }, "inspector": { "title": "Block settings", "empty": "Select a block to configure it", "label": "Block name", "delete": "Delete block", "duplicate": "Duplicate block", "unknownBlock": "This block type no longer exists", "close": "Close" }, "issues": { "title": "Issues", "none": "No issues", "missingTrigger": "The flow needs a trigger", "multipleTriggers": "A flow can have only one trigger", "unknownBlock": "Block of a type that no longer exists", "unreachableNode": "Loose block: no path from the trigger reaches it", "multipleInputs": "Block with more than one input", "portFanOut": "An output can connect to one block only", "cycle": "The blocks form a cycle", "missingTrueBranch": "The condition has no yes path" }, "run": { "passed": "Passed", "failed": "Did not pass", "skipped": "Skipped", "wouldRun": "Would run", "error": "Error", "blocked": "Blocked" }, "controls": { "zoomIn": "Zoom in", "zoomOut": "Zoom out", "fitView": "Fit view", "undo": "Undo", "redo": "Redo", "layout": "Arrange blocks" }, "edge": { "insertBlock": "Insert a block here" }, "node": { "disabled": "Disabled", "addBlock": "Add the next block", "issueCount_one": "{{count}} issue", "issueCount_other": "{{count}} issues" }, "laneMap": { "label": "Flow map" }, "selection": { "count_one": "{{count}} selected", "count_other": "{{count}} selected", "duplicate": "Duplicate", "delete": "Delete", "clear": "Clear selection" }, "annotations": { "toolbar": "Annotation tools", "textPlaceholder": "Write a note", "tools": { "select": "Select", "drawing": "Pencil", "rectangle": "Rectangle", "ellipse": "Ellipse", "arrow": "Arrow", "text": "Text", "shapes": "Shapes", "withShortcut": "{{label}} ({{shortcut}})", "line": "Line", "step": "Step" }, "tones": { "neutral": "Grey", "brand": "Purple", "info": "Blue", "success": "Green", "warning": "Yellow", "danger": "Red" }, "open": "Annotate", "close": "Close annotations", "color": "Colour", "stroke": "Thickness", "deleteSelection": "Delete selection", "strokes": { "thin": "Thin", "medium": "Medium", "thick": "Thick" } } };
16
16
  const followUp = { "messages": "Messages", "chat": { "loadingMessages": "Loading messages", "placeholder": "Write a message...", "invalidMessage": "Please enter a valid message", "noMessagesYet": "No messages yet", "startConversation": "Start a conversation by adding the first message", "loadError": "Messages could not be loaded", "sendFailed": "The message could not be sent. Try again.", "sendFailedInline": "Failed to send", "resend": "Resend", "discard": "Discard", "today": "Today", "yesterday": "Yesterday", "typingOne": "{{name}} is typing...", "typingMany": "{{count}} people are typing...", "someone": "Someone", "you": "You", "newMessages": "New messages", "backToLatest": "Back to latest", "deletedMessage": "Message deleted", "edited": "edited", "readBy": "Read by {{names}}", "copied": "Message copied", "replyingTo": "Replying to {{name}}", "attachmentFallback": "Attachment", "editTitle": "Edit message", "deleteTitle": "Delete message?", "deleteSubtitle": "The message will be shown as deleted for every participant", "sendHint": "Enter sends, Shift+Enter adds a line break", "formatting": "Formatting" }, "actions": { "reply": "Reply", "copy": "Copy text", "edit": "Edit", "delete": "Delete" }, "attachments": { "attach": "Attach", "pickFile": "File", "pickImage": "Image", "takePhoto": "Take photo", "dropHere": "Drop files to attach", "tooMany": "Limit of {{max}} attachments per message", "tooLarge": "The file {{name}} exceeds the 50 MB limit", "uploadFailed": "Upload failed", "uploading": "Uploading attachments...", "imagePreview": "Preview image", "showTranscript": "Show transcript", "hideTranscript": "Hide transcript" }, "voice": { "record": "Record audio", "recording": "Recording", "stop": "Finish recording", "cancel": "Cancel recording", "rerecord": "Record again", "attach": "Add to message", "discard": "Discard", "transcriptLabel": "Transcript", "transcriptPlaceholder": "The speech transcript appears here and can be edited" }, "camera": { "title": "Take photo", "unavailable": "Camera unavailable", "unavailableHint": "Check the browser camera permission", "switch": "Switch camera", "capture": "Capture photo" }, "search": { "open": "Search in conversation", "placeholder": "Search messages", "noResults": "No messages found", "error": "Messages could not be searched" } };
17
17
  const generatedByAI = { "tooltip": "Generated by AI" };
18
18
  const imageAiAdjust = { "action": "Adjust with AI", "title": "Adjust image with AI", "subtitle": "Describe the adjustment. The AI changes only what you ask and preserves the rest of the image.", "original": "Original", "result": "Result", "resultHint": "Describe the adjustment and click Generate to see the result here.", "promptLabel": "Desired adjustment", "promptPlaceholder": "E.g. remove the background and make it white, smooth the sleeve creases, brighten the lighting", "generate": "Generate", "generating": "Generating the image...", "tryAgain": "Generate another", "useResult": "Use this image", "errorGenerate": "Could not adjust the image with AI", "errorSave": "Could not save the adjusted image", "errorRefused": "The AI could not make this adjustment: {{reason}}", "mask": { "start": "Mark the area", "stop": "Use the whole image", "hint": "Paint the area the AI may change. The rest of the image is kept." }, "verification": { "changedMoreThanRequested": "The AI changed more than requested. Check the areas outside the adjustment before using it.", "colorMismatch": "The garment color ({{dominant}}) differs from the requested color ({{target}})." } };
@@ -286,6 +286,47 @@ export declare const uiLocales: {
286
286
  laneMap: {
287
287
  label: string;
288
288
  };
289
+ selection: {
290
+ count_one: string;
291
+ count_other: string;
292
+ duplicate: string;
293
+ delete: string;
294
+ clear: string;
295
+ };
296
+ annotations: {
297
+ toolbar: string;
298
+ textPlaceholder: string;
299
+ tools: {
300
+ select: string;
301
+ drawing: string;
302
+ rectangle: string;
303
+ ellipse: string;
304
+ arrow: string;
305
+ text: string;
306
+ shapes: string;
307
+ withShortcut: string;
308
+ line: string;
309
+ step: string;
310
+ };
311
+ tones: {
312
+ neutral: string;
313
+ brand: string;
314
+ info: string;
315
+ success: string;
316
+ warning: string;
317
+ danger: string;
318
+ };
319
+ open: string;
320
+ close: string;
321
+ color: string;
322
+ stroke: string;
323
+ deleteSelection: string;
324
+ strokes: {
325
+ thin: string;
326
+ medium: string;
327
+ thick: string;
328
+ };
329
+ };
289
330
  };
290
331
  followUp: {
291
332
  messages: string;
@@ -924,6 +965,47 @@ export declare const uiLocales: {
924
965
  laneMap: {
925
966
  label: string;
926
967
  };
968
+ selection: {
969
+ count_one: string;
970
+ count_other: string;
971
+ duplicate: string;
972
+ delete: string;
973
+ clear: string;
974
+ };
975
+ annotations: {
976
+ toolbar: string;
977
+ textPlaceholder: string;
978
+ tools: {
979
+ select: string;
980
+ drawing: string;
981
+ rectangle: string;
982
+ ellipse: string;
983
+ arrow: string;
984
+ text: string;
985
+ shapes: string;
986
+ withShortcut: string;
987
+ line: string;
988
+ step: string;
989
+ };
990
+ tones: {
991
+ neutral: string;
992
+ brand: string;
993
+ info: string;
994
+ success: string;
995
+ warning: string;
996
+ danger: string;
997
+ };
998
+ open: string;
999
+ close: string;
1000
+ color: string;
1001
+ stroke: string;
1002
+ deleteSelection: string;
1003
+ strokes: {
1004
+ thin: string;
1005
+ medium: string;
1006
+ thick: string;
1007
+ };
1008
+ };
927
1009
  };
928
1010
  followUp: {
929
1011
  messages: string;
@@ -283,6 +283,47 @@ declare const _default: {
283
283
  },
284
284
  "laneMap": {
285
285
  "label": "Mapa do fluxo"
286
+ },
287
+ "selection": {
288
+ "count_one": "{{count}} selecionado",
289
+ "count_other": "{{count}} selecionados",
290
+ "duplicate": "Duplicar",
291
+ "delete": "Excluir",
292
+ "clear": "Limpar seleção"
293
+ },
294
+ "annotations": {
295
+ "toolbar": "Ferramentas de anotação",
296
+ "textPlaceholder": "Escreva uma anotação",
297
+ "tools": {
298
+ "select": "Selecionar",
299
+ "drawing": "Lápis",
300
+ "rectangle": "Retângulo",
301
+ "ellipse": "Elipse",
302
+ "arrow": "Seta",
303
+ "text": "Texto",
304
+ "shapes": "Formas",
305
+ "withShortcut": "{{label}} ({{shortcut}})",
306
+ "line": "Linha",
307
+ "step": "Passo"
308
+ },
309
+ "tones": {
310
+ "neutral": "Cinza",
311
+ "brand": "Roxo",
312
+ "info": "Azul",
313
+ "success": "Verde",
314
+ "warning": "Amarelo",
315
+ "danger": "Vermelho"
316
+ },
317
+ "open": "Anotar",
318
+ "close": "Fechar anotações",
319
+ "color": "Cor",
320
+ "stroke": "Espessura",
321
+ "deleteSelection": "Excluir seleção",
322
+ "strokes": {
323
+ "thin": "Fina",
324
+ "medium": "Média",
325
+ "thick": "Grossa"
326
+ }
286
327
  }
287
328
  },
288
329
  "followUp": {