@flowuent-org/diagramming-core 1.3.10 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +13 -13
- package/.eslintignore +1 -1
- package/.eslintrc.json +35 -35
- package/.prettierignore +3 -3
- package/.prettierrc +3 -3
- package/.vscode/settings.json +6 -6
- package/README.md +95 -95
- package/apps/authentication/tsconfig.app.json +23 -23
- package/apps/authentication/tsconfig.json +21 -21
- package/apps/authentication/tsconfig.spec.json +28 -28
- package/apps/diagramming/.eslintrc.json +18 -18
- package/apps/diagramming/index.html +16 -16
- package/apps/diagramming/project.json +53 -53
- package/apps/diagramming/public/aws-icons/compute/App Runner.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Application Auto Scaling.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Batch.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Bottlerocket.svg +19 -19
- package/apps/diagramming/public/aws-icons/compute/Compute Optimizer.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/EC2 Auto Scaling.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/EC2 Image Builder.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/EC2.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Elastic Beanstalk.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Elastic Fabric Adapter.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Fargate.svg +19 -19
- package/apps/diagramming/public/aws-icons/compute/Genomics CLI.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Lambda.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Local Zones.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/NICE DCV.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/NICE EnginFrame.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Nitro Enclaves.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Outposts family.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Outposts rack.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Outposts servers.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/ParallelCluster.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Serverless Application Repository.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/SimSpace Weaver.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Deadline.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Frost.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Sequoia.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Stoke.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox XMesh.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/VMware Cloud on AWS.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Wavelength.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/ECS Anywhere.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/EKS Anywhere.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/EKS Cloud.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/EKS Distro.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/Elastic Container Registry.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/Elastic Container Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/Elastic Kubernetes Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/Fargate.svg +19 -19
- package/apps/diagramming/public/aws-icons/container/Red Hat OpenShift Service on AWS.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Aurora.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Database Migration Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/DocumentDB.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/ElastiCache.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Keyspaces.svg +15 -15
- package/apps/diagramming/public/aws-icons/database/MemoryDB for Redis.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Neptune.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/RDS on VMware.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/RDS.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Timestream.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/App Mesh.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Client VPN.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Cloud Directory.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Cloud Map.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Cloud WAN.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/CloudFront.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Direct Connect.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Elastic Load Balancing.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Global Accelerator.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Private 5G.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/PrivateLink.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Route 53.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Site to Site VPN.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Transit Gateway.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/VPC Lattice.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Verified Access.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Virtual Private Cloud.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Artifact.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Audit Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Certificate Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/CloudHSM.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Cognito.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Detective.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Directory Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Firewall Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/GuardDuty.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/IAM Identity Center.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Identity and Access Management.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Inspector.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Key Management Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Macie.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Network Firewall.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Resource Access Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Secrets Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Security Hub.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Security Lake.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Shield.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Signer.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Verified Permissions.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/WAF.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Backup.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/EFS.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Elastic Block Store.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Elastic Disaster Recovery.svg +15 -15
- package/apps/diagramming/public/aws-icons/storage/FSx for Lustre.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/FSx for NetApp ONTAP.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/FSx for OpenZFS.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/FSx for WFS.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/FSx.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/File Cache.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/S3 on Outposts.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Simple Storage Service Glacier.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Simple Storage Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Snowball Edge.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Snowball.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Snowcone.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Snowmobile.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Storage Gateway.svg +17 -17
- package/apps/diagramming/public/azure-icons/compute/02112-icon-service-Automanaged-VM.svg +1 -1
- package/apps/diagramming/public/azure-icons/databases/02390-icon-service-Azure-SQL.svg +1 -1
- package/apps/diagramming/public/azure-icons/databases/02392-icon-service-SSIS-Lift-And-Shift-IR.svg +1 -1
- package/apps/diagramming/public/azure-icons/networking/02145-icon-service-Resource-Management-Private-Link.svg +1 -1
- package/apps/diagramming/public/azure-icons/security/02247-icon-service-Microsoft-Defender-for-IoT.svg +1 -1
- package/apps/diagramming/public/sample-node-complete.json +63 -63
- package/apps/diagramming/public/sample-node-data.json +63 -63
- package/apps/diagramming/src/AutomationDiagramData.ts +147 -480
- package/apps/diagramming/src/CollaborationDiagramData.ts +127 -127
- package/apps/diagramming/src/CustomIconExamples.ts +233 -233
- package/apps/diagramming/src/DiagramTabs.tsx +205 -205
- package/apps/diagramming/src/Pages/DevPlayground.tsx +3 -3
- package/apps/diagramming/src/Pages/Home.tsx +111 -111
- package/apps/diagramming/src/SequenceDiagramData.ts +215 -215
- package/apps/diagramming/src/StateMachineDiagramData.ts +64 -64
- package/apps/diagramming/src/UseCaseDiagramData.ts +52 -52
- package/apps/diagramming/src/cloud-data.ts +371 -371
- package/apps/diagramming/src/components/AddNodeView.tsx +252 -252
- package/apps/diagramming/src/createSelecors.tsx +17 -17
- package/apps/diagramming/src/index.scss +12 -12
- package/apps/diagramming/src/main.tsx +463 -463
- package/apps/diagramming/src/node-data.ts +664 -664
- package/apps/diagramming/src/sample-workflow-content.ts +55 -55
- package/apps/diagramming/src/stencil-items.ts +31 -31
- package/apps/diagramming/src/style.css +13 -13
- package/apps/diagramming/src/tabsStore.tsx +152 -152
- package/apps/diagramming/src/vite-env.d.ts +1 -1
- package/apps/diagramming/tsconfig.app.json +23 -23
- package/apps/diagramming/tsconfig.json +21 -21
- package/apps/diagramming/tsconfig.spec.json +28 -28
- package/apps/diagramming/vite.config.ts +34 -34
- package/apps/diagramming/webpack.config.js +13 -13
- package/apps/workflow/.eslintrc.json +18 -18
- package/apps/workflow/group/group-form-hooks/index.ts +3 -3
- package/apps/workflow/group/group-form-hooks/useFormValidation.ts +20 -20
- package/apps/workflow/group/group-form-schema/SettingsTabFormSchema.ts +7 -7
- package/apps/workflow/group/group-form-schema/index.ts +3 -3
- package/apps/workflow/i18n.ts +19 -19
- package/apps/workflow/index.html +16 -16
- package/apps/workflow/locales/en/translation.json +713 -713
- package/apps/workflow/project.json +8 -8
- package/apps/workflow/src/app/app.module.css +1 -1
- package/apps/workflow/src/app/app.spec.tsx +15 -15
- package/apps/workflow/src/app/app.tsx +370 -370
- package/apps/workflow/src/main.tsx +13 -13
- package/apps/workflow/src/styles.css +1 -1
- package/apps/workflow/src/theme/index.ts +546 -546
- package/apps/workflow/tsconfig.app.json +23 -23
- package/apps/workflow/tsconfig.json +21 -21
- package/apps/workflow/tsconfig.spec.json +28 -28
- package/apps/workflow/vite.config.ts +51 -51
- package/nx.json +58 -58
- package/package.json +1 -1
- package/packages/api/.babelrc +12 -12
- package/packages/api/.eslintrc.json +18 -18
- package/packages/api/README.md +7 -7
- package/packages/api/project.json +8 -8
- package/packages/api/src/index.ts +3 -3
- package/packages/api/src/lib/jsonPlaceholderService/endpoints.ts +3 -3
- package/packages/api/src/lib/jsonPlaceholderService/service.ts +13 -13
- package/packages/api/tsconfig.json +17 -17
- package/packages/api/tsconfig.lib.json +24 -24
- package/packages/atoms/.babelrc +12 -12
- package/packages/atoms/.eslintrc.json +18 -18
- package/packages/atoms/README.md +7 -7
- package/packages/atoms/forms/AuthenticationTabForm/authenticationTabForm.json +224 -224
- package/packages/atoms/forms/AuthenticationTabForm/index.tsx +255 -255
- package/packages/atoms/forms/SettingsTabForm/index.tsx +85 -85
- package/packages/atoms/forms/SettingsTabForm/settingTabForm.json +27 -27
- package/packages/atoms/forms/index.ts +4 -4
- package/packages/atoms/project.json +8 -8
- package/packages/atoms/src/index.ts +33 -33
- package/packages/atoms/src/lib/Accordion/Accordion.module.css +7 -7
- package/packages/atoms/src/lib/Accordion/Accordion.spec.tsx +13 -13
- package/packages/atoms/src/lib/Accordion/Accordion.tsx +29 -29
- package/packages/atoms/src/lib/Alert/Alert.module.css +7 -7
- package/packages/atoms/src/lib/Alert/Alert.spec.tsx +10 -10
- package/packages/atoms/src/lib/Alert/Alert.tsx +19 -19
- package/packages/atoms/src/lib/Avatar/Avatar.module.css +7 -7
- package/packages/atoms/src/lib/Avatar/Avatar.spec.tsx +10 -10
- package/packages/atoms/src/lib/Avatar/Avatar.tsx +22 -22
- package/packages/atoms/src/lib/Box/Box.module.css +7 -7
- package/packages/atoms/src/lib/Box/Box.spec.tsx +10 -10
- package/packages/atoms/src/lib/Box/Box.tsx +19 -19
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.module.css +7 -7
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.spec.tsx +10 -10
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.tsx +15 -15
- package/packages/atoms/src/lib/Button/Button.module.css +7 -7
- package/packages/atoms/src/lib/Button/Button.spec.tsx +10 -10
- package/packages/atoms/src/lib/Button/Button.tsx +78 -78
- package/packages/atoms/src/lib/Card/Card.module.css +7 -7
- package/packages/atoms/src/lib/Card/Card.spec.tsx +10 -10
- package/packages/atoms/src/lib/Card/Card.tsx +19 -19
- package/packages/atoms/src/lib/CardContent/CardContent.module.css +7 -7
- package/packages/atoms/src/lib/CardContent/CardContent.spec.tsx +10 -10
- package/packages/atoms/src/lib/CardContent/CardContent.tsx +22 -22
- package/packages/atoms/src/lib/FormTextField/FormTextField.module.css +7 -7
- package/packages/atoms/src/lib/FormTextField/FormTextField.spec.tsx +10 -10
- package/packages/atoms/src/lib/FormTextField/FormTextField.tsx +35 -35
- package/packages/atoms/src/lib/Grid/Grid.module.css +7 -7
- package/packages/atoms/src/lib/Grid/Grid.spec.tsx +10 -10
- package/packages/atoms/src/lib/Grid/Grid.tsx +19 -19
- package/packages/atoms/src/lib/Stack/Stack.module.css +7 -7
- package/packages/atoms/src/lib/Stack/Stack.spec.tsx +10 -10
- package/packages/atoms/src/lib/Stack/Stack.tsx +19 -19
- package/packages/atoms/src/lib/Switch/Switch.module.css +7 -7
- package/packages/atoms/src/lib/Switch/Switch.spec.tsx +10 -10
- package/packages/atoms/src/lib/Switch/Switch.tsx +46 -46
- package/packages/atoms/src/lib/TableView/TableView.module.css +7 -7
- package/packages/atoms/src/lib/TableView/TableView.spec.tsx +10 -10
- package/packages/atoms/src/lib/TableView/TableView.tsx +88 -88
- package/packages/atoms/src/lib/TextField/TextField.module.css +7 -7
- package/packages/atoms/src/lib/TextField/TextField.spec.tsx +10 -10
- package/packages/atoms/src/lib/TextField/TextField.tsx +17 -17
- package/packages/atoms/src/lib/Typography/Typography.module.css +7 -7
- package/packages/atoms/src/lib/Typography/Typography.spec.tsx +10 -10
- package/packages/atoms/src/lib/Typography/Typography.tsx +22 -22
- package/packages/atoms/tsconfig.json +17 -17
- package/packages/atoms/tsconfig.lib.json +30 -30
- package/packages/common/.babelrc +12 -12
- package/packages/common/.eslintrc.json +18 -18
- package/packages/common/README.md +7 -7
- package/packages/common/project.json +8 -8
- package/packages/common/src/index.ts +4 -4
- package/packages/common/src/lib/generateRandomProfilePic.ts +3 -3
- package/packages/common/src/lib/generateValidationSchema.ts +46 -46
- package/packages/common/tsconfig.json +17 -17
- package/packages/common/tsconfig.lib.json +24 -24
- package/packages/contexts/.babelrc +12 -12
- package/packages/contexts/.eslintrc.json +18 -18
- package/packages/contexts/README.md +7 -7
- package/packages/contexts/project.json +8 -8
- package/packages/contexts/src/index.ts +2 -2
- package/packages/contexts/tsconfig.json +17 -17
- package/packages/contexts/tsconfig.lib.json +24 -24
- package/packages/diagrams/.babelrc +12 -12
- package/packages/diagrams/.ctirc +208 -208
- package/packages/diagrams/.eslintrc.json +17 -17
- package/packages/diagrams/CUSTOM_ICONS.md +232 -232
- package/packages/diagrams/GOOGLE_SHEETS_IMPLEMENTATION.md +233 -233
- package/packages/diagrams/NODE_DATA_UPDATE_API.md +430 -430
- package/packages/diagrams/README.md +7 -7
- package/packages/diagrams/UNDO_REDO_API.md +306 -306
- package/packages/diagrams/locales/en/translation.json +713 -713
- package/packages/diagrams/package.json +27 -27
- package/packages/diagrams/pnpm-lock.yaml +2606 -2606
- package/packages/diagrams/project.json +42 -42
- package/packages/diagrams/rollup.config.js +26 -26
- package/packages/diagrams/src/DiagramFlow.tsx +7 -7
- package/packages/diagrams/src/declarations.d.ts +7 -7
- package/packages/diagrams/src/index.ts +8 -1
- package/packages/diagrams/src/index.ts.bak +99 -99
- package/packages/diagrams/src/lib/assets/markers/markers.param.tsx +101 -101
- package/packages/diagrams/src/lib/assets/markers/markers.type.ts +10 -10
- package/packages/diagrams/src/lib/atoms/ActorNode.tsx +123 -123
- package/packages/diagrams/src/lib/atoms/AddParallelColButton.tsx +26 -26
- package/packages/diagrams/src/lib/atoms/BendpointNode.tsx +71 -71
- package/packages/diagrams/src/lib/atoms/CardBlockTypeSelector.tsx +35 -35
- package/packages/diagrams/src/lib/atoms/CardEditableTitle.tsx +76 -76
- package/packages/diagrams/src/lib/atoms/CardMainContent.tsx +19 -19
- package/packages/diagrams/src/lib/atoms/ConnectionPoints.tsx +149 -149
- package/packages/diagrams/src/lib/atoms/ContentAssist.tsx +341 -341
- package/packages/diagrams/src/lib/atoms/DiagramControls.tsx +56 -56
- package/packages/diagrams/src/lib/atoms/ExpressionInput.tsx +437 -437
- package/packages/diagrams/src/lib/atoms/FloatingConnectionLine.tsx +73 -73
- package/packages/diagrams/src/lib/atoms/LazyIcon.tsx +31 -31
- package/packages/diagrams/src/lib/atoms/MarkerSelector.tsx +106 -106
- package/packages/diagrams/src/lib/atoms/MultiSelectInput.tsx +87 -87
- package/packages/diagrams/src/lib/atoms/ParameterInput.tsx +199 -199
- package/packages/diagrams/src/lib/atoms/PropertyInput.tsx +114 -114
- package/packages/diagrams/src/lib/atoms/StyledBox.tsx +69 -69
- package/packages/diagrams/src/lib/atoms/ValidationError.tsx +37 -37
- package/packages/diagrams/src/lib/atoms/ValidationMessage.tsx +63 -63
- package/packages/diagrams/src/lib/components/AiButton.tsx +52 -52
- package/packages/diagrams/src/lib/components/ControlEdgeList.tsx +303 -303
- package/packages/diagrams/src/lib/components/CreateFunctionModal.tsx +264 -264
- package/packages/diagrams/src/lib/components/CustomCodeModal.tsx +287 -287
- package/packages/diagrams/src/lib/components/DiagramPanel.tsx +331 -331
- package/packages/diagrams/src/lib/components/FunctionModalTooltip.tsx +30 -30
- package/packages/diagrams/src/lib/components/GraphQLCustomCodeModal.tsx +210 -210
- package/packages/diagrams/src/lib/components/Header.tsx +143 -143
- package/packages/diagrams/src/lib/components/MiniMapCommon.tsx +24 -24
- package/packages/diagrams/src/lib/components/ResizeIcon.tsx +81 -81
- package/packages/diagrams/src/lib/components/SideBar.tsx +40 -40
- package/packages/diagrams/src/lib/components/automation/AISuggestionsModal.tsx +269 -269
- package/packages/diagrams/src/lib/components/automation/AISuggestionsPanel.tsx +28 -175
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationDecisionNode.tsx +94 -0
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationExecutionPanel.tsx +439 -439
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationInteractionNode.tsx +693 -693
- package/packages/diagrams/src/lib/components/automation/AutomationMonitoringNode.tsx +399 -399
- package/packages/diagrams/src/lib/components/automation/AutomationNavigationNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationNodeCard.tsx +414 -0
- package/packages/diagrams/src/lib/components/automation/AutomationSlackNode.tsx +694 -694
- package/packages/diagrams/src/lib/components/automation/AutomationSmsNode.tsx +62 -0
- package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationTelegramNode.tsx +664 -664
- package/packages/diagrams/src/lib/components/automation/AutomationVoiceCallNode.tsx +65 -0
- package/packages/diagrams/src/lib/components/automation/AutomationWorkflowNode.tsx +85 -0
- package/packages/diagrams/src/lib/components/automation/NodeAIAssistantPopup.tsx +504 -504
- package/packages/diagrams/src/lib/components/automation/index.ts +29 -19
- package/packages/diagrams/src/lib/components/automation/statusColors.ts +124 -124
- package/packages/diagrams/src/lib/contexts/CardDataProvider.tsx +111 -111
- package/packages/diagrams/src/lib/contexts/DiagramProvider.tsx +392 -391
- package/packages/diagrams/src/lib/contexts/diagramStoreTypes.tsx +134 -134
- package/packages/diagrams/src/lib/contexts/onConnect.ts +39 -39
- package/packages/diagrams/src/lib/contexts/onDragStart.ts +45 -45
- package/packages/diagrams/src/lib/contexts/onNodeDragEnd.ts +179 -179
- package/packages/diagrams/src/lib/contexts/onNodesChange.ts +406 -406
- package/packages/diagrams/src/lib/contexts/onReconnect.ts +59 -59
- package/packages/diagrams/src/lib/contexts/onWorkflowNodeDelete.ts +65 -65
- package/packages/diagrams/src/lib/contexts/setContentHeight.ts +14 -14
- package/packages/diagrams/src/lib/contexts/setDiagramType.ts +10 -10
- package/packages/diagrams/src/lib/contexts/setEdgeShapeType.ts +7 -7
- package/packages/diagrams/src/lib/contexts/setEdges.ts +9 -9
- package/packages/diagrams/src/lib/contexts/setLayoutDirection.ts +9 -9
- package/packages/diagrams/src/lib/contexts/setNodeSetting.ts +17 -17
- package/packages/diagrams/src/lib/contexts/setNodes.ts +10 -10
- package/packages/diagrams/src/lib/contexts/setPannable.ts +7 -7
- package/packages/diagrams/src/lib/contexts/setSelectedEdge.ts +7 -7
- package/packages/diagrams/src/lib/contexts/setSelectedNode.ts +7 -7
- package/packages/diagrams/src/lib/contexts/undo.ts +163 -163
- package/packages/diagrams/src/lib/contexts/updateNodeSetting.ts +17 -17
- package/packages/diagrams/src/lib/examples/TwilioWhatsAppConfigExample.ts +128 -128
- package/packages/diagrams/src/lib/externals.ts +4 -4
- package/packages/diagrams/src/lib/hooks/customUseReactFlow.tsx +90 -90
- package/packages/diagrams/src/lib/hooks/updateNodes.ts +45 -45
- package/packages/diagrams/src/lib/hooks/useAddChildButton.tsx +61 -61
- package/packages/diagrams/src/lib/hooks/useAddSwitchCase.ts +75 -75
- package/packages/diagrams/src/lib/hooks/useAutoRegisterFunctions.ts +135 -135
- package/packages/diagrams/src/lib/hooks/useAutoRegisterVariables.ts +286 -286
- package/packages/diagrams/src/lib/hooks/useDeleteNodeByEvent.ts +64 -64
- package/packages/diagrams/src/lib/hooks/useDragCallbacks.tsx +122 -122
- package/packages/diagrams/src/lib/hooks/useFormValidation.ts +349 -349
- package/packages/diagrams/src/lib/hooks/useFunctionFormCallbacks.ts +136 -136
- package/packages/diagrams/src/lib/hooks/useIsSelectedNode.ts +11 -11
- package/packages/diagrams/src/lib/hooks/useModalControls.ts +13 -13
- package/packages/diagrams/src/lib/hooks/useNodeDragHandlers.ts +43 -43
- package/packages/diagrams/src/lib/hooks/useNodeSelection.ts +55 -55
- package/packages/diagrams/src/lib/hooks/useRenderDividers.tsx +19 -19
- package/packages/diagrams/src/lib/hooks/useRenderPins.tsx +74 -74
- package/packages/diagrams/src/lib/hooks/useResizeObserver.ts +21 -21
- package/packages/diagrams/src/lib/hooks/useTreeChildNode.ts +536 -536
- package/packages/diagrams/src/lib/i18n.ts +42 -42
- package/packages/diagrams/src/lib/icons/icons.tsx +324 -0
- package/packages/diagrams/src/lib/icons/index.ts +29 -0
- package/packages/diagrams/src/lib/icons/types.ts +7 -0
- package/packages/diagrams/src/lib/molecules/AddingBlock.tsx +147 -147
- package/packages/diagrams/src/lib/molecules/AvailableVariablesDisplay.tsx +57 -57
- package/packages/diagrams/src/lib/molecules/Block.tsx +143 -143
- package/packages/diagrams/src/lib/molecules/BlockProvider.tsx +28 -28
- package/packages/diagrams/src/lib/molecules/BlockWrapper.tsx +14 -14
- package/packages/diagrams/src/lib/molecules/ConditionRule.tsx +131 -131
- package/packages/diagrams/src/lib/molecules/DraggablePane.tsx +198 -198
- package/packages/diagrams/src/lib/molecules/EntityNodeBlocks.tsx +66 -66
- package/packages/diagrams/src/lib/molecules/SideHandles.tsx +215 -215
- package/packages/diagrams/src/lib/molecules/StencilItem.tsx +85 -85
- package/packages/diagrams/src/lib/molecules/WorkflowNodeActionButtons.tsx +62 -62
- package/packages/diagrams/src/lib/molecules/animated-add-button.tsx +83 -83
- package/packages/diagrams/src/lib/molecules/json-viewer.tsx +107 -107
- package/packages/diagrams/src/lib/organisms/AddNodeView.tsx +102 -102
- package/packages/diagrams/src/lib/organisms/Card/EntityNode.tsx +259 -259
- package/packages/diagrams/src/lib/organisms/Card/card.params.ts +29 -29
- package/packages/diagrams/src/lib/organisms/Card/card.types.ts +5 -5
- package/packages/diagrams/src/lib/organisms/CodeModal.tsx +105 -105
- package/packages/diagrams/src/lib/organisms/ConditionRuleGroup.tsx +138 -138
- package/packages/diagrams/src/lib/organisms/CustomEdge/EdgeMarkers.tsx +51 -51
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge-generator.tsx +135 -135
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge.params.ts +6 -6
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge.type.ts +15 -15
- package/packages/diagrams/src/lib/organisms/CustomEdge/useCreateBendPoint.tsx +121 -121
- package/packages/diagrams/src/lib/organisms/CustomEdge/useEdgeModal.ts +37 -37
- package/packages/diagrams/src/lib/organisms/CustomEdge/useEdgePath.tsx +99 -99
- package/packages/diagrams/src/lib/organisms/DownloadPanel.tsx +345 -345
- package/packages/diagrams/src/lib/organisms/EdgeModal.tsx +243 -243
- package/packages/diagrams/src/lib/organisms/HistoryPane.tsx +118 -118
- package/packages/diagrams/src/lib/organisms/NodeContextMenu.tsx +259 -259
- package/packages/diagrams/src/lib/organisms/PropertiesPane.tsx +276 -276
- package/packages/diagrams/src/lib/organisms/SmartDynamicForm.tsx +226 -226
- package/packages/diagrams/src/lib/organisms/Stencil.tsx +60 -60
- package/packages/diagrams/src/lib/organisms/UseDiagramStore.tsx +4 -4
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeActionButtons.tsx +45 -45
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeTypeDisplay.tsx +114 -114
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/WorkflowNodeIcons.tsx +21 -23
- package/packages/diagrams/src/lib/organisms/WorkflowNode.tsx +484 -484
- package/packages/diagrams/src/lib/services/GoogleSheetsService.ts +961 -961
- package/packages/diagrams/src/lib/services/SlackService.ts +134 -134
- package/packages/diagrams/src/lib/services/TwilioWhatsAppService.ts +273 -273
- package/packages/diagrams/src/lib/styles.css +55 -55
- package/packages/diagrams/src/lib/templates/PageLinks.tsx +13 -13
- package/packages/diagrams/src/lib/templates/arch/ArchDiagram.tsx +793 -793
- package/packages/diagrams/src/lib/templates/arch/components/EdgeDialog.tsx +82 -82
- package/packages/diagrams/src/lib/templates/arch/components/EdgeSettings.tsx +81 -81
- package/packages/diagrams/src/lib/templates/arch/components/GroupControls.tsx +59 -59
- package/packages/diagrams/src/lib/templates/arch/components/GroupSizeDialog.tsx +81 -81
- package/packages/diagrams/src/lib/templates/arch/components/NodeDialog.tsx +214 -214
- package/packages/diagrams/src/lib/templates/arch/components/nodeTypes.ts +91 -91
- package/packages/diagrams/src/lib/templates/arch/controls/ArchDiagramControls.tsx +349 -349
- package/packages/diagrams/src/lib/templates/arch/data/templatee-data.ts +212 -212
- package/packages/diagrams/src/lib/templates/cloud-arch/components/CloudArchitectureDiagram.tsx +3912 -3912
- package/packages/diagrams/src/lib/templates/cloud-arch/components/FormatConverter.tsx +809 -809
- package/packages/diagrams/src/lib/templates/cloud-arch/components/FragmentImporter.tsx +276 -276
- package/packages/diagrams/src/lib/templates/cloud-arch/components/PropertiesPanel.tsx +1063 -1063
- package/packages/diagrams/src/lib/templates/cloud-arch/components/ServiceIconPicker.tsx +138 -138
- package/packages/diagrams/src/lib/templates/cloud-arch/components/SettingsSidebar.tsx +656 -656
- package/packages/diagrams/src/lib/templates/cloud-arch/components/SubnetSelector.tsx +468 -468
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/ServiceNode.tsx +310 -310
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/SubnetNode.tsx +475 -475
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/UsersNode.tsx +277 -277
- package/packages/diagrams/src/lib/templates/cloud-arch/data/diagramManager.ts +364 -364
- package/packages/diagrams/src/lib/templates/cloud-arch/data/types.ts +183 -183
- package/packages/diagrams/src/lib/templates/cloud-arch/integration/diagramValidator.ts +379 -379
- package/packages/diagrams/src/lib/templates/cloud-arch/messages/errorMessages.json +95 -95
- package/packages/diagrams/src/lib/templates/cloud-arch/types/localization.ts +368 -368
- package/packages/diagrams/src/lib/templates/cloud-arch/types/validation.ts +72 -72
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/aws-icons.ts +557 -557
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/awsValidation.ts +446 -446
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/azure-icons.ts +802 -802
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/azureValidation.ts +495 -495
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/cloudValidation.ts +929 -929
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/errorMessages.ts +283 -283
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/gcp-icons.ts +186 -186
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/gcpValidation.ts +312 -312
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/genericValidation.ts +689 -689
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/iconMapping.ts +383 -383
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/idGenerator.ts +220 -220
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/localizationManager.ts +300 -300
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/restrictionEngine.ts +550 -550
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/restrictionValidator.ts +400 -400
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/serviceIcons.tsx +129 -129
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/serviceMapping.ts +330 -330
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/translations.ts +54 -54
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/validationExtractor.ts +342 -342
- package/packages/diagrams/src/lib/templates/collaborationDiagram/CollaborationDiagram.tsx +15 -15
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/AddCollabEdgeDialog.tsx +181 -181
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollabLayout.ts +182 -182
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollabObjectNodeSettings.tsx +86 -86
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollaborationDiagramFlow.tsx +731 -731
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/DraggableEdgeLabel.tsx +123 -123
- package/packages/diagrams/src/lib/templates/collaborationDiagram/controls/CollaborationControls.tsx +131 -131
- package/packages/diagrams/src/lib/templates/collaborationDiagram/nodes/CollabEdge.tsx +170 -170
- package/packages/diagrams/src/lib/templates/collaborationDiagram/nodes/CollabObjectNode.tsx +178 -178
- package/packages/diagrams/src/lib/templates/home.style.css +16 -16
- package/packages/diagrams/src/lib/templates/node-forms/ApiForm.tsx +416 -416
- package/packages/diagrams/src/lib/templates/node-forms/CallForm.tsx +370 -370
- package/packages/diagrams/src/lib/templates/node-forms/ForLoopNode.tsx +313 -313
- package/packages/diagrams/src/lib/templates/node-forms/FunctionForm.tsx +211 -211
- package/packages/diagrams/src/lib/templates/node-forms/GraphQLForm.tsx +679 -679
- package/packages/diagrams/src/lib/templates/node-forms/IfNodeForm.tsx +97 -97
- package/packages/diagrams/src/lib/templates/node-forms/LetNodeForm.tsx +533 -533
- package/packages/diagrams/src/lib/templates/node-forms/LoopNode.tsx +149 -149
- package/packages/diagrams/src/lib/templates/node-forms/NodeForm.tsx +383 -383
- package/packages/diagrams/src/lib/templates/node-forms/ReturnNodeForm.tsx +96 -96
- package/packages/diagrams/src/lib/templates/node-forms/SetNodeForm.tsx +141 -141
- package/packages/diagrams/src/lib/templates/node-forms/SwitchNodeForm.tsx +249 -249
- package/packages/diagrams/src/lib/templates/node-forms/TryCatchForm.tsx +167 -167
- package/packages/diagrams/src/lib/templates/nodeFormSchema.json +84 -84
- package/packages/diagrams/src/lib/templates/sequence/SequenceDiagram.tsx +22 -22
- package/packages/diagrams/src/lib/templates/sequence/components/ActivationBarNode.tsx +33 -33
- package/packages/diagrams/src/lib/templates/sequence/components/AddMessageDialog.tsx +302 -302
- package/packages/diagrams/src/lib/templates/sequence/components/CombinedFragmentNode.tsx +65 -65
- package/packages/diagrams/src/lib/templates/sequence/components/EntitySettings.tsx +559 -559
- package/packages/diagrams/src/lib/templates/sequence/components/FragmentSettings.tsx +116 -116
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceComponents.tsx +14 -14
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceDiagramFlow.tsx +1361 -1361
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceEntityNode.tsx +394 -394
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceMessageEdge.tsx +272 -272
- package/packages/diagrams/src/lib/templates/sequence/components/SimpleEntitySettings.tsx +89 -89
- package/packages/diagrams/src/lib/templates/sequence/controls/DiagramControls.tsx +707 -707
- package/packages/diagrams/src/lib/templates/sequence/data/template-data.ts +635 -635
- package/packages/diagrams/src/lib/templates/stateMachine/StateMachineDiagram.tsx +66 -66
- package/packages/diagrams/src/lib/templates/stateMachine/components/CompositeStateNodeSettings.tsx +638 -638
- package/packages/diagrams/src/lib/templates/stateMachine/components/CustomEdge.tsx +220 -220
- package/packages/diagrams/src/lib/templates/stateMachine/components/EdgeSettings.tsx +193 -193
- package/packages/diagrams/src/lib/templates/stateMachine/components/SelfConnectingEdge.tsx +41 -41
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineComponents.tsx +49 -49
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineDiagramFlow.tsx +1238 -1238
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineLayout.ts +350 -350
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateNodeSettings.tsx +188 -188
- package/packages/diagrams/src/lib/templates/stateMachine/components/useSelfTransition.ts +114 -114
- package/packages/diagrams/src/lib/templates/stateMachine/controls/StateDiagramControls.tsx +488 -488
- package/packages/diagrams/src/lib/templates/stateMachine/data/template-data.ts +4 -4
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ChoicePointNode.tsx +54 -54
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/CompositeStateNode.tsx +225 -225
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/EntryPointNode.tsx +31 -31
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ExitPointNode.tsx +44 -44
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/FinalStateNode.tsx +41 -41
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ForkJoinNode.tsx +53 -53
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/HistoryStateNode.tsx +36 -36
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/InitialStateNode.tsx +29 -29
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/JunctionPointNode.tsx +33 -33
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/PinPointNode.tsx +111 -111
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/StateNode.tsx +124 -124
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/bendPoint.tsx +71 -71
- package/packages/diagrams/src/lib/templates/systemFlow/SystemFlowDiagram.tsx +67 -67
- package/packages/diagrams/src/lib/templates/systemFlow/components/EdgeSettings.tsx +330 -330
- package/packages/diagrams/src/lib/templates/systemFlow/components/FloatingEdge.tsx +219 -219
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowComponents.tsx +28 -28
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowDiagramFlow.tsx +1179 -1179
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowNodeSettings.tsx +151 -151
- package/packages/diagrams/src/lib/templates/systemFlow/controls/SystemFlowControls.tsx +435 -435
- package/packages/diagrams/src/lib/templates/systemFlow/data/template-data.ts +308 -308
- package/packages/diagrams/src/lib/templates/systemFlow/demo.tsx +91 -91
- package/packages/diagrams/src/lib/templates/systemFlow/index.ts +5 -5
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/BendpointNode.tsx +80 -80
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/SystemNode.tsx +92 -92
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/TreeSystemNode.tsx +755 -755
- package/packages/diagrams/src/lib/templates/useCaseDiagram/UseCaseDiagram.tsx +16 -16
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/ActorNodeSettings.tsx +103 -103
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/AddRelationshipDialog.tsx +186 -186
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/PackageNodeSettings.tsx +205 -205
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/UseCaseDiagramFlow.tsx +637 -637
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/UseCaseNodeSettings.tsx +120 -120
- package/packages/diagrams/src/lib/templates/useCaseDiagram/controls/UseCaseControls.tsx +289 -289
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/PackageNode.tsx +83 -83
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/UseCaseEdge.tsx +115 -115
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/UseCaseNode.tsx +230 -230
- package/packages/diagrams/src/lib/templates/validationSchema.json +277 -277
- package/packages/diagrams/src/lib/theme.ts +439 -439
- package/packages/diagrams/src/lib/types/FunctionSignature.ts +11 -11
- package/packages/diagrams/src/lib/types/SmartDynamicFormField.ts +37 -37
- package/packages/diagrams/src/lib/types/automation-node-data-types.ts +45 -131
- package/packages/diagrams/src/lib/types/available-variables.ts +18 -18
- package/packages/diagrams/src/lib/types/card-node.ts +68 -68
- package/packages/diagrams/src/lib/types/collaboration-types.ts +114 -114
- package/packages/diagrams/src/lib/types/colors.ts +25 -25
- package/packages/diagrams/src/lib/types/condition-builder.ts +18 -18
- package/packages/diagrams/src/lib/types/diagram-types.ts +14 -14
- package/packages/diagrams/src/lib/types/edge-types.ts +39 -39
- package/packages/diagrams/src/lib/types/function-execution.ts +35 -35
- package/packages/diagrams/src/lib/types/hooks.types.ts +6 -6
- package/packages/diagrams/src/lib/types/ndoe-form-types.ts +139 -139
- package/packages/diagrams/src/lib/types/node-types.ts +13 -23
- package/packages/diagrams/src/lib/types/sequence-types.ts +178 -178
- package/packages/diagrams/src/lib/types/shared-node-types.ts +21 -21
- package/packages/diagrams/src/lib/types/state-machine-types.ts +225 -225
- package/packages/diagrams/src/lib/types/stencil-item.ts +7 -7
- package/packages/diagrams/src/lib/types/structures.ts +80 -80
- package/packages/diagrams/src/lib/types/system-flow-types.ts +129 -129
- package/packages/diagrams/src/lib/types/usecase-types.ts +84 -84
- package/packages/diagrams/src/lib/types/validation-types.ts +134 -134
- package/packages/diagrams/src/lib/types/workflow-content-dynamic-form-type.ts +23 -23
- package/packages/diagrams/src/lib/types/workflow-node-data-types.ts +172 -172
- package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +658 -1179
- package/packages/diagrams/src/lib/utils/add-new-block.ts +34 -34
- package/packages/diagrams/src/lib/utils/add-new-node.ts +52 -52
- package/packages/diagrams/src/lib/utils/addToHistory.ts +15 -15
- package/packages/diagrams/src/lib/utils/automation-flow-processor.ts +556 -619
- package/packages/diagrams/src/lib/utils/closestPoint.ts +45 -45
- package/packages/diagrams/src/lib/utils/color-options.tsx +26 -26
- package/packages/diagrams/src/lib/utils/compress-img.ts +59 -59
- package/packages/diagrams/src/lib/utils/configLoader.ts +329 -329
- package/packages/diagrams/src/lib/utils/constant-lengths.ts +8 -8
- package/packages/diagrams/src/lib/utils/create-updated.tsx +40 -40
- package/packages/diagrams/src/lib/utils/createHistoryChange.ts +3 -3
- package/packages/diagrams/src/lib/utils/dividerUtils.tsx +59 -59
- package/packages/diagrams/src/lib/utils/edge-hooks.ts +53 -53
- package/packages/diagrams/src/lib/utils/elkLayout.ts +297 -297
- package/packages/diagrams/src/lib/utils/event-hooks.ts +69 -69
- package/packages/diagrams/src/lib/utils/event-store.ts +58 -58
- package/packages/diagrams/src/lib/utils/flow-node-hooks.ts +28 -28
- package/packages/diagrams/src/lib/utils/flow-to-js.ts +1465 -1465
- package/packages/diagrams/src/lib/utils/functionGenerator.ts +38 -38
- package/packages/diagrams/src/lib/utils/generateEdgesFromNodeOrder.ts +113 -113
- package/packages/diagrams/src/lib/utils/helpers.types.ts +10 -10
- package/packages/diagrams/src/lib/utils/highlightText.tsx +93 -93
- package/packages/diagrams/src/lib/utils/iconMapper.tsx +76 -76
- package/packages/diagrams/src/lib/utils/logger.ts +5 -5
- package/packages/diagrams/src/lib/utils/model-hooks.ts +129 -129
- package/packages/diagrams/src/lib/utils/node-hooks.ts +78 -78
- package/packages/diagrams/src/lib/utils/nodeAIAssistantConfig.ts +54 -54
- package/packages/diagrams/src/lib/utils/nodeOrderByEdges.ts +437 -437
- package/packages/diagrams/src/lib/utils/nodeutils.ts +242 -242
- package/packages/diagrams/src/lib/utils/object.ts +13 -13
- package/packages/diagrams/src/lib/utils/useDebounce.ts +19 -19
- package/packages/diagrams/src/lib/utils/utilities.ts +105 -105
- package/packages/diagrams/src/lib/utils/validationEngine.ts +610 -610
- package/packages/diagrams/src/lib/utils/vhToPixels.ts +9 -9
- package/packages/diagrams/tsconfig.json +21 -21
- package/packages/diagrams/tsconfig.lib.json +26 -25
- package/packages/diagrams/webpack.config.js +17 -17
- package/packages/interfaces/.babelrc +12 -12
- package/packages/interfaces/.eslintrc.json +18 -18
- package/packages/interfaces/README.md +7 -7
- package/packages/interfaces/project.json +8 -8
- package/packages/interfaces/src/index.ts +21 -21
- package/packages/interfaces/src/lib/Button.tsx +1 -1
- package/packages/interfaces/src/lib/DataGrid.ts +14 -14
- package/packages/interfaces/src/lib/StyledBox.tsx +1 -1
- package/packages/interfaces/src/lib/ToolbarItem.ts +7 -7
- package/packages/interfaces/src/lib/UserCard.ts +8 -8
- package/packages/interfaces/src/lib/yup.ts +3 -3
- package/packages/interfaces/tsconfig.json +17 -17
- package/packages/interfaces/tsconfig.lib.json +24 -24
- package/packages/molecules/.babelrc +12 -12
- package/packages/molecules/.eslintrc.json +18 -18
- package/packages/molecules/README.md +7 -7
- package/packages/molecules/project.json +8 -8
- package/packages/molecules/src/index.ts +47 -41
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.module.css +7 -7
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.spec.tsx +10 -10
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.tsx +33 -33
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.module.css +7 -7
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.spec.tsx +11 -11
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.tsx +69 -69
- package/packages/molecules/src/lib/EditorTab/EditorTab.module.css +7 -7
- package/packages/molecules/src/lib/EditorTab/EditorTab.spec.tsx +10 -10
- package/packages/molecules/src/lib/EditorTab/EditorTab.tsx +42 -42
- package/packages/molecules/src/lib/EditorTab/TabPanel.tsx +25 -25
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.module.css +7 -7
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.spec.tsx +10 -10
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.tsx +24 -24
- package/packages/molecules/src/lib/SvgIcons/icons.tsx +323 -291
- package/packages/molecules/src/lib/SvgIcons/index.ts +31 -25
- package/packages/molecules/src/lib/SvgIcons/types.ts +7 -7
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.module.css +7 -7
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.spec.tsx +10 -10
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.tsx +47 -47
- package/packages/molecules/src/lib/UserCard/UserCard.module.css +7 -7
- package/packages/molecules/src/lib/UserCard/UserCard.spec.tsx +11 -11
- package/packages/molecules/src/lib/UserCard/UserCard.tsx +22 -22
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.module.css +7 -7
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.spec.tsx +10 -10
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.tsx +72 -72
- package/packages/molecules/tsconfig.json +17 -17
- package/packages/molecules/tsconfig.lib.json +30 -30
- package/packages/organisms/.babelrc +12 -12
- package/packages/organisms/.eslintrc.json +18 -18
- package/packages/organisms/README.md +7 -7
- package/packages/organisms/project.json +8 -8
- package/packages/organisms/src/index.ts +4 -4
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.module.css +7 -7
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.spec.tsx +10 -10
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.tsx +117 -117
- package/packages/organisms/src/lib/EditorToolbar/SortableItem.tsx +51 -51
- package/packages/organisms/src/lib/UserList/UserList.module.css +7 -7
- package/packages/organisms/src/lib/UserList/UserList.spec.tsx +11 -11
- package/packages/organisms/src/lib/UserList/UserList.tsx +48 -48
- package/packages/organisms/src/lib/UserList/store.ts +49 -49
- package/packages/organisms/tsconfig.json +17 -17
- package/packages/organisms/tsconfig.lib.json +24 -24
- package/packages/zustand/.babelrc +12 -12
- package/packages/zustand/.eslintrc.json +18 -18
- package/packages/zustand/README.md +7 -7
- package/packages/zustand/project.json +8 -8
- package/packages/zustand/src/index.ts +4 -4
- package/packages/zustand/tsconfig.json +17 -17
- package/packages/zustand/tsconfig.lib.json +24 -24
- package/tsconfig.base.json +29 -29
- package/packages/diagrams/src/lib/components/automation/AutomationAISuggestionNode.tsx +0 -304
- package/packages/diagrams/src/lib/components/automation/AutomationEmailNode.tsx +0 -773
- package/packages/diagrams/src/lib/components/automation/AutomationGoogleServicesNode.tsx +0 -779
- package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +0 -422
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +0 -1135
- package/packages/diagrams/src/lib/examples/GoogleSheetsConfigurationExamples.ts +0 -306
package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineDiagramFlow.tsx
CHANGED
|
@@ -1,1238 +1,1238 @@
|
|
|
1
|
-
import React, {
|
|
2
|
-
useCallback,
|
|
3
|
-
useState,
|
|
4
|
-
useRef,
|
|
5
|
-
useEffect,
|
|
6
|
-
useMemo,
|
|
7
|
-
} from 'react';
|
|
8
|
-
|
|
9
|
-
// Fullscreen API type extensions
|
|
10
|
-
interface FullscreenElement extends HTMLElement {
|
|
11
|
-
webkitRequestFullscreen?: () => Promise<void>;
|
|
12
|
-
msRequestFullscreen?: () => Promise<void>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface FullscreenDocument extends Document {
|
|
16
|
-
webkitExitFullscreen?: () => Promise<void>;
|
|
17
|
-
msExitFullscreen?: () => Promise<void>;
|
|
18
|
-
}
|
|
19
|
-
import {
|
|
20
|
-
ReactFlow,
|
|
21
|
-
Background,
|
|
22
|
-
useNodesState,
|
|
23
|
-
useEdgesState,
|
|
24
|
-
Panel,
|
|
25
|
-
BackgroundVariant,
|
|
26
|
-
Connection,
|
|
27
|
-
NodeChange,
|
|
28
|
-
applyNodeChanges,
|
|
29
|
-
ReactFlowProvider,
|
|
30
|
-
Node,
|
|
31
|
-
Edge,
|
|
32
|
-
NodeProps,
|
|
33
|
-
} from '@xyflow/react';
|
|
34
|
-
import '@xyflow/react/dist/style.css';
|
|
35
|
-
import {
|
|
36
|
-
StateMachineNode,
|
|
37
|
-
StateMachineEdge,
|
|
38
|
-
HistoryState,
|
|
39
|
-
EditingState,
|
|
40
|
-
StateNodeSettingsState,
|
|
41
|
-
EdgeSettingsState,
|
|
42
|
-
NodeSelectionContextType,
|
|
43
|
-
LayoutDirection,
|
|
44
|
-
Position,
|
|
45
|
-
UpdateFunction,
|
|
46
|
-
CloseFunction,
|
|
47
|
-
DeleteFunction
|
|
48
|
-
} from '../../../types/state-machine-types';
|
|
49
|
-
|
|
50
|
-
import {
|
|
51
|
-
Box,
|
|
52
|
-
Button,
|
|
53
|
-
Paper,
|
|
54
|
-
ToggleButton,
|
|
55
|
-
ToggleButtonGroup,
|
|
56
|
-
Snackbar,
|
|
57
|
-
Alert,
|
|
58
|
-
} from '@mui/material';
|
|
59
|
-
import DownloadIcon from '@mui/icons-material/Download';
|
|
60
|
-
import UploadIcon from '@mui/icons-material/Upload';
|
|
61
|
-
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
|
62
|
-
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
|
63
|
-
import CloseIcon from '@mui/icons-material/Close';
|
|
64
|
-
import UndoIcon from '@mui/icons-material/Undo';
|
|
65
|
-
import RedoIcon from '@mui/icons-material/Redo';
|
|
66
|
-
import ViewWeekIcon from '@mui/icons-material/ViewWeek';
|
|
67
|
-
import ViewStreamIcon from '@mui/icons-material/ViewStream';
|
|
68
|
-
import {
|
|
69
|
-
stateMachineDefaultNodes,
|
|
70
|
-
stateMachineDefaultEdges,
|
|
71
|
-
} from '../data/template-data';
|
|
72
|
-
import {
|
|
73
|
-
stateMachineEdgeTypes,
|
|
74
|
-
stateMachineNodeTypes as baseStateMachineNodeTypes,
|
|
75
|
-
CompositeStateNode as CompositeStateNodeType,
|
|
76
|
-
} from './StateMachineComponents';
|
|
77
|
-
import DiagramControls from '../controls/StateDiagramControls';
|
|
78
|
-
import { NodeSelectionContext } from '../controls/StateDiagramControls';
|
|
79
|
-
import StateNodeSettings from './StateNodeSettings';
|
|
80
|
-
import EdgeSettings from './EdgeSettings';
|
|
81
|
-
import CompositeStateNodeSettings from './CompositeStateNodeSettings';
|
|
82
|
-
import { useStateMachineLayout } from './StateMachineLayout';
|
|
83
|
-
import DiagramPanel from '../../../components/DiagramPanel';
|
|
84
|
-
import Sidebar from '../../../components/SideBar';
|
|
85
|
-
import AiButton from '../../../components/AiButton';
|
|
86
|
-
import MiniMapCommon from '../../../components/MiniMapCommon';
|
|
87
|
-
|
|
88
|
-
interface StateMachineDiagramFlowProps {
|
|
89
|
-
nodes: StateMachineNode[];
|
|
90
|
-
setNodes: (nodes: StateMachineNode[] | ((prev: StateMachineNode[]) => StateMachineNode[])) => void;
|
|
91
|
-
edges: StateMachineEdge[];
|
|
92
|
-
setEdges: (edges: StateMachineEdge[] | ((prev: StateMachineEdge[]) => StateMachineEdge[])) => void;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
interface CompositeStateNodeProps extends NodeProps {
|
|
96
|
-
onToggleExpand?: (id: string, expanded: boolean) => void;
|
|
97
|
-
onSettingsClick?: () => void;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Utility: Deep compare node arrays by id, type, data, width, height
|
|
101
|
-
const deepCompareNodes = (a: Node[], b: Node[]) => {
|
|
102
|
-
if (a.length !== b.length) return false;
|
|
103
|
-
for (let i = 0; i < a.length; i++) {
|
|
104
|
-
const nodeA = a[i];
|
|
105
|
-
const nodeB = b[i];
|
|
106
|
-
if (
|
|
107
|
-
nodeA.id !== nodeB.id ||
|
|
108
|
-
nodeA.type !== nodeB.type ||
|
|
109
|
-
JSON.stringify(nodeA.data) !== JSON.stringify(nodeB.data)
|
|
110
|
-
) {
|
|
111
|
-
return false;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
return true;
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
// Utility: Deep compare edge arrays by id, source, target, type, data
|
|
118
|
-
const deepCompareEdges = (a: Edge[], b: Edge[]) => {
|
|
119
|
-
if (a.length !== b.length) return false;
|
|
120
|
-
for (let i = 0; i < a.length; i++) {
|
|
121
|
-
const edgeA = a[i];
|
|
122
|
-
const edgeB = b[i];
|
|
123
|
-
if (
|
|
124
|
-
edgeA.id !== edgeB.id ||
|
|
125
|
-
edgeA.source !== edgeB.source ||
|
|
126
|
-
edgeA.target !== edgeB.target ||
|
|
127
|
-
edgeA.type !== edgeB.type ||
|
|
128
|
-
JSON.stringify(edgeA.data) !== JSON.stringify(edgeB.data)
|
|
129
|
-
) {
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
return true;
|
|
134
|
-
};
|
|
135
|
-
|
|
136
|
-
// Debounce utility
|
|
137
|
-
const debounce = (fn: Function, delay: number) => {
|
|
138
|
-
let timer: NodeJS.Timeout | null = null;
|
|
139
|
-
return (...args: unknown[]) => {
|
|
140
|
-
if (timer) clearTimeout(timer);
|
|
141
|
-
timer = setTimeout(() => fn(...args), delay);
|
|
142
|
-
};
|
|
143
|
-
};
|
|
144
|
-
|
|
145
|
-
// Helper to recursively collect all child node ids of collapsed composite nodes
|
|
146
|
-
function getHiddenNodeIds(allNodes: Node[]): Set<string> {
|
|
147
|
-
const hidden = new Set<string>();
|
|
148
|
-
const collapsedComposites = allNodes.filter(
|
|
149
|
-
(n) => n.type === 'CompositeStateNode' && !n.data.expanded,
|
|
150
|
-
);
|
|
151
|
-
const collectChildren = (parentId: string) => {
|
|
152
|
-
allNodes.forEach((child) => {
|
|
153
|
-
if (child.parentId === parentId) {
|
|
154
|
-
hidden.add(child.id);
|
|
155
|
-
if (child.type === 'CompositeStateNode') {
|
|
156
|
-
collectChildren(child.id);
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
};
|
|
161
|
-
collapsedComposites.forEach((comp) => collectChildren(comp.id));
|
|
162
|
-
return hidden;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// Helper: get center position in diagram
|
|
166
|
-
const getCenterPosition = (width: number, height: number) => {
|
|
167
|
-
return { x: width / 2 - 75, y: height / 2 - 40 };
|
|
168
|
-
};
|
|
169
|
-
|
|
170
|
-
const StateMachineDiagramFlowContent: React.FC<
|
|
171
|
-
StateMachineDiagramFlowProps
|
|
172
|
-
> = ({
|
|
173
|
-
nodes: initialNodes,
|
|
174
|
-
setNodes: parentSetNodes,
|
|
175
|
-
edges: initialEdges,
|
|
176
|
-
setEdges: parentSetEdges,
|
|
177
|
-
}) => {
|
|
178
|
-
// State declarations
|
|
179
|
-
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes as Node[]);
|
|
180
|
-
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges as Edge[]);
|
|
181
|
-
const [history, setHistory] = useState<HistoryState[]>([]);
|
|
182
|
-
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
|
183
|
-
const isUndoRedoAction = useRef(false);
|
|
184
|
-
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
|
185
|
-
const diagramContainerRef = useRef<HTMLDivElement>(null);
|
|
186
|
-
const [editing, setEditing] = useState<{
|
|
187
|
-
nodeId?: string | null;
|
|
188
|
-
edgeId?: string | null;
|
|
189
|
-
label?: string;
|
|
190
|
-
type?: 'node' | 'edge';
|
|
191
|
-
}>({});
|
|
192
|
-
const [stateNodeSettings, setStateNodeSettings] = useState<{
|
|
193
|
-
id: string | null;
|
|
194
|
-
}>({ id: null });
|
|
195
|
-
const [edgeSettings, setEdgeSettings] = useState<{ id: string | null }>({
|
|
196
|
-
id: null,
|
|
197
|
-
});
|
|
198
|
-
const [selectedCompositeStateNodeId, setSelectedCompositeStateNodeId] =
|
|
199
|
-
useState<string | null>(null);
|
|
200
|
-
const [isSettingsSidebarOpen, setIsSettingsSidebarOpen] = useState(false);
|
|
201
|
-
|
|
202
|
-
// Add layout integration
|
|
203
|
-
const { getLayoutedElements, layoutDirection, setLayoutDirection } =
|
|
204
|
-
useStateMachineLayout(nodes as Node[], edges as StateMachineEdge[], setNodes);
|
|
205
|
-
|
|
206
|
-
// Function declarations in correct order
|
|
207
|
-
const handleToggleCompositeExpand = useCallback(
|
|
208
|
-
(id: string, expanded: boolean) => {
|
|
209
|
-
setNodes((nds: Node[]) =>
|
|
210
|
-
nds.map((node: Node) =>
|
|
211
|
-
node.id === id && node.type === 'CompositeStateNode'
|
|
212
|
-
? { ...node, data: { ...node.data, expanded } }
|
|
213
|
-
: node,
|
|
214
|
-
),
|
|
215
|
-
);
|
|
216
|
-
},
|
|
217
|
-
[setNodes],
|
|
218
|
-
);
|
|
219
|
-
|
|
220
|
-
const nodeTypes = useMemo(() => {
|
|
221
|
-
return {
|
|
222
|
-
...baseStateMachineNodeTypes,
|
|
223
|
-
CompositeStateNode: (props: NodeProps) => {
|
|
224
|
-
const compositeProps: CompositeStateNodeProps = {
|
|
225
|
-
...props,
|
|
226
|
-
onToggleExpand: handleToggleCompositeExpand,
|
|
227
|
-
onSettingsClick: undefined,
|
|
228
|
-
};
|
|
229
|
-
return <CompositeStateNodeType {...compositeProps} />;
|
|
230
|
-
},
|
|
231
|
-
};
|
|
232
|
-
}, [handleToggleCompositeExpand]);
|
|
233
|
-
|
|
234
|
-
const saveLabel = useCallback(() => {
|
|
235
|
-
if (editing.nodeId && editing.type === 'node') {
|
|
236
|
-
setNodes((nds: Node[]) =>
|
|
237
|
-
nds.map((node: Node) => {
|
|
238
|
-
if (node.id === editing.nodeId) {
|
|
239
|
-
return {
|
|
240
|
-
...node,
|
|
241
|
-
data: {
|
|
242
|
-
...node.data,
|
|
243
|
-
label: editing.label,
|
|
244
|
-
},
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
return node;
|
|
248
|
-
}),
|
|
249
|
-
);
|
|
250
|
-
setEditing({
|
|
251
|
-
nodeId: undefined,
|
|
252
|
-
edgeId: undefined,
|
|
253
|
-
label: undefined,
|
|
254
|
-
type: undefined,
|
|
255
|
-
});
|
|
256
|
-
} else if (editing.edgeId && editing.type === 'edge') {
|
|
257
|
-
setEdges((eds: Edge[]) =>
|
|
258
|
-
eds.map((edge: Edge) => {
|
|
259
|
-
if (edge.id === editing.edgeId) {
|
|
260
|
-
return {
|
|
261
|
-
...edge,
|
|
262
|
-
data: {
|
|
263
|
-
...edge.data,
|
|
264
|
-
label: editing.label,
|
|
265
|
-
},
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
return edge;
|
|
269
|
-
}),
|
|
270
|
-
);
|
|
271
|
-
setEditing({
|
|
272
|
-
nodeId: undefined,
|
|
273
|
-
edgeId: undefined,
|
|
274
|
-
label: undefined,
|
|
275
|
-
type: undefined,
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
}, [editing, setNodes, setEdges]);
|
|
279
|
-
|
|
280
|
-
const handleKeyDown = useCallback(
|
|
281
|
-
(e: React.KeyboardEvent) => {
|
|
282
|
-
if (e.key === 'Enter') {
|
|
283
|
-
saveLabel();
|
|
284
|
-
} else if (e.key === 'Escape') {
|
|
285
|
-
setEditing({
|
|
286
|
-
nodeId: undefined,
|
|
287
|
-
edgeId: undefined,
|
|
288
|
-
label: undefined,
|
|
289
|
-
type: undefined,
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
},
|
|
293
|
-
[saveLabel],
|
|
294
|
-
);
|
|
295
|
-
|
|
296
|
-
const loadTemplateData = useCallback(
|
|
297
|
-
(templateType: string) => {
|
|
298
|
-
console.log(`Loading template: ${templateType}`);
|
|
299
|
-
// For now, just load the default state machine nodes/edges
|
|
300
|
-
setNodes(stateMachineDefaultNodes as Node[]);
|
|
301
|
-
setEdges(stateMachineDefaultEdges as Edge[]);
|
|
302
|
-
},
|
|
303
|
-
[setNodes, setEdges],
|
|
304
|
-
);
|
|
305
|
-
|
|
306
|
-
// Initialize nodes and edges with our data
|
|
307
|
-
useEffect(() => {
|
|
308
|
-
// Only initialize if history is empty
|
|
309
|
-
if (history.length === 0) {
|
|
310
|
-
const initialState = {
|
|
311
|
-
nodes: [...nodes],
|
|
312
|
-
edges: [...edges],
|
|
313
|
-
};
|
|
314
|
-
setHistory([initialState]);
|
|
315
|
-
setHistoryIndex(0);
|
|
316
|
-
}
|
|
317
|
-
}, []);
|
|
318
|
-
|
|
319
|
-
// Save current state to history when nodes or edges change (debounced)
|
|
320
|
-
const debouncedUpdateHistory = useRef(
|
|
321
|
-
debounce(
|
|
322
|
-
(
|
|
323
|
-
nodes: Node[],
|
|
324
|
-
edges: Edge[],
|
|
325
|
-
history: HistoryState[],
|
|
326
|
-
historyIndex: number,
|
|
327
|
-
setHistory: React.Dispatch<React.SetStateAction<HistoryState[]>>,
|
|
328
|
-
setHistoryIndex: React.Dispatch<React.SetStateAction<number>>,
|
|
329
|
-
) => {
|
|
330
|
-
const newHistoryEntry = {
|
|
331
|
-
nodes: [...nodes],
|
|
332
|
-
edges: [...edges],
|
|
333
|
-
};
|
|
334
|
-
const currentState = history[historyIndex];
|
|
335
|
-
if (
|
|
336
|
-
currentState &&
|
|
337
|
-
deepCompareNodes(currentState.nodes, newHistoryEntry.nodes) &&
|
|
338
|
-
deepCompareEdges(currentState.edges, newHistoryEntry.edges)
|
|
339
|
-
) {
|
|
340
|
-
return; // No change, don't add to history
|
|
341
|
-
}
|
|
342
|
-
// Trim future history if we're in the middle of the history stack
|
|
343
|
-
let newHistory = history.slice(0, historyIndex + 1);
|
|
344
|
-
newHistory.push(newHistoryEntry);
|
|
345
|
-
// Limit to last 50 entries
|
|
346
|
-
if (newHistory.length > 50) {
|
|
347
|
-
newHistory = newHistory.slice(newHistory.length - 50);
|
|
348
|
-
}
|
|
349
|
-
setHistory(newHistory);
|
|
350
|
-
setHistoryIndex(newHistory.length - 1);
|
|
351
|
-
},
|
|
352
|
-
300,
|
|
353
|
-
),
|
|
354
|
-
).current;
|
|
355
|
-
|
|
356
|
-
useEffect(() => {
|
|
357
|
-
if (isUndoRedoAction.current || history.length === 0) {
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
debouncedUpdateHistory(
|
|
361
|
-
nodes,
|
|
362
|
-
edges,
|
|
363
|
-
history,
|
|
364
|
-
historyIndex,
|
|
365
|
-
setHistory,
|
|
366
|
-
setHistoryIndex,
|
|
367
|
-
);
|
|
368
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
369
|
-
}, [nodes, edges]);
|
|
370
|
-
|
|
371
|
-
// Function to perform undo operation
|
|
372
|
-
const handleUndo = useCallback(() => {
|
|
373
|
-
if (historyIndex > 0) {
|
|
374
|
-
isUndoRedoAction.current = true;
|
|
375
|
-
const prevState = history[historyIndex - 1];
|
|
376
|
-
setNodes(prevState.nodes);
|
|
377
|
-
setEdges(prevState.edges);
|
|
378
|
-
setHistoryIndex(historyIndex - 1);
|
|
379
|
-
setTimeout(() => {
|
|
380
|
-
isUndoRedoAction.current = false;
|
|
381
|
-
}, 0);
|
|
382
|
-
}
|
|
383
|
-
}, [history, historyIndex, setNodes, setEdges]);
|
|
384
|
-
|
|
385
|
-
// Function to perform redo operation
|
|
386
|
-
const handleRedo = useCallback(() => {
|
|
387
|
-
if (historyIndex < history.length - 1) {
|
|
388
|
-
isUndoRedoAction.current = true;
|
|
389
|
-
const nextState = history[historyIndex + 1];
|
|
390
|
-
setNodes(nextState.nodes);
|
|
391
|
-
setEdges(nextState.edges);
|
|
392
|
-
setHistoryIndex(historyIndex + 1);
|
|
393
|
-
setTimeout(() => {
|
|
394
|
-
isUndoRedoAction.current = false;
|
|
395
|
-
}, 0);
|
|
396
|
-
}
|
|
397
|
-
}, [history, historyIndex, setNodes, setEdges]);
|
|
398
|
-
|
|
399
|
-
// Clear handler
|
|
400
|
-
const handleClear = useCallback(() => {
|
|
401
|
-
const cleared = { nodes: [], edges: [] };
|
|
402
|
-
const newHistory = history.slice(0, historyIndex + 1);
|
|
403
|
-
newHistory.push(cleared);
|
|
404
|
-
setNodes([]);
|
|
405
|
-
setEdges([]);
|
|
406
|
-
setHistory(newHistory);
|
|
407
|
-
setHistoryIndex(newHistory.length - 1);
|
|
408
|
-
}, [history, historyIndex, setNodes, setEdges]);
|
|
409
|
-
|
|
410
|
-
// Listen for keyboard shortcuts
|
|
411
|
-
useEffect(() => {
|
|
412
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
413
|
-
// Check for Ctrl+Z or Cmd+Z (undo)
|
|
414
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
|
415
|
-
e.preventDefault();
|
|
416
|
-
handleUndo();
|
|
417
|
-
}
|
|
418
|
-
// Check for Ctrl+Y or Cmd+Shift+Z (redo)
|
|
419
|
-
if (
|
|
420
|
-
((e.ctrlKey || e.metaKey) && e.key === 'y') ||
|
|
421
|
-
((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')
|
|
422
|
-
) {
|
|
423
|
-
e.preventDefault();
|
|
424
|
-
handleRedo();
|
|
425
|
-
}
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
429
|
-
return () => {
|
|
430
|
-
window.removeEventListener('keydown', handleKeyDown);
|
|
431
|
-
};
|
|
432
|
-
}, [handleUndo, handleRedo]);
|
|
433
|
-
|
|
434
|
-
// Node type map for state machine nodes
|
|
435
|
-
const nodeTypeMap: Record<string, { type: string; data?: Record<string, unknown> }> = {
|
|
436
|
-
StateNode: { type: 'StateNode', data: { label: 'State' } },
|
|
437
|
-
InitialStateNode: { type: 'InitialStateNode', data: {} },
|
|
438
|
-
FinalStateNode: { type: 'FinalStateNode', data: {} },
|
|
439
|
-
CompositeStateNode: {
|
|
440
|
-
type: 'CompositeStateNode',
|
|
441
|
-
data: { label: 'Composite State', expanded: false },
|
|
442
|
-
},
|
|
443
|
-
HistoryStateNode: { type: 'HistoryStateNode', data: {} },
|
|
444
|
-
JunctionPointNode: { type: 'JunctionPointNode', data: {} },
|
|
445
|
-
ChoicePointNode: { type: 'ChoicePointNode', data: {} },
|
|
446
|
-
EntryPointNode: { type: 'EntryPointNode', data: {} },
|
|
447
|
-
ExitPointNode: { type: 'ExitPointNode', data: {} },
|
|
448
|
-
ForkJoinNode: { type: 'ForkJoinNode', data: { orientation: 'horizontal' } },
|
|
449
|
-
BendpointNode: { type: 'bendpoint', data: {} },
|
|
450
|
-
};
|
|
451
|
-
|
|
452
|
-
// Add node at center (internal)
|
|
453
|
-
const addNodeAtCenter = useCallback(
|
|
454
|
-
(nodeTypeId: string) => {
|
|
455
|
-
const nodeInfo = nodeTypeMap[nodeTypeId];
|
|
456
|
-
if (!nodeInfo) return;
|
|
457
|
-
const { type, data } = nodeInfo;
|
|
458
|
-
const width = diagramContainerRef.current?.offsetWidth || 1200;
|
|
459
|
-
const height = diagramContainerRef.current?.offsetHeight || 800;
|
|
460
|
-
const center = getCenterPosition(width, height);
|
|
461
|
-
const newId = `${nodeTypeId}-${nodes.length + 1}`;
|
|
462
|
-
|
|
463
|
-
// Add onResize handler to node data
|
|
464
|
-
const onResize = (id: string, width: number, height: number) => {
|
|
465
|
-
setNodes((nds) =>
|
|
466
|
-
nds.map((node) =>
|
|
467
|
-
node.id === id
|
|
468
|
-
? {
|
|
469
|
-
...node,
|
|
470
|
-
measured: { width, height },
|
|
471
|
-
width: width,
|
|
472
|
-
height: height,
|
|
473
|
-
}
|
|
474
|
-
: node,
|
|
475
|
-
),
|
|
476
|
-
);
|
|
477
|
-
// Update all selfconnecting edges for this node with new size/position
|
|
478
|
-
setEdges((eds) =>
|
|
479
|
-
eds.map((edge) => {
|
|
480
|
-
if (
|
|
481
|
-
edge.type === 'selfconnecting' &&
|
|
482
|
-
edge.source === id &&
|
|
483
|
-
edge.target === id
|
|
484
|
-
) {
|
|
485
|
-
// Find the node to get its position
|
|
486
|
-
const node = nodes.find((n) => n.id === id);
|
|
487
|
-
const nodeX = node?.position?.x ?? 0;
|
|
488
|
-
const nodeY = node?.position?.y ?? 0;
|
|
489
|
-
return {
|
|
490
|
-
...edge,
|
|
491
|
-
data: {
|
|
492
|
-
...edge.data,
|
|
493
|
-
nodeWidth: width,
|
|
494
|
-
nodeHeight: height,
|
|
495
|
-
nodeX,
|
|
496
|
-
nodeY,
|
|
497
|
-
},
|
|
498
|
-
};
|
|
499
|
-
}
|
|
500
|
-
return edge;
|
|
501
|
-
}),
|
|
502
|
-
);
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
setNodes((nds: Node[]) => [
|
|
506
|
-
...nds,
|
|
507
|
-
{
|
|
508
|
-
id: newId,
|
|
509
|
-
type,
|
|
510
|
-
position: center,
|
|
511
|
-
data: { ...data, onResize },
|
|
512
|
-
draggable: true,
|
|
513
|
-
},
|
|
514
|
-
]);
|
|
515
|
-
},
|
|
516
|
-
[nodes, setNodes, setEdges],
|
|
517
|
-
);
|
|
518
|
-
|
|
519
|
-
// Update onAddEntity to use internal addNodeAtCenter
|
|
520
|
-
const handleAddEntity = useCallback(
|
|
521
|
-
(nodeTypeId = 'StateNode') => {
|
|
522
|
-
addNodeAtCenter(nodeTypeId);
|
|
523
|
-
},
|
|
524
|
-
[addNodeAtCenter],
|
|
525
|
-
);
|
|
526
|
-
|
|
527
|
-
// Function to export diagram as JSON
|
|
528
|
-
const exportDiagram = useCallback(() => {
|
|
529
|
-
const diagramData = {
|
|
530
|
-
nodes: nodes,
|
|
531
|
-
edges: edges,
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
const dataStr = JSON.stringify(diagramData, null, 2);
|
|
535
|
-
const dataUri =
|
|
536
|
-
'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
|
|
537
|
-
|
|
538
|
-
const exportFileDefaultName = 'state-machine-diagram.json';
|
|
539
|
-
|
|
540
|
-
const linkElement = document.createElement('a');
|
|
541
|
-
linkElement.setAttribute('href', dataUri);
|
|
542
|
-
linkElement.setAttribute('download', exportFileDefaultName);
|
|
543
|
-
linkElement.click();
|
|
544
|
-
}, [nodes, edges]);
|
|
545
|
-
|
|
546
|
-
// Function to import diagram from JSON
|
|
547
|
-
const importDiagram = useCallback(
|
|
548
|
-
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
549
|
-
if (!event.target.files || event.target.files.length === 0) return;
|
|
550
|
-
|
|
551
|
-
const fileReader = new FileReader();
|
|
552
|
-
fileReader.readAsText(event.target.files[0], 'UTF-8');
|
|
553
|
-
fileReader.onload = (e) => {
|
|
554
|
-
try {
|
|
555
|
-
if (!e.target || typeof e.target.result !== 'string') return;
|
|
556
|
-
|
|
557
|
-
const imported = JSON.parse(e.target.result);
|
|
558
|
-
if (imported.nodes && imported.edges) {
|
|
559
|
-
setNodes(imported.nodes);
|
|
560
|
-
setEdges(imported.edges);
|
|
561
|
-
}
|
|
562
|
-
} catch (error) {
|
|
563
|
-
console.error('Error importing diagram:', error);
|
|
564
|
-
alert('Failed to import diagram. Invalid file format.');
|
|
565
|
-
}
|
|
566
|
-
};
|
|
567
|
-
},
|
|
568
|
-
[setNodes, setEdges],
|
|
569
|
-
);
|
|
570
|
-
|
|
571
|
-
// Toggle fullscreen mode
|
|
572
|
-
const toggleFullscreen = useCallback(() => {
|
|
573
|
-
if (!diagramContainerRef.current) return;
|
|
574
|
-
|
|
575
|
-
if (!isFullscreen) {
|
|
576
|
-
if (diagramContainerRef.current.requestFullscreen) {
|
|
577
|
-
diagramContainerRef.current.requestFullscreen();
|
|
578
|
-
} else if ((diagramContainerRef.current as FullscreenElement).webkitRequestFullscreen) {
|
|
579
|
-
(diagramContainerRef.current as FullscreenElement).webkitRequestFullscreen?.();
|
|
580
|
-
} else if ((diagramContainerRef.current as FullscreenElement).msRequestFullscreen) {
|
|
581
|
-
(diagramContainerRef.current as FullscreenElement).msRequestFullscreen?.();
|
|
582
|
-
}
|
|
583
|
-
} else {
|
|
584
|
-
if (document.exitFullscreen) {
|
|
585
|
-
document.exitFullscreen();
|
|
586
|
-
} else if ((document as FullscreenDocument).webkitExitFullscreen) {
|
|
587
|
-
(document as FullscreenDocument).webkitExitFullscreen?.();
|
|
588
|
-
} else if ((document as FullscreenDocument).msExitFullscreen) {
|
|
589
|
-
(document as FullscreenDocument).msExitFullscreen?.();
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
|
|
593
|
-
setIsFullscreen(!isFullscreen);
|
|
594
|
-
}, [isFullscreen]);
|
|
595
|
-
|
|
596
|
-
// Listen for fullscreen change events from browser
|
|
597
|
-
useEffect(() => {
|
|
598
|
-
const handleFullscreenChange = () => {
|
|
599
|
-
setIsFullscreen(!!document.fullscreenElement);
|
|
600
|
-
};
|
|
601
|
-
|
|
602
|
-
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
|
603
|
-
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
|
|
604
|
-
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
|
|
605
|
-
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
|
|
606
|
-
|
|
607
|
-
return () => {
|
|
608
|
-
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
|
609
|
-
document.removeEventListener(
|
|
610
|
-
'webkitfullscreenchange',
|
|
611
|
-
handleFullscreenChange,
|
|
612
|
-
);
|
|
613
|
-
document.removeEventListener(
|
|
614
|
-
'mozfullscreenchange',
|
|
615
|
-
handleFullscreenChange,
|
|
616
|
-
);
|
|
617
|
-
document.removeEventListener(
|
|
618
|
-
'MSFullscreenChange',
|
|
619
|
-
handleFullscreenChange,
|
|
620
|
-
);
|
|
621
|
-
};
|
|
622
|
-
}, []);
|
|
623
|
-
|
|
624
|
-
// Modified onConnect to open EdgeSettings after edge is created
|
|
625
|
-
const onConnect = useCallback(
|
|
626
|
-
(params: Connection) => {
|
|
627
|
-
if (!params.source || !params.target) return;
|
|
628
|
-
const sourceNode = nodes.find((n) => n.id === params.source);
|
|
629
|
-
const targetNode = nodes.find((n) => n.id === params.target);
|
|
630
|
-
if (!sourceNode || !targetNode) return;
|
|
631
|
-
|
|
632
|
-
const newEdge = {
|
|
633
|
-
id: `edge-${edges.length + 1}`,
|
|
634
|
-
source: params.source,
|
|
635
|
-
target: params.target,
|
|
636
|
-
type: 'customEdge',
|
|
637
|
-
sourceHandle: params.sourceHandle,
|
|
638
|
-
targetHandle: params.targetHandle,
|
|
639
|
-
data: {
|
|
640
|
-
label: `Transition ${edges.length + 1}`,
|
|
641
|
-
},
|
|
642
|
-
animated: false,
|
|
643
|
-
};
|
|
644
|
-
setEdges((eds: Edge[]) => [...eds, newEdge]);
|
|
645
|
-
setEdgeSettings({ id: newEdge.id });
|
|
646
|
-
setIsSettingsSidebarOpen(true);
|
|
647
|
-
},
|
|
648
|
-
[nodes, edges, setEdges],
|
|
649
|
-
);
|
|
650
|
-
|
|
651
|
-
// Update onNodeClick function
|
|
652
|
-
const onNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
|
|
653
|
-
if (node.type === 'bendpoint') {
|
|
654
|
-
setStateNodeSettings({ id: null });
|
|
655
|
-
setSelectedCompositeStateNodeId(null);
|
|
656
|
-
return;
|
|
657
|
-
}
|
|
658
|
-
|
|
659
|
-
if (node.type === 'CompositeStateNode') {
|
|
660
|
-
setSelectedCompositeStateNodeId(node.id);
|
|
661
|
-
setStateNodeSettings({ id: null });
|
|
662
|
-
setIsSettingsSidebarOpen(true);
|
|
663
|
-
} else if (
|
|
664
|
-
[
|
|
665
|
-
'StateNode',
|
|
666
|
-
'InitialStateNode',
|
|
667
|
-
'FinalStateNode',
|
|
668
|
-
'HistoryStateNode',
|
|
669
|
-
'JunctionPointNode',
|
|
670
|
-
'ChoicePointNode',
|
|
671
|
-
'EntryPointNode',
|
|
672
|
-
'ExitPointNode',
|
|
673
|
-
'ForkJoinNode',
|
|
674
|
-
].includes(node.type || '')
|
|
675
|
-
) {
|
|
676
|
-
setStateNodeSettings({ id: node.id });
|
|
677
|
-
setSelectedCompositeStateNodeId(null);
|
|
678
|
-
setIsSettingsSidebarOpen(true);
|
|
679
|
-
} else {
|
|
680
|
-
setStateNodeSettings({ id: null });
|
|
681
|
-
setSelectedCompositeStateNodeId(null);
|
|
682
|
-
}
|
|
683
|
-
}, []);
|
|
684
|
-
|
|
685
|
-
// Update onNodeDoubleClick function
|
|
686
|
-
const onNodeDoubleClick = useCallback(
|
|
687
|
-
(event: React.MouseEvent, node: Node) => {
|
|
688
|
-
event.preventDefault();
|
|
689
|
-
event.stopPropagation();
|
|
690
|
-
setEditing({
|
|
691
|
-
nodeId: node.id,
|
|
692
|
-
edgeId: null,
|
|
693
|
-
label: (node.data?.label as string) || '',
|
|
694
|
-
type: 'node',
|
|
695
|
-
});
|
|
696
|
-
},
|
|
697
|
-
[setEditing],
|
|
698
|
-
);
|
|
699
|
-
|
|
700
|
-
// Open EdgeSettings on double click
|
|
701
|
-
const onEdgeDoubleClick = useCallback(
|
|
702
|
-
(event: React.MouseEvent, edge: Edge) => {
|
|
703
|
-
event.preventDefault();
|
|
704
|
-
event.stopPropagation();
|
|
705
|
-
if (edge.type === 'customEdge') {
|
|
706
|
-
setEdgeSettings({ id: edge.id });
|
|
707
|
-
setIsSettingsSidebarOpen(true);
|
|
708
|
-
}
|
|
709
|
-
},
|
|
710
|
-
[],
|
|
711
|
-
);
|
|
712
|
-
|
|
713
|
-
// Function to update selected state node
|
|
714
|
-
const updateSelectedStateNode = useCallback(
|
|
715
|
-
(updates: Record<string, unknown>) => {
|
|
716
|
-
if (!stateNodeSettings.id) return;
|
|
717
|
-
setNodes((nds: Node[]) =>
|
|
718
|
-
nds.map((node: Node) => {
|
|
719
|
-
if (node.id === stateNodeSettings.id) {
|
|
720
|
-
return {
|
|
721
|
-
...node,
|
|
722
|
-
data: {
|
|
723
|
-
...node.data,
|
|
724
|
-
...updates,
|
|
725
|
-
},
|
|
726
|
-
};
|
|
727
|
-
}
|
|
728
|
-
return node;
|
|
729
|
-
}),
|
|
730
|
-
);
|
|
731
|
-
},
|
|
732
|
-
[stateNodeSettings.id, setNodes],
|
|
733
|
-
);
|
|
734
|
-
|
|
735
|
-
// Close state node settings
|
|
736
|
-
const closeStateNodeSettings = useCallback(() => {
|
|
737
|
-
setStateNodeSettings({ id: null });
|
|
738
|
-
setIsSettingsSidebarOpen(false);
|
|
739
|
-
}, []);
|
|
740
|
-
|
|
741
|
-
// Update edge from EdgeSettings
|
|
742
|
-
const updateSelectedEdge = useCallback(
|
|
743
|
-
(updates: Record<string, unknown>) => {
|
|
744
|
-
if (!edgeSettings.id) return;
|
|
745
|
-
setEdges((eds: Edge[]) =>
|
|
746
|
-
eds.map((edge: Edge) => {
|
|
747
|
-
if (edge.id === edgeSettings.id) {
|
|
748
|
-
return {
|
|
749
|
-
...edge,
|
|
750
|
-
source: typeof updates.source === 'string' ? updates.source : edge.source,
|
|
751
|
-
target: typeof updates.target === 'string' ? updates.target : edge.target,
|
|
752
|
-
data: {
|
|
753
|
-
...edge.data,
|
|
754
|
-
...updates,
|
|
755
|
-
},
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
return edge;
|
|
759
|
-
}) as Edge[],
|
|
760
|
-
);
|
|
761
|
-
},
|
|
762
|
-
[edgeSettings.id, setEdges],
|
|
763
|
-
);
|
|
764
|
-
|
|
765
|
-
// Close EdgeSettings
|
|
766
|
-
const closeEdgeSettings = useCallback(() => {
|
|
767
|
-
setEdgeSettings({ id: null });
|
|
768
|
-
setIsSettingsSidebarOpen(false);
|
|
769
|
-
}, []);
|
|
770
|
-
|
|
771
|
-
// Close CompositeStateNodeSettings
|
|
772
|
-
const closeCompositeStateNodeSettings = useCallback(() => {
|
|
773
|
-
setSelectedCompositeStateNodeId(null);
|
|
774
|
-
setIsSettingsSidebarOpen(false);
|
|
775
|
-
}, []);
|
|
776
|
-
|
|
777
|
-
// Function to update selected composite state node
|
|
778
|
-
const updateSelectedCompositeStateNode = useCallback(
|
|
779
|
-
(updates: Record<string, unknown>) => {
|
|
780
|
-
if (!selectedCompositeStateNodeId) return;
|
|
781
|
-
setNodes((nds: Node[]) =>
|
|
782
|
-
nds.map((node: Node) => {
|
|
783
|
-
if (node.id === selectedCompositeStateNodeId) {
|
|
784
|
-
return {
|
|
785
|
-
...node,
|
|
786
|
-
data: {
|
|
787
|
-
...node.data,
|
|
788
|
-
...updates,
|
|
789
|
-
},
|
|
790
|
-
};
|
|
791
|
-
}
|
|
792
|
-
return node;
|
|
793
|
-
}),
|
|
794
|
-
);
|
|
795
|
-
},
|
|
796
|
-
[selectedCompositeStateNodeId, setNodes],
|
|
797
|
-
);
|
|
798
|
-
|
|
799
|
-
// Collapse all composite nodes on pane click
|
|
800
|
-
const handlePaneClick = useCallback(() => {
|
|
801
|
-
setNodes((nds: Node[]) =>
|
|
802
|
-
nds.map((node: Node) =>
|
|
803
|
-
node.type === 'CompositeStateNode'
|
|
804
|
-
? { ...node, data: { ...node.data, expanded: false } }
|
|
805
|
-
: node,
|
|
806
|
-
),
|
|
807
|
-
);
|
|
808
|
-
setSelectedCompositeStateNodeId(null);
|
|
809
|
-
setStateNodeSettings({ id: null });
|
|
810
|
-
}, [setNodes]);
|
|
811
|
-
|
|
812
|
-
// Memoize hidden node ids
|
|
813
|
-
const hiddenNodeIds = useMemo(() => getHiddenNodeIds(nodes), [nodes]);
|
|
814
|
-
// Memoize filtered nodes
|
|
815
|
-
const filteredNodes = useMemo(
|
|
816
|
-
() => nodes.filter((n) => !hiddenNodeIds.has(n.id)),
|
|
817
|
-
[nodes, hiddenNodeIds],
|
|
818
|
-
);
|
|
819
|
-
// Memoize collapsed composites
|
|
820
|
-
const collapsedComposites = useMemo(
|
|
821
|
-
() =>
|
|
822
|
-
nodes.filter((n) => n.type === 'CompositeStateNode' && !n.data.expanded),
|
|
823
|
-
[nodes],
|
|
824
|
-
);
|
|
825
|
-
// Memoize proxy edge logic (optimized for performance)
|
|
826
|
-
const { filteredEdges } = useMemo(() => {
|
|
827
|
-
// Precompute collapsed composite node ids for O(1) lookup
|
|
828
|
-
const collapsedCompositeIds = new Set(
|
|
829
|
-
nodes
|
|
830
|
-
.filter((n) => n.type === 'CompositeStateNode' && !n.data.expanded)
|
|
831
|
-
.map((n) => n.id),
|
|
832
|
-
);
|
|
833
|
-
// Precompute a map of nodeId -> node for O(1) lookup
|
|
834
|
-
const nodeIdMap = new Map(nodes.map((n) => [n.id, n]));
|
|
835
|
-
// Map childId to its collapsed composite parent
|
|
836
|
-
const childToCollapsedParent: Record<string, string> = {};
|
|
837
|
-
const collectChildren = (parentId: string) => {
|
|
838
|
-
nodes.forEach((child) => {
|
|
839
|
-
if (child.parentId === parentId) {
|
|
840
|
-
childToCollapsedParent[child.id] = parentId;
|
|
841
|
-
if (child.type === 'CompositeStateNode') {
|
|
842
|
-
collectChildren(child.id);
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
});
|
|
846
|
-
};
|
|
847
|
-
collapsedCompositeIds.forEach((compId) => collectChildren(compId));
|
|
848
|
-
// Generate proxy edges for edges crossing collapsed composite boundaries
|
|
849
|
-
const proxyEdges: Edge[] = [];
|
|
850
|
-
const realEdges: Edge[] = [];
|
|
851
|
-
edges.forEach((edge) => {
|
|
852
|
-
const sourceHidden = hiddenNodeIds.has(edge.source);
|
|
853
|
-
const targetHidden = hiddenNodeIds.has(edge.target);
|
|
854
|
-
|
|
855
|
-
// Self-loop on composite node: hide if collapsed
|
|
856
|
-
if (
|
|
857
|
-
edge.source === edge.target &&
|
|
858
|
-
collapsedCompositeIds.has(edge.source)
|
|
859
|
-
) {
|
|
860
|
-
return; // skip this edge
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
// If both ends are hidden, skip (don't show)
|
|
864
|
-
if (sourceHidden && targetHidden) return;
|
|
865
|
-
// If neither end is hidden, show as normal
|
|
866
|
-
if (!sourceHidden && !targetHidden) {
|
|
867
|
-
realEdges.push(edge);
|
|
868
|
-
return;
|
|
869
|
-
}
|
|
870
|
-
// If one end is hidden and the other is visible, create a proxy edge
|
|
871
|
-
let proxySource = edge.source;
|
|
872
|
-
let proxyTarget = edge.target;
|
|
873
|
-
let proxyId = edge.id + '-proxy';
|
|
874
|
-
// If source is hidden, map to its collapsed composite parent
|
|
875
|
-
if (sourceHidden && childToCollapsedParent[edge.source]) {
|
|
876
|
-
proxySource = childToCollapsedParent[edge.source];
|
|
877
|
-
}
|
|
878
|
-
// If target is hidden, map to its collapsed composite parent
|
|
879
|
-
if (targetHidden && childToCollapsedParent[edge.target]) {
|
|
880
|
-
proxyTarget = childToCollapsedParent[edge.target];
|
|
881
|
-
}
|
|
882
|
-
// Avoid proxying to the same node (self-loop)
|
|
883
|
-
if (proxySource === proxyTarget) return;
|
|
884
|
-
proxyEdges.push({
|
|
885
|
-
...edge,
|
|
886
|
-
id: proxyId,
|
|
887
|
-
source: proxySource,
|
|
888
|
-
target: proxyTarget,
|
|
889
|
-
style: { ...(edge.style || {}), strokeDasharray: '6 4', opacity: 0.7 },
|
|
890
|
-
data: { ...(edge.data || {}), isProxy: true },
|
|
891
|
-
});
|
|
892
|
-
});
|
|
893
|
-
return { filteredEdges: [...realEdges, ...proxyEdges] as Edge[] };
|
|
894
|
-
}, [edges, nodes, hiddenNodeIds]);
|
|
895
|
-
|
|
896
|
-
// Custom onNodesChange to snap PinPointNode to border of parent composite node
|
|
897
|
-
const customOnNodesChange = useCallback(
|
|
898
|
-
(changes: NodeChange[]) => {
|
|
899
|
-
setNodes((nds) => {
|
|
900
|
-
// Apply default node changes
|
|
901
|
-
let updated = applyNodeChanges(changes, nds);
|
|
902
|
-
// Snap PinPointNode nodes to border of parent composite node
|
|
903
|
-
updated = updated.map((node) => {
|
|
904
|
-
if (node.type === 'PinPointNode' && node.parentId) {
|
|
905
|
-
const parent = nds.find((n) => n.id === node.parentId);
|
|
906
|
-
if (!parent) return node;
|
|
907
|
-
const width = (parent.data as Record<string, unknown>).width as number || 400;
|
|
908
|
-
const height = (parent.data as Record<string, unknown>).height as number || 300;
|
|
909
|
-
const size = 22;
|
|
910
|
-
let { x, y } = node.position;
|
|
911
|
-
// Calculate center
|
|
912
|
-
const cx = x + size / 2;
|
|
913
|
-
const cy = y + size / 2;
|
|
914
|
-
// Find closest border (top, bottom, left, right)
|
|
915
|
-
const dTop = Math.abs(cy - 0);
|
|
916
|
-
const dBottom = Math.abs(cy - height);
|
|
917
|
-
const dLeft = Math.abs(cx - 0);
|
|
918
|
-
const dRight = Math.abs(cx - width);
|
|
919
|
-
const minDist = Math.min(dTop, dBottom, dLeft, dRight);
|
|
920
|
-
let newX = x,
|
|
921
|
-
newY = y;
|
|
922
|
-
if (minDist === dTop) {
|
|
923
|
-
newX =
|
|
924
|
-
Math.max(-size / 2, Math.min(width - size / 2, cx)) - size / 2;
|
|
925
|
-
newY = -size / 2;
|
|
926
|
-
} else if (minDist === dBottom) {
|
|
927
|
-
newX =
|
|
928
|
-
Math.max(-size / 2, Math.min(width - size / 2, cx)) - size / 2;
|
|
929
|
-
newY = height - size / 2;
|
|
930
|
-
} else if (minDist === dLeft) {
|
|
931
|
-
newX = -size / 2;
|
|
932
|
-
newY =
|
|
933
|
-
Math.max(-size / 2, Math.min(height - size / 2, cy)) - size / 2;
|
|
934
|
-
} else if (minDist === dRight) {
|
|
935
|
-
newX = width - size / 2;
|
|
936
|
-
newY =
|
|
937
|
-
Math.max(-size / 2, Math.min(height - size / 2, cy)) - size / 2;
|
|
938
|
-
}
|
|
939
|
-
return { ...node, position: { x: newX, y: newY } };
|
|
940
|
-
}
|
|
941
|
-
return node;
|
|
942
|
-
});
|
|
943
|
-
return updated;
|
|
944
|
-
});
|
|
945
|
-
},
|
|
946
|
-
[setNodes],
|
|
947
|
-
);
|
|
948
|
-
|
|
949
|
-
// Add layout direction change handler
|
|
950
|
-
const handleLayoutDirectionChange = useCallback(
|
|
951
|
-
(direction: 'DOWN' | 'RIGHT') => {
|
|
952
|
-
setLayoutDirection(direction);
|
|
953
|
-
getLayoutedElements();
|
|
954
|
-
},
|
|
955
|
-
[setLayoutDirection, getLayoutedElements],
|
|
956
|
-
);
|
|
957
|
-
|
|
958
|
-
// Add layout effect
|
|
959
|
-
useEffect(() => {
|
|
960
|
-
// Only layout if we have nodes/edges
|
|
961
|
-
if (nodes.length > 0 || edges.length > 0) {
|
|
962
|
-
getLayoutedElements();
|
|
963
|
-
}
|
|
964
|
-
}, [handleToggleCompositeExpand]);
|
|
965
|
-
|
|
966
|
-
// Add edge edit handlers
|
|
967
|
-
const handleEditEdge = useCallback((edge: Edge) => {
|
|
968
|
-
setEdgeSettings({ id: edge.id });
|
|
969
|
-
}, []);
|
|
970
|
-
|
|
971
|
-
const handleUpdateEdgeLabel = useCallback(
|
|
972
|
-
(edgeId: string, newLabel: string) => {
|
|
973
|
-
setEdges((eds: Edge[]) =>
|
|
974
|
-
eds.map((edge: Edge) =>
|
|
975
|
-
edge.id === edgeId
|
|
976
|
-
? { ...edge, data: { ...edge.data, label: newLabel } }
|
|
977
|
-
: edge,
|
|
978
|
-
),
|
|
979
|
-
);
|
|
980
|
-
},
|
|
981
|
-
[setEdges],
|
|
982
|
-
);
|
|
983
|
-
|
|
984
|
-
const handleToggleEdgeArrowHead = useCallback(
|
|
985
|
-
(edgeId: string, direction: 'left' | 'right') => {
|
|
986
|
-
setEdges((eds: Edge[]) =>
|
|
987
|
-
eds.map((edge: Edge) => {
|
|
988
|
-
if (edge.id === edgeId) {
|
|
989
|
-
const data = { ...edge.data };
|
|
990
|
-
if (direction === 'left') {
|
|
991
|
-
data.arrowHeadLeft = !data.arrowHeadLeft;
|
|
992
|
-
} else {
|
|
993
|
-
data.arrowHeadRight = !data.arrowHeadRight;
|
|
994
|
-
}
|
|
995
|
-
return { ...edge, data };
|
|
996
|
-
}
|
|
997
|
-
return edge;
|
|
998
|
-
}),
|
|
999
|
-
);
|
|
1000
|
-
},
|
|
1001
|
-
[setEdges],
|
|
1002
|
-
);
|
|
1003
|
-
|
|
1004
|
-
// Memoize node and edge maps for O(1) lookup
|
|
1005
|
-
const nodeMap = useMemo(() => {
|
|
1006
|
-
const map = new Map<string, Node>();
|
|
1007
|
-
nodes.forEach((node) => map.set(node.id, node));
|
|
1008
|
-
return map;
|
|
1009
|
-
}, [nodes]);
|
|
1010
|
-
const edgeMap = useMemo(() => {
|
|
1011
|
-
const map = new Map<string, Edge>();
|
|
1012
|
-
edges.forEach((edge) => map.set(edge.id, edge));
|
|
1013
|
-
return map;
|
|
1014
|
-
}, [edges]);
|
|
1015
|
-
|
|
1016
|
-
return (
|
|
1017
|
-
<Box sx={{ display: 'flex', height: '100vh' }}>
|
|
1018
|
-
<Sidebar>
|
|
1019
|
-
<NodeSelectionContext.Provider
|
|
1020
|
-
value={{
|
|
1021
|
-
selectedNodeType: null,
|
|
1022
|
-
setSelectedNodeType: () => { },
|
|
1023
|
-
addNodeAtCenter,
|
|
1024
|
-
}}
|
|
1025
|
-
>
|
|
1026
|
-
<DiagramControls
|
|
1027
|
-
onAddEntity={handleAddEntity}
|
|
1028
|
-
onUndo={handleUndo}
|
|
1029
|
-
onRedo={handleRedo}
|
|
1030
|
-
canUndo={historyIndex > 0}
|
|
1031
|
-
canRedo={historyIndex < history.length - 1}
|
|
1032
|
-
onLoadTemplate={loadTemplateData}
|
|
1033
|
-
edges={edges as StateMachineEdge[]}
|
|
1034
|
-
nodes={nodes as StateMachineNode[]}
|
|
1035
|
-
onEditEdge={handleEditEdge}
|
|
1036
|
-
onUpdateEdgeLabel={handleUpdateEdgeLabel}
|
|
1037
|
-
onToggleEdgeArrowHead={handleToggleEdgeArrowHead}
|
|
1038
|
-
/>
|
|
1039
|
-
</NodeSelectionContext.Provider>
|
|
1040
|
-
</Sidebar>
|
|
1041
|
-
|
|
1042
|
-
<Box
|
|
1043
|
-
sx={{
|
|
1044
|
-
flexGrow: 1,
|
|
1045
|
-
'& .react-flow__edge': {
|
|
1046
|
-
zIndex: '5000 !important',
|
|
1047
|
-
},
|
|
1048
|
-
'& .react-flow__edge-path': {
|
|
1049
|
-
pointerEvents: 'all !important',
|
|
1050
|
-
},
|
|
1051
|
-
'& .edge-path-selector': {
|
|
1052
|
-
strokeWidth: 20,
|
|
1053
|
-
fill: 'none',
|
|
1054
|
-
stroke: 'transparent',
|
|
1055
|
-
cursor: 'pointer',
|
|
1056
|
-
},
|
|
1057
|
-
'& .react-flow__edges': {
|
|
1058
|
-
zIndex: '5000 !important',
|
|
1059
|
-
},
|
|
1060
|
-
'& .react-flow__node': {
|
|
1061
|
-
pointerEvents: 'all !important',
|
|
1062
|
-
cursor: 'grab',
|
|
1063
|
-
'&:active': {
|
|
1064
|
-
cursor: 'grabbing',
|
|
1065
|
-
},
|
|
1066
|
-
'& .react-flow__edge': {
|
|
1067
|
-
zIndex: '5000 !important',
|
|
1068
|
-
},
|
|
1069
|
-
},
|
|
1070
|
-
'& .react-flow__node.dragging': {
|
|
1071
|
-
cursor: 'grabbing !important',
|
|
1072
|
-
},
|
|
1073
|
-
'& .react-flow.dragging': {
|
|
1074
|
-
cursor: 'grabbing',
|
|
1075
|
-
},
|
|
1076
|
-
}}
|
|
1077
|
-
>
|
|
1078
|
-
<Box
|
|
1079
|
-
sx={{
|
|
1080
|
-
width: '100%',
|
|
1081
|
-
height: '100%',
|
|
1082
|
-
padding: '10px',
|
|
1083
|
-
bgcolor: 'white',
|
|
1084
|
-
}}
|
|
1085
|
-
>
|
|
1086
|
-
<ReactFlow
|
|
1087
|
-
nodes={filteredNodes}
|
|
1088
|
-
edges={filteredEdges}
|
|
1089
|
-
onNodesChange={customOnNodesChange}
|
|
1090
|
-
onEdgesChange={onEdgesChange}
|
|
1091
|
-
onConnect={onConnect}
|
|
1092
|
-
nodeTypes={nodeTypes}
|
|
1093
|
-
edgeTypes={stateMachineEdgeTypes}
|
|
1094
|
-
fitView
|
|
1095
|
-
fitViewOptions={{
|
|
1096
|
-
padding: 0.2,
|
|
1097
|
-
minZoom: 0.5,
|
|
1098
|
-
maxZoom: 2,
|
|
1099
|
-
}}
|
|
1100
|
-
minZoom={0.1}
|
|
1101
|
-
maxZoom={2}
|
|
1102
|
-
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
|
|
1103
|
-
style={{
|
|
1104
|
-
background: '#f8f9fa',
|
|
1105
|
-
}}
|
|
1106
|
-
proOptions={{ hideAttribution: true }}
|
|
1107
|
-
selectNodesOnDrag={false}
|
|
1108
|
-
connectOnClick={false}
|
|
1109
|
-
onNodeClick={onNodeClick}
|
|
1110
|
-
onNodeDoubleClick={onNodeDoubleClick}
|
|
1111
|
-
onEdgeDoubleClick={onEdgeDoubleClick}
|
|
1112
|
-
onPaneClick={handlePaneClick}
|
|
1113
|
-
>
|
|
1114
|
-
<Background
|
|
1115
|
-
color="#f8f9fa"
|
|
1116
|
-
variant={BackgroundVariant.Dots}
|
|
1117
|
-
gap={16}
|
|
1118
|
-
/>
|
|
1119
|
-
|
|
1120
|
-
{/* MiniMap positioned at bottom-left */}
|
|
1121
|
-
<MiniMapCommon />
|
|
1122
|
-
|
|
1123
|
-
{/* Diagram Panel with all controls */}
|
|
1124
|
-
<DiagramPanel
|
|
1125
|
-
historyIndex={historyIndex}
|
|
1126
|
-
historyLength={history.length}
|
|
1127
|
-
onUndo={handleUndo}
|
|
1128
|
-
onRedo={handleRedo}
|
|
1129
|
-
onClear={handleClear}
|
|
1130
|
-
onImport={(data) => {
|
|
1131
|
-
setNodes(data.nodes || []);
|
|
1132
|
-
setEdges(data.edges || []);
|
|
1133
|
-
// Add to history
|
|
1134
|
-
const newHistoryState = {
|
|
1135
|
-
nodes: data.nodes || [],
|
|
1136
|
-
edges: data.edges || [],
|
|
1137
|
-
};
|
|
1138
|
-
setHistory((prev) => [
|
|
1139
|
-
...prev.slice(0, historyIndex + 1),
|
|
1140
|
-
newHistoryState,
|
|
1141
|
-
]);
|
|
1142
|
-
setHistoryIndex((prev) => prev + 1);
|
|
1143
|
-
}}
|
|
1144
|
-
isSettingsOpen={isSettingsSidebarOpen}
|
|
1145
|
-
onToggleSettings={() => setIsSettingsSidebarOpen(true)}
|
|
1146
|
-
showCustomControls={true}
|
|
1147
|
-
onGridLayout={() => handleLayoutDirectionChange('DOWN')}
|
|
1148
|
-
onTreeLayout={() => handleLayoutDirectionChange('RIGHT')}
|
|
1149
|
-
/>
|
|
1150
|
-
|
|
1151
|
-
{/* AI Button positioned at bottom-right */}
|
|
1152
|
-
<AiButton />
|
|
1153
|
-
|
|
1154
|
-
{/* Layout controls positioned at top-right */}
|
|
1155
|
-
|
|
1156
|
-
{/* Fullscreen button positioned at top-left */}
|
|
1157
|
-
</ReactFlow>
|
|
1158
|
-
</Box>
|
|
1159
|
-
</Box>
|
|
1160
|
-
|
|
1161
|
-
{/* Settings panels */}
|
|
1162
|
-
{stateNodeSettings.id && (
|
|
1163
|
-
<StateNodeSettings
|
|
1164
|
-
nodeId={stateNodeSettings.id}
|
|
1165
|
-
data={nodeMap.get(stateNodeSettings.id)?.data || {}}
|
|
1166
|
-
onUpdate={updateSelectedStateNode}
|
|
1167
|
-
onClose={closeStateNodeSettings}
|
|
1168
|
-
edges={edges as StateMachineEdge[]}
|
|
1169
|
-
setEdges={(updater) => {
|
|
1170
|
-
if (typeof updater === 'function') {
|
|
1171
|
-
setEdges((prev) => updater(prev as StateMachineEdge[]) as Edge[]);
|
|
1172
|
-
} else {
|
|
1173
|
-
setEdges(updater as Edge[]);
|
|
1174
|
-
}
|
|
1175
|
-
}}
|
|
1176
|
-
nodes={nodes as StateMachineNode[]}
|
|
1177
|
-
/>
|
|
1178
|
-
)}
|
|
1179
|
-
|
|
1180
|
-
{edgeSettings.id && (
|
|
1181
|
-
<EdgeSettings
|
|
1182
|
-
data={{
|
|
1183
|
-
...(edgeMap.get(edgeSettings.id)?.data || {}),
|
|
1184
|
-
source: edgeMap.get(edgeSettings.id)?.source,
|
|
1185
|
-
target: edgeMap.get(edgeSettings.id)?.target,
|
|
1186
|
-
}}
|
|
1187
|
-
nodes={nodes as StateMachineNode[]}
|
|
1188
|
-
onUpdate={updateSelectedEdge}
|
|
1189
|
-
onClose={closeEdgeSettings}
|
|
1190
|
-
onDelete={() => {
|
|
1191
|
-
setEdges((eds: Edge[]) =>
|
|
1192
|
-
eds.filter((edge: Edge) => edge.id !== edgeSettings.id),
|
|
1193
|
-
);
|
|
1194
|
-
closeEdgeSettings();
|
|
1195
|
-
}}
|
|
1196
|
-
/>
|
|
1197
|
-
)}
|
|
1198
|
-
|
|
1199
|
-
{selectedCompositeStateNodeId && (
|
|
1200
|
-
<CompositeStateNodeSettings
|
|
1201
|
-
nodeId={selectedCompositeStateNodeId}
|
|
1202
|
-
data={nodeMap.get(selectedCompositeStateNodeId)?.data || {}}
|
|
1203
|
-
nodes={nodes as StateMachineNode[]}
|
|
1204
|
-
setNodes={(updater) => {
|
|
1205
|
-
if (typeof updater === 'function') {
|
|
1206
|
-
setNodes((prev) => updater(prev as StateMachineNode[]) as Node[]);
|
|
1207
|
-
} else {
|
|
1208
|
-
setNodes(updater as Node[]);
|
|
1209
|
-
}
|
|
1210
|
-
}}
|
|
1211
|
-
onUpdate={updateSelectedCompositeStateNode}
|
|
1212
|
-
onClose={closeCompositeStateNodeSettings}
|
|
1213
|
-
edges={edges as StateMachineEdge[]}
|
|
1214
|
-
setEdges={(updater) => {
|
|
1215
|
-
if (typeof updater === 'function') {
|
|
1216
|
-
setEdges((prev) => updater(prev as StateMachineEdge[]) as Edge[]);
|
|
1217
|
-
} else {
|
|
1218
|
-
setEdges(updater as Edge[]);
|
|
1219
|
-
}
|
|
1220
|
-
}}
|
|
1221
|
-
/>
|
|
1222
|
-
)}
|
|
1223
|
-
</Box>
|
|
1224
|
-
);
|
|
1225
|
-
};
|
|
1226
|
-
|
|
1227
|
-
// Main wrapper component with ReactFlowProvider
|
|
1228
|
-
const StateMachineDiagramFlow: React.FC<StateMachineDiagramFlowProps> = (
|
|
1229
|
-
props,
|
|
1230
|
-
) => {
|
|
1231
|
-
return (
|
|
1232
|
-
<ReactFlowProvider>
|
|
1233
|
-
<StateMachineDiagramFlowContent {...props} />
|
|
1234
|
-
</ReactFlowProvider>
|
|
1235
|
-
);
|
|
1236
|
-
};
|
|
1237
|
-
|
|
1238
|
-
export default StateMachineDiagramFlow;
|
|
1
|
+
import React, {
|
|
2
|
+
useCallback,
|
|
3
|
+
useState,
|
|
4
|
+
useRef,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
} from 'react';
|
|
8
|
+
|
|
9
|
+
// Fullscreen API type extensions
|
|
10
|
+
interface FullscreenElement extends HTMLElement {
|
|
11
|
+
webkitRequestFullscreen?: () => Promise<void>;
|
|
12
|
+
msRequestFullscreen?: () => Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface FullscreenDocument extends Document {
|
|
16
|
+
webkitExitFullscreen?: () => Promise<void>;
|
|
17
|
+
msExitFullscreen?: () => Promise<void>;
|
|
18
|
+
}
|
|
19
|
+
import {
|
|
20
|
+
ReactFlow,
|
|
21
|
+
Background,
|
|
22
|
+
useNodesState,
|
|
23
|
+
useEdgesState,
|
|
24
|
+
Panel,
|
|
25
|
+
BackgroundVariant,
|
|
26
|
+
Connection,
|
|
27
|
+
NodeChange,
|
|
28
|
+
applyNodeChanges,
|
|
29
|
+
ReactFlowProvider,
|
|
30
|
+
Node,
|
|
31
|
+
Edge,
|
|
32
|
+
NodeProps,
|
|
33
|
+
} from '@xyflow/react';
|
|
34
|
+
import '@xyflow/react/dist/style.css';
|
|
35
|
+
import {
|
|
36
|
+
StateMachineNode,
|
|
37
|
+
StateMachineEdge,
|
|
38
|
+
HistoryState,
|
|
39
|
+
EditingState,
|
|
40
|
+
StateNodeSettingsState,
|
|
41
|
+
EdgeSettingsState,
|
|
42
|
+
NodeSelectionContextType,
|
|
43
|
+
LayoutDirection,
|
|
44
|
+
Position,
|
|
45
|
+
UpdateFunction,
|
|
46
|
+
CloseFunction,
|
|
47
|
+
DeleteFunction
|
|
48
|
+
} from '../../../types/state-machine-types';
|
|
49
|
+
|
|
50
|
+
import {
|
|
51
|
+
Box,
|
|
52
|
+
Button,
|
|
53
|
+
Paper,
|
|
54
|
+
ToggleButton,
|
|
55
|
+
ToggleButtonGroup,
|
|
56
|
+
Snackbar,
|
|
57
|
+
Alert,
|
|
58
|
+
} from '@mui/material';
|
|
59
|
+
import DownloadIcon from '@mui/icons-material/Download';
|
|
60
|
+
import UploadIcon from '@mui/icons-material/Upload';
|
|
61
|
+
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
|
62
|
+
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
|
63
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
64
|
+
import UndoIcon from '@mui/icons-material/Undo';
|
|
65
|
+
import RedoIcon from '@mui/icons-material/Redo';
|
|
66
|
+
import ViewWeekIcon from '@mui/icons-material/ViewWeek';
|
|
67
|
+
import ViewStreamIcon from '@mui/icons-material/ViewStream';
|
|
68
|
+
import {
|
|
69
|
+
stateMachineDefaultNodes,
|
|
70
|
+
stateMachineDefaultEdges,
|
|
71
|
+
} from '../data/template-data';
|
|
72
|
+
import {
|
|
73
|
+
stateMachineEdgeTypes,
|
|
74
|
+
stateMachineNodeTypes as baseStateMachineNodeTypes,
|
|
75
|
+
CompositeStateNode as CompositeStateNodeType,
|
|
76
|
+
} from './StateMachineComponents';
|
|
77
|
+
import DiagramControls from '../controls/StateDiagramControls';
|
|
78
|
+
import { NodeSelectionContext } from '../controls/StateDiagramControls';
|
|
79
|
+
import StateNodeSettings from './StateNodeSettings';
|
|
80
|
+
import EdgeSettings from './EdgeSettings';
|
|
81
|
+
import CompositeStateNodeSettings from './CompositeStateNodeSettings';
|
|
82
|
+
import { useStateMachineLayout } from './StateMachineLayout';
|
|
83
|
+
import DiagramPanel from '../../../components/DiagramPanel';
|
|
84
|
+
import Sidebar from '../../../components/SideBar';
|
|
85
|
+
import AiButton from '../../../components/AiButton';
|
|
86
|
+
import MiniMapCommon from '../../../components/MiniMapCommon';
|
|
87
|
+
|
|
88
|
+
interface StateMachineDiagramFlowProps {
|
|
89
|
+
nodes: StateMachineNode[];
|
|
90
|
+
setNodes: (nodes: StateMachineNode[] | ((prev: StateMachineNode[]) => StateMachineNode[])) => void;
|
|
91
|
+
edges: StateMachineEdge[];
|
|
92
|
+
setEdges: (edges: StateMachineEdge[] | ((prev: StateMachineEdge[]) => StateMachineEdge[])) => void;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
interface CompositeStateNodeProps extends NodeProps {
|
|
96
|
+
onToggleExpand?: (id: string, expanded: boolean) => void;
|
|
97
|
+
onSettingsClick?: () => void;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Utility: Deep compare node arrays by id, type, data, width, height
|
|
101
|
+
const deepCompareNodes = (a: Node[], b: Node[]) => {
|
|
102
|
+
if (a.length !== b.length) return false;
|
|
103
|
+
for (let i = 0; i < a.length; i++) {
|
|
104
|
+
const nodeA = a[i];
|
|
105
|
+
const nodeB = b[i];
|
|
106
|
+
if (
|
|
107
|
+
nodeA.id !== nodeB.id ||
|
|
108
|
+
nodeA.type !== nodeB.type ||
|
|
109
|
+
JSON.stringify(nodeA.data) !== JSON.stringify(nodeB.data)
|
|
110
|
+
) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return true;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
// Utility: Deep compare edge arrays by id, source, target, type, data
|
|
118
|
+
const deepCompareEdges = (a: Edge[], b: Edge[]) => {
|
|
119
|
+
if (a.length !== b.length) return false;
|
|
120
|
+
for (let i = 0; i < a.length; i++) {
|
|
121
|
+
const edgeA = a[i];
|
|
122
|
+
const edgeB = b[i];
|
|
123
|
+
if (
|
|
124
|
+
edgeA.id !== edgeB.id ||
|
|
125
|
+
edgeA.source !== edgeB.source ||
|
|
126
|
+
edgeA.target !== edgeB.target ||
|
|
127
|
+
edgeA.type !== edgeB.type ||
|
|
128
|
+
JSON.stringify(edgeA.data) !== JSON.stringify(edgeB.data)
|
|
129
|
+
) {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
// Debounce utility
|
|
137
|
+
const debounce = (fn: Function, delay: number) => {
|
|
138
|
+
let timer: NodeJS.Timeout | null = null;
|
|
139
|
+
return (...args: unknown[]) => {
|
|
140
|
+
if (timer) clearTimeout(timer);
|
|
141
|
+
timer = setTimeout(() => fn(...args), delay);
|
|
142
|
+
};
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Helper to recursively collect all child node ids of collapsed composite nodes
|
|
146
|
+
function getHiddenNodeIds(allNodes: Node[]): Set<string> {
|
|
147
|
+
const hidden = new Set<string>();
|
|
148
|
+
const collapsedComposites = allNodes.filter(
|
|
149
|
+
(n) => n.type === 'CompositeStateNode' && !n.data.expanded,
|
|
150
|
+
);
|
|
151
|
+
const collectChildren = (parentId: string) => {
|
|
152
|
+
allNodes.forEach((child) => {
|
|
153
|
+
if (child.parentId === parentId) {
|
|
154
|
+
hidden.add(child.id);
|
|
155
|
+
if (child.type === 'CompositeStateNode') {
|
|
156
|
+
collectChildren(child.id);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
};
|
|
161
|
+
collapsedComposites.forEach((comp) => collectChildren(comp.id));
|
|
162
|
+
return hidden;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Helper: get center position in diagram
|
|
166
|
+
const getCenterPosition = (width: number, height: number) => {
|
|
167
|
+
return { x: width / 2 - 75, y: height / 2 - 40 };
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const StateMachineDiagramFlowContent: React.FC<
|
|
171
|
+
StateMachineDiagramFlowProps
|
|
172
|
+
> = ({
|
|
173
|
+
nodes: initialNodes,
|
|
174
|
+
setNodes: parentSetNodes,
|
|
175
|
+
edges: initialEdges,
|
|
176
|
+
setEdges: parentSetEdges,
|
|
177
|
+
}) => {
|
|
178
|
+
// State declarations
|
|
179
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(initialNodes as Node[]);
|
|
180
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(initialEdges as Edge[]);
|
|
181
|
+
const [history, setHistory] = useState<HistoryState[]>([]);
|
|
182
|
+
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
|
183
|
+
const isUndoRedoAction = useRef(false);
|
|
184
|
+
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
|
185
|
+
const diagramContainerRef = useRef<HTMLDivElement>(null);
|
|
186
|
+
const [editing, setEditing] = useState<{
|
|
187
|
+
nodeId?: string | null;
|
|
188
|
+
edgeId?: string | null;
|
|
189
|
+
label?: string;
|
|
190
|
+
type?: 'node' | 'edge';
|
|
191
|
+
}>({});
|
|
192
|
+
const [stateNodeSettings, setStateNodeSettings] = useState<{
|
|
193
|
+
id: string | null;
|
|
194
|
+
}>({ id: null });
|
|
195
|
+
const [edgeSettings, setEdgeSettings] = useState<{ id: string | null }>({
|
|
196
|
+
id: null,
|
|
197
|
+
});
|
|
198
|
+
const [selectedCompositeStateNodeId, setSelectedCompositeStateNodeId] =
|
|
199
|
+
useState<string | null>(null);
|
|
200
|
+
const [isSettingsSidebarOpen, setIsSettingsSidebarOpen] = useState(false);
|
|
201
|
+
|
|
202
|
+
// Add layout integration
|
|
203
|
+
const { getLayoutedElements, layoutDirection, setLayoutDirection } =
|
|
204
|
+
useStateMachineLayout(nodes as Node[], edges as StateMachineEdge[], setNodes);
|
|
205
|
+
|
|
206
|
+
// Function declarations in correct order
|
|
207
|
+
const handleToggleCompositeExpand = useCallback(
|
|
208
|
+
(id: string, expanded: boolean) => {
|
|
209
|
+
setNodes((nds: Node[]) =>
|
|
210
|
+
nds.map((node: Node) =>
|
|
211
|
+
node.id === id && node.type === 'CompositeStateNode'
|
|
212
|
+
? { ...node, data: { ...node.data, expanded } }
|
|
213
|
+
: node,
|
|
214
|
+
),
|
|
215
|
+
);
|
|
216
|
+
},
|
|
217
|
+
[setNodes],
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
const nodeTypes = useMemo(() => {
|
|
221
|
+
return {
|
|
222
|
+
...baseStateMachineNodeTypes,
|
|
223
|
+
CompositeStateNode: (props: NodeProps) => {
|
|
224
|
+
const compositeProps: CompositeStateNodeProps = {
|
|
225
|
+
...props,
|
|
226
|
+
onToggleExpand: handleToggleCompositeExpand,
|
|
227
|
+
onSettingsClick: undefined,
|
|
228
|
+
};
|
|
229
|
+
return <CompositeStateNodeType {...compositeProps} />;
|
|
230
|
+
},
|
|
231
|
+
};
|
|
232
|
+
}, [handleToggleCompositeExpand]);
|
|
233
|
+
|
|
234
|
+
const saveLabel = useCallback(() => {
|
|
235
|
+
if (editing.nodeId && editing.type === 'node') {
|
|
236
|
+
setNodes((nds: Node[]) =>
|
|
237
|
+
nds.map((node: Node) => {
|
|
238
|
+
if (node.id === editing.nodeId) {
|
|
239
|
+
return {
|
|
240
|
+
...node,
|
|
241
|
+
data: {
|
|
242
|
+
...node.data,
|
|
243
|
+
label: editing.label,
|
|
244
|
+
},
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return node;
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
setEditing({
|
|
251
|
+
nodeId: undefined,
|
|
252
|
+
edgeId: undefined,
|
|
253
|
+
label: undefined,
|
|
254
|
+
type: undefined,
|
|
255
|
+
});
|
|
256
|
+
} else if (editing.edgeId && editing.type === 'edge') {
|
|
257
|
+
setEdges((eds: Edge[]) =>
|
|
258
|
+
eds.map((edge: Edge) => {
|
|
259
|
+
if (edge.id === editing.edgeId) {
|
|
260
|
+
return {
|
|
261
|
+
...edge,
|
|
262
|
+
data: {
|
|
263
|
+
...edge.data,
|
|
264
|
+
label: editing.label,
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
return edge;
|
|
269
|
+
}),
|
|
270
|
+
);
|
|
271
|
+
setEditing({
|
|
272
|
+
nodeId: undefined,
|
|
273
|
+
edgeId: undefined,
|
|
274
|
+
label: undefined,
|
|
275
|
+
type: undefined,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
}, [editing, setNodes, setEdges]);
|
|
279
|
+
|
|
280
|
+
const handleKeyDown = useCallback(
|
|
281
|
+
(e: React.KeyboardEvent) => {
|
|
282
|
+
if (e.key === 'Enter') {
|
|
283
|
+
saveLabel();
|
|
284
|
+
} else if (e.key === 'Escape') {
|
|
285
|
+
setEditing({
|
|
286
|
+
nodeId: undefined,
|
|
287
|
+
edgeId: undefined,
|
|
288
|
+
label: undefined,
|
|
289
|
+
type: undefined,
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
},
|
|
293
|
+
[saveLabel],
|
|
294
|
+
);
|
|
295
|
+
|
|
296
|
+
const loadTemplateData = useCallback(
|
|
297
|
+
(templateType: string) => {
|
|
298
|
+
console.log(`Loading template: ${templateType}`);
|
|
299
|
+
// For now, just load the default state machine nodes/edges
|
|
300
|
+
setNodes(stateMachineDefaultNodes as Node[]);
|
|
301
|
+
setEdges(stateMachineDefaultEdges as Edge[]);
|
|
302
|
+
},
|
|
303
|
+
[setNodes, setEdges],
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
// Initialize nodes and edges with our data
|
|
307
|
+
useEffect(() => {
|
|
308
|
+
// Only initialize if history is empty
|
|
309
|
+
if (history.length === 0) {
|
|
310
|
+
const initialState = {
|
|
311
|
+
nodes: [...nodes],
|
|
312
|
+
edges: [...edges],
|
|
313
|
+
};
|
|
314
|
+
setHistory([initialState]);
|
|
315
|
+
setHistoryIndex(0);
|
|
316
|
+
}
|
|
317
|
+
}, []);
|
|
318
|
+
|
|
319
|
+
// Save current state to history when nodes or edges change (debounced)
|
|
320
|
+
const debouncedUpdateHistory = useRef(
|
|
321
|
+
debounce(
|
|
322
|
+
(
|
|
323
|
+
nodes: Node[],
|
|
324
|
+
edges: Edge[],
|
|
325
|
+
history: HistoryState[],
|
|
326
|
+
historyIndex: number,
|
|
327
|
+
setHistory: React.Dispatch<React.SetStateAction<HistoryState[]>>,
|
|
328
|
+
setHistoryIndex: React.Dispatch<React.SetStateAction<number>>,
|
|
329
|
+
) => {
|
|
330
|
+
const newHistoryEntry = {
|
|
331
|
+
nodes: [...nodes],
|
|
332
|
+
edges: [...edges],
|
|
333
|
+
};
|
|
334
|
+
const currentState = history[historyIndex];
|
|
335
|
+
if (
|
|
336
|
+
currentState &&
|
|
337
|
+
deepCompareNodes(currentState.nodes, newHistoryEntry.nodes) &&
|
|
338
|
+
deepCompareEdges(currentState.edges, newHistoryEntry.edges)
|
|
339
|
+
) {
|
|
340
|
+
return; // No change, don't add to history
|
|
341
|
+
}
|
|
342
|
+
// Trim future history if we're in the middle of the history stack
|
|
343
|
+
let newHistory = history.slice(0, historyIndex + 1);
|
|
344
|
+
newHistory.push(newHistoryEntry);
|
|
345
|
+
// Limit to last 50 entries
|
|
346
|
+
if (newHistory.length > 50) {
|
|
347
|
+
newHistory = newHistory.slice(newHistory.length - 50);
|
|
348
|
+
}
|
|
349
|
+
setHistory(newHistory);
|
|
350
|
+
setHistoryIndex(newHistory.length - 1);
|
|
351
|
+
},
|
|
352
|
+
300,
|
|
353
|
+
),
|
|
354
|
+
).current;
|
|
355
|
+
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
if (isUndoRedoAction.current || history.length === 0) {
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
debouncedUpdateHistory(
|
|
361
|
+
nodes,
|
|
362
|
+
edges,
|
|
363
|
+
history,
|
|
364
|
+
historyIndex,
|
|
365
|
+
setHistory,
|
|
366
|
+
setHistoryIndex,
|
|
367
|
+
);
|
|
368
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
369
|
+
}, [nodes, edges]);
|
|
370
|
+
|
|
371
|
+
// Function to perform undo operation
|
|
372
|
+
const handleUndo = useCallback(() => {
|
|
373
|
+
if (historyIndex > 0) {
|
|
374
|
+
isUndoRedoAction.current = true;
|
|
375
|
+
const prevState = history[historyIndex - 1];
|
|
376
|
+
setNodes(prevState.nodes);
|
|
377
|
+
setEdges(prevState.edges);
|
|
378
|
+
setHistoryIndex(historyIndex - 1);
|
|
379
|
+
setTimeout(() => {
|
|
380
|
+
isUndoRedoAction.current = false;
|
|
381
|
+
}, 0);
|
|
382
|
+
}
|
|
383
|
+
}, [history, historyIndex, setNodes, setEdges]);
|
|
384
|
+
|
|
385
|
+
// Function to perform redo operation
|
|
386
|
+
const handleRedo = useCallback(() => {
|
|
387
|
+
if (historyIndex < history.length - 1) {
|
|
388
|
+
isUndoRedoAction.current = true;
|
|
389
|
+
const nextState = history[historyIndex + 1];
|
|
390
|
+
setNodes(nextState.nodes);
|
|
391
|
+
setEdges(nextState.edges);
|
|
392
|
+
setHistoryIndex(historyIndex + 1);
|
|
393
|
+
setTimeout(() => {
|
|
394
|
+
isUndoRedoAction.current = false;
|
|
395
|
+
}, 0);
|
|
396
|
+
}
|
|
397
|
+
}, [history, historyIndex, setNodes, setEdges]);
|
|
398
|
+
|
|
399
|
+
// Clear handler
|
|
400
|
+
const handleClear = useCallback(() => {
|
|
401
|
+
const cleared = { nodes: [], edges: [] };
|
|
402
|
+
const newHistory = history.slice(0, historyIndex + 1);
|
|
403
|
+
newHistory.push(cleared);
|
|
404
|
+
setNodes([]);
|
|
405
|
+
setEdges([]);
|
|
406
|
+
setHistory(newHistory);
|
|
407
|
+
setHistoryIndex(newHistory.length - 1);
|
|
408
|
+
}, [history, historyIndex, setNodes, setEdges]);
|
|
409
|
+
|
|
410
|
+
// Listen for keyboard shortcuts
|
|
411
|
+
useEffect(() => {
|
|
412
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
413
|
+
// Check for Ctrl+Z or Cmd+Z (undo)
|
|
414
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
|
415
|
+
e.preventDefault();
|
|
416
|
+
handleUndo();
|
|
417
|
+
}
|
|
418
|
+
// Check for Ctrl+Y or Cmd+Shift+Z (redo)
|
|
419
|
+
if (
|
|
420
|
+
((e.ctrlKey || e.metaKey) && e.key === 'y') ||
|
|
421
|
+
((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')
|
|
422
|
+
) {
|
|
423
|
+
e.preventDefault();
|
|
424
|
+
handleRedo();
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
429
|
+
return () => {
|
|
430
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
431
|
+
};
|
|
432
|
+
}, [handleUndo, handleRedo]);
|
|
433
|
+
|
|
434
|
+
// Node type map for state machine nodes
|
|
435
|
+
const nodeTypeMap: Record<string, { type: string; data?: Record<string, unknown> }> = {
|
|
436
|
+
StateNode: { type: 'StateNode', data: { label: 'State' } },
|
|
437
|
+
InitialStateNode: { type: 'InitialStateNode', data: {} },
|
|
438
|
+
FinalStateNode: { type: 'FinalStateNode', data: {} },
|
|
439
|
+
CompositeStateNode: {
|
|
440
|
+
type: 'CompositeStateNode',
|
|
441
|
+
data: { label: 'Composite State', expanded: false },
|
|
442
|
+
},
|
|
443
|
+
HistoryStateNode: { type: 'HistoryStateNode', data: {} },
|
|
444
|
+
JunctionPointNode: { type: 'JunctionPointNode', data: {} },
|
|
445
|
+
ChoicePointNode: { type: 'ChoicePointNode', data: {} },
|
|
446
|
+
EntryPointNode: { type: 'EntryPointNode', data: {} },
|
|
447
|
+
ExitPointNode: { type: 'ExitPointNode', data: {} },
|
|
448
|
+
ForkJoinNode: { type: 'ForkJoinNode', data: { orientation: 'horizontal' } },
|
|
449
|
+
BendpointNode: { type: 'bendpoint', data: {} },
|
|
450
|
+
};
|
|
451
|
+
|
|
452
|
+
// Add node at center (internal)
|
|
453
|
+
const addNodeAtCenter = useCallback(
|
|
454
|
+
(nodeTypeId: string) => {
|
|
455
|
+
const nodeInfo = nodeTypeMap[nodeTypeId];
|
|
456
|
+
if (!nodeInfo) return;
|
|
457
|
+
const { type, data } = nodeInfo;
|
|
458
|
+
const width = diagramContainerRef.current?.offsetWidth || 1200;
|
|
459
|
+
const height = diagramContainerRef.current?.offsetHeight || 800;
|
|
460
|
+
const center = getCenterPosition(width, height);
|
|
461
|
+
const newId = `${nodeTypeId}-${nodes.length + 1}`;
|
|
462
|
+
|
|
463
|
+
// Add onResize handler to node data
|
|
464
|
+
const onResize = (id: string, width: number, height: number) => {
|
|
465
|
+
setNodes((nds) =>
|
|
466
|
+
nds.map((node) =>
|
|
467
|
+
node.id === id
|
|
468
|
+
? {
|
|
469
|
+
...node,
|
|
470
|
+
measured: { width, height },
|
|
471
|
+
width: width,
|
|
472
|
+
height: height,
|
|
473
|
+
}
|
|
474
|
+
: node,
|
|
475
|
+
),
|
|
476
|
+
);
|
|
477
|
+
// Update all selfconnecting edges for this node with new size/position
|
|
478
|
+
setEdges((eds) =>
|
|
479
|
+
eds.map((edge) => {
|
|
480
|
+
if (
|
|
481
|
+
edge.type === 'selfconnecting' &&
|
|
482
|
+
edge.source === id &&
|
|
483
|
+
edge.target === id
|
|
484
|
+
) {
|
|
485
|
+
// Find the node to get its position
|
|
486
|
+
const node = nodes.find((n) => n.id === id);
|
|
487
|
+
const nodeX = node?.position?.x ?? 0;
|
|
488
|
+
const nodeY = node?.position?.y ?? 0;
|
|
489
|
+
return {
|
|
490
|
+
...edge,
|
|
491
|
+
data: {
|
|
492
|
+
...edge.data,
|
|
493
|
+
nodeWidth: width,
|
|
494
|
+
nodeHeight: height,
|
|
495
|
+
nodeX,
|
|
496
|
+
nodeY,
|
|
497
|
+
},
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
return edge;
|
|
501
|
+
}),
|
|
502
|
+
);
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
setNodes((nds: Node[]) => [
|
|
506
|
+
...nds,
|
|
507
|
+
{
|
|
508
|
+
id: newId,
|
|
509
|
+
type,
|
|
510
|
+
position: center,
|
|
511
|
+
data: { ...data, onResize },
|
|
512
|
+
draggable: true,
|
|
513
|
+
},
|
|
514
|
+
]);
|
|
515
|
+
},
|
|
516
|
+
[nodes, setNodes, setEdges],
|
|
517
|
+
);
|
|
518
|
+
|
|
519
|
+
// Update onAddEntity to use internal addNodeAtCenter
|
|
520
|
+
const handleAddEntity = useCallback(
|
|
521
|
+
(nodeTypeId = 'StateNode') => {
|
|
522
|
+
addNodeAtCenter(nodeTypeId);
|
|
523
|
+
},
|
|
524
|
+
[addNodeAtCenter],
|
|
525
|
+
);
|
|
526
|
+
|
|
527
|
+
// Function to export diagram as JSON
|
|
528
|
+
const exportDiagram = useCallback(() => {
|
|
529
|
+
const diagramData = {
|
|
530
|
+
nodes: nodes,
|
|
531
|
+
edges: edges,
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const dataStr = JSON.stringify(diagramData, null, 2);
|
|
535
|
+
const dataUri =
|
|
536
|
+
'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
|
|
537
|
+
|
|
538
|
+
const exportFileDefaultName = 'state-machine-diagram.json';
|
|
539
|
+
|
|
540
|
+
const linkElement = document.createElement('a');
|
|
541
|
+
linkElement.setAttribute('href', dataUri);
|
|
542
|
+
linkElement.setAttribute('download', exportFileDefaultName);
|
|
543
|
+
linkElement.click();
|
|
544
|
+
}, [nodes, edges]);
|
|
545
|
+
|
|
546
|
+
// Function to import diagram from JSON
|
|
547
|
+
const importDiagram = useCallback(
|
|
548
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
549
|
+
if (!event.target.files || event.target.files.length === 0) return;
|
|
550
|
+
|
|
551
|
+
const fileReader = new FileReader();
|
|
552
|
+
fileReader.readAsText(event.target.files[0], 'UTF-8');
|
|
553
|
+
fileReader.onload = (e) => {
|
|
554
|
+
try {
|
|
555
|
+
if (!e.target || typeof e.target.result !== 'string') return;
|
|
556
|
+
|
|
557
|
+
const imported = JSON.parse(e.target.result);
|
|
558
|
+
if (imported.nodes && imported.edges) {
|
|
559
|
+
setNodes(imported.nodes);
|
|
560
|
+
setEdges(imported.edges);
|
|
561
|
+
}
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.error('Error importing diagram:', error);
|
|
564
|
+
alert('Failed to import diagram. Invalid file format.');
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
},
|
|
568
|
+
[setNodes, setEdges],
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
// Toggle fullscreen mode
|
|
572
|
+
const toggleFullscreen = useCallback(() => {
|
|
573
|
+
if (!diagramContainerRef.current) return;
|
|
574
|
+
|
|
575
|
+
if (!isFullscreen) {
|
|
576
|
+
if (diagramContainerRef.current.requestFullscreen) {
|
|
577
|
+
diagramContainerRef.current.requestFullscreen();
|
|
578
|
+
} else if ((diagramContainerRef.current as FullscreenElement).webkitRequestFullscreen) {
|
|
579
|
+
(diagramContainerRef.current as FullscreenElement).webkitRequestFullscreen?.();
|
|
580
|
+
} else if ((diagramContainerRef.current as FullscreenElement).msRequestFullscreen) {
|
|
581
|
+
(diagramContainerRef.current as FullscreenElement).msRequestFullscreen?.();
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
if (document.exitFullscreen) {
|
|
585
|
+
document.exitFullscreen();
|
|
586
|
+
} else if ((document as FullscreenDocument).webkitExitFullscreen) {
|
|
587
|
+
(document as FullscreenDocument).webkitExitFullscreen?.();
|
|
588
|
+
} else if ((document as FullscreenDocument).msExitFullscreen) {
|
|
589
|
+
(document as FullscreenDocument).msExitFullscreen?.();
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
setIsFullscreen(!isFullscreen);
|
|
594
|
+
}, [isFullscreen]);
|
|
595
|
+
|
|
596
|
+
// Listen for fullscreen change events from browser
|
|
597
|
+
useEffect(() => {
|
|
598
|
+
const handleFullscreenChange = () => {
|
|
599
|
+
setIsFullscreen(!!document.fullscreenElement);
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
|
603
|
+
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
|
|
604
|
+
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
|
|
605
|
+
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
|
|
606
|
+
|
|
607
|
+
return () => {
|
|
608
|
+
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
|
609
|
+
document.removeEventListener(
|
|
610
|
+
'webkitfullscreenchange',
|
|
611
|
+
handleFullscreenChange,
|
|
612
|
+
);
|
|
613
|
+
document.removeEventListener(
|
|
614
|
+
'mozfullscreenchange',
|
|
615
|
+
handleFullscreenChange,
|
|
616
|
+
);
|
|
617
|
+
document.removeEventListener(
|
|
618
|
+
'MSFullscreenChange',
|
|
619
|
+
handleFullscreenChange,
|
|
620
|
+
);
|
|
621
|
+
};
|
|
622
|
+
}, []);
|
|
623
|
+
|
|
624
|
+
// Modified onConnect to open EdgeSettings after edge is created
|
|
625
|
+
const onConnect = useCallback(
|
|
626
|
+
(params: Connection) => {
|
|
627
|
+
if (!params.source || !params.target) return;
|
|
628
|
+
const sourceNode = nodes.find((n) => n.id === params.source);
|
|
629
|
+
const targetNode = nodes.find((n) => n.id === params.target);
|
|
630
|
+
if (!sourceNode || !targetNode) return;
|
|
631
|
+
|
|
632
|
+
const newEdge = {
|
|
633
|
+
id: `edge-${edges.length + 1}`,
|
|
634
|
+
source: params.source,
|
|
635
|
+
target: params.target,
|
|
636
|
+
type: 'customEdge',
|
|
637
|
+
sourceHandle: params.sourceHandle,
|
|
638
|
+
targetHandle: params.targetHandle,
|
|
639
|
+
data: {
|
|
640
|
+
label: `Transition ${edges.length + 1}`,
|
|
641
|
+
},
|
|
642
|
+
animated: false,
|
|
643
|
+
};
|
|
644
|
+
setEdges((eds: Edge[]) => [...eds, newEdge]);
|
|
645
|
+
setEdgeSettings({ id: newEdge.id });
|
|
646
|
+
setIsSettingsSidebarOpen(true);
|
|
647
|
+
},
|
|
648
|
+
[nodes, edges, setEdges],
|
|
649
|
+
);
|
|
650
|
+
|
|
651
|
+
// Update onNodeClick function
|
|
652
|
+
const onNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
|
|
653
|
+
if (node.type === 'bendpoint') {
|
|
654
|
+
setStateNodeSettings({ id: null });
|
|
655
|
+
setSelectedCompositeStateNodeId(null);
|
|
656
|
+
return;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (node.type === 'CompositeStateNode') {
|
|
660
|
+
setSelectedCompositeStateNodeId(node.id);
|
|
661
|
+
setStateNodeSettings({ id: null });
|
|
662
|
+
setIsSettingsSidebarOpen(true);
|
|
663
|
+
} else if (
|
|
664
|
+
[
|
|
665
|
+
'StateNode',
|
|
666
|
+
'InitialStateNode',
|
|
667
|
+
'FinalStateNode',
|
|
668
|
+
'HistoryStateNode',
|
|
669
|
+
'JunctionPointNode',
|
|
670
|
+
'ChoicePointNode',
|
|
671
|
+
'EntryPointNode',
|
|
672
|
+
'ExitPointNode',
|
|
673
|
+
'ForkJoinNode',
|
|
674
|
+
].includes(node.type || '')
|
|
675
|
+
) {
|
|
676
|
+
setStateNodeSettings({ id: node.id });
|
|
677
|
+
setSelectedCompositeStateNodeId(null);
|
|
678
|
+
setIsSettingsSidebarOpen(true);
|
|
679
|
+
} else {
|
|
680
|
+
setStateNodeSettings({ id: null });
|
|
681
|
+
setSelectedCompositeStateNodeId(null);
|
|
682
|
+
}
|
|
683
|
+
}, []);
|
|
684
|
+
|
|
685
|
+
// Update onNodeDoubleClick function
|
|
686
|
+
const onNodeDoubleClick = useCallback(
|
|
687
|
+
(event: React.MouseEvent, node: Node) => {
|
|
688
|
+
event.preventDefault();
|
|
689
|
+
event.stopPropagation();
|
|
690
|
+
setEditing({
|
|
691
|
+
nodeId: node.id,
|
|
692
|
+
edgeId: null,
|
|
693
|
+
label: (node.data?.label as string) || '',
|
|
694
|
+
type: 'node',
|
|
695
|
+
});
|
|
696
|
+
},
|
|
697
|
+
[setEditing],
|
|
698
|
+
);
|
|
699
|
+
|
|
700
|
+
// Open EdgeSettings on double click
|
|
701
|
+
const onEdgeDoubleClick = useCallback(
|
|
702
|
+
(event: React.MouseEvent, edge: Edge) => {
|
|
703
|
+
event.preventDefault();
|
|
704
|
+
event.stopPropagation();
|
|
705
|
+
if (edge.type === 'customEdge') {
|
|
706
|
+
setEdgeSettings({ id: edge.id });
|
|
707
|
+
setIsSettingsSidebarOpen(true);
|
|
708
|
+
}
|
|
709
|
+
},
|
|
710
|
+
[],
|
|
711
|
+
);
|
|
712
|
+
|
|
713
|
+
// Function to update selected state node
|
|
714
|
+
const updateSelectedStateNode = useCallback(
|
|
715
|
+
(updates: Record<string, unknown>) => {
|
|
716
|
+
if (!stateNodeSettings.id) return;
|
|
717
|
+
setNodes((nds: Node[]) =>
|
|
718
|
+
nds.map((node: Node) => {
|
|
719
|
+
if (node.id === stateNodeSettings.id) {
|
|
720
|
+
return {
|
|
721
|
+
...node,
|
|
722
|
+
data: {
|
|
723
|
+
...node.data,
|
|
724
|
+
...updates,
|
|
725
|
+
},
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
return node;
|
|
729
|
+
}),
|
|
730
|
+
);
|
|
731
|
+
},
|
|
732
|
+
[stateNodeSettings.id, setNodes],
|
|
733
|
+
);
|
|
734
|
+
|
|
735
|
+
// Close state node settings
|
|
736
|
+
const closeStateNodeSettings = useCallback(() => {
|
|
737
|
+
setStateNodeSettings({ id: null });
|
|
738
|
+
setIsSettingsSidebarOpen(false);
|
|
739
|
+
}, []);
|
|
740
|
+
|
|
741
|
+
// Update edge from EdgeSettings
|
|
742
|
+
const updateSelectedEdge = useCallback(
|
|
743
|
+
(updates: Record<string, unknown>) => {
|
|
744
|
+
if (!edgeSettings.id) return;
|
|
745
|
+
setEdges((eds: Edge[]) =>
|
|
746
|
+
eds.map((edge: Edge) => {
|
|
747
|
+
if (edge.id === edgeSettings.id) {
|
|
748
|
+
return {
|
|
749
|
+
...edge,
|
|
750
|
+
source: typeof updates.source === 'string' ? updates.source : edge.source,
|
|
751
|
+
target: typeof updates.target === 'string' ? updates.target : edge.target,
|
|
752
|
+
data: {
|
|
753
|
+
...edge.data,
|
|
754
|
+
...updates,
|
|
755
|
+
},
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
return edge;
|
|
759
|
+
}) as Edge[],
|
|
760
|
+
);
|
|
761
|
+
},
|
|
762
|
+
[edgeSettings.id, setEdges],
|
|
763
|
+
);
|
|
764
|
+
|
|
765
|
+
// Close EdgeSettings
|
|
766
|
+
const closeEdgeSettings = useCallback(() => {
|
|
767
|
+
setEdgeSettings({ id: null });
|
|
768
|
+
setIsSettingsSidebarOpen(false);
|
|
769
|
+
}, []);
|
|
770
|
+
|
|
771
|
+
// Close CompositeStateNodeSettings
|
|
772
|
+
const closeCompositeStateNodeSettings = useCallback(() => {
|
|
773
|
+
setSelectedCompositeStateNodeId(null);
|
|
774
|
+
setIsSettingsSidebarOpen(false);
|
|
775
|
+
}, []);
|
|
776
|
+
|
|
777
|
+
// Function to update selected composite state node
|
|
778
|
+
const updateSelectedCompositeStateNode = useCallback(
|
|
779
|
+
(updates: Record<string, unknown>) => {
|
|
780
|
+
if (!selectedCompositeStateNodeId) return;
|
|
781
|
+
setNodes((nds: Node[]) =>
|
|
782
|
+
nds.map((node: Node) => {
|
|
783
|
+
if (node.id === selectedCompositeStateNodeId) {
|
|
784
|
+
return {
|
|
785
|
+
...node,
|
|
786
|
+
data: {
|
|
787
|
+
...node.data,
|
|
788
|
+
...updates,
|
|
789
|
+
},
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
return node;
|
|
793
|
+
}),
|
|
794
|
+
);
|
|
795
|
+
},
|
|
796
|
+
[selectedCompositeStateNodeId, setNodes],
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
// Collapse all composite nodes on pane click
|
|
800
|
+
const handlePaneClick = useCallback(() => {
|
|
801
|
+
setNodes((nds: Node[]) =>
|
|
802
|
+
nds.map((node: Node) =>
|
|
803
|
+
node.type === 'CompositeStateNode'
|
|
804
|
+
? { ...node, data: { ...node.data, expanded: false } }
|
|
805
|
+
: node,
|
|
806
|
+
),
|
|
807
|
+
);
|
|
808
|
+
setSelectedCompositeStateNodeId(null);
|
|
809
|
+
setStateNodeSettings({ id: null });
|
|
810
|
+
}, [setNodes]);
|
|
811
|
+
|
|
812
|
+
// Memoize hidden node ids
|
|
813
|
+
const hiddenNodeIds = useMemo(() => getHiddenNodeIds(nodes), [nodes]);
|
|
814
|
+
// Memoize filtered nodes
|
|
815
|
+
const filteredNodes = useMemo(
|
|
816
|
+
() => nodes.filter((n) => !hiddenNodeIds.has(n.id)),
|
|
817
|
+
[nodes, hiddenNodeIds],
|
|
818
|
+
);
|
|
819
|
+
// Memoize collapsed composites
|
|
820
|
+
const collapsedComposites = useMemo(
|
|
821
|
+
() =>
|
|
822
|
+
nodes.filter((n) => n.type === 'CompositeStateNode' && !n.data.expanded),
|
|
823
|
+
[nodes],
|
|
824
|
+
);
|
|
825
|
+
// Memoize proxy edge logic (optimized for performance)
|
|
826
|
+
const { filteredEdges } = useMemo(() => {
|
|
827
|
+
// Precompute collapsed composite node ids for O(1) lookup
|
|
828
|
+
const collapsedCompositeIds = new Set(
|
|
829
|
+
nodes
|
|
830
|
+
.filter((n) => n.type === 'CompositeStateNode' && !n.data.expanded)
|
|
831
|
+
.map((n) => n.id),
|
|
832
|
+
);
|
|
833
|
+
// Precompute a map of nodeId -> node for O(1) lookup
|
|
834
|
+
const nodeIdMap = new Map(nodes.map((n) => [n.id, n]));
|
|
835
|
+
// Map childId to its collapsed composite parent
|
|
836
|
+
const childToCollapsedParent: Record<string, string> = {};
|
|
837
|
+
const collectChildren = (parentId: string) => {
|
|
838
|
+
nodes.forEach((child) => {
|
|
839
|
+
if (child.parentId === parentId) {
|
|
840
|
+
childToCollapsedParent[child.id] = parentId;
|
|
841
|
+
if (child.type === 'CompositeStateNode') {
|
|
842
|
+
collectChildren(child.id);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
};
|
|
847
|
+
collapsedCompositeIds.forEach((compId) => collectChildren(compId));
|
|
848
|
+
// Generate proxy edges for edges crossing collapsed composite boundaries
|
|
849
|
+
const proxyEdges: Edge[] = [];
|
|
850
|
+
const realEdges: Edge[] = [];
|
|
851
|
+
edges.forEach((edge) => {
|
|
852
|
+
const sourceHidden = hiddenNodeIds.has(edge.source);
|
|
853
|
+
const targetHidden = hiddenNodeIds.has(edge.target);
|
|
854
|
+
|
|
855
|
+
// Self-loop on composite node: hide if collapsed
|
|
856
|
+
if (
|
|
857
|
+
edge.source === edge.target &&
|
|
858
|
+
collapsedCompositeIds.has(edge.source)
|
|
859
|
+
) {
|
|
860
|
+
return; // skip this edge
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
// If both ends are hidden, skip (don't show)
|
|
864
|
+
if (sourceHidden && targetHidden) return;
|
|
865
|
+
// If neither end is hidden, show as normal
|
|
866
|
+
if (!sourceHidden && !targetHidden) {
|
|
867
|
+
realEdges.push(edge);
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
// If one end is hidden and the other is visible, create a proxy edge
|
|
871
|
+
let proxySource = edge.source;
|
|
872
|
+
let proxyTarget = edge.target;
|
|
873
|
+
let proxyId = edge.id + '-proxy';
|
|
874
|
+
// If source is hidden, map to its collapsed composite parent
|
|
875
|
+
if (sourceHidden && childToCollapsedParent[edge.source]) {
|
|
876
|
+
proxySource = childToCollapsedParent[edge.source];
|
|
877
|
+
}
|
|
878
|
+
// If target is hidden, map to its collapsed composite parent
|
|
879
|
+
if (targetHidden && childToCollapsedParent[edge.target]) {
|
|
880
|
+
proxyTarget = childToCollapsedParent[edge.target];
|
|
881
|
+
}
|
|
882
|
+
// Avoid proxying to the same node (self-loop)
|
|
883
|
+
if (proxySource === proxyTarget) return;
|
|
884
|
+
proxyEdges.push({
|
|
885
|
+
...edge,
|
|
886
|
+
id: proxyId,
|
|
887
|
+
source: proxySource,
|
|
888
|
+
target: proxyTarget,
|
|
889
|
+
style: { ...(edge.style || {}), strokeDasharray: '6 4', opacity: 0.7 },
|
|
890
|
+
data: { ...(edge.data || {}), isProxy: true },
|
|
891
|
+
});
|
|
892
|
+
});
|
|
893
|
+
return { filteredEdges: [...realEdges, ...proxyEdges] as Edge[] };
|
|
894
|
+
}, [edges, nodes, hiddenNodeIds]);
|
|
895
|
+
|
|
896
|
+
// Custom onNodesChange to snap PinPointNode to border of parent composite node
|
|
897
|
+
const customOnNodesChange = useCallback(
|
|
898
|
+
(changes: NodeChange[]) => {
|
|
899
|
+
setNodes((nds) => {
|
|
900
|
+
// Apply default node changes
|
|
901
|
+
let updated = applyNodeChanges(changes, nds);
|
|
902
|
+
// Snap PinPointNode nodes to border of parent composite node
|
|
903
|
+
updated = updated.map((node) => {
|
|
904
|
+
if (node.type === 'PinPointNode' && node.parentId) {
|
|
905
|
+
const parent = nds.find((n) => n.id === node.parentId);
|
|
906
|
+
if (!parent) return node;
|
|
907
|
+
const width = (parent.data as Record<string, unknown>).width as number || 400;
|
|
908
|
+
const height = (parent.data as Record<string, unknown>).height as number || 300;
|
|
909
|
+
const size = 22;
|
|
910
|
+
let { x, y } = node.position;
|
|
911
|
+
// Calculate center
|
|
912
|
+
const cx = x + size / 2;
|
|
913
|
+
const cy = y + size / 2;
|
|
914
|
+
// Find closest border (top, bottom, left, right)
|
|
915
|
+
const dTop = Math.abs(cy - 0);
|
|
916
|
+
const dBottom = Math.abs(cy - height);
|
|
917
|
+
const dLeft = Math.abs(cx - 0);
|
|
918
|
+
const dRight = Math.abs(cx - width);
|
|
919
|
+
const minDist = Math.min(dTop, dBottom, dLeft, dRight);
|
|
920
|
+
let newX = x,
|
|
921
|
+
newY = y;
|
|
922
|
+
if (minDist === dTop) {
|
|
923
|
+
newX =
|
|
924
|
+
Math.max(-size / 2, Math.min(width - size / 2, cx)) - size / 2;
|
|
925
|
+
newY = -size / 2;
|
|
926
|
+
} else if (minDist === dBottom) {
|
|
927
|
+
newX =
|
|
928
|
+
Math.max(-size / 2, Math.min(width - size / 2, cx)) - size / 2;
|
|
929
|
+
newY = height - size / 2;
|
|
930
|
+
} else if (minDist === dLeft) {
|
|
931
|
+
newX = -size / 2;
|
|
932
|
+
newY =
|
|
933
|
+
Math.max(-size / 2, Math.min(height - size / 2, cy)) - size / 2;
|
|
934
|
+
} else if (minDist === dRight) {
|
|
935
|
+
newX = width - size / 2;
|
|
936
|
+
newY =
|
|
937
|
+
Math.max(-size / 2, Math.min(height - size / 2, cy)) - size / 2;
|
|
938
|
+
}
|
|
939
|
+
return { ...node, position: { x: newX, y: newY } };
|
|
940
|
+
}
|
|
941
|
+
return node;
|
|
942
|
+
});
|
|
943
|
+
return updated;
|
|
944
|
+
});
|
|
945
|
+
},
|
|
946
|
+
[setNodes],
|
|
947
|
+
);
|
|
948
|
+
|
|
949
|
+
// Add layout direction change handler
|
|
950
|
+
const handleLayoutDirectionChange = useCallback(
|
|
951
|
+
(direction: 'DOWN' | 'RIGHT') => {
|
|
952
|
+
setLayoutDirection(direction);
|
|
953
|
+
getLayoutedElements();
|
|
954
|
+
},
|
|
955
|
+
[setLayoutDirection, getLayoutedElements],
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
// Add layout effect
|
|
959
|
+
useEffect(() => {
|
|
960
|
+
// Only layout if we have nodes/edges
|
|
961
|
+
if (nodes.length > 0 || edges.length > 0) {
|
|
962
|
+
getLayoutedElements();
|
|
963
|
+
}
|
|
964
|
+
}, [handleToggleCompositeExpand]);
|
|
965
|
+
|
|
966
|
+
// Add edge edit handlers
|
|
967
|
+
const handleEditEdge = useCallback((edge: Edge) => {
|
|
968
|
+
setEdgeSettings({ id: edge.id });
|
|
969
|
+
}, []);
|
|
970
|
+
|
|
971
|
+
const handleUpdateEdgeLabel = useCallback(
|
|
972
|
+
(edgeId: string, newLabel: string) => {
|
|
973
|
+
setEdges((eds: Edge[]) =>
|
|
974
|
+
eds.map((edge: Edge) =>
|
|
975
|
+
edge.id === edgeId
|
|
976
|
+
? { ...edge, data: { ...edge.data, label: newLabel } }
|
|
977
|
+
: edge,
|
|
978
|
+
),
|
|
979
|
+
);
|
|
980
|
+
},
|
|
981
|
+
[setEdges],
|
|
982
|
+
);
|
|
983
|
+
|
|
984
|
+
const handleToggleEdgeArrowHead = useCallback(
|
|
985
|
+
(edgeId: string, direction: 'left' | 'right') => {
|
|
986
|
+
setEdges((eds: Edge[]) =>
|
|
987
|
+
eds.map((edge: Edge) => {
|
|
988
|
+
if (edge.id === edgeId) {
|
|
989
|
+
const data = { ...edge.data };
|
|
990
|
+
if (direction === 'left') {
|
|
991
|
+
data.arrowHeadLeft = !data.arrowHeadLeft;
|
|
992
|
+
} else {
|
|
993
|
+
data.arrowHeadRight = !data.arrowHeadRight;
|
|
994
|
+
}
|
|
995
|
+
return { ...edge, data };
|
|
996
|
+
}
|
|
997
|
+
return edge;
|
|
998
|
+
}),
|
|
999
|
+
);
|
|
1000
|
+
},
|
|
1001
|
+
[setEdges],
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
// Memoize node and edge maps for O(1) lookup
|
|
1005
|
+
const nodeMap = useMemo(() => {
|
|
1006
|
+
const map = new Map<string, Node>();
|
|
1007
|
+
nodes.forEach((node) => map.set(node.id, node));
|
|
1008
|
+
return map;
|
|
1009
|
+
}, [nodes]);
|
|
1010
|
+
const edgeMap = useMemo(() => {
|
|
1011
|
+
const map = new Map<string, Edge>();
|
|
1012
|
+
edges.forEach((edge) => map.set(edge.id, edge));
|
|
1013
|
+
return map;
|
|
1014
|
+
}, [edges]);
|
|
1015
|
+
|
|
1016
|
+
return (
|
|
1017
|
+
<Box sx={{ display: 'flex', height: '100vh' }}>
|
|
1018
|
+
<Sidebar>
|
|
1019
|
+
<NodeSelectionContext.Provider
|
|
1020
|
+
value={{
|
|
1021
|
+
selectedNodeType: null,
|
|
1022
|
+
setSelectedNodeType: () => { },
|
|
1023
|
+
addNodeAtCenter,
|
|
1024
|
+
}}
|
|
1025
|
+
>
|
|
1026
|
+
<DiagramControls
|
|
1027
|
+
onAddEntity={handleAddEntity}
|
|
1028
|
+
onUndo={handleUndo}
|
|
1029
|
+
onRedo={handleRedo}
|
|
1030
|
+
canUndo={historyIndex > 0}
|
|
1031
|
+
canRedo={historyIndex < history.length - 1}
|
|
1032
|
+
onLoadTemplate={loadTemplateData}
|
|
1033
|
+
edges={edges as StateMachineEdge[]}
|
|
1034
|
+
nodes={nodes as StateMachineNode[]}
|
|
1035
|
+
onEditEdge={handleEditEdge}
|
|
1036
|
+
onUpdateEdgeLabel={handleUpdateEdgeLabel}
|
|
1037
|
+
onToggleEdgeArrowHead={handleToggleEdgeArrowHead}
|
|
1038
|
+
/>
|
|
1039
|
+
</NodeSelectionContext.Provider>
|
|
1040
|
+
</Sidebar>
|
|
1041
|
+
|
|
1042
|
+
<Box
|
|
1043
|
+
sx={{
|
|
1044
|
+
flexGrow: 1,
|
|
1045
|
+
'& .react-flow__edge': {
|
|
1046
|
+
zIndex: '5000 !important',
|
|
1047
|
+
},
|
|
1048
|
+
'& .react-flow__edge-path': {
|
|
1049
|
+
pointerEvents: 'all !important',
|
|
1050
|
+
},
|
|
1051
|
+
'& .edge-path-selector': {
|
|
1052
|
+
strokeWidth: 20,
|
|
1053
|
+
fill: 'none',
|
|
1054
|
+
stroke: 'transparent',
|
|
1055
|
+
cursor: 'pointer',
|
|
1056
|
+
},
|
|
1057
|
+
'& .react-flow__edges': {
|
|
1058
|
+
zIndex: '5000 !important',
|
|
1059
|
+
},
|
|
1060
|
+
'& .react-flow__node': {
|
|
1061
|
+
pointerEvents: 'all !important',
|
|
1062
|
+
cursor: 'grab',
|
|
1063
|
+
'&:active': {
|
|
1064
|
+
cursor: 'grabbing',
|
|
1065
|
+
},
|
|
1066
|
+
'& .react-flow__edge': {
|
|
1067
|
+
zIndex: '5000 !important',
|
|
1068
|
+
},
|
|
1069
|
+
},
|
|
1070
|
+
'& .react-flow__node.dragging': {
|
|
1071
|
+
cursor: 'grabbing !important',
|
|
1072
|
+
},
|
|
1073
|
+
'& .react-flow.dragging': {
|
|
1074
|
+
cursor: 'grabbing',
|
|
1075
|
+
},
|
|
1076
|
+
}}
|
|
1077
|
+
>
|
|
1078
|
+
<Box
|
|
1079
|
+
sx={{
|
|
1080
|
+
width: '100%',
|
|
1081
|
+
height: '100%',
|
|
1082
|
+
padding: '10px',
|
|
1083
|
+
bgcolor: 'white',
|
|
1084
|
+
}}
|
|
1085
|
+
>
|
|
1086
|
+
<ReactFlow
|
|
1087
|
+
nodes={filteredNodes}
|
|
1088
|
+
edges={filteredEdges}
|
|
1089
|
+
onNodesChange={customOnNodesChange}
|
|
1090
|
+
onEdgesChange={onEdgesChange}
|
|
1091
|
+
onConnect={onConnect}
|
|
1092
|
+
nodeTypes={nodeTypes}
|
|
1093
|
+
edgeTypes={stateMachineEdgeTypes}
|
|
1094
|
+
fitView
|
|
1095
|
+
fitViewOptions={{
|
|
1096
|
+
padding: 0.2,
|
|
1097
|
+
minZoom: 0.5,
|
|
1098
|
+
maxZoom: 2,
|
|
1099
|
+
}}
|
|
1100
|
+
minZoom={0.1}
|
|
1101
|
+
maxZoom={2}
|
|
1102
|
+
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
|
|
1103
|
+
style={{
|
|
1104
|
+
background: '#f8f9fa',
|
|
1105
|
+
}}
|
|
1106
|
+
proOptions={{ hideAttribution: true }}
|
|
1107
|
+
selectNodesOnDrag={false}
|
|
1108
|
+
connectOnClick={false}
|
|
1109
|
+
onNodeClick={onNodeClick}
|
|
1110
|
+
onNodeDoubleClick={onNodeDoubleClick}
|
|
1111
|
+
onEdgeDoubleClick={onEdgeDoubleClick}
|
|
1112
|
+
onPaneClick={handlePaneClick}
|
|
1113
|
+
>
|
|
1114
|
+
<Background
|
|
1115
|
+
color="#f8f9fa"
|
|
1116
|
+
variant={BackgroundVariant.Dots}
|
|
1117
|
+
gap={16}
|
|
1118
|
+
/>
|
|
1119
|
+
|
|
1120
|
+
{/* MiniMap positioned at bottom-left */}
|
|
1121
|
+
<MiniMapCommon />
|
|
1122
|
+
|
|
1123
|
+
{/* Diagram Panel with all controls */}
|
|
1124
|
+
<DiagramPanel
|
|
1125
|
+
historyIndex={historyIndex}
|
|
1126
|
+
historyLength={history.length}
|
|
1127
|
+
onUndo={handleUndo}
|
|
1128
|
+
onRedo={handleRedo}
|
|
1129
|
+
onClear={handleClear}
|
|
1130
|
+
onImport={(data) => {
|
|
1131
|
+
setNodes(data.nodes || []);
|
|
1132
|
+
setEdges(data.edges || []);
|
|
1133
|
+
// Add to history
|
|
1134
|
+
const newHistoryState = {
|
|
1135
|
+
nodes: data.nodes || [],
|
|
1136
|
+
edges: data.edges || [],
|
|
1137
|
+
};
|
|
1138
|
+
setHistory((prev) => [
|
|
1139
|
+
...prev.slice(0, historyIndex + 1),
|
|
1140
|
+
newHistoryState,
|
|
1141
|
+
]);
|
|
1142
|
+
setHistoryIndex((prev) => prev + 1);
|
|
1143
|
+
}}
|
|
1144
|
+
isSettingsOpen={isSettingsSidebarOpen}
|
|
1145
|
+
onToggleSettings={() => setIsSettingsSidebarOpen(true)}
|
|
1146
|
+
showCustomControls={true}
|
|
1147
|
+
onGridLayout={() => handleLayoutDirectionChange('DOWN')}
|
|
1148
|
+
onTreeLayout={() => handleLayoutDirectionChange('RIGHT')}
|
|
1149
|
+
/>
|
|
1150
|
+
|
|
1151
|
+
{/* AI Button positioned at bottom-right */}
|
|
1152
|
+
<AiButton />
|
|
1153
|
+
|
|
1154
|
+
{/* Layout controls positioned at top-right */}
|
|
1155
|
+
|
|
1156
|
+
{/* Fullscreen button positioned at top-left */}
|
|
1157
|
+
</ReactFlow>
|
|
1158
|
+
</Box>
|
|
1159
|
+
</Box>
|
|
1160
|
+
|
|
1161
|
+
{/* Settings panels */}
|
|
1162
|
+
{stateNodeSettings.id && (
|
|
1163
|
+
<StateNodeSettings
|
|
1164
|
+
nodeId={stateNodeSettings.id}
|
|
1165
|
+
data={nodeMap.get(stateNodeSettings.id)?.data || {}}
|
|
1166
|
+
onUpdate={updateSelectedStateNode}
|
|
1167
|
+
onClose={closeStateNodeSettings}
|
|
1168
|
+
edges={edges as StateMachineEdge[]}
|
|
1169
|
+
setEdges={(updater) => {
|
|
1170
|
+
if (typeof updater === 'function') {
|
|
1171
|
+
setEdges((prev) => updater(prev as StateMachineEdge[]) as Edge[]);
|
|
1172
|
+
} else {
|
|
1173
|
+
setEdges(updater as Edge[]);
|
|
1174
|
+
}
|
|
1175
|
+
}}
|
|
1176
|
+
nodes={nodes as StateMachineNode[]}
|
|
1177
|
+
/>
|
|
1178
|
+
)}
|
|
1179
|
+
|
|
1180
|
+
{edgeSettings.id && (
|
|
1181
|
+
<EdgeSettings
|
|
1182
|
+
data={{
|
|
1183
|
+
...(edgeMap.get(edgeSettings.id)?.data || {}),
|
|
1184
|
+
source: edgeMap.get(edgeSettings.id)?.source,
|
|
1185
|
+
target: edgeMap.get(edgeSettings.id)?.target,
|
|
1186
|
+
}}
|
|
1187
|
+
nodes={nodes as StateMachineNode[]}
|
|
1188
|
+
onUpdate={updateSelectedEdge}
|
|
1189
|
+
onClose={closeEdgeSettings}
|
|
1190
|
+
onDelete={() => {
|
|
1191
|
+
setEdges((eds: Edge[]) =>
|
|
1192
|
+
eds.filter((edge: Edge) => edge.id !== edgeSettings.id),
|
|
1193
|
+
);
|
|
1194
|
+
closeEdgeSettings();
|
|
1195
|
+
}}
|
|
1196
|
+
/>
|
|
1197
|
+
)}
|
|
1198
|
+
|
|
1199
|
+
{selectedCompositeStateNodeId && (
|
|
1200
|
+
<CompositeStateNodeSettings
|
|
1201
|
+
nodeId={selectedCompositeStateNodeId}
|
|
1202
|
+
data={nodeMap.get(selectedCompositeStateNodeId)?.data || {}}
|
|
1203
|
+
nodes={nodes as StateMachineNode[]}
|
|
1204
|
+
setNodes={(updater) => {
|
|
1205
|
+
if (typeof updater === 'function') {
|
|
1206
|
+
setNodes((prev) => updater(prev as StateMachineNode[]) as Node[]);
|
|
1207
|
+
} else {
|
|
1208
|
+
setNodes(updater as Node[]);
|
|
1209
|
+
}
|
|
1210
|
+
}}
|
|
1211
|
+
onUpdate={updateSelectedCompositeStateNode}
|
|
1212
|
+
onClose={closeCompositeStateNodeSettings}
|
|
1213
|
+
edges={edges as StateMachineEdge[]}
|
|
1214
|
+
setEdges={(updater) => {
|
|
1215
|
+
if (typeof updater === 'function') {
|
|
1216
|
+
setEdges((prev) => updater(prev as StateMachineEdge[]) as Edge[]);
|
|
1217
|
+
} else {
|
|
1218
|
+
setEdges(updater as Edge[]);
|
|
1219
|
+
}
|
|
1220
|
+
}}
|
|
1221
|
+
/>
|
|
1222
|
+
)}
|
|
1223
|
+
</Box>
|
|
1224
|
+
);
|
|
1225
|
+
};
|
|
1226
|
+
|
|
1227
|
+
// Main wrapper component with ReactFlowProvider
|
|
1228
|
+
const StateMachineDiagramFlow: React.FC<StateMachineDiagramFlowProps> = (
|
|
1229
|
+
props,
|
|
1230
|
+
) => {
|
|
1231
|
+
return (
|
|
1232
|
+
<ReactFlowProvider>
|
|
1233
|
+
<StateMachineDiagramFlowContent {...props} />
|
|
1234
|
+
</ReactFlowProvider>
|
|
1235
|
+
);
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1238
|
+
export default StateMachineDiagramFlow;
|