dishui 0.0.108 → 0.0.110
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.
- package/dist/.release-verified.json +3 -2
- package/dist/components/Accordion/index.js.map +1 -1
- package/dist/components/Avatar/index.js.map +1 -1
- package/dist/components/Badge/index.js.map +1 -1
- package/dist/components/Button/index.js.map +1 -1
- package/dist/components/ButtonMenu/index.js.map +1 -1
- package/dist/components/Card/Metric2.js.map +1 -1
- package/dist/components/Card/Ticket2.js.map +1 -1
- package/dist/components/Card/index.js.map +1 -1
- package/dist/components/Chart/Chart.js.map +1 -1
- package/dist/components/Checkbox/index.js.map +1 -1
- package/dist/components/ChoicePicker/index.js.map +1 -1
- package/dist/components/Column/index.js.map +1 -1
- package/dist/components/ConfirmDialog/index.js.map +1 -1
- package/dist/components/DateTimeInput/index.js.map +1 -1
- package/dist/components/Demo/index.js.map +1 -1
- package/dist/components/Demo/source.js.map +1 -1
- package/dist/components/Divider/index.js.map +1 -1
- package/dist/components/EmptyState/index.js.map +1 -1
- package/dist/components/Field/index.js.map +1 -1
- package/dist/components/FileUploader/DropZone.js.map +1 -1
- package/dist/components/FileUploader/FileItem.js.map +1 -1
- package/dist/components/FileUploader/FileUploader.js.map +1 -1
- package/dist/components/FileUploader/UploadQueue.js.map +1 -1
- package/dist/components/GroupButton/index.js.map +1 -1
- package/dist/components/GroupedTable/index.js.map +1 -1
- package/dist/components/Icon/index.js.map +1 -1
- package/dist/components/Image/index.js.map +1 -1
- package/dist/components/Input/index.js.map +1 -1
- package/dist/components/Label/index.js.map +1 -1
- package/dist/components/Layout/index.js.map +1 -1
- package/dist/components/LayoutSuite/DashboardSuite.js.map +1 -1
- package/dist/components/LayoutSuite/index.js.map +1 -1
- package/dist/components/List/index.js.map +1 -1
- package/dist/components/Markdown/Markdown.js.map +1 -1
- package/dist/components/Markdown/codeThemes.js.map +1 -1
- package/dist/components/Markdown/markdownComponents.js.map +1 -1
- package/dist/components/Markdown/parseCodeBlockMeta.js.map +1 -1
- package/dist/components/Markdown/useMarkdownSource.js.map +1 -1
- package/dist/components/Markdown/useMarkmapRenderer.js.map +1 -1
- package/dist/components/Markdown/useMermaidRenderer.js.map +1 -1
- package/dist/components/Modal/focusScope.js.map +1 -1
- package/dist/components/Modal/index.js.map +1 -1
- package/dist/components/MultipleChoice/index.js.map +1 -1
- package/dist/components/NavigationList/index.js.map +1 -1
- package/dist/components/Page/Float2.js.map +1 -1
- package/dist/components/Page/index.js.map +1 -1
- package/dist/components/Page/resize2.js.map +1 -1
- package/dist/components/PageHeader/index.js.map +1 -1
- package/dist/components/PagePanel/index.js.map +1 -1
- package/dist/components/ProtectedRoute/index.js.map +1 -1
- package/dist/components/Resizable/ResizeGrip.js.map +1 -1
- package/dist/components/Resizable/index.js.map +1 -1
- package/dist/components/Resizable/useResize.js.map +1 -1
- package/dist/components/Row/index.js.map +1 -1
- package/dist/components/Section/index.js.map +1 -1
- package/dist/components/Select/index.js.map +1 -1
- package/dist/components/SidebarToggleButton/index.js.map +1 -1
- package/dist/components/Skeleton/index.js.map +1 -1
- package/dist/components/Slider/index.js.map +1 -1
- package/dist/components/Switch/index.js.map +1 -1
- package/dist/components/Tab/index.js.map +1 -1
- package/dist/components/Table/index.js.map +1 -1
- package/dist/components/Tabs/index.js.map +1 -1
- package/dist/components/Tabs/tabNavigation.js.map +1 -1
- package/dist/components/Text/index.js.map +1 -1
- package/dist/components/Textarea/index.js.map +1 -1
- package/dist/components/Tooltip/index.js.map +1 -1
- package/dist/components/Tree/Tree.js.map +1 -1
- package/dist/components/ai/AgentChat/index.js.map +1 -1
- package/dist/components/ai/AgentRunCard/index.js.map +1 -1
- package/dist/components/ai/AgentScreen/index.js.map +1 -1
- package/dist/components/ai/ApprovalCard/index.js.map +1 -1
- package/dist/components/ai/CodeView/index.js.map +1 -1
- package/dist/components/ai/CommandSearch/index.js.map +1 -1
- package/dist/components/ai/ContextCards/index.js.map +1 -1
- package/dist/components/ai/DiffTable/index.js.map +1 -1
- package/dist/components/ai/FilterTable/index.js.map +1 -1
- package/dist/components/ai/FineTuneCard/index.js.map +1 -1
- package/dist/components/ai/InsightCards/index.js.map +1 -1
- package/dist/components/ai/IssueCard/index.js.map +1 -1
- package/dist/components/ai/LoadingState/index.js.map +1 -1
- package/dist/components/ai/PromptBar/index.js.map +1 -1
- package/dist/components/ai/RecommendationCard/index.js.map +1 -1
- package/dist/components/ai/RecordsTable/index.js.map +1 -1
- package/dist/components/ai/SelectionActions/index.js.map +1 -1
- package/dist/components/ai/StateFlow/index.js.map +1 -1
- package/dist/components/ai/StreamingText/index.js.map +1 -1
- package/dist/components/ai/TaskRows/index.js.map +1 -1
- package/dist/components/ai/Thinking/index.js.map +1 -1
- package/dist/components/ai/ToolChips/index.js.map +1 -1
- package/dist/components/ai/shared.js.map +1 -1
- package/dist/components/pro/ArticleContextMenu/index.js.map +1 -1
- package/dist/components/pro/ArticleControl/ArticleControl2.js.map +1 -1
- package/dist/components/pro/ArticleControl/buildHeadingTree.js.map +1 -1
- package/dist/components/pro/ArticleControl/useArticleToc.js.map +1 -1
- package/dist/components/pro/Calendar/Calendar.js +165 -160
- package/dist/components/pro/Calendar/Calendar.js.map +1 -1
- package/dist/components/pro/Calendar/hooks/useCalendarDrag.js.map +1 -1
- package/dist/components/pro/Calendar/hooks/useCalendarState.js +31 -31
- package/dist/components/pro/Calendar/hooks/useCalendarState.js.map +1 -1
- package/dist/components/pro/Calendar/utils.js.map +1 -1
- package/dist/components/pro/CodeEditor/editors/CodeEditor.js.map +1 -1
- package/dist/components/pro/CodeEditor/editors/MarkdownEditor.js.map +1 -1
- package/dist/components/pro/CodeEditor/editors/markdownExtensions.js.map +1 -1
- package/dist/components/pro/CodeEditor/extensions/contextMenu.js.map +1 -1
- package/dist/components/pro/CodeEditor/extensions/index.js.map +1 -1
- package/dist/components/pro/CodeEditor/langs/index.js.map +1 -1
- package/dist/components/pro/CodeEditor/langs/javascript.js.map +1 -1
- package/dist/components/pro/CodeEditor/langs/python.js.map +1 -1
- package/dist/components/pro/CodeEditor/langs/snippets.js.map +1 -1
- package/dist/components/pro/CodeEditor/panels/FootnotePanel.js.map +1 -1
- package/dist/components/pro/CodeEditor/panels/HistoryPanel.js +65 -59
- package/dist/components/pro/CodeEditor/panels/HistoryPanel.js.map +1 -1
- package/dist/components/pro/CodeEditor/panels/OutlinePanel.js.map +1 -1
- package/dist/components/pro/CodeEditor/panels/TableOfContentsPanel.js.map +1 -1
- package/dist/components/pro/CodeEditor/panels/VariablePanel.js.map +1 -1
- package/dist/components/pro/CodeEditor/themes.js.map +1 -1
- package/dist/components/pro/CodeEditor/utils.js.map +1 -1
- package/dist/components/pro/DataTable/DataTable.js.map +1 -1
- package/dist/components/pro/DataTable/Filters.js.map +1 -1
- package/dist/components/pro/DataTable/PivotPanel.js.map +1 -1
- package/dist/components/pro/DataTable/PivotView.js.map +1 -1
- package/dist/components/pro/DataTable/components/BulkActions.js.map +1 -1
- package/dist/components/pro/DataTable/components/ColumnSelector.js.map +1 -1
- package/dist/components/pro/DataTable/components/FilterBar.js.map +1 -1
- package/dist/components/pro/DataTable/components/TableBody.js.map +1 -1
- package/dist/components/pro/DataTable/components/TableHeader.js.map +1 -1
- package/dist/components/pro/DataTable/components/TablePagination.js.map +1 -1
- package/dist/components/pro/DataTable/components/TableToolbar.js.map +1 -1
- package/dist/components/pro/DataTable/extension.js.map +1 -1
- package/dist/components/pro/DataTable/pivotEngine.js.map +1 -1
- package/dist/components/pro/DataTable/pivotExtension.js.map +1 -1
- package/dist/components/pro/DataTable/rowDetailExtension.js.map +1 -1
- package/dist/components/pro/DataTable/tanstack-table.js.map +1 -1
- package/dist/components/pro/DataTable/treeDataUtils.js.map +1 -1
- package/dist/components/pro/DataTable/useColumnDragReorder.js.map +1 -1
- package/dist/components/pro/DataTable/useDataTableModel.js +119 -104
- package/dist/components/pro/DataTable/useDataTableModel.js.map +1 -1
- package/dist/components/pro/DataTable/utils.js.map +1 -1
- package/dist/components/pro/FileExplorer/Breadcrumb.js.map +1 -1
- package/dist/components/pro/FileExplorer/CreateDialog.js.map +1 -1
- package/dist/components/pro/FileExplorer/FileExplorer.js.map +1 -1
- package/dist/components/pro/FileExplorer/FileIcon.js.map +1 -1
- package/dist/components/pro/FileExplorer/GridView.js.map +1 -1
- package/dist/components/pro/FileExplorer/ListView.js.map +1 -1
- package/dist/components/pro/FileExplorer/Toolbar.js.map +1 -1
- package/dist/components/pro/Flow/Flow.js.map +1 -1
- package/dist/components/pro/Flow/FlowCanvas.js.map +1 -1
- package/dist/components/pro/Flow/archify/build.js.map +1 -1
- package/dist/components/pro/Flow/archify/convert.js.map +1 -1
- package/dist/components/pro/Flow/archify/palette.js.map +1 -1
- package/dist/components/pro/Flow/archify/presets.js.map +1 -1
- package/dist/components/pro/Flow/archify/spec.js.map +1 -1
- package/dist/components/pro/Flow/canvas/AlignmentGuides.js.map +1 -1
- package/dist/components/pro/Flow/canvas/GridRenderer.js.map +1 -1
- package/dist/components/pro/Flow/canvas/MiniMapRenderer.js.map +1 -1
- package/dist/components/pro/Flow/canvas/PixiApp.js.map +1 -1
- package/dist/components/pro/Flow/canvas/SelectionOverlay.js.map +1 -1
- package/dist/components/pro/Flow/canvas/ViewportContainer.js.map +1 -1
- package/dist/components/pro/Flow/constants.js.map +1 -1
- package/dist/components/pro/Flow/context/FlowAIContext.js.map +1 -1
- package/dist/components/pro/Flow/context/FlowContext.js.map +1 -1
- package/dist/components/pro/Flow/context/FlowUIContext.js.map +1 -1
- package/dist/components/pro/Flow/edges/EdgeRenderer.js.map +1 -1
- package/dist/components/pro/Flow/edges/edgeMarkers.js.map +1 -1
- package/dist/components/pro/Flow/edges/edgePaths.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useAlignmentGuides.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useClipboard.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useCompactMode.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useDeleteSelection.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useDraggablePanel.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useEdgeMutations.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useFlowImportExport.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useKeyboardShortcuts.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useLayoutActions.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useLineMutations.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useMindMap.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useNodeMutations.js.map +1 -1
- package/dist/components/pro/Flow/hooks/usePdfExport.js.map +1 -1
- package/dist/components/pro/Flow/hooks/usePixiApp.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useResizablePanel.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useSelection.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useUndoRedo.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useViewport.js.map +1 -1
- package/dist/components/pro/Flow/lines/FreehandTool.js.map +1 -1
- package/dist/components/pro/Flow/lines/GeometricLineTool.js.map +1 -1
- package/dist/components/pro/Flow/lines/LineRenderer.js.map +1 -1
- package/dist/components/pro/Flow/lines/lineSmoothing.js.map +1 -1
- package/dist/components/pro/Flow/nodes/DataNodeView.js.map +1 -1
- package/dist/components/pro/Flow/nodes/MediaContent.js.map +1 -1
- package/dist/components/pro/Flow/nodes/MediaOverlay.js.map +1 -1
- package/dist/components/pro/Flow/nodes/Model3DView.js.map +1 -1
- package/dist/components/pro/Flow/nodes/NodeHandles.js.map +1 -1
- package/dist/components/pro/Flow/nodes/NodeRenderer.js.map +1 -1
- package/dist/components/pro/Flow/nodes/NodeText.js.map +1 -1
- package/dist/components/pro/Flow/nodes/aiClient.js.map +1 -1
- package/dist/components/pro/Flow/nodes/animAnchors.js.map +1 -1
- package/dist/components/pro/Flow/nodes/chartGeometryBudget.js.map +1 -1
- package/dist/components/pro/Flow/nodes/dataUtils.js.map +1 -1
- package/dist/components/pro/Flow/nodes/demandRender.js.map +1 -1
- package/dist/components/pro/Flow/nodes/nodeUtils.js.map +1 -1
- package/dist/components/pro/Flow/nodes/shapes.js.map +1 -1
- package/dist/components/pro/Flow/nodes/svgIconTexture.js.map +1 -1
- package/dist/components/pro/Flow/nodes/svgIcons.js.map +1 -1
- package/dist/components/pro/Flow/panels/AIPanel.js.map +1 -1
- package/dist/components/pro/Flow/panels/Breadcrumb.js.map +1 -1
- package/dist/components/pro/Flow/panels/ContextMenu.js.map +1 -1
- package/dist/components/pro/Flow/panels/DataInspectorSection.js.map +1 -1
- package/dist/components/pro/Flow/panels/ExpandableTextarea.js.map +1 -1
- package/dist/components/pro/Flow/panels/FlowPanelGroup.js.map +1 -1
- package/dist/components/pro/Flow/panels/FlowToolbar.js.map +1 -1
- package/dist/components/pro/Flow/panels/InspectorPanel.js.map +1 -1
- package/dist/components/pro/Flow/panels/OutlinePanel.js.map +1 -1
- package/dist/components/pro/Flow/panels/PresentationMode.js.map +1 -1
- package/dist/components/pro/Flow/panels/ProjectTitle.js.map +1 -1
- package/dist/components/pro/Flow/panels/ShapePalette.js.map +1 -1
- package/dist/components/pro/Flow/panels/toolbarIcons.js.map +1 -1
- package/dist/components/pro/Flow/types.js.map +1 -1
- package/dist/components/pro/Flow/utils/dashedLine.js.map +1 -1
- package/dist/components/pro/Flow/utils/gradient.js.map +1 -1
- package/dist/components/pro/Flow/utils/pdfExport.js.map +1 -1
- package/dist/components/pro/Flow/utils/themeColor.js.map +1 -1
- package/dist/components/pro/Gantt/Gantt.js.map +1 -1
- package/dist/components/pro/Gantt/hooks/useGanttDrag.js.map +1 -1
- package/dist/components/pro/Gantt/hooks/useGanttState.js +79 -71
- package/dist/components/pro/Gantt/hooks/useGanttState.js.map +1 -1
- package/dist/components/pro/Gantt/utils.js.map +1 -1
- package/dist/components/pro/Kanban/Kanban.js.map +1 -1
- package/dist/components/pro/Kanban/Swimlanes.js.map +1 -1
- package/dist/components/pro/Kanban/useKanbanDrag.js.map +1 -1
- package/dist/components/pro/Kanban/utils.js.map +1 -1
- package/dist/components/pro/RichTextEditor/AIRewrite.js.map +1 -1
- package/dist/components/pro/RichTextEditor/BlockHandle.js.map +1 -1
- package/dist/components/pro/RichTextEditor/BubbleToolbar.js.map +1 -1
- package/dist/components/pro/RichTextEditor/CommentDialog.js.map +1 -1
- package/dist/components/pro/RichTextEditor/CommentPanel.js.map +1 -1
- package/dist/components/pro/RichTextEditor/CommentPopover.js.map +1 -1
- package/dist/components/pro/RichTextEditor/ContextMenu.js.map +1 -1
- package/dist/components/pro/RichTextEditor/RichTextEditor.js.map +1 -1
- package/dist/components/pro/RichTextEditor/SlashMenu.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/ColorPicker.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/FloatingDialogMenu.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/MarkdownTemplates.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/SelectDropdown.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/TableOpsMenu.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/Toolbar.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/ToolbarButton.js.map +1 -1
- package/dist/components/pro/RichTextEditor/Toolbar/useAnchoredDropdown.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/CodeBlockView.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/CommentMark.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/FloatingDialog.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/FontSize.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/MarkdownBlock.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/ResizableImage.js.map +1 -1
- package/dist/components/pro/RichTextEditor/extensions/legacyMermaid.js.map +1 -1
- package/dist/components/pro/RichTextEditor/types.js.map +1 -1
- package/dist/components/pro/Sprite/Molly/animations.d.ts +6 -0
- package/dist/components/pro/Sprite/Molly/animations.js +52 -0
- package/dist/components/pro/Sprite/Molly/animations.js.map +1 -0
- package/dist/components/pro/Sprite/Molly/events.d.ts +2 -0
- package/dist/components/pro/Sprite/Molly/events.js +46 -0
- package/dist/components/pro/Sprite/Molly/events.js.map +1 -0
- package/dist/components/pro/Sprite/Molly/index.d.ts +1 -0
- package/dist/components/pro/Sprite/Molly/index.js +12 -0
- package/dist/components/pro/Sprite/Molly/index.js.map +1 -0
- package/dist/components/pro/Sprite/Molly/legFlexion.d.ts +3 -0
- package/dist/components/pro/Sprite/Molly/legFlexion.js +48 -0
- package/dist/components/pro/Sprite/Molly/legFlexion.js.map +1 -0
- package/dist/components/pro/Sprite/Molly/locomotion.d.ts +4 -0
- package/dist/components/pro/Sprite/Molly/locomotion.js +51 -0
- package/dist/components/pro/Sprite/Molly/locomotion.js.map +1 -0
- package/dist/components/pro/Sprite/Molly/model.d.ts +23 -0
- package/dist/components/pro/Sprite/Molly/model.js +614 -0
- package/dist/components/pro/Sprite/Molly/model.js.map +1 -0
- package/dist/components/pro/Sprite/Molly/rig.d.ts +25 -0
- package/dist/components/pro/Sprite/Molly/rig.js +102 -0
- package/dist/components/pro/Sprite/Molly/rig.js.map +1 -0
- package/dist/components/pro/Sprite/Ruby/animations.d.ts +6 -0
- package/dist/components/pro/Sprite/Ruby/animations.js +58 -0
- package/dist/components/pro/Sprite/Ruby/animations.js.map +1 -0
- package/dist/components/pro/Sprite/Ruby/events.d.ts +2 -0
- package/dist/components/pro/Sprite/Ruby/events.js +46 -0
- package/dist/components/pro/Sprite/Ruby/events.js.map +1 -0
- package/dist/components/pro/Sprite/Ruby/index.d.ts +1 -0
- package/dist/components/pro/Sprite/Ruby/index.js +12 -0
- package/dist/components/pro/Sprite/Ruby/index.js.map +1 -0
- package/dist/components/pro/Sprite/Ruby/locomotion.d.ts +4 -0
- package/dist/components/pro/Sprite/Ruby/locomotion.js +41 -0
- package/dist/components/pro/Sprite/Ruby/locomotion.js.map +1 -0
- package/dist/components/pro/Sprite/Ruby/model.d.ts +23 -0
- package/dist/components/pro/Sprite/Ruby/model.js +605 -0
- package/dist/components/pro/Sprite/Ruby/model.js.map +1 -0
- package/dist/components/pro/Sprite/Ruby/rig.d.ts +26 -0
- package/dist/components/pro/Sprite/Ruby/rig.js +112 -0
- package/dist/components/pro/Sprite/Ruby/rig.js.map +1 -0
- package/dist/components/pro/Sprite/Sprite.d.ts +23 -0
- package/dist/components/pro/Sprite/Sprite2.js +224 -0
- package/dist/components/pro/Sprite/Sprite2.js.map +1 -0
- package/dist/components/pro/Sprite/actions.d.ts +3 -0
- package/dist/components/pro/Sprite/actions.js +16 -0
- package/dist/components/pro/Sprite/actions.js.map +1 -0
- package/dist/components/pro/Sprite/createSpriteScene.d.ts +4 -0
- package/dist/components/pro/Sprite/createSpriteScene.js +14 -0
- package/dist/components/pro/Sprite/createSpriteScene.js.map +1 -0
- package/dist/components/pro/Sprite/index.d.ts +4 -0
- package/dist/components/pro/Sprite/shared/animation.d.ts +33 -0
- package/dist/components/pro/Sprite/shared/animation.js +39 -0
- package/dist/components/pro/Sprite/shared/animation.js.map +1 -0
- package/dist/components/pro/Sprite/shared/createSpriteStage.d.ts +14 -0
- package/dist/components/pro/Sprite/shared/createSpriteStage.js +127 -0
- package/dist/components/pro/Sprite/shared/createSpriteStage.js.map +1 -0
- package/dist/components/pro/Sprite/shared/events.d.ts +5 -0
- package/dist/components/pro/Sprite/shared/events.js +21 -0
- package/dist/components/pro/Sprite/shared/events.js.map +1 -0
- package/dist/components/pro/Sprite/shared/face.d.ts +15 -0
- package/dist/components/pro/Sprite/shared/face.js +33 -0
- package/dist/components/pro/Sprite/shared/face.js.map +1 -0
- package/dist/components/pro/Sprite/shared/faceSurface.d.ts +6 -0
- package/dist/components/pro/Sprite/shared/faceSurface.js +66 -0
- package/dist/components/pro/Sprite/shared/faceSurface.js.map +1 -0
- package/dist/components/pro/Sprite/shared/gait.d.ts +5 -0
- package/dist/components/pro/Sprite/shared/gait.js +19 -0
- package/dist/components/pro/Sprite/shared/gait.js.map +1 -0
- package/dist/components/pro/Sprite/shared/geometry.d.ts +8 -0
- package/dist/components/pro/Sprite/shared/geometry.js +59 -0
- package/dist/components/pro/Sprite/shared/geometry.js.map +1 -0
- package/dist/components/pro/Sprite/shared/magicEffect.d.ts +27 -0
- package/dist/components/pro/Sprite/shared/magicEffect.js +61 -0
- package/dist/components/pro/Sprite/shared/magicEffect.js.map +1 -0
- package/dist/components/pro/Sprite/shared/playback.d.ts +8 -0
- package/dist/components/pro/Sprite/shared/playback.js +74 -0
- package/dist/components/pro/Sprite/shared/playback.js.map +1 -0
- package/dist/components/pro/Sprite/shared/rig.d.ts +6 -0
- package/dist/components/pro/Sprite/shared/rig.js +48 -0
- package/dist/components/pro/Sprite/shared/rig.js.map +1 -0
- package/dist/components/pro/Sprite/shared/scene.d.ts +9 -0
- package/dist/components/pro/Sprite/shared/scene.js +21 -0
- package/dist/components/pro/Sprite/shared/scene.js.map +1 -0
- package/dist/components/pro/Sprite/shared/sculptor.d.ts +14 -0
- package/dist/components/pro/Sprite/shared/sculptor.js +78 -0
- package/dist/components/pro/Sprite/shared/sculptor.js.map +1 -0
- package/dist/components/pro/Sprite/shared/speechBubble.d.ts +15 -0
- package/dist/components/pro/Sprite/shared/speechBubble.js +129 -0
- package/dist/components/pro/Sprite/shared/speechBubble.js.map +1 -0
- package/dist/components/pro/Sprite/types.d.ts +40 -0
- package/dist/contexts/AppContext.js.map +1 -1
- package/dist/contexts/authentication.js.map +1 -1
- package/dist/data-table.js +3 -3
- package/dist/data-table.js.map +1 -1
- package/dist/dishui.css +1 -1
- package/dist/hooks/useClickOutside.js.map +1 -1
- package/dist/hooks/useContainerElement.js.map +1 -1
- package/dist/hooks/useControllableState.js.map +1 -1
- package/dist/hooks/useCopyToClipboard.js.map +1 -1
- package/dist/hooks/useDebounce.js.map +1 -1
- package/dist/hooks/useDraggable.js.map +1 -1
- package/dist/hooks/useTableColumns.js.map +1 -1
- package/dist/hooks/useTableData.js.map +1 -1
- package/dist/index-nostyled.d.ts +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +116 -115
- package/dist/layouts/MainLayout.js.map +1 -1
- package/dist/layouts/Navbar.js.map +1 -1
- package/dist/layouts/Sidebar.js.map +1 -1
- package/dist/layouts/headbar/Headbar.js.map +1 -1
- package/dist/layouts/headbar/HeadbarLangSwitcher.js.map +1 -1
- package/dist/layouts/headbar/HeadbarSearch.js.map +1 -1
- package/dist/layouts/headbar/HeadbarThemeSwitcher.js.map +1 -1
- package/dist/layouts/headbar/HeadbarTitle.js.map +1 -1
- package/dist/sprite.js +2 -0
- package/dist/styles/themeStyles.js.map +1 -1
- package/dist/utils/cn.js.map +1 -1
- package/dist/utils/flexLayout.js.map +1 -1
- package/dist/utils/i18n.js.map +1 -1
- package/dist/utils/menu.js.map +1 -1
- package/dist/utils/rehype-code-meta.js.map +1 -1
- package/dist/utils/remark-code-meta.js.map +1 -1
- package/dist/utils/routerBuilder.js.map +1 -1
- package/dist/utils/safeStorage.js.map +1 -1
- package/dist/utils/selection.js +29 -20
- package/dist/utils/selection.js.map +1 -1
- package/dist/utils/themes.js.map +1 -1
- package/package.json +15 -11
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeRenderer.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/NodeRenderer.ts"],"sourcesContent":["import { Container, Graphics, Text, Sprite, TextStyle } from 'pixi.js';\nimport type { FlowNode, FlowTextPlacement } from '../types';\nimport { drawRoundRectBorder, shapeDrawers, hasBorderSideOverrides, drawBorderSides, type ShapeDrawOptions } from './shapes';\nimport { createNodeText, formatIconLabel, normalizeIconLabelPlacement, updateNodeText } from './NodeText';\nimport { getHandlePositions, createHandleGraphics, updateHandleGraphics, type HandleDef } from './NodeHandles';\nimport { getRotationRad, getFlipScale } from './nodeUtils';\nimport { getIconTexture, subscribeIconTextureReady, ICON_TEXTURE_SIZE } from './svgIconTexture';\nimport { getSvgIcon } from './svgIcons';\nimport { resolveThemeColor } from '../utils/themeColor';\n\n/**\n * Resolve the center point of a text block (title + optional sublabel) inside a\n * node of the given size, for a 9-way placement. Non-center placements pull the\n * block against the corresponding edge/corner with a small inset.\n */\nfunction resolveTextBlockCenter(\n placement: FlowTextPlacement,\n nodeW: number,\n nodeH: number,\n blockW: number,\n blockH: number,\n): { x: number; y: number } {\n const inset = 6;\n const left = inset + blockW / 2;\n const right = nodeW - inset - blockW / 2;\n const cx = nodeW / 2;\n const top = inset + blockH / 2;\n const bottom = nodeH - inset - blockH / 2;\n const cy = nodeH / 2;\n switch (placement) {\n case 'top': return { x: cx, y: top };\n case 'bottom': return { x: cx, y: bottom };\n case 'left': return { x: left, y: cy };\n case 'right': return { x: right, y: cy };\n case 'top-left': return { x: left, y: top };\n case 'top-right': return { x: right, y: top };\n case 'bottom-left': return { x: left, y: bottom };\n case 'bottom-right': return { x: right, y: bottom };\n case 'center':\n default:\n return { x: cx, y: cy };\n }\n}\n\nexport interface NodeContainer {\n container: Container;\n shape: Graphics;\n /** Rotated/flipped visual content for SVG icon stamps. */\n svgTransform: Container;\n icon: Sprite;\n text: Text;\n /** Optional registry glyph rendered beside the title text (left/right). */\n titleIcon: Sprite;\n /** Optional muted second line (Archify card sublabel). */\n subtext: Text;\n /** Optional top-right pill background + label (Archify card tag). */\n tagBg: Graphics;\n tagText: Text;\n /** Sequence badge shown on `animAnchor` nodes. */\n animBadge: Text;\n handles: Graphics;\n handleDefs: HandleDef[];\n nodeId: string;\n subCanvasGlow: Graphics;\n}\n\n/**\n * Manages PixiJS display objects for all nodes on the current canvas.\n * Syncs from FlowNode[] data → PixiJS scene graph.\n */\nexport class NodeRenderer {\n private world: Container;\n private nodeLayer: Container;\n private nodes = new Map<string, NodeContainer>();\n private _dynamicOffset = 0;\n private _unsubscribeTextureReady: () => void;\n\n /** Call once per frame to advance the dynamic dash animation. */\n advanceAnimation(deltaMs: number): void {\n this._dynamicOffset = (this._dynamicOffset + deltaMs * 0.06) % 1000;\n }\n\n constructor(world: Container) {\n this.world = world;\n this.nodeLayer = new Container({ label: 'nodes', sortableChildren: true });\n this.world.addChild(this.nodeLayer);\n this._unsubscribeTextureReady = subscribeIconTextureReady(() => this._resync?.());\n }\n\n private _resync: (() => void) | null = null;\n\n /**\n * Reconcile PixiJS containers with the current node data.\n * Creates new containers, updates existing, removes stale.\n */\n sync(\n flowNodes: FlowNode[],\n selectedIds: Set<string>,\n hoveredId: string | null,\n animSeqMap?: Map<string, number>,\n options?: {\n showAnchors?: boolean;\n presentation?: boolean;\n presentationAnchorId?: string;\n presentationAnchorVisible?: boolean;\n },\n ): void {\n const seen = new Set<string>();\n this._resync = () => this.sync(flowNodes, selectedIds, hoveredId, animSeqMap, options);\n\n // Animation-anchor display sequence (1-based). When a global map is\n // provided (document-wide, shared across sub-canvases) it is used directly;\n // otherwise derive a per-canvas order from stable creation index.\n const seqOf = animSeqMap ?? (() => {\n const anchors = flowNodes\n .filter((n) => n.type === 'animAnchor')\n .sort((a, b) => (a.data.animIndex ?? 0) - (b.data.animIndex ?? 0));\n const m = new Map<string, number>();\n anchors.forEach((n, i) => m.set(n.id, i + 1));\n return m;\n })();\n\n for (const node of flowNodes) {\n seen.add(node.id);\n let nc = this.nodes.get(node.id);\n if (!nc) {\n nc = this._createNode(node);\n this.nodes.set(node.id, nc);\n }\n this._updateNode(\n nc,\n node,\n selectedIds.has(node.id),\n hoveredId === node.id,\n seqOf.get(node.id),\n options,\n );\n }\n\n // Remove nodes no longer in data\n for (const [id, nc] of this.nodes) {\n if (!seen.has(id)) {\n this.nodeLayer.removeChild(nc.container);\n nc.container.destroy({ children: true });\n this.nodes.delete(id);\n }\n }\n }\n\n getNodeContainer(id: string): NodeContainer | undefined {\n return this.nodes.get(id);\n }\n\n getAllNodeContainers(): NodeContainer[] {\n return [...this.nodes.values()];\n }\n\n private _createNode(node: FlowNode): NodeContainer {\n const container = new Container({ label: `node-${node.id}` });\n container.eventMode = 'static';\n container.cursor = 'pointer';\n\n const svgTransform = new Container({ label: 'svg-transform' });\n container.addChild(svgTransform);\n\n const subCanvasGlow = new Graphics();\n subCanvasGlow.label = 'sub-canvas-glow';\n container.addChild(subCanvasGlow);\n\n const shape = new Graphics();\n shape.label = 'shape';\n container.addChild(shape);\n\n const icon = new Sprite();\n icon.label = 'svg-icon';\n icon.anchor.set(0.5, 0.5);\n icon.visible = false;\n container.addChild(icon);\n\n const text = createNodeText({\n label: node.data.label,\n color: resolveThemeColor(node.data.labelColor ?? node.data.color, '#111827'),\n fontSize: node.data.fontSize,\n width: node.width,\n height: node.height,\n });\n container.addChild(text);\n\n // Optional glyph beside the title (lazy-rasterized registry texture).\n const titleIcon = new Sprite();\n titleIcon.label = 'node-title-icon';\n titleIcon.anchor.set(0.5, 0.5);\n titleIcon.visible = false;\n container.addChild(titleIcon);\n\n // Optional muted second line (Archify card sublabel).\n const subtext = new Text({\n text: '',\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n fontSize: 11,\n fill: '#94a3b8',\n align: 'center',\n },\n });\n subtext.label = 'node-subtext';\n subtext.anchor.set(0.5, 0.5);\n subtext.visible = false;\n container.addChild(subtext);\n\n // Optional top-right pill (Archify card tag).\n const tagBg = new Graphics();\n tagBg.label = 'node-tag-bg';\n tagBg.visible = false;\n container.addChild(tagBg);\n const tagText = new Text({\n text: '',\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n fontSize: 9,\n fill: '#cbd5e1',\n },\n });\n tagText.label = 'node-tag-text';\n tagText.anchor.set(0.5, 0.5);\n tagText.visible = false;\n container.addChild(tagText);\n\n const handleDefs = getHandlePositions(node.width, node.height);\n const handles = createHandleGraphics(container, handleDefs, false);\n\n // Sequence badge for animation anchors (top-left corner).\n const animBadge = new Text({\n text: '',\n style: { fontFamily: 'system-ui, sans-serif', fontSize: 13, fill: '#ffffff', fontWeight: '700' },\n });\n animBadge.label = 'anim-badge';\n animBadge.anchor.set(0.5, 0.5);\n animBadge.visible = false;\n container.addChild(animBadge);\n\n this.nodeLayer.addChild(container);\n\n return { container, shape, svgTransform, icon, text, titleIcon, subtext, tagBg, tagText, animBadge, handles, handleDefs, nodeId: node.id, subCanvasGlow };\n }\n\n private _updateNode(\n nc: NodeContainer,\n node: FlowNode,\n selected: boolean,\n hovered: boolean,\n animSeq?: number,\n options?: {\n showAnchors?: boolean;\n presentation?: boolean;\n presentationAnchorId?: string;\n presentationAnchorVisible?: boolean;\n },\n ): void {\n const { container, svgTransform, shape, icon, text, titleIcon, subtext, tagBg, tagText, animBadge, handles, subCanvasGlow } = nc;\n\n const isSvg = node.type === 'shapeSvg';\n const rad = getRotationRad(node.data);\n const flip = getFlipScale(node.data);\n\n container.zIndex = node.zIndex ?? 0;\n if (isSvg) {\n // Keep the label in the untransformed root. Only the icon box, icon and\n // connection handles rotate/flip around the node center.\n if (shape.parent !== svgTransform) svgTransform.addChild(shape);\n if (icon.parent !== svgTransform) svgTransform.addChild(icon);\n if (handles.parent !== svgTransform) svgTransform.addChild(handles);\n container.position.set(node.position.x, node.position.y);\n container.rotation = 0;\n container.scale.set(1, 1);\n container.pivot.set(0, 0);\n svgTransform.position.set(node.width / 2, node.height / 2);\n svgTransform.pivot.set(node.width / 2, node.height / 2);\n svgTransform.rotation = rad;\n svgTransform.scale.set(flip.sx, flip.sy);\n } else {\n if (shape.parent !== container) container.addChildAt(shape, Math.min(2, container.children.length));\n if (icon.parent !== container) container.addChild(icon);\n if (handles.parent !== container) container.addChild(handles);\n svgTransform.rotation = 0;\n svgTransform.scale.set(1, 1);\n container.position.set(node.position.x, node.position.y);\n container.rotation = rad;\n container.scale.set(flip.sx, flip.sy);\n if (rad !== 0 || flip.sx < 0 || flip.sy < 0) {\n container.pivot.set(node.width / 2, node.height / 2);\n container.position.set(node.position.x + node.width / 2, node.position.y + node.height / 2);\n } else {\n container.pivot.set(0, 0);\n }\n }\n\n const isMedia = node.type === 'mediaNode';\n const isAnchor = node.type === 'animAnchor';\n // Anchors only show their label/badge when the anchor itself is visible.\n let anchorVisible = true;\n\n // Redraw shape\n shape.clear();\n shape.alpha = 1;\n if (isSvg) {\n this._updateSvgNode(nc, node, selected);\n } else if (isMedia) {\n // Media nodes render their content in a synced DOM overlay; PixiJS only\n // draws the frame (background + border) beneath the overlay.\n icon.visible = false;\n const r = 6;\n const fill = node.data.fill && node.data.fill !== 'transparent' ? resolveThemeColor(node.data.fill, '#ffffff') : '#ffffff';\n const borderHidden = node.data.borderStyle === 'none';\n const perSideBorders = hasBorderSideOverrides(node.data, node.type);\n const strokeColor = selected ? '#3b82f6' : resolveThemeColor(node.data.stroke, '#374151');\n const strokeW = selected ? Math.max(node.data.strokeWidth, 2) : node.data.strokeWidth;\n shapeDrawers.shapeRoundRect(shape, {\n width: node.width,\n height: node.height,\n fill,\n stroke: perSideBorders ? 'transparent' : strokeColor,\n strokeWidth: strokeW,\n borderStyle: selected && borderHidden ? 'solid' : node.data.borderStyle,\n dashOffset: this._dynamicOffset,\n });\n if (perSideBorders) {\n // Independent sides replace the full outline; the selection ring is\n // drawn separately so selection stays visible.\n const stroke = resolveThemeColor(node.data.stroke, '#374151');\n if (selected) {\n shape.roundRect(0, 0, node.width, node.height, r).stroke({ color: '#3b82f6', width: Math.max(node.data.strokeWidth, 2) });\n }\n drawBorderSides(shape, 'mediaNode', node.width, node.height, node.data.borderSides, stroke, node.data.strokeWidth, borderHidden ? undefined : node.data.borderStyle, this._dynamicOffset);\n }\n } else if (isAnchor) {\n // Animation anchors are globally visible/hidden in editing mode. During\n // presentation the current frame remains as a subtle dashed overlay.\n icon.visible = false;\n const accent = '#8b5cf6';\n const presentation = options?.presentation ?? false;\n anchorVisible = presentation\n ? (options?.presentationAnchorVisible ?? false) && options?.presentationAnchorId === node.id\n : (options?.showAnchors ?? true);\n if (anchorVisible) {\n shape.alpha = presentation ? 0.48 : 0.9;\n shapeDrawers.shapeRoundRect(shape, {\n width: node.width,\n height: node.height,\n fill: 'transparent',\n stroke: selected && !presentation ? '#3b82f6' : accent,\n strokeWidth: selected && !presentation ? 2.5 : 1.5,\n borderStyle: 'dashed',\n });\n shape.circle(0, 0, presentation ? 11 : 13).fill({\n color: selected && !presentation ? '#3b82f6' : accent,\n });\n }\n animBadge.text = String(animSeq ?? '');\n animBadge.alpha = presentation ? 0.7 : 1;\n animBadge.visible = anchorVisible;\n animBadge.position.set(0, 0);\n } else {\n icon.visible = false;\n const drawFn = shapeDrawers[node.type] ?? shapeDrawers.shapeRect;\n // Independent per-side borders (top/right/bottom/left) replace the full\n // outline for shapes with four well-defined sides; the drawer then only\n // fills and the sides are stroked separately below.\n const perSideBorders = hasBorderSideOverrides(node.data, node.type);\n const borderHidden = node.data.borderStyle === 'none';\n const baseStroke = resolveThemeColor(node.data.stroke, '#374151');\n const opts: ShapeDrawOptions = {\n width: node.width,\n height: node.height,\n fill: resolveThemeColor(node.data.fill, '#ffffff'),\n stroke: perSideBorders ? 'transparent' : (selected ? '#3b82f6' : baseStroke),\n strokeWidth: selected ? Math.max(node.data.strokeWidth, 2) : node.data.strokeWidth,\n borderStyle: (selected && borderHidden) ? 'solid' : node.data.borderStyle,\n dashOffset: this._dynamicOffset,\n };\n drawFn(shape, opts);\n if (perSideBorders) {\n if (selected) {\n const r = node.type === 'shapeRect' ? 0 : Math.min(12, node.width * 0.1, node.height * 0.1);\n shape.roundRect(0, 0, node.width, node.height, r).stroke({ color: '#3b82f6', width: Math.max(node.data.strokeWidth, 2) });\n }\n const style = (selected && borderHidden) ? 'solid' : node.data.borderStyle;\n drawBorderSides(shape, node.type, node.width, node.height, node.data.borderSides, baseStroke, node.data.strokeWidth, style, this._dynamicOffset);\n }\n }\n if (!isAnchor) animBadge.visible = false;\n\n // Sub-canvas glow indicator\n subCanvasGlow.clear();\n if (node.data.childCanvasId) {\n const pad = 4;\n const w = node.width + pad * 2;\n const h = node.height + pad * 2;\n const pulse = 0.5 + 0.3 * Math.sin(this._dynamicOffset * 0.008);\n subCanvasGlow.roundRect(-pad, -pad, w, h, 6);\n subCanvasGlow.stroke({ color: 0x8b5cf6, width: 2.5, alpha: pulse });\n subCanvasGlow.roundRect(-pad - 2, -pad - 2, w + 4, h + 4, 8);\n subCanvasGlow.stroke({ color: 0x8b5cf6, width: 1, alpha: pulse * 0.4 });\n }\n\n // Text: counter-rotate and counter-flip so it stays upright\n const iconLabelPlacement = normalizeIconLabelPlacement(node.data.labelPlacement);\n // SVG-icon labels default to the theme font color (so they read on any\n // theme) even when the icon body uses a brand color; other node types fall\n // back to the node's own text color.\n const labelColor = node.data.labelColor\n ?? (isSvg ? 'theme:base-content' : node.data.color);\n updateNodeText(text, {\n label: isMedia\n ? ''\n : isSvg\n ? formatIconLabel(node.data.label, iconLabelPlacement)\n : node.data.label,\n color: resolveThemeColor(labelColor, '#111827'),\n fontSize: node.data.fontSize,\n width: node.width,\n height: node.height,\n });\n text.visible = !isMedia && (!isAnchor || anchorVisible);\n\n // Card extras: muted sublabel line + tag pill. Available on ordinary\n // shapes, group frames, and (when shown) animation anchors.\n const showExtras = !isSvg && !isMedia && (!isAnchor || anchorVisible);\n const sublabel = showExtras ? (node.data.sublabel ?? '').trim() : '';\n const hasSub = sublabel.length > 0;\n\n // Title icon: optional registry glyph beside the title text. Ordinary\n // shapes, group frames and (visible) anchors only — SVG stamps already are\n // an icon, and media nodes render their own overlay.\n const titleIconId = showExtras ? (node.data.titleIcon ?? '').trim() : '';\n const titleIconGap = 6;\n const titleIconSize = Math.max(8, node.data.titleIconSize ?? Math.round(node.data.fontSize * 1.15));\n const titleIconScale = titleIconSize / ICON_TEXTURE_SIZE;\n const hasTitleIcon = titleIconId.length > 0;\n if (hasTitleIcon) {\n // Default tint keeps each icon's own identity: brand icons (languages /\n // frameworks) use their authentic brandColor, everything else follows the\n // title color (a theme token, so it tracks theme switches). An explicit\n // `titleIconColor` always wins.\n const fallbackTint = getSvgIcon(titleIconId)?.brandColor ?? labelColor;\n const tint = resolveThemeColor(node.data.titleIconColor ?? fallbackTint, '#111827') || '#111827';\n const tex = getIconTexture(titleIconId, tint);\n if (tex) {\n titleIcon.texture = tex;\n titleIcon.visible = true;\n } else {\n titleIcon.visible = false;\n }\n } else {\n titleIcon.visible = false;\n }\n\n if (node.type === 'shapeCorner') {\n titleIcon.visible = false;\n const half = Math.min(node.width, node.height) * 0.4;\n text.position.set((half + node.width) / 2, (half + node.height) / 2);\n } else if (isSvg) {\n const gap = 4;\n const halfTextWidth = text.width / 2;\n const halfTextHeight = text.height / 2;\n if (iconLabelPlacement === 'top') {\n text.position.set(node.width / 2, -gap - halfTextHeight);\n } else if (iconLabelPlacement === 'bottom') {\n text.position.set(node.width / 2, node.height + gap + halfTextHeight);\n } else if (iconLabelPlacement === 'left') {\n text.position.set(-gap - halfTextWidth, node.height / 2);\n } else if (iconLabelPlacement === 'right') {\n text.position.set(node.width + gap + halfTextWidth, node.height / 2);\n } else {\n text.position.set(node.width / 2, node.height / 2);\n }\n text.position.x += node.data.labelOffsetX ?? 0;\n text.position.y += node.data.labelOffsetY ?? 0;\n } else if (!isMedia) {\n // Ordinary shapes, group frames, and anchors: 9-way text placement, with\n // an optional sublabel line stacked beneath the title. Both lines stay\n // center-anchored; only the block's center point moves per placement.\n const gap = 3;\n const subFont = Math.max(9, node.data.fontSize - 3);\n if (hasSub) {\n const s = subtext.style as TextStyle;\n s.fill = resolveThemeColor(node.data.sublabelColor ?? '#94a3b8', '#94a3b8');\n s.fontSize = subFont;\n subtext.text = sublabel;\n subtext.anchor.set(0.5, 0.5);\n subtext.visible = true;\n subtext.rotation = -rad;\n subtext.scale.set(flip.sx, flip.sy);\n }\n text.anchor.set(0.5, 0.5);\n // Title + optional icon form a horizontally centered group; a sublabel\n // (when present) stays centered under the whole block.\n const iconInBlock = titleIcon.visible;\n const titleGroupW = text.width + (iconInBlock ? titleIconSize + titleIconGap : 0);\n const blockH = Math.max(text.height, iconInBlock ? titleIconSize : 0) + (hasSub ? gap + subtext.height : 0);\n const blockW = Math.max(titleGroupW, hasSub ? subtext.width : 0);\n // Anchors and group frames default their text to the top band; ordinary\n // shapes default to centered.\n const isGroupFrame = node.data.isGroup || node.type === 'flowGroup' || node.type === 'geometryGroup';\n const placement = node.data.textPlacement ?? (isAnchor || isGroupFrame ? 'top' : 'center');\n const c = resolveTextBlockCenter(\n placement,\n node.width,\n node.height,\n blockW,\n blockH,\n );\n const bx = c.x + (node.data.labelOffsetX ?? 0);\n const by = c.y + (node.data.labelOffsetY ?? 0);\n const titleCy = by - blockH / 2 + text.height / 2;\n if (iconInBlock) {\n const groupLeft = bx - titleGroupW / 2;\n if ((node.data.titleIconSide ?? 'left') === 'right') {\n text.position.set(groupLeft + text.width / 2, titleCy);\n titleIcon.position.set(groupLeft + text.width + titleIconGap + titleIconSize / 2, titleCy);\n } else {\n titleIcon.position.set(groupLeft + titleIconSize / 2, titleCy);\n text.position.set(groupLeft + titleIconSize + titleIconGap + text.width / 2, titleCy);\n }\n } else {\n text.position.set(bx, titleCy);\n }\n if (hasSub) subtext.position.set(bx, by + blockH / 2 - subtext.height / 2);\n }\n if (!hasSub) subtext.visible = false;\n\n if (isSvg) {\n text.rotation = 0;\n text.scale.set(1, 1);\n } else {\n text.rotation = -rad;\n text.scale.set(flip.sx, flip.sy);\n // Keep the glyph upright and mirrored with the title, like the text.\n // Scale (not width/height — those are scale setters in disguise and the\n // write here would clobber them) is derived from the texture's natural\n // raster size so `titleIconSize` stays authoritative.\n titleIcon.rotation = -rad;\n titleIcon.scale.set(titleIconScale * flip.sx, titleIconScale * flip.sy);\n }\n\n // Tag pill: placed in one of four corners with its own color.\n const tag = showExtras ? (node.data.tag ?? '').trim() : '';\n if (tag) {\n tagText.text = tag;\n tagText.visible = true;\n const padX = 5;\n const padY = 2;\n const tw = tagText.width + padX * 2;\n const th = tagText.height + padY * 2;\n const margin = 6;\n const placement = node.data.tagPlacement ?? 'top-right';\n const tx = placement === 'top-left' || placement === 'bottom-left'\n ? margin\n : node.width - tw - margin;\n const ty = placement === 'top-left' || placement === 'top-right'\n ? margin\n : node.height - th - margin;\n const tagColor = resolveThemeColor(node.data.tagColor ?? node.data.stroke, '#334155');\n tagBg.clear();\n tagBg\n .roundRect(tx, ty, tw, th, 3)\n .fill({ color: tagColor, alpha: 0.18 })\n .stroke({ color: tagColor, width: 1, alpha: 0.55 });\n tagBg.visible = true;\n (tagText.style as TextStyle).fill = tagColor;\n tagText.position.set(tx + tw / 2, ty + th / 2);\n tagText.rotation = 0;\n tagText.scale.set(1, 1);\n } else {\n tagBg.visible = false;\n tagText.visible = false;\n }\n\n // Handles visibility\n handles.visible = (hovered || selected) && !(isAnchor && options?.presentation);\n if (handles.visible) {\n const defs = getHandlePositions(node.width, node.height);\n nc.handleDefs = defs;\n updateHandleGraphics(handles, defs);\n }\n }\n\n /**\n * Draw an SVG-stamp node: optional bordered box + centered icon sprite.\n * The icon texture is rasterized lazily; a redraw is requested when ready.\n */\n private _updateSvgNode(nc: NodeContainer, node: FlowNode, selected: boolean): void {\n const { shape, icon } = nc;\n const { width, height, data } = node;\n\n // Render the configured icon box independently from the solid selection\n // outline. Otherwise selecting an icon makes dotted/dashed borders appear\n // solid and hides whether the chosen border style is actually applied.\n const showBox = data.iconBox ?? false;\n const borderStyle = data.borderStyle ?? (showBox ? 'solid' : 'none');\n const borderHidden = borderStyle === 'none';\n const r = Math.min(8, width * 0.12, height * 0.12);\n if (data.fill && data.fill !== 'transparent' && (showBox || !borderHidden)) {\n shape.roundRect(0, 0, width, height, r).fill({ color: resolveThemeColor(data.fill, '#ffffff') });\n }\n if (!borderHidden && data.stroke && data.stroke !== 'transparent' && data.strokeWidth > 0) {\n drawRoundRectBorder(shape, {\n width,\n height,\n fill: 'transparent',\n stroke: resolveThemeColor(data.stroke, '#374151'),\n strokeWidth: data.strokeWidth,\n borderStyle,\n dashOffset: this._dynamicOffset,\n }, r);\n }\n if (selected) {\n shape.roundRect(0, 0, width, height, r).stroke({\n color: '#3b82f6',\n width: Math.max(data.strokeWidth, 2),\n });\n }\n\n // Icon sprite: fit within the box with padding, centered. The texture is\n // rasterized lazily; because the ticker re-runs sync every frame, it will\n // be picked up automatically once cached.\n const iconColor = resolveThemeColor(data.color, '#334155') || '#334155';\n const tex = getIconTexture(data.svgId ?? '', iconColor);\n if (tex) {\n icon.texture = tex;\n icon.visible = true;\n const pad = showBox ? Math.min(width, height) * 0.18 : 2;\n const box = Math.max(4, Math.min(width, height) - pad * 2);\n icon.width = box;\n icon.height = box;\n icon.position.set(width / 2, height / 2);\n } else {\n icon.visible = false;\n }\n }\n\n destroy(): void {\n this._unsubscribeTextureReady();\n this._resync = null;\n for (const [, nc] of this.nodes) {\n nc.container.destroy({ children: true });\n }\n this.nodes.clear();\n this.nodeLayer.destroy({ children: true });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"NodeRenderer.js","names":["resolveTextBlockCenter","left","blockW","right","nodeW","cx","top","blockH","bottom","nodeH","cy","placement","NodeRenderer","deltaMs","world","Container","subscribeIconTextureReady","seen","flowNodes","selectedIds","hoveredId","animSeqMap","options","seqOf","anchors","n","a","b","m","i","node","nc","id","container","svgTransform","subCanvasGlow","Graphics","shape","icon","Sprite","text","createNodeText","resolveThemeColor","titleIcon","subtext","Text","tagBg","tagText","handleDefs","getHandlePositions","handles","createHandleGraphics","animBadge","isSvg","rad","getRotationRad","flip","getFlipScale","isMedia","isAnchor","anchorVisible","selected","fill","borderHidden","perSideBorders","hasBorderSideOverrides","strokeColor","strokeW","shapeDrawers","stroke","drawBorderSides","accent","presentation","animSeq","drawFn","baseStroke","r","style","w","h","pulse","iconLabelPlacement","normalizeIconLabelPlacement","labelColor","updateNodeText","formatIconLabel","showExtras","sublabel","hasSub","titleIconId","titleIconSize","titleIconScale","fallbackTint","getSvgIcon","tint","tex","getIconTexture","half","halfTextWidth","halfTextHeight","subFont","s","iconInBlock","titleGroupW","isGroupFrame","bx","by","titleCy","groupLeft","tag","tw","th","tx","ty","tagColor","hovered","defs","updateHandleGraphics","showBox","data","borderStyle","width","height","drawRoundRectBorder","iconColor","pad","box"],"sources":["../../../../../src/components/pro/Flow/nodes/NodeRenderer.ts"],"sourcesContent":["import { Container, Graphics, Text, Sprite, TextStyle } from 'pixi.js';\nimport type { FlowNode, FlowTextPlacement } from '../types';\nimport { drawRoundRectBorder, shapeDrawers, hasBorderSideOverrides, drawBorderSides, type ShapeDrawOptions } from './shapes';\nimport { createNodeText, formatIconLabel, normalizeIconLabelPlacement, updateNodeText } from './NodeText';\nimport { getHandlePositions, createHandleGraphics, updateHandleGraphics, type HandleDef } from './NodeHandles';\nimport { getRotationRad, getFlipScale } from './nodeUtils';\nimport { getIconTexture, subscribeIconTextureReady, ICON_TEXTURE_SIZE } from './svgIconTexture';\nimport { getSvgIcon } from './svgIcons';\nimport { resolveThemeColor } from '../utils/themeColor';\n\n/**\n * Resolve the center point of a text block (title + optional sublabel) inside a\n * node of the given size, for a 9-way placement. Non-center placements pull the\n * block against the corresponding edge/corner with a small inset.\n */\nfunction resolveTextBlockCenter(\n placement: FlowTextPlacement,\n nodeW: number,\n nodeH: number,\n blockW: number,\n blockH: number,\n): { x: number; y: number } {\n const inset = 6;\n const left = inset + blockW / 2;\n const right = nodeW - inset - blockW / 2;\n const cx = nodeW / 2;\n const top = inset + blockH / 2;\n const bottom = nodeH - inset - blockH / 2;\n const cy = nodeH / 2;\n switch (placement) {\n case 'top': return { x: cx, y: top };\n case 'bottom': return { x: cx, y: bottom };\n case 'left': return { x: left, y: cy };\n case 'right': return { x: right, y: cy };\n case 'top-left': return { x: left, y: top };\n case 'top-right': return { x: right, y: top };\n case 'bottom-left': return { x: left, y: bottom };\n case 'bottom-right': return { x: right, y: bottom };\n case 'center':\n default:\n return { x: cx, y: cy };\n }\n}\n\nexport interface NodeContainer {\n container: Container;\n shape: Graphics;\n /** Rotated/flipped visual content for SVG icon stamps. */\n svgTransform: Container;\n icon: Sprite;\n text: Text;\n /** Optional registry glyph rendered beside the title text (left/right). */\n titleIcon: Sprite;\n /** Optional muted second line (Archify card sublabel). */\n subtext: Text;\n /** Optional top-right pill background + label (Archify card tag). */\n tagBg: Graphics;\n tagText: Text;\n /** Sequence badge shown on `animAnchor` nodes. */\n animBadge: Text;\n handles: Graphics;\n handleDefs: HandleDef[];\n nodeId: string;\n subCanvasGlow: Graphics;\n}\n\n/**\n * Manages PixiJS display objects for all nodes on the current canvas.\n * Syncs from FlowNode[] data → PixiJS scene graph.\n */\nexport class NodeRenderer {\n private world: Container;\n private nodeLayer: Container;\n private nodes = new Map<string, NodeContainer>();\n private _dynamicOffset = 0;\n private _unsubscribeTextureReady: () => void;\n\n /** Call once per frame to advance the dynamic dash animation. */\n advanceAnimation(deltaMs: number): void {\n this._dynamicOffset = (this._dynamicOffset + deltaMs * 0.06) % 1000;\n }\n\n constructor(world: Container) {\n this.world = world;\n this.nodeLayer = new Container({ label: 'nodes', sortableChildren: true });\n this.world.addChild(this.nodeLayer);\n this._unsubscribeTextureReady = subscribeIconTextureReady(() => this._resync?.());\n }\n\n private _resync: (() => void) | null = null;\n\n /**\n * Reconcile PixiJS containers with the current node data.\n * Creates new containers, updates existing, removes stale.\n */\n sync(\n flowNodes: FlowNode[],\n selectedIds: Set<string>,\n hoveredId: string | null,\n animSeqMap?: Map<string, number>,\n options?: {\n showAnchors?: boolean;\n presentation?: boolean;\n presentationAnchorId?: string;\n presentationAnchorVisible?: boolean;\n },\n ): void {\n const seen = new Set<string>();\n this._resync = () => this.sync(flowNodes, selectedIds, hoveredId, animSeqMap, options);\n\n // Animation-anchor display sequence (1-based). When a global map is\n // provided (document-wide, shared across sub-canvases) it is used directly;\n // otherwise derive a per-canvas order from stable creation index.\n const seqOf = animSeqMap ?? (() => {\n const anchors = flowNodes\n .filter((n) => n.type === 'animAnchor')\n .sort((a, b) => (a.data.animIndex ?? 0) - (b.data.animIndex ?? 0));\n const m = new Map<string, number>();\n anchors.forEach((n, i) => m.set(n.id, i + 1));\n return m;\n })();\n\n for (const node of flowNodes) {\n seen.add(node.id);\n let nc = this.nodes.get(node.id);\n if (!nc) {\n nc = this._createNode(node);\n this.nodes.set(node.id, nc);\n }\n this._updateNode(\n nc,\n node,\n selectedIds.has(node.id),\n hoveredId === node.id,\n seqOf.get(node.id),\n options,\n );\n }\n\n // Remove nodes no longer in data\n for (const [id, nc] of this.nodes) {\n if (!seen.has(id)) {\n this.nodeLayer.removeChild(nc.container);\n nc.container.destroy({ children: true });\n this.nodes.delete(id);\n }\n }\n }\n\n getNodeContainer(id: string): NodeContainer | undefined {\n return this.nodes.get(id);\n }\n\n getAllNodeContainers(): NodeContainer[] {\n return [...this.nodes.values()];\n }\n\n private _createNode(node: FlowNode): NodeContainer {\n const container = new Container({ label: `node-${node.id}` });\n container.eventMode = 'static';\n container.cursor = 'pointer';\n\n const svgTransform = new Container({ label: 'svg-transform' });\n container.addChild(svgTransform);\n\n const subCanvasGlow = new Graphics();\n subCanvasGlow.label = 'sub-canvas-glow';\n container.addChild(subCanvasGlow);\n\n const shape = new Graphics();\n shape.label = 'shape';\n container.addChild(shape);\n\n const icon = new Sprite();\n icon.label = 'svg-icon';\n icon.anchor.set(0.5, 0.5);\n icon.visible = false;\n container.addChild(icon);\n\n const text = createNodeText({\n label: node.data.label,\n color: resolveThemeColor(node.data.labelColor ?? node.data.color, '#111827'),\n fontSize: node.data.fontSize,\n width: node.width,\n height: node.height,\n });\n container.addChild(text);\n\n // Optional glyph beside the title (lazy-rasterized registry texture).\n const titleIcon = new Sprite();\n titleIcon.label = 'node-title-icon';\n titleIcon.anchor.set(0.5, 0.5);\n titleIcon.visible = false;\n container.addChild(titleIcon);\n\n // Optional muted second line (Archify card sublabel).\n const subtext = new Text({\n text: '',\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n fontSize: 11,\n fill: '#94a3b8',\n align: 'center',\n },\n });\n subtext.label = 'node-subtext';\n subtext.anchor.set(0.5, 0.5);\n subtext.visible = false;\n container.addChild(subtext);\n\n // Optional top-right pill (Archify card tag).\n const tagBg = new Graphics();\n tagBg.label = 'node-tag-bg';\n tagBg.visible = false;\n container.addChild(tagBg);\n const tagText = new Text({\n text: '',\n style: {\n fontFamily: 'system-ui, -apple-system, sans-serif',\n fontSize: 9,\n fill: '#cbd5e1',\n },\n });\n tagText.label = 'node-tag-text';\n tagText.anchor.set(0.5, 0.5);\n tagText.visible = false;\n container.addChild(tagText);\n\n const handleDefs = getHandlePositions(node.width, node.height);\n const handles = createHandleGraphics(container, handleDefs, false);\n\n // Sequence badge for animation anchors (top-left corner).\n const animBadge = new Text({\n text: '',\n style: { fontFamily: 'system-ui, sans-serif', fontSize: 13, fill: '#ffffff', fontWeight: '700' },\n });\n animBadge.label = 'anim-badge';\n animBadge.anchor.set(0.5, 0.5);\n animBadge.visible = false;\n container.addChild(animBadge);\n\n this.nodeLayer.addChild(container);\n\n return { container, shape, svgTransform, icon, text, titleIcon, subtext, tagBg, tagText, animBadge, handles, handleDefs, nodeId: node.id, subCanvasGlow };\n }\n\n private _updateNode(\n nc: NodeContainer,\n node: FlowNode,\n selected: boolean,\n hovered: boolean,\n animSeq?: number,\n options?: {\n showAnchors?: boolean;\n presentation?: boolean;\n presentationAnchorId?: string;\n presentationAnchorVisible?: boolean;\n },\n ): void {\n const { container, svgTransform, shape, icon, text, titleIcon, subtext, tagBg, tagText, animBadge, handles, subCanvasGlow } = nc;\n\n const isSvg = node.type === 'shapeSvg';\n const rad = getRotationRad(node.data);\n const flip = getFlipScale(node.data);\n\n container.zIndex = node.zIndex ?? 0;\n if (isSvg) {\n // Keep the label in the untransformed root. Only the icon box, icon and\n // connection handles rotate/flip around the node center.\n if (shape.parent !== svgTransform) svgTransform.addChild(shape);\n if (icon.parent !== svgTransform) svgTransform.addChild(icon);\n if (handles.parent !== svgTransform) svgTransform.addChild(handles);\n container.position.set(node.position.x, node.position.y);\n container.rotation = 0;\n container.scale.set(1, 1);\n container.pivot.set(0, 0);\n svgTransform.position.set(node.width / 2, node.height / 2);\n svgTransform.pivot.set(node.width / 2, node.height / 2);\n svgTransform.rotation = rad;\n svgTransform.scale.set(flip.sx, flip.sy);\n } else {\n if (shape.parent !== container) container.addChildAt(shape, Math.min(2, container.children.length));\n if (icon.parent !== container) container.addChild(icon);\n if (handles.parent !== container) container.addChild(handles);\n svgTransform.rotation = 0;\n svgTransform.scale.set(1, 1);\n container.position.set(node.position.x, node.position.y);\n container.rotation = rad;\n container.scale.set(flip.sx, flip.sy);\n if (rad !== 0 || flip.sx < 0 || flip.sy < 0) {\n container.pivot.set(node.width / 2, node.height / 2);\n container.position.set(node.position.x + node.width / 2, node.position.y + node.height / 2);\n } else {\n container.pivot.set(0, 0);\n }\n }\n\n const isMedia = node.type === 'mediaNode';\n const isAnchor = node.type === 'animAnchor';\n // Anchors only show their label/badge when the anchor itself is visible.\n let anchorVisible = true;\n\n // Redraw shape\n shape.clear();\n shape.alpha = 1;\n if (isSvg) {\n this._updateSvgNode(nc, node, selected);\n } else if (isMedia) {\n // Media nodes render their content in a synced DOM overlay; PixiJS only\n // draws the frame (background + border) beneath the overlay.\n icon.visible = false;\n const r = 6;\n const fill = node.data.fill && node.data.fill !== 'transparent' ? resolveThemeColor(node.data.fill, '#ffffff') : '#ffffff';\n const borderHidden = node.data.borderStyle === 'none';\n const perSideBorders = hasBorderSideOverrides(node.data, node.type);\n const strokeColor = selected ? '#3b82f6' : resolveThemeColor(node.data.stroke, '#374151');\n const strokeW = selected ? Math.max(node.data.strokeWidth, 2) : node.data.strokeWidth;\n shapeDrawers.shapeRoundRect(shape, {\n width: node.width,\n height: node.height,\n fill,\n stroke: perSideBorders ? 'transparent' : strokeColor,\n strokeWidth: strokeW,\n borderStyle: selected && borderHidden ? 'solid' : node.data.borderStyle,\n dashOffset: this._dynamicOffset,\n });\n if (perSideBorders) {\n // Independent sides replace the full outline; the selection ring is\n // drawn separately so selection stays visible.\n const stroke = resolveThemeColor(node.data.stroke, '#374151');\n if (selected) {\n shape.roundRect(0, 0, node.width, node.height, r).stroke({ color: '#3b82f6', width: Math.max(node.data.strokeWidth, 2) });\n }\n drawBorderSides(shape, 'mediaNode', node.width, node.height, node.data.borderSides, stroke, node.data.strokeWidth, borderHidden ? undefined : node.data.borderStyle, this._dynamicOffset);\n }\n } else if (isAnchor) {\n // Animation anchors are globally visible/hidden in editing mode. During\n // presentation the current frame remains as a subtle dashed overlay.\n icon.visible = false;\n const accent = '#8b5cf6';\n const presentation = options?.presentation ?? false;\n anchorVisible = presentation\n ? (options?.presentationAnchorVisible ?? false) && options?.presentationAnchorId === node.id\n : (options?.showAnchors ?? true);\n if (anchorVisible) {\n shape.alpha = presentation ? 0.48 : 0.9;\n shapeDrawers.shapeRoundRect(shape, {\n width: node.width,\n height: node.height,\n fill: 'transparent',\n stroke: selected && !presentation ? '#3b82f6' : accent,\n strokeWidth: selected && !presentation ? 2.5 : 1.5,\n borderStyle: 'dashed',\n });\n shape.circle(0, 0, presentation ? 11 : 13).fill({\n color: selected && !presentation ? '#3b82f6' : accent,\n });\n }\n animBadge.text = String(animSeq ?? '');\n animBadge.alpha = presentation ? 0.7 : 1;\n animBadge.visible = anchorVisible;\n animBadge.position.set(0, 0);\n } else {\n icon.visible = false;\n const drawFn = shapeDrawers[node.type] ?? shapeDrawers.shapeRect;\n // Independent per-side borders (top/right/bottom/left) replace the full\n // outline for shapes with four well-defined sides; the drawer then only\n // fills and the sides are stroked separately below.\n const perSideBorders = hasBorderSideOverrides(node.data, node.type);\n const borderHidden = node.data.borderStyle === 'none';\n const baseStroke = resolveThemeColor(node.data.stroke, '#374151');\n const opts: ShapeDrawOptions = {\n width: node.width,\n height: node.height,\n fill: resolveThemeColor(node.data.fill, '#ffffff'),\n stroke: perSideBorders ? 'transparent' : (selected ? '#3b82f6' : baseStroke),\n strokeWidth: selected ? Math.max(node.data.strokeWidth, 2) : node.data.strokeWidth,\n borderStyle: (selected && borderHidden) ? 'solid' : node.data.borderStyle,\n dashOffset: this._dynamicOffset,\n };\n drawFn(shape, opts);\n if (perSideBorders) {\n if (selected) {\n const r = node.type === 'shapeRect' ? 0 : Math.min(12, node.width * 0.1, node.height * 0.1);\n shape.roundRect(0, 0, node.width, node.height, r).stroke({ color: '#3b82f6', width: Math.max(node.data.strokeWidth, 2) });\n }\n const style = (selected && borderHidden) ? 'solid' : node.data.borderStyle;\n drawBorderSides(shape, node.type, node.width, node.height, node.data.borderSides, baseStroke, node.data.strokeWidth, style, this._dynamicOffset);\n }\n }\n if (!isAnchor) animBadge.visible = false;\n\n // Sub-canvas glow indicator\n subCanvasGlow.clear();\n if (node.data.childCanvasId) {\n const pad = 4;\n const w = node.width + pad * 2;\n const h = node.height + pad * 2;\n const pulse = 0.5 + 0.3 * Math.sin(this._dynamicOffset * 0.008);\n subCanvasGlow.roundRect(-pad, -pad, w, h, 6);\n subCanvasGlow.stroke({ color: 0x8b5cf6, width: 2.5, alpha: pulse });\n subCanvasGlow.roundRect(-pad - 2, -pad - 2, w + 4, h + 4, 8);\n subCanvasGlow.stroke({ color: 0x8b5cf6, width: 1, alpha: pulse * 0.4 });\n }\n\n // Text: counter-rotate and counter-flip so it stays upright\n const iconLabelPlacement = normalizeIconLabelPlacement(node.data.labelPlacement);\n // SVG-icon labels default to the theme font color (so they read on any\n // theme) even when the icon body uses a brand color; other node types fall\n // back to the node's own text color.\n const labelColor = node.data.labelColor\n ?? (isSvg ? 'theme:base-content' : node.data.color);\n updateNodeText(text, {\n label: isMedia\n ? ''\n : isSvg\n ? formatIconLabel(node.data.label, iconLabelPlacement)\n : node.data.label,\n color: resolveThemeColor(labelColor, '#111827'),\n fontSize: node.data.fontSize,\n width: node.width,\n height: node.height,\n });\n text.visible = !isMedia && (!isAnchor || anchorVisible);\n\n // Card extras: muted sublabel line + tag pill. Available on ordinary\n // shapes, group frames, and (when shown) animation anchors.\n const showExtras = !isSvg && !isMedia && (!isAnchor || anchorVisible);\n const sublabel = showExtras ? (node.data.sublabel ?? '').trim() : '';\n const hasSub = sublabel.length > 0;\n\n // Title icon: optional registry glyph beside the title text. Ordinary\n // shapes, group frames and (visible) anchors only — SVG stamps already are\n // an icon, and media nodes render their own overlay.\n const titleIconId = showExtras ? (node.data.titleIcon ?? '').trim() : '';\n const titleIconGap = 6;\n const titleIconSize = Math.max(8, node.data.titleIconSize ?? Math.round(node.data.fontSize * 1.15));\n const titleIconScale = titleIconSize / ICON_TEXTURE_SIZE;\n const hasTitleIcon = titleIconId.length > 0;\n if (hasTitleIcon) {\n // Default tint keeps each icon's own identity: brand icons (languages /\n // frameworks) use their authentic brandColor, everything else follows the\n // title color (a theme token, so it tracks theme switches). An explicit\n // `titleIconColor` always wins.\n const fallbackTint = getSvgIcon(titleIconId)?.brandColor ?? labelColor;\n const tint = resolveThemeColor(node.data.titleIconColor ?? fallbackTint, '#111827') || '#111827';\n const tex = getIconTexture(titleIconId, tint);\n if (tex) {\n titleIcon.texture = tex;\n titleIcon.visible = true;\n } else {\n titleIcon.visible = false;\n }\n } else {\n titleIcon.visible = false;\n }\n\n if (node.type === 'shapeCorner') {\n titleIcon.visible = false;\n const half = Math.min(node.width, node.height) * 0.4;\n text.position.set((half + node.width) / 2, (half + node.height) / 2);\n } else if (isSvg) {\n const gap = 4;\n const halfTextWidth = text.width / 2;\n const halfTextHeight = text.height / 2;\n if (iconLabelPlacement === 'top') {\n text.position.set(node.width / 2, -gap - halfTextHeight);\n } else if (iconLabelPlacement === 'bottom') {\n text.position.set(node.width / 2, node.height + gap + halfTextHeight);\n } else if (iconLabelPlacement === 'left') {\n text.position.set(-gap - halfTextWidth, node.height / 2);\n } else if (iconLabelPlacement === 'right') {\n text.position.set(node.width + gap + halfTextWidth, node.height / 2);\n } else {\n text.position.set(node.width / 2, node.height / 2);\n }\n text.position.x += node.data.labelOffsetX ?? 0;\n text.position.y += node.data.labelOffsetY ?? 0;\n } else if (!isMedia) {\n // Ordinary shapes, group frames, and anchors: 9-way text placement, with\n // an optional sublabel line stacked beneath the title. Both lines stay\n // center-anchored; only the block's center point moves per placement.\n const gap = 3;\n const subFont = Math.max(9, node.data.fontSize - 3);\n if (hasSub) {\n const s = subtext.style as TextStyle;\n s.fill = resolveThemeColor(node.data.sublabelColor ?? '#94a3b8', '#94a3b8');\n s.fontSize = subFont;\n subtext.text = sublabel;\n subtext.anchor.set(0.5, 0.5);\n subtext.visible = true;\n subtext.rotation = -rad;\n subtext.scale.set(flip.sx, flip.sy);\n }\n text.anchor.set(0.5, 0.5);\n // Title + optional icon form a horizontally centered group; a sublabel\n // (when present) stays centered under the whole block.\n const iconInBlock = titleIcon.visible;\n const titleGroupW = text.width + (iconInBlock ? titleIconSize + titleIconGap : 0);\n const blockH = Math.max(text.height, iconInBlock ? titleIconSize : 0) + (hasSub ? gap + subtext.height : 0);\n const blockW = Math.max(titleGroupW, hasSub ? subtext.width : 0);\n // Anchors and group frames default their text to the top band; ordinary\n // shapes default to centered.\n const isGroupFrame = node.data.isGroup || node.type === 'flowGroup' || node.type === 'geometryGroup';\n const placement = node.data.textPlacement ?? (isAnchor || isGroupFrame ? 'top' : 'center');\n const c = resolveTextBlockCenter(\n placement,\n node.width,\n node.height,\n blockW,\n blockH,\n );\n const bx = c.x + (node.data.labelOffsetX ?? 0);\n const by = c.y + (node.data.labelOffsetY ?? 0);\n const titleCy = by - blockH / 2 + text.height / 2;\n if (iconInBlock) {\n const groupLeft = bx - titleGroupW / 2;\n if ((node.data.titleIconSide ?? 'left') === 'right') {\n text.position.set(groupLeft + text.width / 2, titleCy);\n titleIcon.position.set(groupLeft + text.width + titleIconGap + titleIconSize / 2, titleCy);\n } else {\n titleIcon.position.set(groupLeft + titleIconSize / 2, titleCy);\n text.position.set(groupLeft + titleIconSize + titleIconGap + text.width / 2, titleCy);\n }\n } else {\n text.position.set(bx, titleCy);\n }\n if (hasSub) subtext.position.set(bx, by + blockH / 2 - subtext.height / 2);\n }\n if (!hasSub) subtext.visible = false;\n\n if (isSvg) {\n text.rotation = 0;\n text.scale.set(1, 1);\n } else {\n text.rotation = -rad;\n text.scale.set(flip.sx, flip.sy);\n // Keep the glyph upright and mirrored with the title, like the text.\n // Scale (not width/height — those are scale setters in disguise and the\n // write here would clobber them) is derived from the texture's natural\n // raster size so `titleIconSize` stays authoritative.\n titleIcon.rotation = -rad;\n titleIcon.scale.set(titleIconScale * flip.sx, titleIconScale * flip.sy);\n }\n\n // Tag pill: placed in one of four corners with its own color.\n const tag = showExtras ? (node.data.tag ?? '').trim() : '';\n if (tag) {\n tagText.text = tag;\n tagText.visible = true;\n const padX = 5;\n const padY = 2;\n const tw = tagText.width + padX * 2;\n const th = tagText.height + padY * 2;\n const margin = 6;\n const placement = node.data.tagPlacement ?? 'top-right';\n const tx = placement === 'top-left' || placement === 'bottom-left'\n ? margin\n : node.width - tw - margin;\n const ty = placement === 'top-left' || placement === 'top-right'\n ? margin\n : node.height - th - margin;\n const tagColor = resolveThemeColor(node.data.tagColor ?? node.data.stroke, '#334155');\n tagBg.clear();\n tagBg\n .roundRect(tx, ty, tw, th, 3)\n .fill({ color: tagColor, alpha: 0.18 })\n .stroke({ color: tagColor, width: 1, alpha: 0.55 });\n tagBg.visible = true;\n (tagText.style as TextStyle).fill = tagColor;\n tagText.position.set(tx + tw / 2, ty + th / 2);\n tagText.rotation = 0;\n tagText.scale.set(1, 1);\n } else {\n tagBg.visible = false;\n tagText.visible = false;\n }\n\n // Handles visibility\n handles.visible = (hovered || selected) && !(isAnchor && options?.presentation);\n if (handles.visible) {\n const defs = getHandlePositions(node.width, node.height);\n nc.handleDefs = defs;\n updateHandleGraphics(handles, defs);\n }\n }\n\n /**\n * Draw an SVG-stamp node: optional bordered box + centered icon sprite.\n * The icon texture is rasterized lazily; a redraw is requested when ready.\n */\n private _updateSvgNode(nc: NodeContainer, node: FlowNode, selected: boolean): void {\n const { shape, icon } = nc;\n const { width, height, data } = node;\n\n // Render the configured icon box independently from the solid selection\n // outline. Otherwise selecting an icon makes dotted/dashed borders appear\n // solid and hides whether the chosen border style is actually applied.\n const showBox = data.iconBox ?? false;\n const borderStyle = data.borderStyle ?? (showBox ? 'solid' : 'none');\n const borderHidden = borderStyle === 'none';\n const r = Math.min(8, width * 0.12, height * 0.12);\n if (data.fill && data.fill !== 'transparent' && (showBox || !borderHidden)) {\n shape.roundRect(0, 0, width, height, r).fill({ color: resolveThemeColor(data.fill, '#ffffff') });\n }\n if (!borderHidden && data.stroke && data.stroke !== 'transparent' && data.strokeWidth > 0) {\n drawRoundRectBorder(shape, {\n width,\n height,\n fill: 'transparent',\n stroke: resolveThemeColor(data.stroke, '#374151'),\n strokeWidth: data.strokeWidth,\n borderStyle,\n dashOffset: this._dynamicOffset,\n }, r);\n }\n if (selected) {\n shape.roundRect(0, 0, width, height, r).stroke({\n color: '#3b82f6',\n width: Math.max(data.strokeWidth, 2),\n });\n }\n\n // Icon sprite: fit within the box with padding, centered. The texture is\n // rasterized lazily; because the ticker re-runs sync every frame, it will\n // be picked up automatically once cached.\n const iconColor = resolveThemeColor(data.color, '#334155') || '#334155';\n const tex = getIconTexture(data.svgId ?? '', iconColor);\n if (tex) {\n icon.texture = tex;\n icon.visible = true;\n const pad = showBox ? Math.min(width, height) * 0.18 : 2;\n const box = Math.max(4, Math.min(width, height) - pad * 2);\n icon.width = box;\n icon.height = box;\n icon.position.set(width / 2, height / 2);\n } else {\n icon.visible = false;\n }\n }\n\n destroy(): void {\n this._unsubscribeTextureReady();\n this._resync = null;\n for (const [, nc] of this.nodes) {\n nc.container.destroy({ children: true });\n }\n this.nodes.clear();\n this.nodeLayer.destroy({ children: true });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"NodeText.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/NodeText.ts"],"sourcesContent":["import { Text, TextStyle, Container } from 'pixi.js';\nimport type { FlowIconLabelPlacement } from '../types';\n\nexport interface NodeTextOptions {\n label: string;\n color: string;\n fontSize: number;\n width: number;\n height: number;\n fontFamily?: string;\n}\n\n/**\n * Creates or updates a PixiJS Text object for a node label,\n * centered within the node bounds.\n */\nexport function createNodeText(opts: NodeTextOptions): Text {\n const style = new TextStyle({\n fontFamily: opts.fontFamily ?? 'system-ui, -apple-system, sans-serif',\n fontSize: opts.fontSize,\n fill: opts.color,\n align: 'center',\n wordWrap: true,\n wordWrapWidth: opts.width - 16,\n });\n\n const text = new Text({ text: opts.label, style });\n text.anchor.set(0.5, 0.5);\n text.position.set(opts.width / 2, opts.height / 2);\n text.label = 'node-text';\n return text;\n}\n\nexport function normalizeIconLabelPlacement(value: unknown): FlowIconLabelPlacement {\n if (value === 'above') return 'top';\n if (value === 'center') return 'middle';\n if (value === 'below') return 'bottom';\n return value === 'top' || value === 'middle' || value === 'bottom' || value === 'left' || value === 'right'\n ? value\n : 'bottom';\n}\n\nexport function formatIconLabel(label: string, placement: FlowIconLabelPlacement): string {\n if (placement !== 'left' && placement !== 'right') return label;\n const segments = typeof Intl !== 'undefined' && 'Segmenter' in Intl\n ? Array.from(new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(label), item => item.segment)\n : Array.from(label);\n return segments.join('\\n');\n}\n\nexport function updateNodeText(text: Text, opts: NodeTextOptions): void {\n text.text = opts.label;\n const s = text.style as TextStyle;\n s.fontSize = opts.fontSize;\n s.fill = opts.color;\n s.wordWrapWidth = opts.width - 16;\n text.position.set(opts.width / 2, opts.height / 2);\n}\n"],"mappings":";;AAgBA,
|
|
1
|
+
{"version":3,"file":"NodeText.js","names":["createNodeText","style","TextStyle","opts","text","Text","normalizeIconLabelPlacement","value","formatIconLabel","placement","label","item","updateNodeText","s"],"sources":["../../../../../src/components/pro/Flow/nodes/NodeText.ts"],"sourcesContent":["import { Text, TextStyle, Container } from 'pixi.js';\nimport type { FlowIconLabelPlacement } from '../types';\n\nexport interface NodeTextOptions {\n label: string;\n color: string;\n fontSize: number;\n width: number;\n height: number;\n fontFamily?: string;\n}\n\n/**\n * Creates or updates a PixiJS Text object for a node label,\n * centered within the node bounds.\n */\nexport function createNodeText(opts: NodeTextOptions): Text {\n const style = new TextStyle({\n fontFamily: opts.fontFamily ?? 'system-ui, -apple-system, sans-serif',\n fontSize: opts.fontSize,\n fill: opts.color,\n align: 'center',\n wordWrap: true,\n wordWrapWidth: opts.width - 16,\n });\n\n const text = new Text({ text: opts.label, style });\n text.anchor.set(0.5, 0.5);\n text.position.set(opts.width / 2, opts.height / 2);\n text.label = 'node-text';\n return text;\n}\n\nexport function normalizeIconLabelPlacement(value: unknown): FlowIconLabelPlacement {\n if (value === 'above') return 'top';\n if (value === 'center') return 'middle';\n if (value === 'below') return 'bottom';\n return value === 'top' || value === 'middle' || value === 'bottom' || value === 'left' || value === 'right'\n ? value\n : 'bottom';\n}\n\nexport function formatIconLabel(label: string, placement: FlowIconLabelPlacement): string {\n if (placement !== 'left' && placement !== 'right') return label;\n const segments = typeof Intl !== 'undefined' && 'Segmenter' in Intl\n ? Array.from(new Intl.Segmenter(undefined, { granularity: 'grapheme' }).segment(label), item => item.segment)\n : Array.from(label);\n return segments.join('\\n');\n}\n\nexport function updateNodeText(text: Text, opts: NodeTextOptions): void {\n text.text = opts.label;\n const s = text.style as TextStyle;\n s.fontSize = opts.fontSize;\n s.fill = opts.color;\n s.wordWrapWidth = opts.width - 16;\n text.position.set(opts.width / 2, opts.height / 2);\n}\n"],"mappings":";;AAgBA,SAAgBA,EAAe,GAA6B;CAC1D,IAAMC,IAAQ,IAAIC,EAAU;EAC1B,YAAYC,EAAK,cAAc;EAC/B,UAAUA,EAAK;EACf,MAAMA,EAAK;EACX,OAAO;EACP,UAAU;EACV,eAAeA,EAAK,QAAQ;CAC9B,CAAC,GAEKC,IAAO,IAAIC,EAAK;EAAE,MAAMF,EAAK;EAAO;CAAM,CAAC;CAIjD,OAHAC,EAAK,OAAO,IAAI,IAAK,EAAG,GACxBA,EAAK,SAAS,IAAID,EAAK,QAAQ,GAAGA,EAAK,SAAS,CAAC,GACjD,EAAK,QAAQ,aACNC;AACT;AAEA,SAAgBE,EAA4B,GAAwC;CAIlF,OAHIC,MAAU,UAAgB,QAC1BA,MAAU,WAAiB,WAC3BA,MAAU,UAAgB,WACvBA,MAAU,SAASA,MAAU,YAAYA,MAAU,YAAYA,MAAU,UAAUA,MAAU,UAChGA,IACA;AACN;AAEA,SAAgBC,EAAgB,GAAe,GAA2C;CAKxF,OAJIC,MAAc,UAAUA,MAAc,UAAgBC,KACzC,OAAO,OAAS,OAAe,eAAe,OAC3D,MAAM,KAAK,IAAI,KAAK,UAAU,KAAA,GAAW,EAAE,aAAa,WAAW,CAAC,CAAC,CAAC,QAAQA,CAAK,IAAG,MAAQC,EAAK,OAAO,IAC1G,MAAM,KAAKD,CAAK,EAAA,CACJ,KAAK,IAAI;AAC3B;AAEA,SAAgBE,EAAe,GAAY,GAA6B;CACtE,EAAK,OAAOT,EAAK;CACjB,IAAMU,IAAIT,EAAK;CAIf,AAHA,EAAE,WAAWD,EAAK,UAClB,EAAE,OAAOA,EAAK,OACd,EAAE,gBAAgBA,EAAK,QAAQ,IAC/BC,EAAK,SAAS,IAAID,EAAK,QAAQ,GAAGA,EAAK,SAAS,CAAC;AACnD"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"aiClient.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/aiClient.ts"],"sourcesContent":["import type { FlowAIConfig } from '../types';\n\n/**\n * Minimal streaming chat client for the Flow AI media node. Supports\n * OpenAI-compatible `/chat/completions` and Anthropic `/messages` endpoints.\n *\n * Yields incremental text deltas. Callers accumulate them to render a live,\n * streaming response. This intentionally mirrors the RichTextEditor AIRewrite\n * streaming logic but is scoped to the Flow package (no tiptap dependency).\n */\nexport interface AIChatMessage {\n role: 'user' | 'assistant';\n content: string;\n}\n\n/**\n * Resolve the full request URL from a possibly-partial gateway.\n *\n * Users often paste a base URL (e.g. `https://api.moonshot.cn/v1`) instead of\n * the full endpoint, which then 404s. We append the protocol's default path\n * when the gateway doesn't already point at a completions/messages endpoint.\n */\nfunction resolveEndpoint(config: FlowAIConfig): string {\n const raw = (config.gateway || '').trim().replace(/\\/+$/, '');\n if (!raw) return raw;\n const path = config.protocol === 'anthropic' ? '/messages' : '/chat/completions';\n // Already a full endpoint.\n if (/\\/(chat\\/completions|messages|responses|completions)$/i.test(raw)) return raw;\n // Bare `/v1` (or version segment) base URL → append the default path.\n if (/\\/v\\d+$/i.test(raw)) return raw + path;\n // Host root with no path → assume `/v1` + default path.\n try {\n const u = new URL(raw);\n if (u.pathname === '' || u.pathname === '/') return raw + '/v1' + path;\n } catch { /* not a parseable URL; fall through */ }\n // Some other path was given; append the default endpoint segment.\n return raw + path;\n}\n\nasync function* streamOpenAI(\n config: FlowAIConfig,\n messages: ReadonlyArray<{ role: string; content: unknown }>,\n signal?: AbortSignal,\n): AsyncGenerator<string> {\n const body = JSON.stringify({\n model: config.model,\n messages: [\n ...(config.systemPrompt ? [{ role: 'system', content: config.systemPrompt }] : []),\n ...messages,\n ],\n stream: true,\n });\n\n const response = await fetch(resolveEndpoint(config), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...config.headers,\n },\n body,\n signal,\n });\n\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No response body');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed === 'data: [DONE]' || !trimmed.startsWith('data: ')) continue;\n try {\n const data = JSON.parse(trimmed.slice(6));\n const content = data.choices?.[0]?.delta?.content;\n if (content) yield content;\n } catch { /* skip malformed chunk */ }\n }\n }\n}\n\nasync function* streamAnthropic(\n config: FlowAIConfig,\n messages: ReadonlyArray<{ role: string; content: unknown }>,\n signal?: AbortSignal,\n): AsyncGenerator<string> {\n const body = JSON.stringify({\n model: config.model,\n max_tokens: 8192,\n ...(config.systemPrompt ? { system: config.systemPrompt } : {}),\n messages,\n stream: true,\n });\n\n const response = await fetch(resolveEndpoint(config), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { 'x-api-key': config.apiKey } : {}),\n 'anthropic-version': '2023-06-01',\n 'anthropic-dangerous-direct-browser-access': 'true',\n ...config.headers,\n },\n body,\n signal,\n });\n\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No response body');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith('data: ')) continue;\n try {\n const data = JSON.parse(trimmed.slice(6));\n if (data.type === 'content_block_delta' && data.delta?.text) yield data.delta.text;\n } catch { /* skip malformed chunk */ }\n }\n }\n}\n\nexport function streamAIChat(\n config: FlowAIConfig,\n messages: AIChatMessage[],\n signal: AbortSignal,\n): AsyncIterable<string> {\n if (config.protocol === 'anthropic') return streamAnthropic(config, messages, signal);\n return streamOpenAI(config, messages, signal);\n}\n\n/**\n * Non-streaming completion used by the AI flow generator. Accepts an optional\n * image (data URL) for \"reconstruct this diagram\" requests. Returns the full\n * assistant text.\n */\nexport async function completeAIChat(\n config: FlowAIConfig,\n userText: string,\n options?: { imageDataUrl?: string; systemPrompt?: string; signal?: AbortSignal },\n): Promise<string> {\n const system = options?.systemPrompt ?? config.systemPrompt;\n const image = options?.imageDataUrl;\n\n if (config.protocol === 'anthropic') {\n const content: any[] = [{ type: 'text', text: userText }];\n if (image) {\n const m = /^data:(.+?);base64,(.*)$/.exec(image);\n if (m) {\n content.unshift({\n type: 'image',\n source: { type: 'base64', media_type: m[1], data: m[2] },\n });\n }\n }\n const response = await fetch(resolveEndpoint(config), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { 'x-api-key': config.apiKey } : {}),\n 'anthropic-version': '2023-06-01',\n 'anthropic-dangerous-direct-browser-access': 'true',\n ...config.headers,\n },\n body: JSON.stringify({\n model: config.model,\n max_tokens: 4096,\n ...(system ? { system } : {}),\n messages: [{ role: 'user', content }],\n }),\n signal: options?.signal,\n });\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n const json = await response.json();\n return json.content?.map((c: any) => c.text ?? '').join('') ?? '';\n }\n\n // OpenAI-compatible (supports multimodal `image_url` content parts).\n const userContent: any = image\n ? [\n { type: 'text', text: userText },\n { type: 'image_url', image_url: { url: image } },\n ]\n : userText;\n const response = await fetch(resolveEndpoint(config), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...config.headers,\n },\n body: JSON.stringify({\n model: config.model,\n messages: [\n ...(system ? [{ role: 'system', content: system }] : []),\n { role: 'user', content: userContent },\n ],\n }),\n signal: options?.signal,\n });\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n const json = await response.json();\n return json.choices?.[0]?.message?.content ?? '';\n}\n\n/**\n * Streaming variant of `completeAIChat`: same multimodal request shape\n * (optional image + system prompt override), but the reply is consumed as SSE\n * deltas. `onDelta` fires with the accumulated text after every chunk so\n * callers can render the response live. Returns the full assistant text.\n */\nexport async function completeAIChatStream(\n config: FlowAIConfig,\n userText: string,\n options?: {\n imageDataUrl?: string;\n systemPrompt?: string;\n signal?: AbortSignal;\n onDelta?: (accumulated: string) => void;\n },\n): Promise<string> {\n const system = options?.systemPrompt ?? config.systemPrompt;\n const image = options?.imageDataUrl;\n\n let content: unknown = userText;\n if (config.protocol === 'anthropic') {\n const parts: any[] = [{ type: 'text', text: userText }];\n if (image) {\n const m = /^data:(.+?);base64,(.*)$/.exec(image);\n if (m) {\n parts.unshift({\n type: 'image',\n source: { type: 'base64', media_type: m[1], data: m[2] },\n });\n }\n }\n content = parts;\n } else if (image) {\n content = [\n { type: 'text', text: userText },\n { type: 'image_url', image_url: { url: image } },\n ];\n }\n\n const effective = system ? { ...config, systemPrompt: system } : config;\n const messages = [{ role: 'user', content }];\n const stream = effective.protocol === 'anthropic'\n ? streamAnthropic(effective, messages, options?.signal)\n : streamOpenAI(effective, messages, options?.signal);\n\n let acc = '';\n for await (const delta of stream) {\n acc += delta;\n options?.onDelta?.(acc);\n }\n return acc;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"aiClient.js","names":["resolveEndpoint","raw","config","path","u","streamOpenAI","body","messages","response","reader","decoder","buffer","done","value","lines","line","trimmed","content","streamAnthropic","data","streamAIChat","signal","completeAIChatStream","system","options","image","userText","parts","m","effective","stream","acc","delta"],"sources":["../../../../../src/components/pro/Flow/nodes/aiClient.ts"],"sourcesContent":["import type { FlowAIConfig } from '../types';\n\n/**\n * Minimal streaming chat client for the Flow AI media node. Supports\n * OpenAI-compatible `/chat/completions` and Anthropic `/messages` endpoints.\n *\n * Yields incremental text deltas. Callers accumulate them to render a live,\n * streaming response. This intentionally mirrors the RichTextEditor AIRewrite\n * streaming logic but is scoped to the Flow package (no tiptap dependency).\n */\nexport interface AIChatMessage {\n role: 'user' | 'assistant';\n content: string;\n}\n\n/**\n * Resolve the full request URL from a possibly-partial gateway.\n *\n * Users often paste a base URL (e.g. `https://api.moonshot.cn/v1`) instead of\n * the full endpoint, which then 404s. We append the protocol's default path\n * when the gateway doesn't already point at a completions/messages endpoint.\n */\nfunction resolveEndpoint(config: FlowAIConfig): string {\n const raw = (config.gateway || '').trim().replace(/\\/+$/, '');\n if (!raw) return raw;\n const path = config.protocol === 'anthropic' ? '/messages' : '/chat/completions';\n // Already a full endpoint.\n if (/\\/(chat\\/completions|messages|responses|completions)$/i.test(raw)) return raw;\n // Bare `/v1` (or version segment) base URL → append the default path.\n if (/\\/v\\d+$/i.test(raw)) return raw + path;\n // Host root with no path → assume `/v1` + default path.\n try {\n const u = new URL(raw);\n if (u.pathname === '' || u.pathname === '/') return raw + '/v1' + path;\n } catch { /* not a parseable URL; fall through */ }\n // Some other path was given; append the default endpoint segment.\n return raw + path;\n}\n\nasync function* streamOpenAI(\n config: FlowAIConfig,\n messages: ReadonlyArray<{ role: string; content: unknown }>,\n signal?: AbortSignal,\n): AsyncGenerator<string> {\n const body = JSON.stringify({\n model: config.model,\n messages: [\n ...(config.systemPrompt ? [{ role: 'system', content: config.systemPrompt }] : []),\n ...messages,\n ],\n stream: true,\n });\n\n const response = await fetch(resolveEndpoint(config), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...config.headers,\n },\n body,\n signal,\n });\n\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No response body');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed || trimmed === 'data: [DONE]' || !trimmed.startsWith('data: ')) continue;\n try {\n const data = JSON.parse(trimmed.slice(6));\n const content = data.choices?.[0]?.delta?.content;\n if (content) yield content;\n } catch { /* skip malformed chunk */ }\n }\n }\n}\n\nasync function* streamAnthropic(\n config: FlowAIConfig,\n messages: ReadonlyArray<{ role: string; content: unknown }>,\n signal?: AbortSignal,\n): AsyncGenerator<string> {\n const body = JSON.stringify({\n model: config.model,\n max_tokens: 8192,\n ...(config.systemPrompt ? { system: config.systemPrompt } : {}),\n messages,\n stream: true,\n });\n\n const response = await fetch(resolveEndpoint(config), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { 'x-api-key': config.apiKey } : {}),\n 'anthropic-version': '2023-06-01',\n 'anthropic-dangerous-direct-browser-access': 'true',\n ...config.headers,\n },\n body,\n signal,\n });\n\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n const reader = response.body?.getReader();\n if (!reader) throw new Error('No response body');\n\n const decoder = new TextDecoder();\n let buffer = '';\n\n while (true) {\n const { done, value } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split('\\n');\n buffer = lines.pop() || '';\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (!trimmed.startsWith('data: ')) continue;\n try {\n const data = JSON.parse(trimmed.slice(6));\n if (data.type === 'content_block_delta' && data.delta?.text) yield data.delta.text;\n } catch { /* skip malformed chunk */ }\n }\n }\n}\n\nexport function streamAIChat(\n config: FlowAIConfig,\n messages: AIChatMessage[],\n signal: AbortSignal,\n): AsyncIterable<string> {\n if (config.protocol === 'anthropic') return streamAnthropic(config, messages, signal);\n return streamOpenAI(config, messages, signal);\n}\n\n/**\n * Non-streaming completion used by the AI flow generator. Accepts an optional\n * image (data URL) for \"reconstruct this diagram\" requests. Returns the full\n * assistant text.\n */\nexport async function completeAIChat(\n config: FlowAIConfig,\n userText: string,\n options?: { imageDataUrl?: string; systemPrompt?: string; signal?: AbortSignal },\n): Promise<string> {\n const system = options?.systemPrompt ?? config.systemPrompt;\n const image = options?.imageDataUrl;\n\n if (config.protocol === 'anthropic') {\n const content: any[] = [{ type: 'text', text: userText }];\n if (image) {\n const m = /^data:(.+?);base64,(.*)$/.exec(image);\n if (m) {\n content.unshift({\n type: 'image',\n source: { type: 'base64', media_type: m[1], data: m[2] },\n });\n }\n }\n const response = await fetch(resolveEndpoint(config), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { 'x-api-key': config.apiKey } : {}),\n 'anthropic-version': '2023-06-01',\n 'anthropic-dangerous-direct-browser-access': 'true',\n ...config.headers,\n },\n body: JSON.stringify({\n model: config.model,\n max_tokens: 4096,\n ...(system ? { system } : {}),\n messages: [{ role: 'user', content }],\n }),\n signal: options?.signal,\n });\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n const json = await response.json();\n return json.content?.map((c: any) => c.text ?? '').join('') ?? '';\n }\n\n // OpenAI-compatible (supports multimodal `image_url` content parts).\n const userContent: any = image\n ? [\n { type: 'text', text: userText },\n { type: 'image_url', image_url: { url: image } },\n ]\n : userText;\n const response = await fetch(resolveEndpoint(config), {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n ...(config.apiKey ? { Authorization: `Bearer ${config.apiKey}` } : {}),\n ...config.headers,\n },\n body: JSON.stringify({\n model: config.model,\n messages: [\n ...(system ? [{ role: 'system', content: system }] : []),\n { role: 'user', content: userContent },\n ],\n }),\n signal: options?.signal,\n });\n if (!response.ok) throw new Error(`AI API error: ${response.status} ${response.statusText}`);\n const json = await response.json();\n return json.choices?.[0]?.message?.content ?? '';\n}\n\n/**\n * Streaming variant of `completeAIChat`: same multimodal request shape\n * (optional image + system prompt override), but the reply is consumed as SSE\n * deltas. `onDelta` fires with the accumulated text after every chunk so\n * callers can render the response live. Returns the full assistant text.\n */\nexport async function completeAIChatStream(\n config: FlowAIConfig,\n userText: string,\n options?: {\n imageDataUrl?: string;\n systemPrompt?: string;\n signal?: AbortSignal;\n onDelta?: (accumulated: string) => void;\n },\n): Promise<string> {\n const system = options?.systemPrompt ?? config.systemPrompt;\n const image = options?.imageDataUrl;\n\n let content: unknown = userText;\n if (config.protocol === 'anthropic') {\n const parts: any[] = [{ type: 'text', text: userText }];\n if (image) {\n const m = /^data:(.+?);base64,(.*)$/.exec(image);\n if (m) {\n parts.unshift({\n type: 'image',\n source: { type: 'base64', media_type: m[1], data: m[2] },\n });\n }\n }\n content = parts;\n } else if (image) {\n content = [\n { type: 'text', text: userText },\n { type: 'image_url', image_url: { url: image } },\n ];\n }\n\n const effective = system ? { ...config, systemPrompt: system } : config;\n const messages = [{ role: 'user', content }];\n const stream = effective.protocol === 'anthropic'\n ? streamAnthropic(effective, messages, options?.signal)\n : streamOpenAI(effective, messages, options?.signal);\n\n let acc = '';\n for await (const delta of stream) {\n acc += delta;\n options?.onDelta?.(acc);\n }\n return acc;\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"animAnchors.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/animAnchors.ts"],"sourcesContent":["import type { FlowDocument, FlowNode } from '../types';\n\n/**\n * Animation-anchor helpers.\n *\n * An `animAnchor` node marks a \"slide\" region on a canvas. The presentation\n * sequence is *derived*, not stored: anchors are ordered by stable creation\n * index (`animIndex`, assigned on creation) across ALL canvases (including\n * sub-canvases), so deleting one renumbers the rest automatically and the whole\n * document shares a single slide order.\n */\n\n/** A slide in the presentation sequence: which canvas it lives on + its node. */\nexport interface AnimSlide {\n canvasId: string;\n node: FlowNode;\n /** Stable creation order across the document. */\n index: number;\n}\n\n/**\n * Collect all animation anchors across every canvas (root + nested sub-canvases)\n * into one globally-ordered slide list, sorted by stable creation index.\n */\nexport function getDocumentSlides(doc: FlowDocument): AnimSlide[] {\n const slides: AnimSlide[] = [];\n const rootId = doc.stack[0] ?? 'root';\n const pending = [rootId];\n const visited = new Set<string>();\n\n // Only include canvases reachable from the root. Imported/edited documents\n // can retain orphaned canvas records after their parent node is deleted; such\n // records must not create invisible presentation slides.\n while (pending.length > 0) {\n const canvasId = pending.shift()!;\n if (visited.has(canvasId)) continue;\n visited.add(canvasId);\n const canvas = doc.canvases[canvasId];\n if (!canvas) continue;\n\n for (const node of canvas.nodes) {\n if (node.type === 'animAnchor') {\n slides.push({ canvasId, node, index: node.data.animIndex ?? 0 });\n }\n if (node.data.childCanvasId && !visited.has(node.data.childCanvasId)) {\n pending.push(node.data.childCanvasId);\n }\n }\n }\n\n slides.sort((a, b) => a.index - b.index);\n return slides;\n}\n\n/** Slides for a single canvas (ordered), used for the on-canvas badge. */\nexport function getCanvasSlides(nodes: FlowNode[]): FlowNode[] {\n return nodes\n .filter((n) => n.type === 'animAnchor')\n .sort((a, b) => (a.data.animIndex ?? 0) - (b.data.animIndex ?? 0));\n}\n\n/** Next `animIndex` to assign to a newly created anchor (document-wide). */\nexport function nextAnimIndex(doc: FlowDocument): number {\n let max = -1;\n for (const canvas of Object.values(doc.canvases)) {\n for (const n of canvas.nodes) {\n if (n.type === 'animAnchor') max = Math.max(max, n.data.animIndex ?? -1);\n }\n }\n return max + 1;\n}\n\n/** Exact world-space frame used as the presentation viewport. */\nexport function anchorFocusRect(node: FlowNode): { x: number; y: number; w: number; h: number } {\n return {\n x: node.position.x,\n y: node.position.y,\n w: node.width,\n h: node.height,\n };\n}\n\n/**\n * Compute the canvas stack (root → … → target) needed to display `targetCanvasId`,\n * by walking `childCanvasId` links from the root. Returns null if unreachable.\n */\nexport function canvasStackTo(doc: FlowDocument, targetCanvasId: string): string[] | null {\n const rootId = doc.stack[0] ?? 'root';\n if (targetCanvasId === rootId) return [rootId];\n const parentOf = new Map<string, string>();\n for (const canvas of Object.values(doc.canvases)) {\n for (const n of canvas.nodes) {\n if (n.data.childCanvasId) parentOf.set(n.data.childCanvasId, canvasIdOfNode(doc, n.id) ?? '');\n }\n }\n // Walk up from target to root, then reverse.\n const chain: string[] = [];\n let cur: string | undefined = targetCanvasId;\n const seen = new Set<string>();\n while (cur && !seen.has(cur)) {\n seen.add(cur);\n chain.unshift(cur);\n if (cur === rootId) return chain;\n cur = parentOf.get(cur);\n }\n return null;\n}\n\nfunction canvasIdOfNode(doc: FlowDocument, nodeId: string): string | null {\n for (const [canvasId, canvas] of Object.entries(doc.canvases)) {\n if (canvas.nodes.some((n) => n.id === nodeId)) return canvasId;\n }\n return null;\n}\n"],"mappings":";AAwBA,
|
|
1
|
+
{"version":3,"file":"animAnchors.js","names":["getDocumentSlides","slides","pending","doc","visited","canvasId","canvas","node","a","b","nextAnimIndex","max","n","anchorFocusRect","canvasStackTo","rootId","targetCanvasId","parentOf","canvasIdOfNode","chain","cur","seen","nodeId"],"sources":["../../../../../src/components/pro/Flow/nodes/animAnchors.ts"],"sourcesContent":["import type { FlowDocument, FlowNode } from '../types';\n\n/**\n * Animation-anchor helpers.\n *\n * An `animAnchor` node marks a \"slide\" region on a canvas. The presentation\n * sequence is *derived*, not stored: anchors are ordered by stable creation\n * index (`animIndex`, assigned on creation) across ALL canvases (including\n * sub-canvases), so deleting one renumbers the rest automatically and the whole\n * document shares a single slide order.\n */\n\n/** A slide in the presentation sequence: which canvas it lives on + its node. */\nexport interface AnimSlide {\n canvasId: string;\n node: FlowNode;\n /** Stable creation order across the document. */\n index: number;\n}\n\n/**\n * Collect all animation anchors across every canvas (root + nested sub-canvases)\n * into one globally-ordered slide list, sorted by stable creation index.\n */\nexport function getDocumentSlides(doc: FlowDocument): AnimSlide[] {\n const slides: AnimSlide[] = [];\n const rootId = doc.stack[0] ?? 'root';\n const pending = [rootId];\n const visited = new Set<string>();\n\n // Only include canvases reachable from the root. Imported/edited documents\n // can retain orphaned canvas records after their parent node is deleted; such\n // records must not create invisible presentation slides.\n while (pending.length > 0) {\n const canvasId = pending.shift()!;\n if (visited.has(canvasId)) continue;\n visited.add(canvasId);\n const canvas = doc.canvases[canvasId];\n if (!canvas) continue;\n\n for (const node of canvas.nodes) {\n if (node.type === 'animAnchor') {\n slides.push({ canvasId, node, index: node.data.animIndex ?? 0 });\n }\n if (node.data.childCanvasId && !visited.has(node.data.childCanvasId)) {\n pending.push(node.data.childCanvasId);\n }\n }\n }\n\n slides.sort((a, b) => a.index - b.index);\n return slides;\n}\n\n/** Slides for a single canvas (ordered), used for the on-canvas badge. */\nexport function getCanvasSlides(nodes: FlowNode[]): FlowNode[] {\n return nodes\n .filter((n) => n.type === 'animAnchor')\n .sort((a, b) => (a.data.animIndex ?? 0) - (b.data.animIndex ?? 0));\n}\n\n/** Next `animIndex` to assign to a newly created anchor (document-wide). */\nexport function nextAnimIndex(doc: FlowDocument): number {\n let max = -1;\n for (const canvas of Object.values(doc.canvases)) {\n for (const n of canvas.nodes) {\n if (n.type === 'animAnchor') max = Math.max(max, n.data.animIndex ?? -1);\n }\n }\n return max + 1;\n}\n\n/** Exact world-space frame used as the presentation viewport. */\nexport function anchorFocusRect(node: FlowNode): { x: number; y: number; w: number; h: number } {\n return {\n x: node.position.x,\n y: node.position.y,\n w: node.width,\n h: node.height,\n };\n}\n\n/**\n * Compute the canvas stack (root → … → target) needed to display `targetCanvasId`,\n * by walking `childCanvasId` links from the root. Returns null if unreachable.\n */\nexport function canvasStackTo(doc: FlowDocument, targetCanvasId: string): string[] | null {\n const rootId = doc.stack[0] ?? 'root';\n if (targetCanvasId === rootId) return [rootId];\n const parentOf = new Map<string, string>();\n for (const canvas of Object.values(doc.canvases)) {\n for (const n of canvas.nodes) {\n if (n.data.childCanvasId) parentOf.set(n.data.childCanvasId, canvasIdOfNode(doc, n.id) ?? '');\n }\n }\n // Walk up from target to root, then reverse.\n const chain: string[] = [];\n let cur: string | undefined = targetCanvasId;\n const seen = new Set<string>();\n while (cur && !seen.has(cur)) {\n seen.add(cur);\n chain.unshift(cur);\n if (cur === rootId) return chain;\n cur = parentOf.get(cur);\n }\n return null;\n}\n\nfunction canvasIdOfNode(doc: FlowDocument, nodeId: string): string | null {\n for (const [canvasId, canvas] of Object.entries(doc.canvases)) {\n if (canvas.nodes.some((n) => n.id === nodeId)) return canvasId;\n }\n return null;\n}\n"],"mappings":";AAwBA,SAAgBA,EAAkB,GAAgC;CAChE,IAAMC,IAAsB,CAAC,GAEvBC,IAAU,CADDC,EAAI,MAAM,MAAM,MACR,GACjBC,oBAAU,IAAI,IAAY;CAKhC,OAAOF,EAAQ,SAAS,IAAG;EACzB,IAAMG,IAAWH,EAAQ,MAAM;EAC/B,IAAIE,EAAQ,IAAIC,CAAQ,GAAG;EAC3B,EAAQ,IAAIA,CAAQ;EACpB,IAAMC,IAASH,EAAI,SAASE;EAC5B,IAAKC,GAEL,KAAK,IAAMC,KAAQD,EAAO,OAIxB,AAHIC,EAAK,SAAS,gBAChBN,EAAO,KAAK;GAAE;GAAU;GAAM,OAAOM,EAAK,KAAK,aAAa;EAAE,CAAC,GAE7DA,EAAK,KAAK,iBAAiB,CAACH,EAAQ,IAAIG,EAAK,KAAK,aAAa,KACjEL,EAAQ,KAAKK,EAAK,KAAK,aAAa;CAG1C;CAGA,OADAN,EAAO,MAAM,GAAG,MAAMO,EAAE,QAAQC,EAAE,KAAK,GAChCR;AACT;AAUA,SAAgBS,EAAc,GAA2B;CACvD,IAAIC,IAAM;CACV,KAAK,IAAML,KAAU,OAAO,OAAOH,EAAI,QAAQ,GAC7C,KAAK,IAAMS,KAAKN,EAAO,OACrB,AAAIM,EAAE,SAAS,iBAAc,IAAM,KAAK,IAAID,GAAKC,EAAE,KAAK,aAAa,EAAE;CAG3E,OAAOD,IAAM;AACf;AAGA,SAAgBE,EAAgB,GAAgE;CAC9F,OAAO;EACL,GAAGN,EAAK,SAAS;EACjB,GAAGA,EAAK,SAAS;EACjB,GAAGA,EAAK;EACR,GAAGA,EAAK;CACV;AACF;AAMA,SAAgBO,EAAc,GAAmB,GAAyC;CACxF,IAAMC,IAASZ,EAAI,MAAM,MAAM;CAC/B,IAAIa,MAAmBD,GAAQ,OAAO,CAACA,CAAM;CAC7C,IAAME,oBAAW,IAAI,IAAoB;CACzC,KAAK,IAAMX,KAAU,OAAO,OAAOH,EAAI,QAAQ,GAC7C,KAAK,IAAM,KAAKG,EAAO,OACrB,AAAI,EAAE,KAAK,iBAAeW,EAAS,IAAI,EAAE,KAAK,eAAeC,EAAef,GAAK,EAAE,EAAE,KAAK,EAAE;CAIhG,IAAMgB,IAAkB,CAAC,GACrBC,IAA0BJ,GACxBK,oBAAO,IAAI,IAAY;CAC7B,OAAOD,KAAO,CAACC,EAAK,IAAID,CAAG,IAAG;EAG5B,IAFAC,EAAK,IAAID,CAAG,GACZD,EAAM,QAAQC,CAAG,GACbA,MAAQL,GAAQ,OAAOI;EAC3B,IAAMF,EAAS,IAAIG,CAAG;CACxB;CACA,OAAO;AACT;AAEA,SAASF,EAAe,GAAmB,GAA+B;CACxE,KAAK,IAAM,CAACb,GAAUC,MAAW,OAAO,QAAQH,EAAI,QAAQ,GAC1D,IAAIG,EAAO,MAAM,MAAM,MAAMM,EAAE,OAAOU,CAAM,GAAG,OAAOjB;CAExD,OAAO;AACT"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chartGeometryBudget.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/chartGeometryBudget.ts"],"sourcesContent":["import type { ChartMark, SceneNode } from '@tanstack/charts';\n\ntype Position = readonly [number, number];\n\n/** Keep sparse charts' dots, but not overlapping dots in dense/multi-series lines. */\nexport function showLinePoints(rowCount: number, seriesCount: number, width: number) {\n return rowCount * seriesCount <= Math.min(200, Math.floor(Math.max(0, width) / 6));\n}\n\n/**\n * First/min/max/last per two CSS plot pixels (at most ~2 vertices/pixel).\n * Keep source order and extrema. Repeated/non-monotonic category positions are\n * deliberately left alone: bucketing them can change backtracking geometry.\n */\nexport function budgetPolyline(points: readonly Position[]): readonly Position[] {\n if (points.length < 3) return points;\n let previousX = -Infinity;\n for (const [x, y] of points) {\n if (!Number.isFinite(x) || !Number.isFinite(y) || x <= previousX) return points;\n previousX = x;\n }\n const result: Position[] = [];\n for (let start = 0; start < points.length;) {\n const bucket = Math.floor(points[start][0] / 2);\n let end = start + 1, min = start, max = start;\n while (end < points.length && Math.floor(points[end][0] / 2) === bucket) {\n if (points[end][1] < points[min][1]) min = end;\n if (points[end][1] > points[max][1]) max = end;\n end++;\n }\n const indices = [start, Math.min(min, max), Math.max(min, max), end - 1];\n for (let i = 0; i < indices.length; i++) {\n if (i === 0 || indices[i] !== indices[i - 1]) result.push(points[indices[i]]);\n }\n start = end;\n }\n return result.length < points.length ? result : points;\n}\n\nexport function budgetChartNode(node: SceneNode): SceneNode {\n if (node.kind === 'group') return { ...node, children: node.children.map(budgetChartNode) };\n // Never rewrite precomputed curves or arbitrary polygons. Interaction points\n // remain the full-resolution source data, separate from the painted vertices.\n if (node.kind === 'polyline' && !node.path) {\n const points = budgetPolyline(node.points);\n return points === node.points ? node : { ...node, points };\n }\n if (node.kind === 'area' && !node.path && !node.polygons) {\n const count = node.interaction?.points?.length;\n // areaY emits the upper edge then the reversed lower edge for each gap-\n // separated segment. Check this shape before applying the budget.\n if (count && node.points.length === count * 2) {\n const top = node.points.slice(0, count);\n const bottom = Array.from({ length: count }, (_, i) => node.points[node.points.length - 1 - i]);\n const upper = budgetPolyline(top), lower = budgetPolyline(bottom);\n if (upper !== top || lower !== bottom) {\n const points = [...upper];\n for (let i = lower.length - 1; i >= 0; i--) points.push(lower[i]);\n return { ...node, points };\n }\n }\n }\n return node;\n}\n\n/** Budget only display geometry, after full-data domains and tooltip points exist. */\nexport function withChartGeometryBudget<T extends ChartMark<any, any, any, any, any, any, any>>(mark: T): T {\n return {\n ...mark,\n initialize(context) {\n const initialized = mark.initialize(context);\n return {\n ...initialized,\n render(renderContext) {\n const scene = initialized.render(renderContext);\n return { ...scene, nodes: scene.nodes.map(budgetChartNode) };\n },\n };\n },\n };\n}\n"],"mappings":";AAKA,
|
|
1
|
+
{"version":3,"file":"chartGeometryBudget.js","names":["showLinePoints","rowCount","seriesCount","width","budgetPolyline","points","previousX","x","y","result","start","bucket","end","min","max","indices","i","budgetChartNode","node","count","top","bottom","upper","lower","withChartGeometryBudget","mark","initialized","context","scene","renderContext"],"sources":["../../../../../src/components/pro/Flow/nodes/chartGeometryBudget.ts"],"sourcesContent":["import type { ChartMark, SceneNode } from '@tanstack/charts';\n\ntype Position = readonly [number, number];\n\n/** Keep sparse charts' dots, but not overlapping dots in dense/multi-series lines. */\nexport function showLinePoints(rowCount: number, seriesCount: number, width: number) {\n return rowCount * seriesCount <= Math.min(200, Math.floor(Math.max(0, width) / 6));\n}\n\n/**\n * First/min/max/last per two CSS plot pixels (at most ~2 vertices/pixel).\n * Keep source order and extrema. Repeated/non-monotonic category positions are\n * deliberately left alone: bucketing them can change backtracking geometry.\n */\nexport function budgetPolyline(points: readonly Position[]): readonly Position[] {\n if (points.length < 3) return points;\n let previousX = -Infinity;\n for (const [x, y] of points) {\n if (!Number.isFinite(x) || !Number.isFinite(y) || x <= previousX) return points;\n previousX = x;\n }\n const result: Position[] = [];\n for (let start = 0; start < points.length;) {\n const bucket = Math.floor(points[start][0] / 2);\n let end = start + 1, min = start, max = start;\n while (end < points.length && Math.floor(points[end][0] / 2) === bucket) {\n if (points[end][1] < points[min][1]) min = end;\n if (points[end][1] > points[max][1]) max = end;\n end++;\n }\n const indices = [start, Math.min(min, max), Math.max(min, max), end - 1];\n for (let i = 0; i < indices.length; i++) {\n if (i === 0 || indices[i] !== indices[i - 1]) result.push(points[indices[i]]);\n }\n start = end;\n }\n return result.length < points.length ? result : points;\n}\n\nexport function budgetChartNode(node: SceneNode): SceneNode {\n if (node.kind === 'group') return { ...node, children: node.children.map(budgetChartNode) };\n // Never rewrite precomputed curves or arbitrary polygons. Interaction points\n // remain the full-resolution source data, separate from the painted vertices.\n if (node.kind === 'polyline' && !node.path) {\n const points = budgetPolyline(node.points);\n return points === node.points ? node : { ...node, points };\n }\n if (node.kind === 'area' && !node.path && !node.polygons) {\n const count = node.interaction?.points?.length;\n // areaY emits the upper edge then the reversed lower edge for each gap-\n // separated segment. Check this shape before applying the budget.\n if (count && node.points.length === count * 2) {\n const top = node.points.slice(0, count);\n const bottom = Array.from({ length: count }, (_, i) => node.points[node.points.length - 1 - i]);\n const upper = budgetPolyline(top), lower = budgetPolyline(bottom);\n if (upper !== top || lower !== bottom) {\n const points = [...upper];\n for (let i = lower.length - 1; i >= 0; i--) points.push(lower[i]);\n return { ...node, points };\n }\n }\n }\n return node;\n}\n\n/** Budget only display geometry, after full-data domains and tooltip points exist. */\nexport function withChartGeometryBudget<T extends ChartMark<any, any, any, any, any, any, any>>(mark: T): T {\n return {\n ...mark,\n initialize(context) {\n const initialized = mark.initialize(context);\n return {\n ...initialized,\n render(renderContext) {\n const scene = initialized.render(renderContext);\n return { ...scene, nodes: scene.nodes.map(budgetChartNode) };\n },\n };\n },\n };\n}\n"],"mappings":";AAKA,SAAgBA,EAAe,GAAkB,GAAqB,GAAe;CACnF,OAAOC,IAAWC,KAAe,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,IAAI,GAAGC,CAAK,IAAI,CAAC,CAAC;AACnF;AAOA,SAAgBC,EAAe,GAAkD;CAC/E,IAAIC,EAAO,SAAS,GAAG,OAAOA;CAC9B,IAAIC,IAAY;CAChB,KAAK,IAAM,CAACC,GAAGC,MAAMH,GAAQ;EAC3B,IAAI,CAAC,OAAO,SAASE,CAAC,KAAK,CAAC,OAAO,SAASC,CAAC,KAAKD,KAAKD,GAAW,OAAOD;EACzE,IAAYE;CACd;CACA,IAAME,IAAqB,CAAC;CAC5B,KAAK,IAAIC,IAAQ,GAAGA,IAAQL,EAAO,SAAS;EAC1C,IAAMM,IAAS,KAAK,MAAMN,EAAOK,EAAM,CAAC,KAAK,CAAC,GAC1CE,IAAMF,IAAQ,GAAGG,IAAMH,GAAOI,IAAMJ;EACxC,OAAOE,IAAMP,EAAO,UAAU,KAAK,MAAMA,EAAOO,EAAI,CAAC,KAAK,CAAC,MAAMD,IAG/D,AAFIN,EAAOO,EAAI,CAAC,KAAKP,EAAOQ,EAAI,CAAC,OAAI,IAAMD,IACvCP,EAAOO,EAAI,CAAC,KAAKP,EAAOS,EAAI,CAAC,OAAI,IAAMF,IAC3C;EAEF,IAAMG,IAAU;GAACL;GAAO,KAAK,IAAIG,GAAKC,CAAG;GAAG,KAAK,IAAID,GAAKC,CAAG;GAAGF,IAAM;EAAC;EACvE,KAAK,IAAII,IAAI,GAAGA,IAAID,EAAQ,QAAQ,KAClC,CAAIC,MAAM,KAAKD,EAAQC,OAAOD,EAAQC,IAAI,OAAIP,EAAO,KAAKJ,EAAOU,EAAQC,GAAG;EAE9E,IAAQJ;CACV;CACA,OAAOH,EAAO,SAASJ,EAAO,SAASI,IAASJ;AAClD;AAEA,SAAgBY,EAAgB,GAA4B;CAC1D,IAAIC,EAAK,SAAS,SAAS,OAAO;EAAE,GAAGA;EAAM,UAAUA,EAAK,SAAS,IAAID,CAAe;CAAE;CAG1F,IAAIC,EAAK,SAAS,cAAc,CAACA,EAAK,MAAM;EAC1C,IAAMb,IAASD,EAAec,EAAK,MAAM;EACzC,OAAOb,MAAWa,EAAK,SAASA,IAAO;GAAE,GAAGA;GAAM;EAAO;CAC3D;CACA,IAAIA,EAAK,SAAS,UAAU,CAACA,EAAK,QAAQ,CAACA,EAAK,UAAU;EACxD,IAAMC,IAAQD,EAAK,aAAa,QAAQ;EAGxC,IAAIC,KAASD,EAAK,OAAO,WAAWC,IAAQ,GAAG;GAC7C,IAAMC,IAAMF,EAAK,OAAO,MAAM,GAAGC,CAAK,GAChCE,IAAS,MAAM,KAAK,EAAE,QAAQF,EAAM,IAAI,GAAG,MAAMD,EAAK,OAAOA,EAAK,OAAO,SAAS,IAAIF,EAAE,GACxFM,IAAQlB,EAAegB,CAAG,GAAGG,IAAQnB,EAAeiB,CAAM;GAChE,IAAIC,MAAUF,KAAOG,MAAUF,GAAQ;IACrC,IAAMhB,IAAS,CAAC,GAAGiB,CAAK;IACxB,KAAK,IAAIN,IAAIO,EAAM,SAAS,GAAGP,KAAK,GAAG,KAAK,EAAO,KAAKO,EAAMP,EAAE;IAChE,OAAO;KAAE,GAAGE;KAAM;IAAO;GAC3B;EACF;CACF;CACA,OAAOA;AACT;AAGA,SAAgBM,EAAgF,GAAY;CAC1G,OAAO;EACL,GAAGC;EACH,WAAW,GAAS;GAClB,IAAMC,IAAcD,EAAK,WAAWE,CAAO;GAC3C,OAAO;IACL,GAAGD;IACH,OAAO,GAAe;KACpB,IAAME,IAAQF,EAAY,OAAOG,CAAa;KAC9C,OAAO;MAAE,GAAGD;MAAO,OAAOA,EAAM,MAAM,IAAIX,CAAe;KAAE;IAC7D;GACF;EACF;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataUtils.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/dataUtils.ts"],"sourcesContent":["// Data-node helpers: CSV/XLSX parsing, column type inference, and building\n// TanStack Charts definitions from parsed rows. Kept dependency-light so the\n// base `dishui/flow` bundle stays small — `fflate` (already a transitive dep of\n// markmap) is the only heavy import and only loads when an .xlsx is opened.\n\nimport type {\n FlowDataColumn,\n FlowDataColumnType,\n FlowDataConfig,\n FlowDataChartConfig,\n} from '../types';\n\n/** Result of parsing a raw table into columns + row records. */\nexport interface ParsedTable {\n columns: FlowDataColumn[];\n rows: Array<Record<string, unknown>>;\n}\n\n// ── CSV parsing ────────────────────────────────────────────────────────────\n\n/**\n * Parse CSV text into a matrix of string cells. Handles quoted fields,\n * escaped quotes (\"\"), and embedded newlines/commas. Auto-detects `,` or `;`\n * as the delimiter from the first non-empty line.\n */\nexport function parseCsv(text: string): string[][] {\n const src = text.replace(/^\\uFEFF/, ''); // strip BOM\n const delimiter = detectDelimiter(src);\n const rows: string[][] = [];\n let field = '';\n let row: string[] = [];\n let inQuotes = false;\n\n for (let i = 0; i < src.length; i++) {\n const ch = src[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (src[i + 1] === '\"') {\n field += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n field += ch;\n }\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n } else if (ch === delimiter) {\n row.push(field);\n field = '';\n } else if (ch === '\\n') {\n row.push(field);\n rows.push(row);\n field = '';\n row = [];\n } else if (ch === '\\r') {\n // ignore; handled by the following \\n (or end of input)\n } else {\n field += ch;\n }\n }\n // flush trailing field/row\n if (field.length > 0 || row.length > 0) {\n row.push(field);\n rows.push(row);\n }\n // drop fully-empty trailing rows\n return rows.filter((r) => !(r.length === 1 && r[0].trim() === ''));\n}\n\nfunction detectDelimiter(src: string): string {\n const firstLine = src.split(/\\r?\\n/, 1)[0] ?? '';\n const commas = (firstLine.match(/,/g) ?? []).length;\n const semis = (firstLine.match(/;/g) ?? []).length;\n const tabs = (firstLine.match(/\\t/g) ?? []).length;\n if (tabs > commas && tabs > semis) return '\\t';\n if (semis > commas) return ';';\n return ',';\n}\n\n/** Serialize columns + rows back to CSV text (for round-tripping/exports). */\nexport function toCsv(columns: FlowDataColumn[], rows: Array<Record<string, unknown>>): string {\n const esc = (v: unknown) => {\n const s = v == null ? '' : String(v);\n return /[\",\\n\\r]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n const header = columns.map((c) => esc(c.label ?? c.key)).join(',');\n const body = rows\n .map((r) => columns.map((c) => esc(r[c.key])).join(','))\n .join('\\n');\n return body ? `${header}\\n${body}` : header;\n}\n\n// ── XLSX parsing ─────────────────────────────────────────────────────────────\n\n/**\n * Parse the first worksheet of an XLSX workbook (an OOXML zip) into a string\n * matrix. Uses `fflate` to unzip and DOMParser to read the sheet XML + shared\n * strings. Loaded dynamically so it never enters the base bundle.\n */\nexport async function parseXlsx(buffer: ArrayBuffer): Promise<string[][]> {\n const { unzipSync, strFromU8 } = await import('fflate');\n const files = unzipSync(new Uint8Array(buffer));\n\n const parser = new DOMParser();\n const readXml = (path: string): Document | null => {\n const entry = files[path];\n if (!entry) return null;\n return parser.parseFromString(strFromU8(entry), 'application/xml');\n };\n\n // Shared strings table (cells of type \"s\" reference these by index).\n const sharedStrings: string[] = [];\n const sst = readXml('xl/sharedStrings.xml');\n if (sst) {\n for (const si of Array.from(sst.getElementsByTagName('si'))) {\n // Concatenate all <t> runs within the string item.\n const ts = Array.from(si.getElementsByTagName('t'));\n sharedStrings.push(ts.map((t) => t.textContent ?? '').join(''));\n }\n }\n\n // Resolve the first sheet's target from the workbook relationships.\n const sheetPath = resolveFirstSheetPath(files, readXml);\n const sheetDoc = readXml(sheetPath);\n if (!sheetDoc) throw new Error('无法读取 Excel 工作表');\n\n const rowsEls = Array.from(sheetDoc.getElementsByTagName('row'));\n const matrix: string[][] = [];\n for (const rowEl of rowsEls) {\n const cells = Array.from(rowEl.getElementsByTagName('c'));\n const rowArr: string[] = [];\n for (const c of cells) {\n const ref = c.getAttribute('r') ?? '';\n const colIdx = columnRefToIndex(ref);\n const type = c.getAttribute('t');\n let value = '';\n if (type === 's') {\n const idx = Number(c.getElementsByTagName('v')[0]?.textContent ?? '');\n value = Number.isFinite(idx) ? sharedStrings[idx] ?? '' : '';\n } else if (type === 'inlineStr') {\n value = c.getElementsByTagName('t')[0]?.textContent ?? '';\n } else {\n value = c.getElementsByTagName('v')[0]?.textContent ?? '';\n }\n if (colIdx >= 0) rowArr[colIdx] = value;\n }\n // normalize holes to empty strings\n for (let i = 0; i < rowArr.length; i++) if (rowArr[i] == null) rowArr[i] = '';\n matrix.push(rowArr);\n }\n return matrix.filter((r) => r.some((cell) => cell.trim() !== ''));\n}\n\nfunction resolveFirstSheetPath(\n files: Record<string, Uint8Array>,\n readXml: (path: string) => Document | null,\n): string {\n // Prefer following workbook.xml → rels; fall back to the conventional path.\n const workbook = readXml('xl/workbook.xml');\n const rels = readXml('xl/_rels/workbook.xml.rels');\n if (workbook && rels) {\n const sheet = workbook.getElementsByTagName('sheet')[0];\n const rid = sheet?.getAttribute('r:id') ?? sheet?.getAttributeNS(\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',\n 'id',\n );\n if (rid) {\n for (const rel of Array.from(rels.getElementsByTagName('Relationship'))) {\n if (rel.getAttribute('Id') === rid) {\n const target = rel.getAttribute('Target') ?? '';\n const norm = target.replace(/^\\//, '').replace(/^xl\\//, '');\n return `xl/${norm}`;\n }\n }\n }\n }\n if (files['xl/worksheets/sheet1.xml']) return 'xl/worksheets/sheet1.xml';\n // last resort: first worksheet entry found\n const first = Object.keys(files).find((p) => /^xl\\/worksheets\\/.*\\.xml$/.test(p));\n return first ?? 'xl/worksheets/sheet1.xml';\n}\n\n/** Convert an A1-style cell ref (e.g. \"C5\") to a zero-based column index. */\nfunction columnRefToIndex(ref: string): number {\n const letters = ref.match(/^[A-Z]+/i)?.[0];\n if (!letters) return -1;\n let idx = 0;\n for (const ch of letters.toUpperCase()) {\n idx = idx * 26 + (ch.charCodeAt(0) - 64);\n }\n return idx - 1;\n}\n\n// ── Matrix → table (columns + typed rows) ────────────────────────────────────\n\n/** Slugify a header label into a safe object key, deduping collisions. */\nfunction toKey(label: string, used: Set<string>): string {\n let base = label.trim().replace(/\\s+/g, '_').replace(/[^\\w\\u4e00-\\u9fa5]/g, '');\n if (!base) base = 'col';\n let key = base;\n let n = 1;\n while (used.has(key)) key = `${base}_${n++}`;\n used.add(key);\n return key;\n}\n\nconst DATE_RE = /^\\d{4}[-/]\\d{1,2}[-/]\\d{1,2}([ T]\\d{1,2}:\\d{2}(:\\d{2})?)?$/;\n\n/** Infer a column type from a sample of raw string cell values. */\nexport function inferColumnType(samples: string[]): FlowDataColumnType {\n const nonEmpty = samples.map((s) => s?.trim()).filter((s) => s);\n if (nonEmpty.length === 0) return 'string';\n const isBool = nonEmpty.every((s) => /^(true|false|yes|no|是|否)$/i.test(s));\n if (isBool) return 'boolean';\n const isNum = nonEmpty.every((s) => /^-?\\d+(\\.\\d+)?%?$/.test(s.replace(/,/g, '')));\n if (isNum) return 'number';\n const isDate = nonEmpty.every((s) => DATE_RE.test(s));\n if (isDate) return 'date';\n return 'string';\n}\n\n/** Coerce a raw string cell to a typed value based on the column type. */\nexport function coerceValue(raw: string, type: FlowDataColumnType): unknown {\n const s = (raw ?? '').trim();\n if (s === '') return type === 'number' ? null : '';\n switch (type) {\n case 'number': {\n const cleaned = s.replace(/,/g, '').replace(/%$/, '');\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : null;\n }\n case 'boolean':\n return /^(true|yes|是)$/i.test(s);\n case 'date':\n case 'string':\n default:\n return s;\n }\n}\n\n/**\n * Build a typed table from a raw string matrix. The first row is treated as the\n * header. Column types are inferred from the data rows.\n */\nexport function matrixToTable(matrix: string[][]): ParsedTable {\n if (matrix.length === 0) return { columns: [], rows: [] };\n const [header, ...dataRows] = matrix;\n const used = new Set<string>();\n const colCount = Math.max(header.length, ...dataRows.map((r) => r.length), 0);\n\n const columns: FlowDataColumn[] = [];\n for (let c = 0; c < colCount; c++) {\n const label = (header[c] ?? `列${c + 1}`).trim() || `列${c + 1}`;\n const samples = dataRows.slice(0, 50).map((r) => r[c] ?? '');\n columns.push({ key: toKey(label, used), label, type: inferColumnType(samples) });\n }\n\n const rows = dataRows.map((r) => {\n const record: Record<string, unknown> = {};\n columns.forEach((col, c) => {\n record[col.key] = coerceValue(r[c] ?? '', col.type);\n });\n return record;\n });\n\n return { columns, rows };\n}\n\n/** Parse a File (csv or xlsx) into a typed table. */\nexport async function parseDataFile(file: File): Promise<ParsedTable> {\n const name = file.name.toLowerCase();\n if (name.endsWith('.xlsx') || name.endsWith('.xlsm')) {\n const buffer = await file.arrayBuffer();\n return matrixToTable(await parseXlsx(buffer));\n }\n // treat everything else as delimited text (csv/tsv/txt)\n const text = await file.text();\n return matrixToTable(parseCsv(text));\n}\n\n// ── Config helpers ───────────────────────────────────────────────────────────\n\n/** Default palette for chart series, mirroring the demo examples. */\nexport const DATA_CHART_PALETTE = [\n '#6366f1', '#10b981', '#f59e0b', '#f43f5e', '#06b6d4',\n '#8b5cf6', '#84cc16', '#ec4899', '#0ea5e9', '#f97316',\n];\n\n/** Column key → display label lookup. */\nexport function labelFor(columns: FlowDataColumn[], key: string): string {\n return columns.find((c) => c.key === key)?.label ?? key;\n}\n\n/** First non-numeric column key (a sensible default X/category field). */\nexport function firstDimension(columns: FlowDataColumn[]): string | undefined {\n return (columns.find((c) => c.type !== 'number') ?? columns[0])?.key;\n}\n\n/** All numeric column keys (candidate measures/series). */\nexport function numericFields(columns: FlowDataColumn[]): string[] {\n return columns.filter((c) => c.type === 'number').map((c) => c.key);\n}\n\n/**\n * Produce a sensible default `FlowDataConfig` for a freshly parsed table:\n * table view, first numeric field charted against the first dimension.\n */\nexport function makeDefaultDataConfig(\n parsed: ParsedTable,\n sourceName?: string,\n): FlowDataConfig {\n const x = firstDimension(parsed.columns);\n const nums = numericFields(parsed.columns);\n return {\n columns: parsed.columns,\n rows: parsed.rows,\n view: 'table',\n sourceName,\n chart: {\n type: 'bar',\n xField: x,\n yFields: nums.slice(0, 1),\n },\n pivot: {\n enabled: false,\n rowFields: x ? [x] : [],\n colFields: [],\n values: nums[0] ? [{ field: nums[0], aggFn: 'sum' }] : [],\n },\n };\n}\n\n/**\n * Re-parse CSV text into an existing config, preserving column keys and types\n * for headers that match an existing column (by label or key). New headers get\n * fresh inferred columns; chart/pivot references to removed fields are pruned.\n */\nexport function applyCsvText(config: FlowDataConfig, text: string): FlowDataConfig {\n const matrix = parseCsv(text);\n if (matrix.length === 0) {\n return { ...config, columns: [], rows: [], chart: { type: 'bar' }, pivot: { enabled: false, rowFields: [], colFields: [], values: [] } };\n }\n const [header, ...dataRows] = matrix;\n const colCount = Math.max(header.length, ...dataRows.map((r) => r.length), 0);\n const used = new Set<string>();\n const byLabel = new Map(config.columns.map((c) => [c.label ?? c.key, c]));\n const byKey = new Map(config.columns.map((c) => [c.key, c]));\n\n const columns: FlowDataColumn[] = [];\n for (let c = 0; c < colCount; c++) {\n const label = (header[c] ?? `列${c + 1}`).trim() || `列${c + 1}`;\n const existing = byLabel.get(label) ?? byKey.get(label);\n if (existing) {\n used.add(existing.key);\n columns.push({ ...existing, label });\n } else {\n const samples = dataRows.slice(0, 50).map((r) => r[c] ?? '');\n columns.push({ key: toKey(label, used), label, type: inferColumnType(samples) });\n }\n }\n\n const rows = dataRows.map((r) => {\n const record: Record<string, unknown> = {};\n columns.forEach((col, c) => {\n record[col.key] = coerceValue(r[c] ?? '', col.type);\n });\n return record;\n });\n\n const keys = new Set(columns.map((c) => c.key));\n\n const chart = config.chart\n ? {\n ...config.chart,\n xField: config.chart.xField && keys.has(config.chart.xField)\n ? config.chart.xField\n : firstDimension(columns),\n yFields: (config.chart.yFields ?? []).filter((f) => keys.has(f)),\n seriesColors: config.chart.seriesColors\n ? Object.fromEntries(Object.entries(config.chart.seriesColors).filter(([f]) => keys.has(f)))\n : undefined,\n }\n : config.chart;\n\n const pivot = config.pivot\n ? {\n ...config.pivot,\n rowFields: config.pivot.rowFields.filter((f) => keys.has(f)),\n colFields: config.pivot.colFields.filter((f) => keys.has(f)),\n values: config.pivot.values.filter((v) => keys.has(v.field)),\n }\n : config.pivot;\n\n return { ...config, columns, rows, chart, pivot };\n}\n\n/** A small starter table used when creating an empty Data node from the palette. */\nexport function makeStarterDataConfig(): FlowDataConfig {\n const columns: FlowDataColumn[] = [\n { key: 'category', label: '类别', type: 'string' },\n { key: 'value', label: '数值', type: 'number' },\n ];\n const rows = [\n { category: 'A', value: 30 },\n { category: 'B', value: 45 },\n { category: 'C', value: 28 },\n ];\n return makeDefaultDataConfig({ columns, rows });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"dataUtils.js","names":["parseCsv","src","text","delimiter","detectDelimiter","rows","field","row","inQuotes","i","ch","r","firstLine","commas","semis","tabs","toCsv","esc","s","v","header","columns","c","body","parseXlsx","files","unzipSync","buffer","parser","readXml","entry","path","strFromU8","sharedStrings","sst","si","ts","t","sheetDoc","resolveFirstSheetPath","rowsEls","matrix","rowEl","cells","rowArr","colIdx","columnRefToIndex","type","value","idx","cell","workbook","rels","sheet","rid","rel","p","letters","ref","toKey","base","label","key","n","used","DATE_RE","inferColumnType","nonEmpty","samples","coerceValue","raw","cleaned","matrixToTable","dataRows","colCount","record","col","parseDataFile","name","file","DATA_CHART_PALETTE","labelFor","firstDimension","numericFields","makeDefaultDataConfig","x","parsed","nums","applyCsvText","config","byLabel","byKey","existing","keys","chart","f","pivot","makeStarterDataConfig"],"sources":["../../../../../src/components/pro/Flow/nodes/dataUtils.ts"],"sourcesContent":["// Data-node helpers: CSV/XLSX parsing, column type inference, and building\n// TanStack Charts definitions from parsed rows. Kept dependency-light so the\n// base `dishui/flow` bundle stays small — `fflate` (already a transitive dep of\n// markmap) is the only heavy import and only loads when an .xlsx is opened.\n\nimport type {\n FlowDataColumn,\n FlowDataColumnType,\n FlowDataConfig,\n FlowDataChartConfig,\n} from '../types';\n\n/** Result of parsing a raw table into columns + row records. */\nexport interface ParsedTable {\n columns: FlowDataColumn[];\n rows: Array<Record<string, unknown>>;\n}\n\n// ── CSV parsing ────────────────────────────────────────────────────────────\n\n/**\n * Parse CSV text into a matrix of string cells. Handles quoted fields,\n * escaped quotes (\"\"), and embedded newlines/commas. Auto-detects `,` or `;`\n * as the delimiter from the first non-empty line.\n */\nexport function parseCsv(text: string): string[][] {\n const src = text.replace(/^\\uFEFF/, ''); // strip BOM\n const delimiter = detectDelimiter(src);\n const rows: string[][] = [];\n let field = '';\n let row: string[] = [];\n let inQuotes = false;\n\n for (let i = 0; i < src.length; i++) {\n const ch = src[i];\n if (inQuotes) {\n if (ch === '\"') {\n if (src[i + 1] === '\"') {\n field += '\"';\n i++;\n } else {\n inQuotes = false;\n }\n } else {\n field += ch;\n }\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n } else if (ch === delimiter) {\n row.push(field);\n field = '';\n } else if (ch === '\\n') {\n row.push(field);\n rows.push(row);\n field = '';\n row = [];\n } else if (ch === '\\r') {\n // ignore; handled by the following \\n (or end of input)\n } else {\n field += ch;\n }\n }\n // flush trailing field/row\n if (field.length > 0 || row.length > 0) {\n row.push(field);\n rows.push(row);\n }\n // drop fully-empty trailing rows\n return rows.filter((r) => !(r.length === 1 && r[0].trim() === ''));\n}\n\nfunction detectDelimiter(src: string): string {\n const firstLine = src.split(/\\r?\\n/, 1)[0] ?? '';\n const commas = (firstLine.match(/,/g) ?? []).length;\n const semis = (firstLine.match(/;/g) ?? []).length;\n const tabs = (firstLine.match(/\\t/g) ?? []).length;\n if (tabs > commas && tabs > semis) return '\\t';\n if (semis > commas) return ';';\n return ',';\n}\n\n/** Serialize columns + rows back to CSV text (for round-tripping/exports). */\nexport function toCsv(columns: FlowDataColumn[], rows: Array<Record<string, unknown>>): string {\n const esc = (v: unknown) => {\n const s = v == null ? '' : String(v);\n return /[\",\\n\\r]/.test(s) ? `\"${s.replace(/\"/g, '\"\"')}\"` : s;\n };\n const header = columns.map((c) => esc(c.label ?? c.key)).join(',');\n const body = rows\n .map((r) => columns.map((c) => esc(r[c.key])).join(','))\n .join('\\n');\n return body ? `${header}\\n${body}` : header;\n}\n\n// ── XLSX parsing ─────────────────────────────────────────────────────────────\n\n/**\n * Parse the first worksheet of an XLSX workbook (an OOXML zip) into a string\n * matrix. Uses `fflate` to unzip and DOMParser to read the sheet XML + shared\n * strings. Loaded dynamically so it never enters the base bundle.\n */\nexport async function parseXlsx(buffer: ArrayBuffer): Promise<string[][]> {\n const { unzipSync, strFromU8 } = await import('fflate');\n const files = unzipSync(new Uint8Array(buffer));\n\n const parser = new DOMParser();\n const readXml = (path: string): Document | null => {\n const entry = files[path];\n if (!entry) return null;\n return parser.parseFromString(strFromU8(entry), 'application/xml');\n };\n\n // Shared strings table (cells of type \"s\" reference these by index).\n const sharedStrings: string[] = [];\n const sst = readXml('xl/sharedStrings.xml');\n if (sst) {\n for (const si of Array.from(sst.getElementsByTagName('si'))) {\n // Concatenate all <t> runs within the string item.\n const ts = Array.from(si.getElementsByTagName('t'));\n sharedStrings.push(ts.map((t) => t.textContent ?? '').join(''));\n }\n }\n\n // Resolve the first sheet's target from the workbook relationships.\n const sheetPath = resolveFirstSheetPath(files, readXml);\n const sheetDoc = readXml(sheetPath);\n if (!sheetDoc) throw new Error('无法读取 Excel 工作表');\n\n const rowsEls = Array.from(sheetDoc.getElementsByTagName('row'));\n const matrix: string[][] = [];\n for (const rowEl of rowsEls) {\n const cells = Array.from(rowEl.getElementsByTagName('c'));\n const rowArr: string[] = [];\n for (const c of cells) {\n const ref = c.getAttribute('r') ?? '';\n const colIdx = columnRefToIndex(ref);\n const type = c.getAttribute('t');\n let value = '';\n if (type === 's') {\n const idx = Number(c.getElementsByTagName('v')[0]?.textContent ?? '');\n value = Number.isFinite(idx) ? sharedStrings[idx] ?? '' : '';\n } else if (type === 'inlineStr') {\n value = c.getElementsByTagName('t')[0]?.textContent ?? '';\n } else {\n value = c.getElementsByTagName('v')[0]?.textContent ?? '';\n }\n if (colIdx >= 0) rowArr[colIdx] = value;\n }\n // normalize holes to empty strings\n for (let i = 0; i < rowArr.length; i++) if (rowArr[i] == null) rowArr[i] = '';\n matrix.push(rowArr);\n }\n return matrix.filter((r) => r.some((cell) => cell.trim() !== ''));\n}\n\nfunction resolveFirstSheetPath(\n files: Record<string, Uint8Array>,\n readXml: (path: string) => Document | null,\n): string {\n // Prefer following workbook.xml → rels; fall back to the conventional path.\n const workbook = readXml('xl/workbook.xml');\n const rels = readXml('xl/_rels/workbook.xml.rels');\n if (workbook && rels) {\n const sheet = workbook.getElementsByTagName('sheet')[0];\n const rid = sheet?.getAttribute('r:id') ?? sheet?.getAttributeNS(\n 'http://schemas.openxmlformats.org/officeDocument/2006/relationships',\n 'id',\n );\n if (rid) {\n for (const rel of Array.from(rels.getElementsByTagName('Relationship'))) {\n if (rel.getAttribute('Id') === rid) {\n const target = rel.getAttribute('Target') ?? '';\n const norm = target.replace(/^\\//, '').replace(/^xl\\//, '');\n return `xl/${norm}`;\n }\n }\n }\n }\n if (files['xl/worksheets/sheet1.xml']) return 'xl/worksheets/sheet1.xml';\n // last resort: first worksheet entry found\n const first = Object.keys(files).find((p) => /^xl\\/worksheets\\/.*\\.xml$/.test(p));\n return first ?? 'xl/worksheets/sheet1.xml';\n}\n\n/** Convert an A1-style cell ref (e.g. \"C5\") to a zero-based column index. */\nfunction columnRefToIndex(ref: string): number {\n const letters = ref.match(/^[A-Z]+/i)?.[0];\n if (!letters) return -1;\n let idx = 0;\n for (const ch of letters.toUpperCase()) {\n idx = idx * 26 + (ch.charCodeAt(0) - 64);\n }\n return idx - 1;\n}\n\n// ── Matrix → table (columns + typed rows) ────────────────────────────────────\n\n/** Slugify a header label into a safe object key, deduping collisions. */\nfunction toKey(label: string, used: Set<string>): string {\n let base = label.trim().replace(/\\s+/g, '_').replace(/[^\\w\\u4e00-\\u9fa5]/g, '');\n if (!base) base = 'col';\n let key = base;\n let n = 1;\n while (used.has(key)) key = `${base}_${n++}`;\n used.add(key);\n return key;\n}\n\nconst DATE_RE = /^\\d{4}[-/]\\d{1,2}[-/]\\d{1,2}([ T]\\d{1,2}:\\d{2}(:\\d{2})?)?$/;\n\n/** Infer a column type from a sample of raw string cell values. */\nexport function inferColumnType(samples: string[]): FlowDataColumnType {\n const nonEmpty = samples.map((s) => s?.trim()).filter((s) => s);\n if (nonEmpty.length === 0) return 'string';\n const isBool = nonEmpty.every((s) => /^(true|false|yes|no|是|否)$/i.test(s));\n if (isBool) return 'boolean';\n const isNum = nonEmpty.every((s) => /^-?\\d+(\\.\\d+)?%?$/.test(s.replace(/,/g, '')));\n if (isNum) return 'number';\n const isDate = nonEmpty.every((s) => DATE_RE.test(s));\n if (isDate) return 'date';\n return 'string';\n}\n\n/** Coerce a raw string cell to a typed value based on the column type. */\nexport function coerceValue(raw: string, type: FlowDataColumnType): unknown {\n const s = (raw ?? '').trim();\n if (s === '') return type === 'number' ? null : '';\n switch (type) {\n case 'number': {\n const cleaned = s.replace(/,/g, '').replace(/%$/, '');\n const n = Number(cleaned);\n return Number.isFinite(n) ? n : null;\n }\n case 'boolean':\n return /^(true|yes|是)$/i.test(s);\n case 'date':\n case 'string':\n default:\n return s;\n }\n}\n\n/**\n * Build a typed table from a raw string matrix. The first row is treated as the\n * header. Column types are inferred from the data rows.\n */\nexport function matrixToTable(matrix: string[][]): ParsedTable {\n if (matrix.length === 0) return { columns: [], rows: [] };\n const [header, ...dataRows] = matrix;\n const used = new Set<string>();\n const colCount = Math.max(header.length, ...dataRows.map((r) => r.length), 0);\n\n const columns: FlowDataColumn[] = [];\n for (let c = 0; c < colCount; c++) {\n const label = (header[c] ?? `列${c + 1}`).trim() || `列${c + 1}`;\n const samples = dataRows.slice(0, 50).map((r) => r[c] ?? '');\n columns.push({ key: toKey(label, used), label, type: inferColumnType(samples) });\n }\n\n const rows = dataRows.map((r) => {\n const record: Record<string, unknown> = {};\n columns.forEach((col, c) => {\n record[col.key] = coerceValue(r[c] ?? '', col.type);\n });\n return record;\n });\n\n return { columns, rows };\n}\n\n/** Parse a File (csv or xlsx) into a typed table. */\nexport async function parseDataFile(file: File): Promise<ParsedTable> {\n const name = file.name.toLowerCase();\n if (name.endsWith('.xlsx') || name.endsWith('.xlsm')) {\n const buffer = await file.arrayBuffer();\n return matrixToTable(await parseXlsx(buffer));\n }\n // treat everything else as delimited text (csv/tsv/txt)\n const text = await file.text();\n return matrixToTable(parseCsv(text));\n}\n\n// ── Config helpers ───────────────────────────────────────────────────────────\n\n/** Default palette for chart series, mirroring the demo examples. */\nexport const DATA_CHART_PALETTE = [\n '#6366f1', '#10b981', '#f59e0b', '#f43f5e', '#06b6d4',\n '#8b5cf6', '#84cc16', '#ec4899', '#0ea5e9', '#f97316',\n];\n\n/** Column key → display label lookup. */\nexport function labelFor(columns: FlowDataColumn[], key: string): string {\n return columns.find((c) => c.key === key)?.label ?? key;\n}\n\n/** First non-numeric column key (a sensible default X/category field). */\nexport function firstDimension(columns: FlowDataColumn[]): string | undefined {\n return (columns.find((c) => c.type !== 'number') ?? columns[0])?.key;\n}\n\n/** All numeric column keys (candidate measures/series). */\nexport function numericFields(columns: FlowDataColumn[]): string[] {\n return columns.filter((c) => c.type === 'number').map((c) => c.key);\n}\n\n/**\n * Produce a sensible default `FlowDataConfig` for a freshly parsed table:\n * table view, first numeric field charted against the first dimension.\n */\nexport function makeDefaultDataConfig(\n parsed: ParsedTable,\n sourceName?: string,\n): FlowDataConfig {\n const x = firstDimension(parsed.columns);\n const nums = numericFields(parsed.columns);\n return {\n columns: parsed.columns,\n rows: parsed.rows,\n view: 'table',\n sourceName,\n chart: {\n type: 'bar',\n xField: x,\n yFields: nums.slice(0, 1),\n },\n pivot: {\n enabled: false,\n rowFields: x ? [x] : [],\n colFields: [],\n values: nums[0] ? [{ field: nums[0], aggFn: 'sum' }] : [],\n },\n };\n}\n\n/**\n * Re-parse CSV text into an existing config, preserving column keys and types\n * for headers that match an existing column (by label or key). New headers get\n * fresh inferred columns; chart/pivot references to removed fields are pruned.\n */\nexport function applyCsvText(config: FlowDataConfig, text: string): FlowDataConfig {\n const matrix = parseCsv(text);\n if (matrix.length === 0) {\n return { ...config, columns: [], rows: [], chart: { type: 'bar' }, pivot: { enabled: false, rowFields: [], colFields: [], values: [] } };\n }\n const [header, ...dataRows] = matrix;\n const colCount = Math.max(header.length, ...dataRows.map((r) => r.length), 0);\n const used = new Set<string>();\n const byLabel = new Map(config.columns.map((c) => [c.label ?? c.key, c]));\n const byKey = new Map(config.columns.map((c) => [c.key, c]));\n\n const columns: FlowDataColumn[] = [];\n for (let c = 0; c < colCount; c++) {\n const label = (header[c] ?? `列${c + 1}`).trim() || `列${c + 1}`;\n const existing = byLabel.get(label) ?? byKey.get(label);\n if (existing) {\n used.add(existing.key);\n columns.push({ ...existing, label });\n } else {\n const samples = dataRows.slice(0, 50).map((r) => r[c] ?? '');\n columns.push({ key: toKey(label, used), label, type: inferColumnType(samples) });\n }\n }\n\n const rows = dataRows.map((r) => {\n const record: Record<string, unknown> = {};\n columns.forEach((col, c) => {\n record[col.key] = coerceValue(r[c] ?? '', col.type);\n });\n return record;\n });\n\n const keys = new Set(columns.map((c) => c.key));\n\n const chart = config.chart\n ? {\n ...config.chart,\n xField: config.chart.xField && keys.has(config.chart.xField)\n ? config.chart.xField\n : firstDimension(columns),\n yFields: (config.chart.yFields ?? []).filter((f) => keys.has(f)),\n seriesColors: config.chart.seriesColors\n ? Object.fromEntries(Object.entries(config.chart.seriesColors).filter(([f]) => keys.has(f)))\n : undefined,\n }\n : config.chart;\n\n const pivot = config.pivot\n ? {\n ...config.pivot,\n rowFields: config.pivot.rowFields.filter((f) => keys.has(f)),\n colFields: config.pivot.colFields.filter((f) => keys.has(f)),\n values: config.pivot.values.filter((v) => keys.has(v.field)),\n }\n : config.pivot;\n\n return { ...config, columns, rows, chart, pivot };\n}\n\n/** A small starter table used when creating an empty Data node from the palette. */\nexport function makeStarterDataConfig(): FlowDataConfig {\n const columns: FlowDataColumn[] = [\n { key: 'category', label: '类别', type: 'string' },\n { key: 'value', label: '数值', type: 'number' },\n ];\n const rows = [\n { category: 'A', value: 30 },\n { category: 'B', value: 45 },\n { category: 'C', value: 28 },\n ];\n return makeDefaultDataConfig({ columns, rows });\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"demandRender.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/demandRender.ts"],"sourcesContent":["/** One pending frame at most; OrbitControls.update() reports when damping settles. */\nexport function createDemandRender({\n update,\n render,\n request = requestAnimationFrame,\n cancel = cancelAnimationFrame,\n}: {\n update: () => boolean;\n render: () => void;\n request?: (callback: FrameRequestCallback) => number;\n cancel?: (id: number) => void;\n}) {\n let frame: number | null = null;\n let active = false;\n let disposed = false;\n let updating = false;\n let dirty = true;\n\n const schedule = () => {\n if (active && !disposed && frame === null) frame = request(tick);\n };\n const tick = () => {\n frame = null;\n if (!active || disposed) return;\n updating = true;\n const changed = update();\n updating = false;\n if (dirty || changed) render();\n dirty = false;\n if (changed) schedule();\n };\n const invalidate = () => {\n // update() emits change synchronously; its boolean result already accounts\n // for that change. Scheduling here would leave an extra perpetual RAF.\n if (updating || disposed) return;\n dirty = true;\n schedule();\n };\n return {\n invalidate,\n setActive(next: boolean) {\n if (active === next || disposed) return;\n active = next;\n if (active) invalidate();\n else if (frame !== null) {\n cancel(frame);\n frame = null;\n }\n },\n dispose() {\n disposed = true;\n if (frame !== null) cancel(frame);\n frame = null;\n },\n };\n}\n"],"mappings":";AACA,
|
|
1
|
+
{"version":3,"file":"demandRender.js","names":["createDemandRender","frame","active","disposed","updating","dirty","schedule","request","tick","changed","update","render","invalidate","next","cancel"],"sources":["../../../../../src/components/pro/Flow/nodes/demandRender.ts"],"sourcesContent":["/** One pending frame at most; OrbitControls.update() reports when damping settles. */\nexport function createDemandRender({\n update,\n render,\n request = requestAnimationFrame,\n cancel = cancelAnimationFrame,\n}: {\n update: () => boolean;\n render: () => void;\n request?: (callback: FrameRequestCallback) => number;\n cancel?: (id: number) => void;\n}) {\n let frame: number | null = null;\n let active = false;\n let disposed = false;\n let updating = false;\n let dirty = true;\n\n const schedule = () => {\n if (active && !disposed && frame === null) frame = request(tick);\n };\n const tick = () => {\n frame = null;\n if (!active || disposed) return;\n updating = true;\n const changed = update();\n updating = false;\n if (dirty || changed) render();\n dirty = false;\n if (changed) schedule();\n };\n const invalidate = () => {\n // update() emits change synchronously; its boolean result already accounts\n // for that change. Scheduling here would leave an extra perpetual RAF.\n if (updating || disposed) return;\n dirty = true;\n schedule();\n };\n return {\n invalidate,\n setActive(next: boolean) {\n if (active === next || disposed) return;\n active = next;\n if (active) invalidate();\n else if (frame !== null) {\n cancel(frame);\n frame = null;\n }\n },\n dispose() {\n disposed = true;\n if (frame !== null) cancel(frame);\n frame = null;\n },\n };\n}\n"],"mappings":";AACA,SAAgBA,EAAmB,EACjC,WACA,WACA,aAAU,uBACV,YAAS,wBAMR;CACD,IAAIC,IAAuB,MACvBC,IAAS,IACTC,IAAW,IACXC,IAAW,IACXC,IAAQ,IAENC,UAAiB;EACrB,AAAIJ,KAAU,CAACC,KAAYF,MAAU,SAAM,IAAQM,EAAQC,CAAI;CACjE,GACMA,UAAa;EAEjB,IADA,IAAQ,MACJ,CAACN,KAAUC,GAAU;EACzB,IAAW;EACX,IAAMM,IAAUC,EAAO;EAIvB,AAHA,IAAW,KACPL,KAASI,MAASE,EAAO,GAC7B,IAAQ,IACJF,KAASH,EAAS;CACxB,GACMM,UAAmB;EAGvB,AAAIR,KAAYD,MAChB,IAAQ,IACRG,EAAS;CACX;CACA,OAAO;EACL;EACA,UAAU,GAAe;GACvB,AAAIJ,MAAWW,KAAQV,MACvB,IAASU,GACLX,IAAQU,EAAW,IACdX,MAAU,SACjBa,EAAOb,CAAK,GACZ,IAAQ;EAEZ;EACA,UAAU;GAGR,AAFA,IAAW,IACPA,MAAU,QAAMa,EAAOb,CAAK,GAChC,IAAQ;EACV;CACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"nodeUtils.js","names":[],"sources":["../../../../../src/components/pro/Flow/nodes/nodeUtils.ts"],"sourcesContent":["import type { FlowNodeData, FlowNode } from '../types';\n\n/** Get CSS-parseable color number from hex string */\nexport function colorToNumber(hex: string): number {\n if (!hex || hex === 'transparent') return 0x000000;\n const clean = hex.replace('#', '');\n return parseInt(clean, 16) || 0x000000;\n}\n\n/** Compute rotation transform in radians */\nexport function getRotationRad(data: FlowNodeData): number {\n return ((data.rotation ?? 0) * Math.PI) / 180;\n}\n\n/** Compute scale for flip */\nexport function getFlipScale(data: FlowNodeData): { sx: number; sy: number } {\n return {\n sx: data.flipX ? -1 : 1,\n sy: data.flipY ? -1 : 1,\n };\n}\n\n/** Generate a unique ID */\nlet _counter = 0;\nexport function generateId(prefix = 'flow'): string {\n return `${prefix}_${Date.now().toString(36)}_${(++_counter).toString(36)}`;\n}\n\n/** Check if a point is within a node's bounding box (optionally padded) */\nexport function isPointInNode(\n px: number,\n py: number,\n node: FlowNode,\n pad = 0,\n): boolean {\n const { x, y } = node.position;\n return px >= x - pad && px <= x + node.width + pad && py >= y - pad && py <= y + node.height + pad;\n}\n\n/** Check if two axis-aligned rects overlap */\nexport function rectsOverlap(\n ax: number, ay: number, aw: number, ah: number,\n bx: number, by: number, bw: number, bh: number,\n): boolean {\n return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;\n}\n\n/** Shortest distance from point (px,py) to line segment (ax,ay)-(bx,by) */\nexport function distToSegment(\n px: number, py: number,\n ax: number, ay: number,\n bx: number, by: number,\n): number {\n const dx = bx - ax, dy = by - ay;\n const lenSq = dx * dx + dy * dy;\n if (lenSq === 0) return Math.hypot(px - ax, py - ay);\n const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));\n return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));\n}\n"],"mappings":";AAUA,
|
|
1
|
+
{"version":3,"file":"nodeUtils.js","names":["getRotationRad","data","getFlipScale","_counter","generateId","prefix","isPointInNode","node","px","x","pad","py","y","rectsOverlap","ax","bx","bw","aw","ay","by","bh","ah","distToSegment","dx","dy","lenSq","t"],"sources":["../../../../../src/components/pro/Flow/nodes/nodeUtils.ts"],"sourcesContent":["import type { FlowNodeData, FlowNode } from '../types';\n\n/** Get CSS-parseable color number from hex string */\nexport function colorToNumber(hex: string): number {\n if (!hex || hex === 'transparent') return 0x000000;\n const clean = hex.replace('#', '');\n return parseInt(clean, 16) || 0x000000;\n}\n\n/** Compute rotation transform in radians */\nexport function getRotationRad(data: FlowNodeData): number {\n return ((data.rotation ?? 0) * Math.PI) / 180;\n}\n\n/** Compute scale for flip */\nexport function getFlipScale(data: FlowNodeData): { sx: number; sy: number } {\n return {\n sx: data.flipX ? -1 : 1,\n sy: data.flipY ? -1 : 1,\n };\n}\n\n/** Generate a unique ID */\nlet _counter = 0;\nexport function generateId(prefix = 'flow'): string {\n return `${prefix}_${Date.now().toString(36)}_${(++_counter).toString(36)}`;\n}\n\n/** Check if a point is within a node's bounding box (optionally padded) */\nexport function isPointInNode(\n px: number,\n py: number,\n node: FlowNode,\n pad = 0,\n): boolean {\n const { x, y } = node.position;\n return px >= x - pad && px <= x + node.width + pad && py >= y - pad && py <= y + node.height + pad;\n}\n\n/** Check if two axis-aligned rects overlap */\nexport function rectsOverlap(\n ax: number, ay: number, aw: number, ah: number,\n bx: number, by: number, bw: number, bh: number,\n): boolean {\n return ax < bx + bw && ax + aw > bx && ay < by + bh && ay + ah > by;\n}\n\n/** Shortest distance from point (px,py) to line segment (ax,ay)-(bx,by) */\nexport function distToSegment(\n px: number, py: number,\n ax: number, ay: number,\n bx: number, by: number,\n): number {\n const dx = bx - ax, dy = by - ay;\n const lenSq = dx * dx + dy * dy;\n if (lenSq === 0) return Math.hypot(px - ax, py - ay);\n const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / lenSq));\n return Math.hypot(px - (ax + t * dx), py - (ay + t * dy));\n}\n"],"mappings":";AAUA,SAAgBA,EAAe,GAA4B;CACzD,QAASC,EAAK,YAAY,KAAK,KAAK,KAAM;AAC5C;AAGA,SAAgBC,EAAa,GAAgD;CAC3E,OAAO;EACL,IAAID,EAAK,QAAQ,KAAK;EACtB,IAAIA,EAAK,QAAQ,KAAK;CACxB;AACF;AAGA,IAAIE,IAAW;AACf,SAAgBC,EAAW,IAAS,QAAgB;CAClD,OAAO,GAAGC,EAAO,GAAG,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,IAAI,EAAEF,EAAA,CAAU,SAAS,EAAE;AACzE;AAGA,SAAgBG,EACd,GACA,GACA,GACA,IAAM,GACG;CACT,IAAM,EAAE,MAAG,SAAMC,EAAK;CACtB,OAAOC,KAAMC,IAAIC,KAAOF,KAAMC,IAAIF,EAAK,QAAQG,KAAOC,KAAMC,IAAIF,KAAOC,KAAMC,IAAIL,EAAK,SAASG;AACjG;AAGA,SAAgBG,EACd,GAAY,GAAY,GAAY,GACpC,GAAY,GAAY,GAAY,GAC3B;CACT,OAAOC,IAAKC,IAAKC,KAAMF,IAAKG,IAAKF,KAAMG,IAAKC,IAAKC,KAAMF,IAAKG,IAAKF;AACnE;AAGA,SAAgBG,EACd,GAAY,GACZ,GAAY,GACZ,GAAY,GACJ;CACR,IAAMC,IAAKR,IAAKD,GAAIU,IAAKL,IAAKD,GACxBO,IAAQF,IAAKA,IAAKC,IAAKA;CAC7B,IAAIC,MAAU,GAAG,OAAO,KAAK,MAAMjB,IAAKM,GAAIH,IAAKO,CAAE;CACnD,IAAMQ,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,KAAKlB,IAAKM,KAAMS,KAAMZ,IAAKO,KAAMM,KAAMC,CAAK,CAAC;CAC5E,OAAO,KAAK,MAAMjB,KAAMM,IAAKY,IAAIH,IAAKZ,KAAMO,IAAKQ,IAAIF,EAAG;AAC1D"}
|