@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
|
@@ -1,1361 +1,1361 @@
|
|
|
1
|
-
import React, { useCallback, useState, useRef, useEffect } from 'react';
|
|
2
|
-
import {
|
|
3
|
-
ReactFlow,
|
|
4
|
-
Background,
|
|
5
|
-
useNodesState,
|
|
6
|
-
useEdgesState,
|
|
7
|
-
Panel,
|
|
8
|
-
BackgroundVariant,
|
|
9
|
-
Connection,
|
|
10
|
-
NodeChange,
|
|
11
|
-
Node,
|
|
12
|
-
Edge,
|
|
13
|
-
MiniMap,
|
|
14
|
-
NodeMouseHandler,
|
|
15
|
-
EdgeMouseHandler,
|
|
16
|
-
} from '@xyflow/react';
|
|
17
|
-
import '@xyflow/react/dist/style.css';
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
Box,
|
|
21
|
-
Button,
|
|
22
|
-
TextField,
|
|
23
|
-
Typography,
|
|
24
|
-
IconButton,
|
|
25
|
-
Paper,
|
|
26
|
-
} from '@mui/material';
|
|
27
|
-
import DownloadIcon from '@mui/icons-material/Download';
|
|
28
|
-
import UploadIcon from '@mui/icons-material/Upload';
|
|
29
|
-
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
|
30
|
-
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
|
31
|
-
import CloseIcon from '@mui/icons-material/Close';
|
|
32
|
-
import UndoIcon from '@mui/icons-material/Undo';
|
|
33
|
-
import RedoIcon from '@mui/icons-material/Redo';
|
|
34
|
-
import { useTranslation } from 'react-i18next';
|
|
35
|
-
import {
|
|
36
|
-
SequenceDiagramProps,
|
|
37
|
-
ThemeData,
|
|
38
|
-
SequenceNode,
|
|
39
|
-
SequenceEdge,
|
|
40
|
-
SequenceHistoryEntry,
|
|
41
|
-
SequenceEntityShape,
|
|
42
|
-
CombinedFragmentType,
|
|
43
|
-
SequenceMessageType,
|
|
44
|
-
SequenceEntityNode,
|
|
45
|
-
SequenceEntityData,
|
|
46
|
-
FragmentSettingsUpdateData,
|
|
47
|
-
EntitySettingsUpdateData,
|
|
48
|
-
ActivationBarNode,
|
|
49
|
-
CombinedFragmentNode,
|
|
50
|
-
SequenceMessageData,
|
|
51
|
-
ActivationBarData,
|
|
52
|
-
CombinedFragmentData
|
|
53
|
-
} from '../../../types/sequence-types';
|
|
54
|
-
import { getTemplateData } from '../data/template-data';
|
|
55
|
-
import { sequenceEdgeTypes, sequenceNodeTypes } from './SequenceComponents';
|
|
56
|
-
import DiagramControls from '../controls/DiagramControls';
|
|
57
|
-
import FragmentSettings from './FragmentSettings';
|
|
58
|
-
import EntitySettings from './EntitySettings';
|
|
59
|
-
import AddMessageDialog from './AddMessageDialog';
|
|
60
|
-
import Sidebar from '../../../components/SideBar';
|
|
61
|
-
import AiButton from '../../../components/AiButton';
|
|
62
|
-
import MiniMapCommon from '../../../components/MiniMapCommon';
|
|
63
|
-
import DiagramPanel from '../../../components/DiagramPanel';
|
|
64
|
-
|
|
65
|
-
// Types for browser fullscreen API extensions
|
|
66
|
-
interface HTMLElementWithFullscreen extends HTMLElement {
|
|
67
|
-
webkitRequestFullscreen?: () => Promise<void>;
|
|
68
|
-
msRequestFullscreen?: () => Promise<void>;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
interface DocumentWithFullscreen extends Document {
|
|
72
|
-
webkitExitFullscreen?: () => Promise<void>;
|
|
73
|
-
msExitFullscreen?: () => Promise<void>;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Main component that manages the diagram state
|
|
77
|
-
|
|
78
|
-
const SequenceDiagramFlow: React.FC<SequenceDiagramProps> = ({
|
|
79
|
-
sequenceNodes,
|
|
80
|
-
sequenceEdges,
|
|
81
|
-
}) => {
|
|
82
|
-
const { t } = useTranslation();
|
|
83
|
-
|
|
84
|
-
// Initialize nodes and edges with our data - use xy/flow base types with proper generics
|
|
85
|
-
const [nodes, setNodes, onNodesChange] = useNodesState(sequenceNodes);
|
|
86
|
-
|
|
87
|
-
const [edges, setEdges, onEdgesChange] = useEdgesState(sequenceEdges);
|
|
88
|
-
|
|
89
|
-
// History state for undo/redo functionality
|
|
90
|
-
const [history, setHistory] = useState<SequenceHistoryEntry[]>([]);
|
|
91
|
-
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
|
92
|
-
const isUndoRedoAction = useRef(false);
|
|
93
|
-
|
|
94
|
-
// Initialize history with initial state
|
|
95
|
-
useEffect(() => {
|
|
96
|
-
// Only initialize if history is empty
|
|
97
|
-
if (history.length === 0) {
|
|
98
|
-
const initialState: SequenceHistoryEntry = {
|
|
99
|
-
nodes: [...nodes] as SequenceNode[],
|
|
100
|
-
edges: [...edges] as SequenceEdge[],
|
|
101
|
-
};
|
|
102
|
-
setHistory([initialState]);
|
|
103
|
-
setHistoryIndex(0);
|
|
104
|
-
}
|
|
105
|
-
}, []);
|
|
106
|
-
|
|
107
|
-
// Save current state to history when nodes or edges change
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
// Don't record history when performing undo/redo actions or when we're still initializing
|
|
110
|
-
if (isUndoRedoAction.current || history.length === 0) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Debounce: Only update history if nodes/edges actually changed (deep compare)
|
|
115
|
-
const newHistoryEntry: SequenceHistoryEntry = {
|
|
116
|
-
nodes: [...nodes] as SequenceNode[],
|
|
117
|
-
edges: [...edges] as SequenceEdge[],
|
|
118
|
-
};
|
|
119
|
-
const currentState = history[historyIndex];
|
|
120
|
-
if (
|
|
121
|
-
JSON.stringify(currentState.nodes) ===
|
|
122
|
-
JSON.stringify(newHistoryEntry.nodes) &&
|
|
123
|
-
JSON.stringify(currentState.edges) ===
|
|
124
|
-
JSON.stringify(newHistoryEntry.edges)
|
|
125
|
-
) {
|
|
126
|
-
return; // No change, don't add to history
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// Trim future history if we're in the middle of the history stack
|
|
130
|
-
const newHistory = history.slice(0, historyIndex + 1);
|
|
131
|
-
newHistory.push(newHistoryEntry);
|
|
132
|
-
setHistory(newHistory);
|
|
133
|
-
setHistoryIndex(newHistory.length - 1);
|
|
134
|
-
}, [nodes, edges, history, historyIndex]);
|
|
135
|
-
|
|
136
|
-
// Function to perform undo operation
|
|
137
|
-
const handleUndo = useCallback(() => {
|
|
138
|
-
if (historyIndex > 0) {
|
|
139
|
-
isUndoRedoAction.current = true;
|
|
140
|
-
const prevState = history[historyIndex - 1];
|
|
141
|
-
setNodes(prevState.nodes);
|
|
142
|
-
setEdges(prevState.edges);
|
|
143
|
-
setHistoryIndex(historyIndex - 1);
|
|
144
|
-
setTimeout(() => {
|
|
145
|
-
isUndoRedoAction.current = false;
|
|
146
|
-
}, 0);
|
|
147
|
-
}
|
|
148
|
-
}, [history, historyIndex, setNodes, setEdges]);
|
|
149
|
-
|
|
150
|
-
// Function to perform redo operation
|
|
151
|
-
const handleRedo = useCallback(() => {
|
|
152
|
-
if (historyIndex < history.length - 1) {
|
|
153
|
-
isUndoRedoAction.current = true;
|
|
154
|
-
const nextState = history[historyIndex + 1];
|
|
155
|
-
setNodes(nextState.nodes);
|
|
156
|
-
setEdges(nextState.edges);
|
|
157
|
-
setHistoryIndex(historyIndex + 1);
|
|
158
|
-
setTimeout(() => {
|
|
159
|
-
isUndoRedoAction.current = false;
|
|
160
|
-
}, 0);
|
|
161
|
-
}
|
|
162
|
-
}, [history, historyIndex, setNodes, setEdges]);
|
|
163
|
-
|
|
164
|
-
// Listen for keyboard shortcuts
|
|
165
|
-
useEffect(() => {
|
|
166
|
-
const handleKeyDown = (e: KeyboardEvent) => {
|
|
167
|
-
// Check for Ctrl+Z or Cmd+Z (undo)
|
|
168
|
-
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
|
169
|
-
e.preventDefault();
|
|
170
|
-
handleUndo();
|
|
171
|
-
}
|
|
172
|
-
// Check for Ctrl+Y or Cmd+Shift+Z (redo)
|
|
173
|
-
if (
|
|
174
|
-
((e.ctrlKey || e.metaKey) && e.key === 'y') ||
|
|
175
|
-
((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')
|
|
176
|
-
) {
|
|
177
|
-
e.preventDefault();
|
|
178
|
-
handleRedo();
|
|
179
|
-
}
|
|
180
|
-
};
|
|
181
|
-
|
|
182
|
-
window.addEventListener('keydown', handleKeyDown);
|
|
183
|
-
return () => {
|
|
184
|
-
window.removeEventListener('keydown', handleKeyDown);
|
|
185
|
-
};
|
|
186
|
-
}, [handleUndo, handleRedo]);
|
|
187
|
-
|
|
188
|
-
const [theme, setTheme] = useState<ThemeData>({
|
|
189
|
-
primary: '#61dafb',
|
|
190
|
-
secondary: '#2a2a2a',
|
|
191
|
-
});
|
|
192
|
-
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
|
193
|
-
const [selectedFragmentId, setSelectedFragmentId] = useState<string | null>(
|
|
194
|
-
null,
|
|
195
|
-
);
|
|
196
|
-
const [fragmentSettings, setFragmentSettings] = useState({
|
|
197
|
-
width: 400,
|
|
198
|
-
height: 150,
|
|
199
|
-
});
|
|
200
|
-
const [showFragmentSettings, setShowFragmentSettings] =
|
|
201
|
-
useState<boolean>(false);
|
|
202
|
-
const diagramContainerRef = useRef<HTMLDivElement>(null);
|
|
203
|
-
const [editingNode, setEditingNode] = useState<string | null>(null);
|
|
204
|
-
const [editingEdge, setEditingEdge] = useState<string | null>(null);
|
|
205
|
-
const [editingLabel, setEditingLabel] = useState<string>('');
|
|
206
|
-
const [editingConditions, setEditingConditions] = useState<string[]>([]);
|
|
207
|
-
const [editingType, setEditingType] = useState<'node' | 'edge' | 'fragment'>(
|
|
208
|
-
'node',
|
|
209
|
-
);
|
|
210
|
-
const [selectedEntityId, setSelectedEntityId] = useState<string | null>(null);
|
|
211
|
-
const [showEntitySettings, setShowEntitySettings] = useState<boolean>(false);
|
|
212
|
-
const [showMessageDialog, setShowMessageDialog] = useState<boolean>(false);
|
|
213
|
-
const ENTITY_FIXED_Y = 50; // Fixed y position for all entities
|
|
214
|
-
|
|
215
|
-
// Type-safe node access helpers
|
|
216
|
-
const getSequenceEntityNodes = useCallback(() => {
|
|
217
|
-
return nodes.filter((node): node is Node<SequenceEntityData> =>
|
|
218
|
-
node.type === 'sequenceEntity'
|
|
219
|
-
);
|
|
220
|
-
}, [nodes]);
|
|
221
|
-
|
|
222
|
-
const getCombinedFragmentNodes = useCallback(() => {
|
|
223
|
-
return nodes.filter((node): node is Node<CombinedFragmentData> =>
|
|
224
|
-
node.type === 'combinedFragment'
|
|
225
|
-
);
|
|
226
|
-
}, [nodes]);
|
|
227
|
-
|
|
228
|
-
// Intercept node position changes to lock y for sequenceEntity nodes
|
|
229
|
-
const onNodesChangeLockedY = useCallback(
|
|
230
|
-
(changes: NodeChange[]) => {
|
|
231
|
-
let hasPositionChange = false;
|
|
232
|
-
const updatedNodes = nodes.map((node) => {
|
|
233
|
-
// Find if there's a position change for this node
|
|
234
|
-
const change = changes.find(
|
|
235
|
-
(c: NodeChange) => c.type === 'position' && c.id === node.id,
|
|
236
|
-
);
|
|
237
|
-
if (
|
|
238
|
-
change &&
|
|
239
|
-
change.type === 'position' &&
|
|
240
|
-
node.type === 'sequenceEntity' &&
|
|
241
|
-
change.position
|
|
242
|
-
) {
|
|
243
|
-
hasPositionChange = true;
|
|
244
|
-
// Lock y position
|
|
245
|
-
return {
|
|
246
|
-
...node,
|
|
247
|
-
position: {
|
|
248
|
-
x: change.position.x,
|
|
249
|
-
y: ENTITY_FIXED_Y, // always fixed
|
|
250
|
-
},
|
|
251
|
-
};
|
|
252
|
-
}
|
|
253
|
-
return node;
|
|
254
|
-
});
|
|
255
|
-
if (hasPositionChange) {
|
|
256
|
-
setNodes(updatedNodes);
|
|
257
|
-
} else {
|
|
258
|
-
onNodesChange(changes);
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
[nodes, setNodes, onNodesChange],
|
|
262
|
-
);
|
|
263
|
-
|
|
264
|
-
// Handler to add a new entity
|
|
265
|
-
const handleAddEntity = useCallback(
|
|
266
|
-
(shape: SequenceEntityShape = 'rectangle') => {
|
|
267
|
-
console.log('Adding new entity with shape:', shape);
|
|
268
|
-
console.log('Current nodes:', nodes);
|
|
269
|
-
|
|
270
|
-
// Find all entity nodes
|
|
271
|
-
const entityNodes = getSequenceEntityNodes();
|
|
272
|
-
const entityCount = entityNodes.length;
|
|
273
|
-
const newEntityId = `entity-${entityCount + 1}`;
|
|
274
|
-
|
|
275
|
-
// Find the rightmost x position among entities
|
|
276
|
-
const rightmostX =
|
|
277
|
-
entityNodes.length > 0
|
|
278
|
-
? Math.max(...entityNodes.map((node) => node.position.x))
|
|
279
|
-
: 100; // Default starting x
|
|
280
|
-
|
|
281
|
-
const xOffset = 150; // Space between entities
|
|
282
|
-
const xPosition =
|
|
283
|
-
entityNodes.length > 0 ? rightmostX + xOffset : rightmostX;
|
|
284
|
-
|
|
285
|
-
// Create a new entity node with extended properties
|
|
286
|
-
const newEntity: Node<SequenceEntityData> = {
|
|
287
|
-
id: newEntityId,
|
|
288
|
-
type: 'sequenceEntity',
|
|
289
|
-
position: { x: xPosition, y: 50 },
|
|
290
|
-
data: {
|
|
291
|
-
label: `Entity ${entityCount + 1}`,
|
|
292
|
-
color: theme.primary,
|
|
293
|
-
backgroundColor: theme.secondary,
|
|
294
|
-
lifelineLength: 500, // Use same height as others
|
|
295
|
-
shape: shape, // Use the provided shape
|
|
296
|
-
fontSize: 12,
|
|
297
|
-
borderWidth: 2,
|
|
298
|
-
description: '',
|
|
299
|
-
collapsed: false,
|
|
300
|
-
},
|
|
301
|
-
draggable: true,
|
|
302
|
-
zIndex: 5, // Higher z-index to appear above other elements
|
|
303
|
-
};
|
|
304
|
-
|
|
305
|
-
// Adjust position based on entity shape
|
|
306
|
-
let offsetX = 55;
|
|
307
|
-
switch (shape) {
|
|
308
|
-
case 'circle':
|
|
309
|
-
offsetX = 40;
|
|
310
|
-
break;
|
|
311
|
-
case 'database':
|
|
312
|
-
offsetX = 45;
|
|
313
|
-
break;
|
|
314
|
-
case 'actor':
|
|
315
|
-
offsetX = 60;
|
|
316
|
-
break;
|
|
317
|
-
default:
|
|
318
|
-
offsetX = 55;
|
|
319
|
-
break;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
const activationBar: Node<ActivationBarData> = {
|
|
323
|
-
id: `${newEntityId}-activation`,
|
|
324
|
-
type: 'activationBar',
|
|
325
|
-
position: { x: xPosition + offsetX, y: 180 }, // Position below the entity
|
|
326
|
-
data: {
|
|
327
|
-
entityId: newEntityId,
|
|
328
|
-
color: theme.secondary,
|
|
329
|
-
borderColor: theme.primary,
|
|
330
|
-
length: 150,
|
|
331
|
-
},
|
|
332
|
-
draggable: true,
|
|
333
|
-
zIndex: 1, // Lower z-index to appear behind entity boxes but above combined fragments
|
|
334
|
-
};
|
|
335
|
-
|
|
336
|
-
setNodes((nds) => [...nds, newEntity, activationBar]);
|
|
337
|
-
},
|
|
338
|
-
[nodes, setNodes, theme, getSequenceEntityNodes],
|
|
339
|
-
);
|
|
340
|
-
|
|
341
|
-
// Filter entities for Add Message Dialog
|
|
342
|
-
const sequenceEntities = useCallback((): SequenceEntityNode[] => {
|
|
343
|
-
return getSequenceEntityNodes().map((node) => ({
|
|
344
|
-
id: node.id,
|
|
345
|
-
type: 'sequenceEntity',
|
|
346
|
-
data: node.data,
|
|
347
|
-
position: node.position,
|
|
348
|
-
draggable: node.draggable,
|
|
349
|
-
zIndex: node.zIndex,
|
|
350
|
-
}));
|
|
351
|
-
}, [getSequenceEntityNodes]);
|
|
352
|
-
|
|
353
|
-
// Handler to open add message dialog
|
|
354
|
-
const handleOpenAddMessageDialog = useCallback(() => {
|
|
355
|
-
// Filter to get only entity nodes
|
|
356
|
-
const entityNodes = getSequenceEntityNodes();
|
|
357
|
-
|
|
358
|
-
if (entityNodes.length < 1) {
|
|
359
|
-
alert(t('sequenceDiagram.errors.needEntitiesForMessage'));
|
|
360
|
-
return;
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
setShowMessageDialog(true);
|
|
364
|
-
}, [getSequenceEntityNodes]);
|
|
365
|
-
|
|
366
|
-
// Handler to create a new message with specified source, target, and type
|
|
367
|
-
const handleAddCustomMessage = useCallback(
|
|
368
|
-
(
|
|
369
|
-
sourceId: string,
|
|
370
|
-
targetId: string,
|
|
371
|
-
messageType: SequenceMessageType,
|
|
372
|
-
label: string,
|
|
373
|
-
) => {
|
|
374
|
-
// Find the source and target nodes
|
|
375
|
-
const sourceNode = nodes.find((n) => n.id === sourceId);
|
|
376
|
-
const targetNode = nodes.find((n) => n.id === targetId);
|
|
377
|
-
|
|
378
|
-
if (!sourceNode || !targetNode) {
|
|
379
|
-
console.error(t('sequenceDiagram.errors.sourceTargetNotFound'));
|
|
380
|
-
return;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Calculate a reasonable Y position for the new message
|
|
384
|
-
const messageY =
|
|
385
|
-
Math.max(
|
|
386
|
-
...edges.map((edge) => (edge.data as SequenceMessageData)?.y || 0),
|
|
387
|
-
180, // Default minimum Y position
|
|
388
|
-
) + 60; // Add some spacing
|
|
389
|
-
|
|
390
|
-
// Calculate handle positions based on nodes
|
|
391
|
-
const sourceX = sourceNode.position?.x || 0;
|
|
392
|
-
const targetX = targetNode.position?.x || 0;
|
|
393
|
-
|
|
394
|
-
// Calculate source and target handles
|
|
395
|
-
let sourceHandle = 'right';
|
|
396
|
-
let targetHandle = 'left';
|
|
397
|
-
|
|
398
|
-
if (messageType === 'self') {
|
|
399
|
-
// For self messages, always use the right side
|
|
400
|
-
sourceHandle = 'right';
|
|
401
|
-
targetHandle = 'right';
|
|
402
|
-
} else if (sourceX > targetX) {
|
|
403
|
-
// If source is to the right of target (server-to-client)
|
|
404
|
-
sourceHandle = 'left';
|
|
405
|
-
targetHandle = 'right';
|
|
406
|
-
} else {
|
|
407
|
-
// If source is to the left of target (client-to-server)
|
|
408
|
-
sourceHandle = 'right';
|
|
409
|
-
targetHandle = 'left';
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Create a message from the source to the target entity
|
|
413
|
-
const newEdge: Edge<SequenceMessageData> = {
|
|
414
|
-
id: `seq-edge-${edges.length + 1}`,
|
|
415
|
-
source: sourceId,
|
|
416
|
-
target: targetId,
|
|
417
|
-
type: 'sequenceMessage',
|
|
418
|
-
sourceHandle: sourceHandle,
|
|
419
|
-
targetHandle: targetHandle,
|
|
420
|
-
data: {
|
|
421
|
-
label: label,
|
|
422
|
-
type: messageType,
|
|
423
|
-
y: messageY,
|
|
424
|
-
},
|
|
425
|
-
animated: messageType === 'async',
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
setEdges((eds) => [...eds, newEdge]);
|
|
429
|
-
|
|
430
|
-
setNodes((nds) => {
|
|
431
|
-
return nds.map((node) => {
|
|
432
|
-
if (
|
|
433
|
-
node.type === 'sequenceEntity' &&
|
|
434
|
-
(node.id === sourceId || node.id === targetId)
|
|
435
|
-
) {
|
|
436
|
-
const nodeData = node.data as SequenceEntityData;
|
|
437
|
-
// Find all messages (edges) connected to this node, including the new one
|
|
438
|
-
const connectedMessages = [...edges, newEdge].filter(
|
|
439
|
-
(edge) => edge.source === node.id || edge.target === node.id,
|
|
440
|
-
);
|
|
441
|
-
// Find the max Y position among these messages
|
|
442
|
-
const maxY = Math.max(
|
|
443
|
-
...connectedMessages.map((edge) => (edge.data as SequenceMessageData)?.y || 0),
|
|
444
|
-
nodeData.lifelineLength || 0, // at least current length
|
|
445
|
-
);
|
|
446
|
-
const newLength = Math.max(maxY + 40, nodeData.lifelineLength || 0);
|
|
447
|
-
// Only update if needed
|
|
448
|
-
if (newLength > (nodeData.lifelineLength || 0)) {
|
|
449
|
-
return {
|
|
450
|
-
...node,
|
|
451
|
-
data: {
|
|
452
|
-
...nodeData,
|
|
453
|
-
lifelineLength: newLength,
|
|
454
|
-
},
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
return node;
|
|
459
|
-
});
|
|
460
|
-
});
|
|
461
|
-
},
|
|
462
|
-
[nodes, edges, setEdges, setNodes],
|
|
463
|
-
);
|
|
464
|
-
|
|
465
|
-
// Handler for the original add message button (now opens the dialog)
|
|
466
|
-
const handleAddMessage = useCallback(() => {
|
|
467
|
-
handleOpenAddMessageDialog();
|
|
468
|
-
}, [handleOpenAddMessageDialog]);
|
|
469
|
-
|
|
470
|
-
// Function to export diagram as JSON
|
|
471
|
-
const exportDiagram = useCallback(() => {
|
|
472
|
-
const diagramData = {
|
|
473
|
-
nodes: nodes,
|
|
474
|
-
edges: edges,
|
|
475
|
-
};
|
|
476
|
-
|
|
477
|
-
const dataStr = JSON.stringify(diagramData, null, 2);
|
|
478
|
-
const dataUri =
|
|
479
|
-
'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
|
|
480
|
-
|
|
481
|
-
const exportFileDefaultName = t('sequenceDiagram.export.filename');
|
|
482
|
-
|
|
483
|
-
const linkElement = document.createElement('a');
|
|
484
|
-
linkElement.setAttribute('href', dataUri);
|
|
485
|
-
linkElement.setAttribute('download', exportFileDefaultName);
|
|
486
|
-
linkElement.click();
|
|
487
|
-
}, [nodes, edges]);
|
|
488
|
-
|
|
489
|
-
// Function to import diagram from JSON
|
|
490
|
-
const importDiagram = useCallback(
|
|
491
|
-
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
492
|
-
if (!event.target.files || event.target.files.length === 0) return;
|
|
493
|
-
|
|
494
|
-
const fileReader = new FileReader();
|
|
495
|
-
fileReader.readAsText(event.target.files[0], 'UTF-8');
|
|
496
|
-
fileReader.onload = (e) => {
|
|
497
|
-
try {
|
|
498
|
-
if (!e.target || typeof e.target.result !== 'string') return;
|
|
499
|
-
|
|
500
|
-
const imported = JSON.parse(e.target.result);
|
|
501
|
-
if (imported.nodes && imported.edges) {
|
|
502
|
-
setNodes(imported.nodes);
|
|
503
|
-
setEdges(imported.edges);
|
|
504
|
-
}
|
|
505
|
-
} catch (error) {
|
|
506
|
-
console.error('Error importing diagram:', error);
|
|
507
|
-
alert(t('sequenceDiagram.errors.importFailed'));
|
|
508
|
-
}
|
|
509
|
-
};
|
|
510
|
-
},
|
|
511
|
-
[setNodes, setEdges],
|
|
512
|
-
);
|
|
513
|
-
|
|
514
|
-
// Function to handle theme changes
|
|
515
|
-
const handleThemeChange = useCallback(
|
|
516
|
-
(newTheme: ThemeData) => {
|
|
517
|
-
setTheme(newTheme);
|
|
518
|
-
|
|
519
|
-
// Update nodes with new theme colors
|
|
520
|
-
setNodes((nds) =>
|
|
521
|
-
nds.map((node) => {
|
|
522
|
-
if (node.type === 'sequenceEntity') {
|
|
523
|
-
const nodeData = node.data;
|
|
524
|
-
return {
|
|
525
|
-
...node,
|
|
526
|
-
data: {
|
|
527
|
-
...nodeData,
|
|
528
|
-
color: newTheme.primary,
|
|
529
|
-
backgroundColor: newTheme.secondary,
|
|
530
|
-
},
|
|
531
|
-
};
|
|
532
|
-
} else if (node.type === 'activationBar') {
|
|
533
|
-
const nodeData = node.data;
|
|
534
|
-
return {
|
|
535
|
-
...node,
|
|
536
|
-
data: {
|
|
537
|
-
...nodeData,
|
|
538
|
-
color: newTheme.secondary,
|
|
539
|
-
borderColor: newTheme.primary,
|
|
540
|
-
},
|
|
541
|
-
};
|
|
542
|
-
}
|
|
543
|
-
return node;
|
|
544
|
-
}),
|
|
545
|
-
);
|
|
546
|
-
},
|
|
547
|
-
[setNodes],
|
|
548
|
-
);
|
|
549
|
-
|
|
550
|
-
// Toggle fullscreen mode
|
|
551
|
-
const toggleFullscreen = useCallback(() => {
|
|
552
|
-
if (!diagramContainerRef.current) return;
|
|
553
|
-
|
|
554
|
-
const element = diagramContainerRef.current as HTMLElementWithFullscreen;
|
|
555
|
-
const doc = document as DocumentWithFullscreen;
|
|
556
|
-
|
|
557
|
-
if (!isFullscreen) {
|
|
558
|
-
if (element.requestFullscreen) {
|
|
559
|
-
element.requestFullscreen();
|
|
560
|
-
} else if (element.webkitRequestFullscreen) {
|
|
561
|
-
element.webkitRequestFullscreen();
|
|
562
|
-
} else if (element.msRequestFullscreen) {
|
|
563
|
-
element.msRequestFullscreen();
|
|
564
|
-
}
|
|
565
|
-
} else {
|
|
566
|
-
if (doc.exitFullscreen) {
|
|
567
|
-
doc.exitFullscreen();
|
|
568
|
-
} else if (doc.webkitExitFullscreen) {
|
|
569
|
-
doc.webkitExitFullscreen();
|
|
570
|
-
} else if (doc.msExitFullscreen) {
|
|
571
|
-
doc.msExitFullscreen();
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
|
|
575
|
-
setIsFullscreen(!isFullscreen);
|
|
576
|
-
}, [isFullscreen]);
|
|
577
|
-
|
|
578
|
-
// Listen for fullscreen change events from browser
|
|
579
|
-
React.useEffect(() => {
|
|
580
|
-
const handleFullscreenChange = () => {
|
|
581
|
-
setIsFullscreen(!!document.fullscreenElement);
|
|
582
|
-
};
|
|
583
|
-
|
|
584
|
-
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
|
585
|
-
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
|
|
586
|
-
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
|
|
587
|
-
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
|
|
588
|
-
|
|
589
|
-
return () => {
|
|
590
|
-
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
|
591
|
-
document.removeEventListener(
|
|
592
|
-
'webkitfullscreenchange',
|
|
593
|
-
handleFullscreenChange,
|
|
594
|
-
);
|
|
595
|
-
document.removeEventListener(
|
|
596
|
-
'mozfullscreenchange',
|
|
597
|
-
handleFullscreenChange,
|
|
598
|
-
);
|
|
599
|
-
document.removeEventListener(
|
|
600
|
-
'MSFullscreenChange',
|
|
601
|
-
handleFullscreenChange,
|
|
602
|
-
);
|
|
603
|
-
};
|
|
604
|
-
}, []);
|
|
605
|
-
|
|
606
|
-
// Properly handle connections between nodes
|
|
607
|
-
const onConnect = useCallback(
|
|
608
|
-
(params: Connection) => {
|
|
609
|
-
console.log('Connection params:', params);
|
|
610
|
-
if (!params.source || !params.target) return;
|
|
611
|
-
|
|
612
|
-
// Find the source and target nodes
|
|
613
|
-
const sourceNode = nodes.find((n) => n.id === params.source);
|
|
614
|
-
const targetNode = nodes.find((n) => n.id === params.target);
|
|
615
|
-
|
|
616
|
-
if (!sourceNode || !targetNode) return;
|
|
617
|
-
|
|
618
|
-
// Only allow connections between sequence entities
|
|
619
|
-
if (
|
|
620
|
-
sourceNode.type !== 'sequenceEntity' ||
|
|
621
|
-
targetNode.type !== 'sequenceEntity'
|
|
622
|
-
) {
|
|
623
|
-
alert(t('sequenceDiagram.errors.onlyConnectEntities'));
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
// Calculate a Y position for the new message
|
|
628
|
-
const messageY =
|
|
629
|
-
Math.max(
|
|
630
|
-
...edges.map((edge) => (edge.data as SequenceMessageData)?.y || 0),
|
|
631
|
-
180, // Default minimum Y position
|
|
632
|
-
) + 60; // Add some spacing
|
|
633
|
-
|
|
634
|
-
// Calculate handle positions based on nodes
|
|
635
|
-
const sourceX = sourceNode.position?.x || 0;
|
|
636
|
-
const targetX = targetNode.position?.x || 0;
|
|
637
|
-
|
|
638
|
-
// Determine correct handles based on relative positions
|
|
639
|
-
let sourceHandle = params.sourceHandle;
|
|
640
|
-
let targetHandle = params.targetHandle;
|
|
641
|
-
|
|
642
|
-
// If handles aren't explicitly provided by the connection event,
|
|
643
|
-
// calculate them based on node positions
|
|
644
|
-
if (!sourceHandle || !targetHandle) {
|
|
645
|
-
if (sourceX > targetX) {
|
|
646
|
-
// Source is to the right of target (server-to-client)
|
|
647
|
-
sourceHandle = 'left';
|
|
648
|
-
targetHandle = 'right';
|
|
649
|
-
} else {
|
|
650
|
-
// Source is to the left of target (client-to-server)
|
|
651
|
-
sourceHandle = 'right';
|
|
652
|
-
targetHandle = 'left';
|
|
653
|
-
}
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
// Create a new edge for the message
|
|
657
|
-
const newEdge: Edge<SequenceMessageData> = {
|
|
658
|
-
id: `seq-edge-${edges.length + 1}`,
|
|
659
|
-
source: params.source,
|
|
660
|
-
target: params.target,
|
|
661
|
-
type: 'sequenceMessage',
|
|
662
|
-
sourceHandle: sourceHandle,
|
|
663
|
-
targetHandle: targetHandle,
|
|
664
|
-
data: {
|
|
665
|
-
label: `Message ${edges.length + 1}`,
|
|
666
|
-
type: 'sync', // Default to sync message
|
|
667
|
-
y: messageY,
|
|
668
|
-
},
|
|
669
|
-
animated: false,
|
|
670
|
-
};
|
|
671
|
-
|
|
672
|
-
setEdges((eds) => [...eds, newEdge]);
|
|
673
|
-
|
|
674
|
-
setNodes((nds) => {
|
|
675
|
-
return nds.map((node) => {
|
|
676
|
-
if (
|
|
677
|
-
node.type === 'sequenceEntity' &&
|
|
678
|
-
(node.id === params.source || node.id === params.target)
|
|
679
|
-
) {
|
|
680
|
-
const nodeData = node.data as SequenceEntityData;
|
|
681
|
-
// Find all messages (edges) connected to this node, including the new one
|
|
682
|
-
const connectedMessages = [...edges, newEdge].filter(
|
|
683
|
-
(edge) => edge.source === node.id || edge.target === node.id,
|
|
684
|
-
);
|
|
685
|
-
// Find the max Y position among these messages
|
|
686
|
-
const maxY = Math.max(
|
|
687
|
-
...connectedMessages.map((edge) => (edge.data as SequenceMessageData)?.y || 0),
|
|
688
|
-
nodeData.lifelineLength || 0, // at least current length
|
|
689
|
-
);
|
|
690
|
-
const newLength = Math.max(maxY + 40, nodeData.lifelineLength || 0);
|
|
691
|
-
// Only update if needed
|
|
692
|
-
if (newLength > (nodeData.lifelineLength || 0)) {
|
|
693
|
-
return {
|
|
694
|
-
...node,
|
|
695
|
-
data: {
|
|
696
|
-
...nodeData,
|
|
697
|
-
lifelineLength: newLength,
|
|
698
|
-
},
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
return node;
|
|
703
|
-
});
|
|
704
|
-
});
|
|
705
|
-
},
|
|
706
|
-
[nodes, edges, setEdges, setNodes],
|
|
707
|
-
);
|
|
708
|
-
|
|
709
|
-
// Handle node click events - use ReactFlow's NodeMouseHandler type
|
|
710
|
-
const onNodeClick: NodeMouseHandler = useCallback(
|
|
711
|
-
(event, node) => {
|
|
712
|
-
// If the node is a combined fragment, select it for editing
|
|
713
|
-
if (node.type === 'combinedFragment') {
|
|
714
|
-
setSelectedFragmentId(node.id);
|
|
715
|
-
|
|
716
|
-
// Set current fragment dimensions
|
|
717
|
-
const width = node.style?.width || 400;
|
|
718
|
-
const height = node.style?.height || 150;
|
|
719
|
-
setFragmentSettings({ width: Number(width), height: Number(height) });
|
|
720
|
-
|
|
721
|
-
// Show the settings panel
|
|
722
|
-
setShowFragmentSettings(true);
|
|
723
|
-
setShowEntitySettings(false);
|
|
724
|
-
}
|
|
725
|
-
// If the node is an entity, select it for settings
|
|
726
|
-
else if (node.type === 'sequenceEntity') {
|
|
727
|
-
setSelectedEntityId(node.id);
|
|
728
|
-
setShowEntitySettings(true);
|
|
729
|
-
setShowFragmentSettings(false);
|
|
730
|
-
} else {
|
|
731
|
-
// If clicking on a non-fragment node while settings are open, close them
|
|
732
|
-
if (showFragmentSettings) {
|
|
733
|
-
setShowFragmentSettings(false);
|
|
734
|
-
setSelectedFragmentId(null);
|
|
735
|
-
}
|
|
736
|
-
if (showEntitySettings) {
|
|
737
|
-
setShowEntitySettings(false);
|
|
738
|
-
setSelectedEntityId(null);
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
},
|
|
742
|
-
[showFragmentSettings, showEntitySettings],
|
|
743
|
-
);
|
|
744
|
-
|
|
745
|
-
// Handle node double-click for label editing - use ReactFlow's NodeMouseHandler type
|
|
746
|
-
const onNodeDoubleClick: NodeMouseHandler = useCallback(
|
|
747
|
-
(event, node) => {
|
|
748
|
-
event.preventDefault();
|
|
749
|
-
event.stopPropagation();
|
|
750
|
-
|
|
751
|
-
if (node.type === 'sequenceEntity') {
|
|
752
|
-
const nodeData = node.data as SequenceEntityData;
|
|
753
|
-
setEditingNode(node.id);
|
|
754
|
-
setEditingEdge(null);
|
|
755
|
-
setEditingLabel(nodeData.label || '');
|
|
756
|
-
setEditingType('node');
|
|
757
|
-
} else if (node.type === 'combinedFragment') {
|
|
758
|
-
const nodeData = node.data as CombinedFragmentData;
|
|
759
|
-
setEditingNode(node.id);
|
|
760
|
-
setEditingEdge(null);
|
|
761
|
-
setEditingLabel(nodeData.label || '');
|
|
762
|
-
setEditingType('fragment');
|
|
763
|
-
setEditingConditions(nodeData.conditions || []);
|
|
764
|
-
}
|
|
765
|
-
},
|
|
766
|
-
[],
|
|
767
|
-
);
|
|
768
|
-
|
|
769
|
-
// Handle edge double-click for label editing - use ReactFlow's EdgeMouseHandler type
|
|
770
|
-
const onEdgeDoubleClick: EdgeMouseHandler = useCallback(
|
|
771
|
-
(event, edge) => {
|
|
772
|
-
event.preventDefault();
|
|
773
|
-
event.stopPropagation();
|
|
774
|
-
|
|
775
|
-
if (edge.type === 'sequenceMessage') {
|
|
776
|
-
const edgeData = edge.data as SequenceMessageData;
|
|
777
|
-
setEditingEdge(edge.id);
|
|
778
|
-
setEditingNode(null);
|
|
779
|
-
setEditingLabel(edgeData?.label || '');
|
|
780
|
-
setEditingType('edge');
|
|
781
|
-
}
|
|
782
|
-
},
|
|
783
|
-
[],
|
|
784
|
-
);
|
|
785
|
-
|
|
786
|
-
// Handle label input change
|
|
787
|
-
const handleLabelChange = useCallback(
|
|
788
|
-
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
789
|
-
setEditingLabel(e.target.value);
|
|
790
|
-
},
|
|
791
|
-
[],
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
// Handle condition change
|
|
795
|
-
const handleConditionChange = (idx: number, value: string) => {
|
|
796
|
-
const updated = [...editingConditions];
|
|
797
|
-
updated[idx] = value;
|
|
798
|
-
setEditingConditions(updated);
|
|
799
|
-
};
|
|
800
|
-
|
|
801
|
-
// Handle add condition
|
|
802
|
-
const handleAddCondition = () => {
|
|
803
|
-
setEditingConditions([...editingConditions, '']);
|
|
804
|
-
};
|
|
805
|
-
|
|
806
|
-
// Handle remove condition
|
|
807
|
-
const handleRemoveCondition = (idx: number) => {
|
|
808
|
-
setEditingConditions(editingConditions.filter((_, i) => i !== idx));
|
|
809
|
-
};
|
|
810
|
-
|
|
811
|
-
// Save the edited label
|
|
812
|
-
const saveLabel = useCallback(() => {
|
|
813
|
-
if (editingNode && editingType === 'node') {
|
|
814
|
-
setNodes((nds) =>
|
|
815
|
-
nds.map((node) => {
|
|
816
|
-
if (node.id === editingNode) {
|
|
817
|
-
return {
|
|
818
|
-
...node,
|
|
819
|
-
data: {
|
|
820
|
-
...node.data,
|
|
821
|
-
label: editingLabel,
|
|
822
|
-
},
|
|
823
|
-
};
|
|
824
|
-
}
|
|
825
|
-
return node;
|
|
826
|
-
}),
|
|
827
|
-
);
|
|
828
|
-
setEditingNode(null);
|
|
829
|
-
} else if (editingNode && editingType === 'fragment') {
|
|
830
|
-
setNodes((nds) =>
|
|
831
|
-
nds.map((node) => {
|
|
832
|
-
if (node.id === editingNode) {
|
|
833
|
-
return {
|
|
834
|
-
...node,
|
|
835
|
-
data: {
|
|
836
|
-
...node.data,
|
|
837
|
-
label: editingLabel,
|
|
838
|
-
conditions: editingConditions,
|
|
839
|
-
},
|
|
840
|
-
};
|
|
841
|
-
}
|
|
842
|
-
return node;
|
|
843
|
-
}),
|
|
844
|
-
);
|
|
845
|
-
setEditingNode(null);
|
|
846
|
-
setEditingType('node');
|
|
847
|
-
} else if (editingEdge && editingType === 'edge') {
|
|
848
|
-
setEdges((eds) =>
|
|
849
|
-
eds.map((edge) => {
|
|
850
|
-
if (edge.id === editingEdge) {
|
|
851
|
-
return {
|
|
852
|
-
...edge,
|
|
853
|
-
data: {
|
|
854
|
-
...edge.data,
|
|
855
|
-
label: editingLabel,
|
|
856
|
-
},
|
|
857
|
-
};
|
|
858
|
-
}
|
|
859
|
-
return edge;
|
|
860
|
-
}),
|
|
861
|
-
);
|
|
862
|
-
setEditingEdge(null);
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
// Reset editing state
|
|
866
|
-
setEditingType('node');
|
|
867
|
-
}, [
|
|
868
|
-
editingNode,
|
|
869
|
-
editingEdge,
|
|
870
|
-
editingType,
|
|
871
|
-
editingLabel,
|
|
872
|
-
editingConditions,
|
|
873
|
-
setNodes,
|
|
874
|
-
setEdges,
|
|
875
|
-
]);
|
|
876
|
-
|
|
877
|
-
// Handle keyboard events for the input field
|
|
878
|
-
const handleKeyDown = useCallback(
|
|
879
|
-
(e: React.KeyboardEvent) => {
|
|
880
|
-
if (e.key === 'Enter') {
|
|
881
|
-
saveLabel();
|
|
882
|
-
} else if (e.key === 'Escape') {
|
|
883
|
-
setEditingNode(null);
|
|
884
|
-
setEditingEdge(null);
|
|
885
|
-
}
|
|
886
|
-
},
|
|
887
|
-
[saveLabel],
|
|
888
|
-
);
|
|
889
|
-
|
|
890
|
-
// Cancel editing
|
|
891
|
-
const cancelEditing = useCallback(() => {
|
|
892
|
-
setEditingNode(null);
|
|
893
|
-
setEditingEdge(null);
|
|
894
|
-
setEditingType('node');
|
|
895
|
-
}, []);
|
|
896
|
-
|
|
897
|
-
// Handler to add a new alt fragment
|
|
898
|
-
const handleAddAltFragment = useCallback(
|
|
899
|
-
(fragmentType: CombinedFragmentType = 'alt') => {
|
|
900
|
-
console.log(`Adding new ${fragmentType} fragment...`);
|
|
901
|
-
|
|
902
|
-
// Find all fragment nodes
|
|
903
|
-
const fragmentNodes = getCombinedFragmentNodes();
|
|
904
|
-
const fragmentCount = fragmentNodes.length;
|
|
905
|
-
const newFragmentId = `${fragmentType}-fragment-${fragmentCount + 1}`;
|
|
906
|
-
|
|
907
|
-
// Find the lowest y position among fragments
|
|
908
|
-
const defaultX = 250; // Fixed x position for all fragments
|
|
909
|
-
const defaultY = 300; // Default starting y for fragments
|
|
910
|
-
|
|
911
|
-
const lowestY =
|
|
912
|
-
fragmentNodes.length > 0
|
|
913
|
-
? Math.max(...fragmentNodes.map((node) => node.position.y))
|
|
914
|
-
: defaultY;
|
|
915
|
-
|
|
916
|
-
const yOffset = 200; // Space between fragments vertically
|
|
917
|
-
const centerX = defaultX;
|
|
918
|
-
const centerY = fragmentNodes.length > 0 ? lowestY + yOffset : lowestY;
|
|
919
|
-
|
|
920
|
-
// Set up conditions based on fragment type
|
|
921
|
-
let conditions: string[] = [];
|
|
922
|
-
switch (fragmentType) {
|
|
923
|
-
case 'alt':
|
|
924
|
-
conditions = ['[condition]', '[else]'];
|
|
925
|
-
break;
|
|
926
|
-
case 'opt':
|
|
927
|
-
conditions = ['[condition]'];
|
|
928
|
-
break;
|
|
929
|
-
case 'loop':
|
|
930
|
-
conditions = ['[loop condition]'];
|
|
931
|
-
break;
|
|
932
|
-
case 'par':
|
|
933
|
-
conditions = ['[parallel 1]', '[parallel 2]'];
|
|
934
|
-
break;
|
|
935
|
-
default:
|
|
936
|
-
conditions = ['[condition]'];
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// Create a new fragment node
|
|
940
|
-
const newFragment: Node<CombinedFragmentData> = {
|
|
941
|
-
id: newFragmentId,
|
|
942
|
-
type: 'combinedFragment',
|
|
943
|
-
position: { x: centerX, y: centerY },
|
|
944
|
-
style: {
|
|
945
|
-
width: fragmentSettings.width,
|
|
946
|
-
height: fragmentSettings.height,
|
|
947
|
-
},
|
|
948
|
-
data: {
|
|
949
|
-
label: fragmentType,
|
|
950
|
-
conditions: conditions,
|
|
951
|
-
style: {
|
|
952
|
-
width: fragmentSettings.width,
|
|
953
|
-
height: fragmentSettings.height,
|
|
954
|
-
},
|
|
955
|
-
color: '#000000',
|
|
956
|
-
backgroundColor: '#ffffff',
|
|
957
|
-
lifelineLength: 0,
|
|
958
|
-
},
|
|
959
|
-
draggable: true,
|
|
960
|
-
zIndex: 0, // Lower z-index to appear behind other elements
|
|
961
|
-
};
|
|
962
|
-
|
|
963
|
-
setNodes((nds) => [...nds, newFragment]);
|
|
964
|
-
setSelectedFragmentId(newFragmentId);
|
|
965
|
-
setShowFragmentSettings(true);
|
|
966
|
-
},
|
|
967
|
-
[fragmentSettings, getCombinedFragmentNodes, setNodes],
|
|
968
|
-
);
|
|
969
|
-
|
|
970
|
-
// Function to update selected fragment
|
|
971
|
-
const updateSelectedFragment = useCallback(
|
|
972
|
-
(fragmentId: string, updates: FragmentSettingsUpdateData) => {
|
|
973
|
-
setNodes((nds) =>
|
|
974
|
-
nds.map((node) => {
|
|
975
|
-
if (node.id === fragmentId) {
|
|
976
|
-
// Safely handle the style spread by ensuring the fragment data has a style property
|
|
977
|
-
const fragmentData = node.data;
|
|
978
|
-
const currentStyle = fragmentData.style || {};
|
|
979
|
-
// Apply updates to both style and data.style to ensure consistency
|
|
980
|
-
const updatedNode = {
|
|
981
|
-
...node,
|
|
982
|
-
style: { ...node.style, ...updates },
|
|
983
|
-
data: {
|
|
984
|
-
...fragmentData,
|
|
985
|
-
style: { ...currentStyle, ...updates },
|
|
986
|
-
},
|
|
987
|
-
};
|
|
988
|
-
return updatedNode;
|
|
989
|
-
}
|
|
990
|
-
return node;
|
|
991
|
-
}),
|
|
992
|
-
);
|
|
993
|
-
},
|
|
994
|
-
[setNodes],
|
|
995
|
-
);
|
|
996
|
-
|
|
997
|
-
const handleFragmentWidthChange = useCallback(
|
|
998
|
-
(width: number) => {
|
|
999
|
-
setFragmentSettings((prev) => ({ ...prev, width }));
|
|
1000
|
-
|
|
1001
|
-
if (selectedFragmentId) {
|
|
1002
|
-
updateSelectedFragment(selectedFragmentId, { width });
|
|
1003
|
-
}
|
|
1004
|
-
},
|
|
1005
|
-
[selectedFragmentId, updateSelectedFragment],
|
|
1006
|
-
);
|
|
1007
|
-
|
|
1008
|
-
const handleFragmentHeightChange = useCallback(
|
|
1009
|
-
(height: number) => {
|
|
1010
|
-
setFragmentSettings((prev) => ({ ...prev, height }));
|
|
1011
|
-
|
|
1012
|
-
if (selectedFragmentId) {
|
|
1013
|
-
updateSelectedFragment(selectedFragmentId, { height });
|
|
1014
|
-
}
|
|
1015
|
-
},
|
|
1016
|
-
[selectedFragmentId, updateSelectedFragment],
|
|
1017
|
-
);
|
|
1018
|
-
|
|
1019
|
-
const closeFragmentSettings = useCallback(() => {
|
|
1020
|
-
setShowFragmentSettings(false);
|
|
1021
|
-
setSelectedFragmentId(null);
|
|
1022
|
-
}, []);
|
|
1023
|
-
|
|
1024
|
-
// Function to update selected entity
|
|
1025
|
-
const updateSelectedEntity = useCallback(
|
|
1026
|
-
(updates: EntitySettingsUpdateData) => {
|
|
1027
|
-
if (!selectedEntityId) return;
|
|
1028
|
-
|
|
1029
|
-
setNodes((nds) =>
|
|
1030
|
-
nds.map((node) => {
|
|
1031
|
-
if (node.id === selectedEntityId) {
|
|
1032
|
-
return {
|
|
1033
|
-
...node,
|
|
1034
|
-
data: {
|
|
1035
|
-
...node.data,
|
|
1036
|
-
...updates,
|
|
1037
|
-
},
|
|
1038
|
-
};
|
|
1039
|
-
}
|
|
1040
|
-
return node;
|
|
1041
|
-
}),
|
|
1042
|
-
);
|
|
1043
|
-
},
|
|
1044
|
-
[selectedEntityId, setNodes],
|
|
1045
|
-
);
|
|
1046
|
-
|
|
1047
|
-
// Close entity settings
|
|
1048
|
-
const closeEntitySettings = useCallback(() => {
|
|
1049
|
-
setShowEntitySettings(false);
|
|
1050
|
-
setSelectedEntityId(null);
|
|
1051
|
-
}, []);
|
|
1052
|
-
|
|
1053
|
-
// Handler to load a template diagram
|
|
1054
|
-
const loadTemplateData = useCallback(
|
|
1055
|
-
(templateType: string) => {
|
|
1056
|
-
console.log(`Loading template: ${templateType}`);
|
|
1057
|
-
|
|
1058
|
-
const template = getTemplateData(templateType);
|
|
1059
|
-
|
|
1060
|
-
if (template.nodes.length > 0) {
|
|
1061
|
-
setNodes(template.nodes);
|
|
1062
|
-
setEdges(template.edges);
|
|
1063
|
-
}
|
|
1064
|
-
},
|
|
1065
|
-
[setNodes, setEdges],
|
|
1066
|
-
);
|
|
1067
|
-
|
|
1068
|
-
return (
|
|
1069
|
-
<>
|
|
1070
|
-
<Box sx={{ display: 'flex', width: '100%', height: '100vh' }}>
|
|
1071
|
-
<Sidebar>
|
|
1072
|
-
<DiagramControls
|
|
1073
|
-
onAddEntity={handleAddEntity}
|
|
1074
|
-
onAddMessage={handleAddMessage}
|
|
1075
|
-
onAddAltFragment={handleAddAltFragment}
|
|
1076
|
-
onUndo={handleUndo}
|
|
1077
|
-
onRedo={handleRedo}
|
|
1078
|
-
canUndo={historyIndex > 0}
|
|
1079
|
-
canRedo={historyIndex < history.length - 1}
|
|
1080
|
-
onLoadTemplate={loadTemplateData}
|
|
1081
|
-
/>
|
|
1082
|
-
</Sidebar>
|
|
1083
|
-
<Box sx={{ flex: 1, height: '100%', position: 'relative' }}>
|
|
1084
|
-
<div
|
|
1085
|
-
ref={diagramContainerRef}
|
|
1086
|
-
className={`diagram-container ${isFullscreen ? 'fullscreen' : ''}`}
|
|
1087
|
-
style={{
|
|
1088
|
-
width: '100%',
|
|
1089
|
-
height: '100%',
|
|
1090
|
-
border: '1px solid #333',
|
|
1091
|
-
position: 'relative',
|
|
1092
|
-
backgroundColor: '#1a1a1a',
|
|
1093
|
-
color: '#ffffff',
|
|
1094
|
-
}}
|
|
1095
|
-
>
|
|
1096
|
-
<ReactFlow<Node, Edge>
|
|
1097
|
-
nodes={nodes}
|
|
1098
|
-
edges={edges}
|
|
1099
|
-
onNodesChange={onNodesChangeLockedY}
|
|
1100
|
-
onEdgesChange={onEdgesChange}
|
|
1101
|
-
onConnect={onConnect}
|
|
1102
|
-
nodeTypes={sequenceNodeTypes}
|
|
1103
|
-
edgeTypes={sequenceEdgeTypes}
|
|
1104
|
-
fitView
|
|
1105
|
-
attributionPosition="bottom-left"
|
|
1106
|
-
connectOnClick={false}
|
|
1107
|
-
onNodeClick={onNodeClick}
|
|
1108
|
-
onNodeDoubleClick={onNodeDoubleClick}
|
|
1109
|
-
onEdgeDoubleClick={onEdgeDoubleClick}
|
|
1110
|
-
style={{
|
|
1111
|
-
backgroundColor: '#1a1a1a',
|
|
1112
|
-
}}
|
|
1113
|
-
>
|
|
1114
|
-
<Background
|
|
1115
|
-
color="#333"
|
|
1116
|
-
variant={BackgroundVariant.Dots}
|
|
1117
|
-
gap={16}
|
|
1118
|
-
/>
|
|
1119
|
-
</ReactFlow>
|
|
1120
|
-
|
|
1121
|
-
{/* Components outside ReactFlow */}
|
|
1122
|
-
<DiagramPanel
|
|
1123
|
-
historyIndex={historyIndex}
|
|
1124
|
-
historyLength={history.length}
|
|
1125
|
-
onUndo={handleUndo}
|
|
1126
|
-
onRedo={handleRedo}
|
|
1127
|
-
onClear={() => {
|
|
1128
|
-
const clearedState: SequenceHistoryEntry = { nodes: [], edges: [] };
|
|
1129
|
-
const newHistory = history.slice(0, historyIndex + 1);
|
|
1130
|
-
newHistory.push(clearedState);
|
|
1131
|
-
setNodes([]);
|
|
1132
|
-
setEdges([]);
|
|
1133
|
-
setHistory(newHistory);
|
|
1134
|
-
setHistoryIndex(newHistory.length - 1);
|
|
1135
|
-
}}
|
|
1136
|
-
onImport={() => { }}
|
|
1137
|
-
isSettingsOpen={false}
|
|
1138
|
-
onToggleSettings={() => { }}
|
|
1139
|
-
/>
|
|
1140
|
-
|
|
1141
|
-
<AiButton />
|
|
1142
|
-
|
|
1143
|
-
<MiniMapCommon />
|
|
1144
|
-
|
|
1145
|
-
{/* Fragment settings panel */}
|
|
1146
|
-
{showFragmentSettings && (
|
|
1147
|
-
<FragmentSettings
|
|
1148
|
-
fragmentId={selectedFragmentId}
|
|
1149
|
-
width={fragmentSettings.width}
|
|
1150
|
-
height={fragmentSettings.height}
|
|
1151
|
-
onWidthChange={handleFragmentWidthChange}
|
|
1152
|
-
onHeightChange={handleFragmentHeightChange}
|
|
1153
|
-
onClose={closeFragmentSettings}
|
|
1154
|
-
/>
|
|
1155
|
-
)}
|
|
1156
|
-
|
|
1157
|
-
{/* Entity settings panel */}
|
|
1158
|
-
{showEntitySettings && selectedEntityId && (
|
|
1159
|
-
<EntitySettings
|
|
1160
|
-
entityId={selectedEntityId}
|
|
1161
|
-
data={
|
|
1162
|
-
(nodes.find((node) =>
|
|
1163
|
-
node.id === selectedEntityId && node.type === 'sequenceEntity'
|
|
1164
|
-
)?.data as SequenceEntityData) || {
|
|
1165
|
-
label: '',
|
|
1166
|
-
color: '#61dafb',
|
|
1167
|
-
backgroundColor: '#2a2a2a',
|
|
1168
|
-
lifelineLength: 500
|
|
1169
|
-
}
|
|
1170
|
-
}
|
|
1171
|
-
onUpdate={updateSelectedEntity}
|
|
1172
|
-
onClose={closeEntitySettings}
|
|
1173
|
-
/>
|
|
1174
|
-
)}
|
|
1175
|
-
|
|
1176
|
-
{/* Label editing overlay */}
|
|
1177
|
-
{(editingNode || editingEdge) && (
|
|
1178
|
-
<Box
|
|
1179
|
-
sx={{
|
|
1180
|
-
position: 'absolute',
|
|
1181
|
-
zIndex: 10,
|
|
1182
|
-
top: 10,
|
|
1183
|
-
left: '50%',
|
|
1184
|
-
transform: 'translateX(-50%)',
|
|
1185
|
-
bgcolor: '#242424',
|
|
1186
|
-
border: '1px solid #333',
|
|
1187
|
-
color: '#fff',
|
|
1188
|
-
p: 2,
|
|
1189
|
-
borderRadius: 1,
|
|
1190
|
-
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
1191
|
-
minWidth: 320,
|
|
1192
|
-
'& .MuiTextField-root': {
|
|
1193
|
-
'& .MuiInputLabel-root': {
|
|
1194
|
-
color: '#999',
|
|
1195
|
-
},
|
|
1196
|
-
'& .MuiOutlinedInput-root': {
|
|
1197
|
-
color: '#fff',
|
|
1198
|
-
'& fieldset': {
|
|
1199
|
-
borderColor: '#444',
|
|
1200
|
-
},
|
|
1201
|
-
'&:hover fieldset': {
|
|
1202
|
-
borderColor: '#61dafb',
|
|
1203
|
-
},
|
|
1204
|
-
'&.Mui-focused fieldset': {
|
|
1205
|
-
borderColor: '#61dafb',
|
|
1206
|
-
},
|
|
1207
|
-
},
|
|
1208
|
-
},
|
|
1209
|
-
'& .MuiButton-contained': {
|
|
1210
|
-
bgcolor: '#61dafb',
|
|
1211
|
-
color: '#000',
|
|
1212
|
-
'&:hover': {
|
|
1213
|
-
bgcolor: '#4db6d6',
|
|
1214
|
-
},
|
|
1215
|
-
},
|
|
1216
|
-
'& .MuiButton-outlined': {
|
|
1217
|
-
borderColor: '#333',
|
|
1218
|
-
color: '#fff',
|
|
1219
|
-
'&:hover': {
|
|
1220
|
-
borderColor: '#61dafb',
|
|
1221
|
-
bgcolor: 'rgba(97, 218, 251, 0.1)',
|
|
1222
|
-
},
|
|
1223
|
-
},
|
|
1224
|
-
'& .MuiIconButton-root': {
|
|
1225
|
-
color: '#999',
|
|
1226
|
-
'&:hover': {
|
|
1227
|
-
color: '#fff',
|
|
1228
|
-
bgcolor: 'rgba(255, 255, 255, 0.1)',
|
|
1229
|
-
},
|
|
1230
|
-
},
|
|
1231
|
-
}}
|
|
1232
|
-
>
|
|
1233
|
-
<Typography sx={{ mb: 1, fontWeight: 'bold', color: '#fff' }}>
|
|
1234
|
-
{editingType === 'node'
|
|
1235
|
-
? t('sequenceFlow.editLabels.entityLabel')
|
|
1236
|
-
: editingType === 'edge'
|
|
1237
|
-
? t('sequenceFlow.editLabels.messageLabel')
|
|
1238
|
-
: t('sequenceFlow.editLabels.editFragment')}
|
|
1239
|
-
</Typography>
|
|
1240
|
-
{editingType === 'fragment' ? (
|
|
1241
|
-
<>
|
|
1242
|
-
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
|
1243
|
-
<TextField
|
|
1244
|
-
type="text"
|
|
1245
|
-
value={editingLabel}
|
|
1246
|
-
onChange={handleLabelChange}
|
|
1247
|
-
onKeyDown={handleKeyDown}
|
|
1248
|
-
autoFocus
|
|
1249
|
-
size="small"
|
|
1250
|
-
sx={{ width: 200 }}
|
|
1251
|
-
label={t('fragmentSettings.fragmentName')}
|
|
1252
|
-
/>
|
|
1253
|
-
</Box>
|
|
1254
|
-
{/* Conditions editing */}
|
|
1255
|
-
<Box mb={2}>
|
|
1256
|
-
<Typography
|
|
1257
|
-
variant="body2"
|
|
1258
|
-
fontWeight="bold"
|
|
1259
|
-
mb={0.5}
|
|
1260
|
-
sx={{ color: '#61dafb' }}
|
|
1261
|
-
>
|
|
1262
|
-
{t('fragmentSettings.conditions')}
|
|
1263
|
-
</Typography>
|
|
1264
|
-
{editingConditions.map((cond, idx) => (
|
|
1265
|
-
<Box
|
|
1266
|
-
key={idx}
|
|
1267
|
-
display="flex"
|
|
1268
|
-
alignItems="center"
|
|
1269
|
-
mb={1}
|
|
1270
|
-
>
|
|
1271
|
-
<TextField
|
|
1272
|
-
value={cond}
|
|
1273
|
-
onChange={(e) =>
|
|
1274
|
-
handleConditionChange(idx, e.target.value)
|
|
1275
|
-
}
|
|
1276
|
-
size="small"
|
|
1277
|
-
fullWidth
|
|
1278
|
-
sx={{ mr: 1 }}
|
|
1279
|
-
/>
|
|
1280
|
-
<IconButton
|
|
1281
|
-
onClick={() => handleRemoveCondition(idx)}
|
|
1282
|
-
size="small"
|
|
1283
|
-
>
|
|
1284
|
-
<CloseIcon fontSize="small" />
|
|
1285
|
-
</IconButton>
|
|
1286
|
-
</Box>
|
|
1287
|
-
))}
|
|
1288
|
-
<Button
|
|
1289
|
-
onClick={handleAddCondition}
|
|
1290
|
-
size="small"
|
|
1291
|
-
variant="outlined"
|
|
1292
|
-
>
|
|
1293
|
-
{t('fragmentSettings.addCondition')}
|
|
1294
|
-
</Button>
|
|
1295
|
-
</Box>
|
|
1296
|
-
<Box display="flex" gap={2}>
|
|
1297
|
-
<Button
|
|
1298
|
-
variant="contained"
|
|
1299
|
-
size="small"
|
|
1300
|
-
onClick={saveLabel}
|
|
1301
|
-
sx={{ textTransform: 'none' }}
|
|
1302
|
-
>
|
|
1303
|
-
{t('common.save')}
|
|
1304
|
-
</Button>
|
|
1305
|
-
<Button
|
|
1306
|
-
variant="outlined"
|
|
1307
|
-
size="small"
|
|
1308
|
-
onClick={cancelEditing}
|
|
1309
|
-
sx={{ textTransform: 'none' }}
|
|
1310
|
-
>
|
|
1311
|
-
{t('common.cancel')}
|
|
1312
|
-
</Button>
|
|
1313
|
-
</Box>
|
|
1314
|
-
</>
|
|
1315
|
-
) : (
|
|
1316
|
-
<Box display="flex" alignItems="center" gap={2}>
|
|
1317
|
-
<TextField
|
|
1318
|
-
type="text"
|
|
1319
|
-
value={editingLabel}
|
|
1320
|
-
onChange={handleLabelChange}
|
|
1321
|
-
onKeyDown={handleKeyDown}
|
|
1322
|
-
autoFocus
|
|
1323
|
-
size="small"
|
|
1324
|
-
sx={{ width: 200 }}
|
|
1325
|
-
/>
|
|
1326
|
-
<Button
|
|
1327
|
-
variant="contained"
|
|
1328
|
-
size="small"
|
|
1329
|
-
onClick={saveLabel}
|
|
1330
|
-
sx={{ textTransform: 'none' }}
|
|
1331
|
-
>
|
|
1332
|
-
{t('common.save')}
|
|
1333
|
-
</Button>
|
|
1334
|
-
<Button
|
|
1335
|
-
variant="outlined"
|
|
1336
|
-
size="small"
|
|
1337
|
-
onClick={cancelEditing}
|
|
1338
|
-
sx={{ textTransform: 'none' }}
|
|
1339
|
-
>
|
|
1340
|
-
{t('common.cancel')}
|
|
1341
|
-
</Button>
|
|
1342
|
-
</Box>
|
|
1343
|
-
)}
|
|
1344
|
-
</Box>
|
|
1345
|
-
)}
|
|
1346
|
-
|
|
1347
|
-
{/* Add Message Dialog */}
|
|
1348
|
-
<AddMessageDialog
|
|
1349
|
-
open={showMessageDialog}
|
|
1350
|
-
onClose={() => setShowMessageDialog(false)}
|
|
1351
|
-
onAddMessage={handleAddCustomMessage}
|
|
1352
|
-
entities={sequenceEntities()}
|
|
1353
|
-
/>
|
|
1354
|
-
</div>
|
|
1355
|
-
</Box>
|
|
1356
|
-
</Box>
|
|
1357
|
-
</>
|
|
1358
|
-
);
|
|
1359
|
-
};
|
|
1360
|
-
|
|
1361
|
-
export default SequenceDiagramFlow;
|
|
1
|
+
import React, { useCallback, useState, useRef, useEffect } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
ReactFlow,
|
|
4
|
+
Background,
|
|
5
|
+
useNodesState,
|
|
6
|
+
useEdgesState,
|
|
7
|
+
Panel,
|
|
8
|
+
BackgroundVariant,
|
|
9
|
+
Connection,
|
|
10
|
+
NodeChange,
|
|
11
|
+
Node,
|
|
12
|
+
Edge,
|
|
13
|
+
MiniMap,
|
|
14
|
+
NodeMouseHandler,
|
|
15
|
+
EdgeMouseHandler,
|
|
16
|
+
} from '@xyflow/react';
|
|
17
|
+
import '@xyflow/react/dist/style.css';
|
|
18
|
+
|
|
19
|
+
import {
|
|
20
|
+
Box,
|
|
21
|
+
Button,
|
|
22
|
+
TextField,
|
|
23
|
+
Typography,
|
|
24
|
+
IconButton,
|
|
25
|
+
Paper,
|
|
26
|
+
} from '@mui/material';
|
|
27
|
+
import DownloadIcon from '@mui/icons-material/Download';
|
|
28
|
+
import UploadIcon from '@mui/icons-material/Upload';
|
|
29
|
+
import FullscreenIcon from '@mui/icons-material/Fullscreen';
|
|
30
|
+
import FullscreenExitIcon from '@mui/icons-material/FullscreenExit';
|
|
31
|
+
import CloseIcon from '@mui/icons-material/Close';
|
|
32
|
+
import UndoIcon from '@mui/icons-material/Undo';
|
|
33
|
+
import RedoIcon from '@mui/icons-material/Redo';
|
|
34
|
+
import { useTranslation } from 'react-i18next';
|
|
35
|
+
import {
|
|
36
|
+
SequenceDiagramProps,
|
|
37
|
+
ThemeData,
|
|
38
|
+
SequenceNode,
|
|
39
|
+
SequenceEdge,
|
|
40
|
+
SequenceHistoryEntry,
|
|
41
|
+
SequenceEntityShape,
|
|
42
|
+
CombinedFragmentType,
|
|
43
|
+
SequenceMessageType,
|
|
44
|
+
SequenceEntityNode,
|
|
45
|
+
SequenceEntityData,
|
|
46
|
+
FragmentSettingsUpdateData,
|
|
47
|
+
EntitySettingsUpdateData,
|
|
48
|
+
ActivationBarNode,
|
|
49
|
+
CombinedFragmentNode,
|
|
50
|
+
SequenceMessageData,
|
|
51
|
+
ActivationBarData,
|
|
52
|
+
CombinedFragmentData
|
|
53
|
+
} from '../../../types/sequence-types';
|
|
54
|
+
import { getTemplateData } from '../data/template-data';
|
|
55
|
+
import { sequenceEdgeTypes, sequenceNodeTypes } from './SequenceComponents';
|
|
56
|
+
import DiagramControls from '../controls/DiagramControls';
|
|
57
|
+
import FragmentSettings from './FragmentSettings';
|
|
58
|
+
import EntitySettings from './EntitySettings';
|
|
59
|
+
import AddMessageDialog from './AddMessageDialog';
|
|
60
|
+
import Sidebar from '../../../components/SideBar';
|
|
61
|
+
import AiButton from '../../../components/AiButton';
|
|
62
|
+
import MiniMapCommon from '../../../components/MiniMapCommon';
|
|
63
|
+
import DiagramPanel from '../../../components/DiagramPanel';
|
|
64
|
+
|
|
65
|
+
// Types for browser fullscreen API extensions
|
|
66
|
+
interface HTMLElementWithFullscreen extends HTMLElement {
|
|
67
|
+
webkitRequestFullscreen?: () => Promise<void>;
|
|
68
|
+
msRequestFullscreen?: () => Promise<void>;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface DocumentWithFullscreen extends Document {
|
|
72
|
+
webkitExitFullscreen?: () => Promise<void>;
|
|
73
|
+
msExitFullscreen?: () => Promise<void>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Main component that manages the diagram state
|
|
77
|
+
|
|
78
|
+
const SequenceDiagramFlow: React.FC<SequenceDiagramProps> = ({
|
|
79
|
+
sequenceNodes,
|
|
80
|
+
sequenceEdges,
|
|
81
|
+
}) => {
|
|
82
|
+
const { t } = useTranslation();
|
|
83
|
+
|
|
84
|
+
// Initialize nodes and edges with our data - use xy/flow base types with proper generics
|
|
85
|
+
const [nodes, setNodes, onNodesChange] = useNodesState(sequenceNodes);
|
|
86
|
+
|
|
87
|
+
const [edges, setEdges, onEdgesChange] = useEdgesState(sequenceEdges);
|
|
88
|
+
|
|
89
|
+
// History state for undo/redo functionality
|
|
90
|
+
const [history, setHistory] = useState<SequenceHistoryEntry[]>([]);
|
|
91
|
+
const [historyIndex, setHistoryIndex] = useState<number>(-1);
|
|
92
|
+
const isUndoRedoAction = useRef(false);
|
|
93
|
+
|
|
94
|
+
// Initialize history with initial state
|
|
95
|
+
useEffect(() => {
|
|
96
|
+
// Only initialize if history is empty
|
|
97
|
+
if (history.length === 0) {
|
|
98
|
+
const initialState: SequenceHistoryEntry = {
|
|
99
|
+
nodes: [...nodes] as SequenceNode[],
|
|
100
|
+
edges: [...edges] as SequenceEdge[],
|
|
101
|
+
};
|
|
102
|
+
setHistory([initialState]);
|
|
103
|
+
setHistoryIndex(0);
|
|
104
|
+
}
|
|
105
|
+
}, []);
|
|
106
|
+
|
|
107
|
+
// Save current state to history when nodes or edges change
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
// Don't record history when performing undo/redo actions or when we're still initializing
|
|
110
|
+
if (isUndoRedoAction.current || history.length === 0) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Debounce: Only update history if nodes/edges actually changed (deep compare)
|
|
115
|
+
const newHistoryEntry: SequenceHistoryEntry = {
|
|
116
|
+
nodes: [...nodes] as SequenceNode[],
|
|
117
|
+
edges: [...edges] as SequenceEdge[],
|
|
118
|
+
};
|
|
119
|
+
const currentState = history[historyIndex];
|
|
120
|
+
if (
|
|
121
|
+
JSON.stringify(currentState.nodes) ===
|
|
122
|
+
JSON.stringify(newHistoryEntry.nodes) &&
|
|
123
|
+
JSON.stringify(currentState.edges) ===
|
|
124
|
+
JSON.stringify(newHistoryEntry.edges)
|
|
125
|
+
) {
|
|
126
|
+
return; // No change, don't add to history
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Trim future history if we're in the middle of the history stack
|
|
130
|
+
const newHistory = history.slice(0, historyIndex + 1);
|
|
131
|
+
newHistory.push(newHistoryEntry);
|
|
132
|
+
setHistory(newHistory);
|
|
133
|
+
setHistoryIndex(newHistory.length - 1);
|
|
134
|
+
}, [nodes, edges, history, historyIndex]);
|
|
135
|
+
|
|
136
|
+
// Function to perform undo operation
|
|
137
|
+
const handleUndo = useCallback(() => {
|
|
138
|
+
if (historyIndex > 0) {
|
|
139
|
+
isUndoRedoAction.current = true;
|
|
140
|
+
const prevState = history[historyIndex - 1];
|
|
141
|
+
setNodes(prevState.nodes);
|
|
142
|
+
setEdges(prevState.edges);
|
|
143
|
+
setHistoryIndex(historyIndex - 1);
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
isUndoRedoAction.current = false;
|
|
146
|
+
}, 0);
|
|
147
|
+
}
|
|
148
|
+
}, [history, historyIndex, setNodes, setEdges]);
|
|
149
|
+
|
|
150
|
+
// Function to perform redo operation
|
|
151
|
+
const handleRedo = useCallback(() => {
|
|
152
|
+
if (historyIndex < history.length - 1) {
|
|
153
|
+
isUndoRedoAction.current = true;
|
|
154
|
+
const nextState = history[historyIndex + 1];
|
|
155
|
+
setNodes(nextState.nodes);
|
|
156
|
+
setEdges(nextState.edges);
|
|
157
|
+
setHistoryIndex(historyIndex + 1);
|
|
158
|
+
setTimeout(() => {
|
|
159
|
+
isUndoRedoAction.current = false;
|
|
160
|
+
}, 0);
|
|
161
|
+
}
|
|
162
|
+
}, [history, historyIndex, setNodes, setEdges]);
|
|
163
|
+
|
|
164
|
+
// Listen for keyboard shortcuts
|
|
165
|
+
useEffect(() => {
|
|
166
|
+
const handleKeyDown = (e: KeyboardEvent) => {
|
|
167
|
+
// Check for Ctrl+Z or Cmd+Z (undo)
|
|
168
|
+
if ((e.ctrlKey || e.metaKey) && e.key === 'z' && !e.shiftKey) {
|
|
169
|
+
e.preventDefault();
|
|
170
|
+
handleUndo();
|
|
171
|
+
}
|
|
172
|
+
// Check for Ctrl+Y or Cmd+Shift+Z (redo)
|
|
173
|
+
if (
|
|
174
|
+
((e.ctrlKey || e.metaKey) && e.key === 'y') ||
|
|
175
|
+
((e.ctrlKey || e.metaKey) && e.shiftKey && e.key === 'z')
|
|
176
|
+
) {
|
|
177
|
+
e.preventDefault();
|
|
178
|
+
handleRedo();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
window.addEventListener('keydown', handleKeyDown);
|
|
183
|
+
return () => {
|
|
184
|
+
window.removeEventListener('keydown', handleKeyDown);
|
|
185
|
+
};
|
|
186
|
+
}, [handleUndo, handleRedo]);
|
|
187
|
+
|
|
188
|
+
const [theme, setTheme] = useState<ThemeData>({
|
|
189
|
+
primary: '#61dafb',
|
|
190
|
+
secondary: '#2a2a2a',
|
|
191
|
+
});
|
|
192
|
+
const [isFullscreen, setIsFullscreen] = useState<boolean>(false);
|
|
193
|
+
const [selectedFragmentId, setSelectedFragmentId] = useState<string | null>(
|
|
194
|
+
null,
|
|
195
|
+
);
|
|
196
|
+
const [fragmentSettings, setFragmentSettings] = useState({
|
|
197
|
+
width: 400,
|
|
198
|
+
height: 150,
|
|
199
|
+
});
|
|
200
|
+
const [showFragmentSettings, setShowFragmentSettings] =
|
|
201
|
+
useState<boolean>(false);
|
|
202
|
+
const diagramContainerRef = useRef<HTMLDivElement>(null);
|
|
203
|
+
const [editingNode, setEditingNode] = useState<string | null>(null);
|
|
204
|
+
const [editingEdge, setEditingEdge] = useState<string | null>(null);
|
|
205
|
+
const [editingLabel, setEditingLabel] = useState<string>('');
|
|
206
|
+
const [editingConditions, setEditingConditions] = useState<string[]>([]);
|
|
207
|
+
const [editingType, setEditingType] = useState<'node' | 'edge' | 'fragment'>(
|
|
208
|
+
'node',
|
|
209
|
+
);
|
|
210
|
+
const [selectedEntityId, setSelectedEntityId] = useState<string | null>(null);
|
|
211
|
+
const [showEntitySettings, setShowEntitySettings] = useState<boolean>(false);
|
|
212
|
+
const [showMessageDialog, setShowMessageDialog] = useState<boolean>(false);
|
|
213
|
+
const ENTITY_FIXED_Y = 50; // Fixed y position for all entities
|
|
214
|
+
|
|
215
|
+
// Type-safe node access helpers
|
|
216
|
+
const getSequenceEntityNodes = useCallback(() => {
|
|
217
|
+
return nodes.filter((node): node is Node<SequenceEntityData> =>
|
|
218
|
+
node.type === 'sequenceEntity'
|
|
219
|
+
);
|
|
220
|
+
}, [nodes]);
|
|
221
|
+
|
|
222
|
+
const getCombinedFragmentNodes = useCallback(() => {
|
|
223
|
+
return nodes.filter((node): node is Node<CombinedFragmentData> =>
|
|
224
|
+
node.type === 'combinedFragment'
|
|
225
|
+
);
|
|
226
|
+
}, [nodes]);
|
|
227
|
+
|
|
228
|
+
// Intercept node position changes to lock y for sequenceEntity nodes
|
|
229
|
+
const onNodesChangeLockedY = useCallback(
|
|
230
|
+
(changes: NodeChange[]) => {
|
|
231
|
+
let hasPositionChange = false;
|
|
232
|
+
const updatedNodes = nodes.map((node) => {
|
|
233
|
+
// Find if there's a position change for this node
|
|
234
|
+
const change = changes.find(
|
|
235
|
+
(c: NodeChange) => c.type === 'position' && c.id === node.id,
|
|
236
|
+
);
|
|
237
|
+
if (
|
|
238
|
+
change &&
|
|
239
|
+
change.type === 'position' &&
|
|
240
|
+
node.type === 'sequenceEntity' &&
|
|
241
|
+
change.position
|
|
242
|
+
) {
|
|
243
|
+
hasPositionChange = true;
|
|
244
|
+
// Lock y position
|
|
245
|
+
return {
|
|
246
|
+
...node,
|
|
247
|
+
position: {
|
|
248
|
+
x: change.position.x,
|
|
249
|
+
y: ENTITY_FIXED_Y, // always fixed
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
return node;
|
|
254
|
+
});
|
|
255
|
+
if (hasPositionChange) {
|
|
256
|
+
setNodes(updatedNodes);
|
|
257
|
+
} else {
|
|
258
|
+
onNodesChange(changes);
|
|
259
|
+
}
|
|
260
|
+
},
|
|
261
|
+
[nodes, setNodes, onNodesChange],
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
// Handler to add a new entity
|
|
265
|
+
const handleAddEntity = useCallback(
|
|
266
|
+
(shape: SequenceEntityShape = 'rectangle') => {
|
|
267
|
+
console.log('Adding new entity with shape:', shape);
|
|
268
|
+
console.log('Current nodes:', nodes);
|
|
269
|
+
|
|
270
|
+
// Find all entity nodes
|
|
271
|
+
const entityNodes = getSequenceEntityNodes();
|
|
272
|
+
const entityCount = entityNodes.length;
|
|
273
|
+
const newEntityId = `entity-${entityCount + 1}`;
|
|
274
|
+
|
|
275
|
+
// Find the rightmost x position among entities
|
|
276
|
+
const rightmostX =
|
|
277
|
+
entityNodes.length > 0
|
|
278
|
+
? Math.max(...entityNodes.map((node) => node.position.x))
|
|
279
|
+
: 100; // Default starting x
|
|
280
|
+
|
|
281
|
+
const xOffset = 150; // Space between entities
|
|
282
|
+
const xPosition =
|
|
283
|
+
entityNodes.length > 0 ? rightmostX + xOffset : rightmostX;
|
|
284
|
+
|
|
285
|
+
// Create a new entity node with extended properties
|
|
286
|
+
const newEntity: Node<SequenceEntityData> = {
|
|
287
|
+
id: newEntityId,
|
|
288
|
+
type: 'sequenceEntity',
|
|
289
|
+
position: { x: xPosition, y: 50 },
|
|
290
|
+
data: {
|
|
291
|
+
label: `Entity ${entityCount + 1}`,
|
|
292
|
+
color: theme.primary,
|
|
293
|
+
backgroundColor: theme.secondary,
|
|
294
|
+
lifelineLength: 500, // Use same height as others
|
|
295
|
+
shape: shape, // Use the provided shape
|
|
296
|
+
fontSize: 12,
|
|
297
|
+
borderWidth: 2,
|
|
298
|
+
description: '',
|
|
299
|
+
collapsed: false,
|
|
300
|
+
},
|
|
301
|
+
draggable: true,
|
|
302
|
+
zIndex: 5, // Higher z-index to appear above other elements
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
// Adjust position based on entity shape
|
|
306
|
+
let offsetX = 55;
|
|
307
|
+
switch (shape) {
|
|
308
|
+
case 'circle':
|
|
309
|
+
offsetX = 40;
|
|
310
|
+
break;
|
|
311
|
+
case 'database':
|
|
312
|
+
offsetX = 45;
|
|
313
|
+
break;
|
|
314
|
+
case 'actor':
|
|
315
|
+
offsetX = 60;
|
|
316
|
+
break;
|
|
317
|
+
default:
|
|
318
|
+
offsetX = 55;
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const activationBar: Node<ActivationBarData> = {
|
|
323
|
+
id: `${newEntityId}-activation`,
|
|
324
|
+
type: 'activationBar',
|
|
325
|
+
position: { x: xPosition + offsetX, y: 180 }, // Position below the entity
|
|
326
|
+
data: {
|
|
327
|
+
entityId: newEntityId,
|
|
328
|
+
color: theme.secondary,
|
|
329
|
+
borderColor: theme.primary,
|
|
330
|
+
length: 150,
|
|
331
|
+
},
|
|
332
|
+
draggable: true,
|
|
333
|
+
zIndex: 1, // Lower z-index to appear behind entity boxes but above combined fragments
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
setNodes((nds) => [...nds, newEntity, activationBar]);
|
|
337
|
+
},
|
|
338
|
+
[nodes, setNodes, theme, getSequenceEntityNodes],
|
|
339
|
+
);
|
|
340
|
+
|
|
341
|
+
// Filter entities for Add Message Dialog
|
|
342
|
+
const sequenceEntities = useCallback((): SequenceEntityNode[] => {
|
|
343
|
+
return getSequenceEntityNodes().map((node) => ({
|
|
344
|
+
id: node.id,
|
|
345
|
+
type: 'sequenceEntity',
|
|
346
|
+
data: node.data,
|
|
347
|
+
position: node.position,
|
|
348
|
+
draggable: node.draggable,
|
|
349
|
+
zIndex: node.zIndex,
|
|
350
|
+
}));
|
|
351
|
+
}, [getSequenceEntityNodes]);
|
|
352
|
+
|
|
353
|
+
// Handler to open add message dialog
|
|
354
|
+
const handleOpenAddMessageDialog = useCallback(() => {
|
|
355
|
+
// Filter to get only entity nodes
|
|
356
|
+
const entityNodes = getSequenceEntityNodes();
|
|
357
|
+
|
|
358
|
+
if (entityNodes.length < 1) {
|
|
359
|
+
alert(t('sequenceDiagram.errors.needEntitiesForMessage'));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
setShowMessageDialog(true);
|
|
364
|
+
}, [getSequenceEntityNodes]);
|
|
365
|
+
|
|
366
|
+
// Handler to create a new message with specified source, target, and type
|
|
367
|
+
const handleAddCustomMessage = useCallback(
|
|
368
|
+
(
|
|
369
|
+
sourceId: string,
|
|
370
|
+
targetId: string,
|
|
371
|
+
messageType: SequenceMessageType,
|
|
372
|
+
label: string,
|
|
373
|
+
) => {
|
|
374
|
+
// Find the source and target nodes
|
|
375
|
+
const sourceNode = nodes.find((n) => n.id === sourceId);
|
|
376
|
+
const targetNode = nodes.find((n) => n.id === targetId);
|
|
377
|
+
|
|
378
|
+
if (!sourceNode || !targetNode) {
|
|
379
|
+
console.error(t('sequenceDiagram.errors.sourceTargetNotFound'));
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Calculate a reasonable Y position for the new message
|
|
384
|
+
const messageY =
|
|
385
|
+
Math.max(
|
|
386
|
+
...edges.map((edge) => (edge.data as SequenceMessageData)?.y || 0),
|
|
387
|
+
180, // Default minimum Y position
|
|
388
|
+
) + 60; // Add some spacing
|
|
389
|
+
|
|
390
|
+
// Calculate handle positions based on nodes
|
|
391
|
+
const sourceX = sourceNode.position?.x || 0;
|
|
392
|
+
const targetX = targetNode.position?.x || 0;
|
|
393
|
+
|
|
394
|
+
// Calculate source and target handles
|
|
395
|
+
let sourceHandle = 'right';
|
|
396
|
+
let targetHandle = 'left';
|
|
397
|
+
|
|
398
|
+
if (messageType === 'self') {
|
|
399
|
+
// For self messages, always use the right side
|
|
400
|
+
sourceHandle = 'right';
|
|
401
|
+
targetHandle = 'right';
|
|
402
|
+
} else if (sourceX > targetX) {
|
|
403
|
+
// If source is to the right of target (server-to-client)
|
|
404
|
+
sourceHandle = 'left';
|
|
405
|
+
targetHandle = 'right';
|
|
406
|
+
} else {
|
|
407
|
+
// If source is to the left of target (client-to-server)
|
|
408
|
+
sourceHandle = 'right';
|
|
409
|
+
targetHandle = 'left';
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Create a message from the source to the target entity
|
|
413
|
+
const newEdge: Edge<SequenceMessageData> = {
|
|
414
|
+
id: `seq-edge-${edges.length + 1}`,
|
|
415
|
+
source: sourceId,
|
|
416
|
+
target: targetId,
|
|
417
|
+
type: 'sequenceMessage',
|
|
418
|
+
sourceHandle: sourceHandle,
|
|
419
|
+
targetHandle: targetHandle,
|
|
420
|
+
data: {
|
|
421
|
+
label: label,
|
|
422
|
+
type: messageType,
|
|
423
|
+
y: messageY,
|
|
424
|
+
},
|
|
425
|
+
animated: messageType === 'async',
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
setEdges((eds) => [...eds, newEdge]);
|
|
429
|
+
|
|
430
|
+
setNodes((nds) => {
|
|
431
|
+
return nds.map((node) => {
|
|
432
|
+
if (
|
|
433
|
+
node.type === 'sequenceEntity' &&
|
|
434
|
+
(node.id === sourceId || node.id === targetId)
|
|
435
|
+
) {
|
|
436
|
+
const nodeData = node.data as SequenceEntityData;
|
|
437
|
+
// Find all messages (edges) connected to this node, including the new one
|
|
438
|
+
const connectedMessages = [...edges, newEdge].filter(
|
|
439
|
+
(edge) => edge.source === node.id || edge.target === node.id,
|
|
440
|
+
);
|
|
441
|
+
// Find the max Y position among these messages
|
|
442
|
+
const maxY = Math.max(
|
|
443
|
+
...connectedMessages.map((edge) => (edge.data as SequenceMessageData)?.y || 0),
|
|
444
|
+
nodeData.lifelineLength || 0, // at least current length
|
|
445
|
+
);
|
|
446
|
+
const newLength = Math.max(maxY + 40, nodeData.lifelineLength || 0);
|
|
447
|
+
// Only update if needed
|
|
448
|
+
if (newLength > (nodeData.lifelineLength || 0)) {
|
|
449
|
+
return {
|
|
450
|
+
...node,
|
|
451
|
+
data: {
|
|
452
|
+
...nodeData,
|
|
453
|
+
lifelineLength: newLength,
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return node;
|
|
459
|
+
});
|
|
460
|
+
});
|
|
461
|
+
},
|
|
462
|
+
[nodes, edges, setEdges, setNodes],
|
|
463
|
+
);
|
|
464
|
+
|
|
465
|
+
// Handler for the original add message button (now opens the dialog)
|
|
466
|
+
const handleAddMessage = useCallback(() => {
|
|
467
|
+
handleOpenAddMessageDialog();
|
|
468
|
+
}, [handleOpenAddMessageDialog]);
|
|
469
|
+
|
|
470
|
+
// Function to export diagram as JSON
|
|
471
|
+
const exportDiagram = useCallback(() => {
|
|
472
|
+
const diagramData = {
|
|
473
|
+
nodes: nodes,
|
|
474
|
+
edges: edges,
|
|
475
|
+
};
|
|
476
|
+
|
|
477
|
+
const dataStr = JSON.stringify(diagramData, null, 2);
|
|
478
|
+
const dataUri =
|
|
479
|
+
'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr);
|
|
480
|
+
|
|
481
|
+
const exportFileDefaultName = t('sequenceDiagram.export.filename');
|
|
482
|
+
|
|
483
|
+
const linkElement = document.createElement('a');
|
|
484
|
+
linkElement.setAttribute('href', dataUri);
|
|
485
|
+
linkElement.setAttribute('download', exportFileDefaultName);
|
|
486
|
+
linkElement.click();
|
|
487
|
+
}, [nodes, edges]);
|
|
488
|
+
|
|
489
|
+
// Function to import diagram from JSON
|
|
490
|
+
const importDiagram = useCallback(
|
|
491
|
+
(event: React.ChangeEvent<HTMLInputElement>) => {
|
|
492
|
+
if (!event.target.files || event.target.files.length === 0) return;
|
|
493
|
+
|
|
494
|
+
const fileReader = new FileReader();
|
|
495
|
+
fileReader.readAsText(event.target.files[0], 'UTF-8');
|
|
496
|
+
fileReader.onload = (e) => {
|
|
497
|
+
try {
|
|
498
|
+
if (!e.target || typeof e.target.result !== 'string') return;
|
|
499
|
+
|
|
500
|
+
const imported = JSON.parse(e.target.result);
|
|
501
|
+
if (imported.nodes && imported.edges) {
|
|
502
|
+
setNodes(imported.nodes);
|
|
503
|
+
setEdges(imported.edges);
|
|
504
|
+
}
|
|
505
|
+
} catch (error) {
|
|
506
|
+
console.error('Error importing diagram:', error);
|
|
507
|
+
alert(t('sequenceDiagram.errors.importFailed'));
|
|
508
|
+
}
|
|
509
|
+
};
|
|
510
|
+
},
|
|
511
|
+
[setNodes, setEdges],
|
|
512
|
+
);
|
|
513
|
+
|
|
514
|
+
// Function to handle theme changes
|
|
515
|
+
const handleThemeChange = useCallback(
|
|
516
|
+
(newTheme: ThemeData) => {
|
|
517
|
+
setTheme(newTheme);
|
|
518
|
+
|
|
519
|
+
// Update nodes with new theme colors
|
|
520
|
+
setNodes((nds) =>
|
|
521
|
+
nds.map((node) => {
|
|
522
|
+
if (node.type === 'sequenceEntity') {
|
|
523
|
+
const nodeData = node.data;
|
|
524
|
+
return {
|
|
525
|
+
...node,
|
|
526
|
+
data: {
|
|
527
|
+
...nodeData,
|
|
528
|
+
color: newTheme.primary,
|
|
529
|
+
backgroundColor: newTheme.secondary,
|
|
530
|
+
},
|
|
531
|
+
};
|
|
532
|
+
} else if (node.type === 'activationBar') {
|
|
533
|
+
const nodeData = node.data;
|
|
534
|
+
return {
|
|
535
|
+
...node,
|
|
536
|
+
data: {
|
|
537
|
+
...nodeData,
|
|
538
|
+
color: newTheme.secondary,
|
|
539
|
+
borderColor: newTheme.primary,
|
|
540
|
+
},
|
|
541
|
+
};
|
|
542
|
+
}
|
|
543
|
+
return node;
|
|
544
|
+
}),
|
|
545
|
+
);
|
|
546
|
+
},
|
|
547
|
+
[setNodes],
|
|
548
|
+
);
|
|
549
|
+
|
|
550
|
+
// Toggle fullscreen mode
|
|
551
|
+
const toggleFullscreen = useCallback(() => {
|
|
552
|
+
if (!diagramContainerRef.current) return;
|
|
553
|
+
|
|
554
|
+
const element = diagramContainerRef.current as HTMLElementWithFullscreen;
|
|
555
|
+
const doc = document as DocumentWithFullscreen;
|
|
556
|
+
|
|
557
|
+
if (!isFullscreen) {
|
|
558
|
+
if (element.requestFullscreen) {
|
|
559
|
+
element.requestFullscreen();
|
|
560
|
+
} else if (element.webkitRequestFullscreen) {
|
|
561
|
+
element.webkitRequestFullscreen();
|
|
562
|
+
} else if (element.msRequestFullscreen) {
|
|
563
|
+
element.msRequestFullscreen();
|
|
564
|
+
}
|
|
565
|
+
} else {
|
|
566
|
+
if (doc.exitFullscreen) {
|
|
567
|
+
doc.exitFullscreen();
|
|
568
|
+
} else if (doc.webkitExitFullscreen) {
|
|
569
|
+
doc.webkitExitFullscreen();
|
|
570
|
+
} else if (doc.msExitFullscreen) {
|
|
571
|
+
doc.msExitFullscreen();
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
setIsFullscreen(!isFullscreen);
|
|
576
|
+
}, [isFullscreen]);
|
|
577
|
+
|
|
578
|
+
// Listen for fullscreen change events from browser
|
|
579
|
+
React.useEffect(() => {
|
|
580
|
+
const handleFullscreenChange = () => {
|
|
581
|
+
setIsFullscreen(!!document.fullscreenElement);
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
document.addEventListener('fullscreenchange', handleFullscreenChange);
|
|
585
|
+
document.addEventListener('webkitfullscreenchange', handleFullscreenChange);
|
|
586
|
+
document.addEventListener('mozfullscreenchange', handleFullscreenChange);
|
|
587
|
+
document.addEventListener('MSFullscreenChange', handleFullscreenChange);
|
|
588
|
+
|
|
589
|
+
return () => {
|
|
590
|
+
document.removeEventListener('fullscreenchange', handleFullscreenChange);
|
|
591
|
+
document.removeEventListener(
|
|
592
|
+
'webkitfullscreenchange',
|
|
593
|
+
handleFullscreenChange,
|
|
594
|
+
);
|
|
595
|
+
document.removeEventListener(
|
|
596
|
+
'mozfullscreenchange',
|
|
597
|
+
handleFullscreenChange,
|
|
598
|
+
);
|
|
599
|
+
document.removeEventListener(
|
|
600
|
+
'MSFullscreenChange',
|
|
601
|
+
handleFullscreenChange,
|
|
602
|
+
);
|
|
603
|
+
};
|
|
604
|
+
}, []);
|
|
605
|
+
|
|
606
|
+
// Properly handle connections between nodes
|
|
607
|
+
const onConnect = useCallback(
|
|
608
|
+
(params: Connection) => {
|
|
609
|
+
console.log('Connection params:', params);
|
|
610
|
+
if (!params.source || !params.target) return;
|
|
611
|
+
|
|
612
|
+
// Find the source and target nodes
|
|
613
|
+
const sourceNode = nodes.find((n) => n.id === params.source);
|
|
614
|
+
const targetNode = nodes.find((n) => n.id === params.target);
|
|
615
|
+
|
|
616
|
+
if (!sourceNode || !targetNode) return;
|
|
617
|
+
|
|
618
|
+
// Only allow connections between sequence entities
|
|
619
|
+
if (
|
|
620
|
+
sourceNode.type !== 'sequenceEntity' ||
|
|
621
|
+
targetNode.type !== 'sequenceEntity'
|
|
622
|
+
) {
|
|
623
|
+
alert(t('sequenceDiagram.errors.onlyConnectEntities'));
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
// Calculate a Y position for the new message
|
|
628
|
+
const messageY =
|
|
629
|
+
Math.max(
|
|
630
|
+
...edges.map((edge) => (edge.data as SequenceMessageData)?.y || 0),
|
|
631
|
+
180, // Default minimum Y position
|
|
632
|
+
) + 60; // Add some spacing
|
|
633
|
+
|
|
634
|
+
// Calculate handle positions based on nodes
|
|
635
|
+
const sourceX = sourceNode.position?.x || 0;
|
|
636
|
+
const targetX = targetNode.position?.x || 0;
|
|
637
|
+
|
|
638
|
+
// Determine correct handles based on relative positions
|
|
639
|
+
let sourceHandle = params.sourceHandle;
|
|
640
|
+
let targetHandle = params.targetHandle;
|
|
641
|
+
|
|
642
|
+
// If handles aren't explicitly provided by the connection event,
|
|
643
|
+
// calculate them based on node positions
|
|
644
|
+
if (!sourceHandle || !targetHandle) {
|
|
645
|
+
if (sourceX > targetX) {
|
|
646
|
+
// Source is to the right of target (server-to-client)
|
|
647
|
+
sourceHandle = 'left';
|
|
648
|
+
targetHandle = 'right';
|
|
649
|
+
} else {
|
|
650
|
+
// Source is to the left of target (client-to-server)
|
|
651
|
+
sourceHandle = 'right';
|
|
652
|
+
targetHandle = 'left';
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Create a new edge for the message
|
|
657
|
+
const newEdge: Edge<SequenceMessageData> = {
|
|
658
|
+
id: `seq-edge-${edges.length + 1}`,
|
|
659
|
+
source: params.source,
|
|
660
|
+
target: params.target,
|
|
661
|
+
type: 'sequenceMessage',
|
|
662
|
+
sourceHandle: sourceHandle,
|
|
663
|
+
targetHandle: targetHandle,
|
|
664
|
+
data: {
|
|
665
|
+
label: `Message ${edges.length + 1}`,
|
|
666
|
+
type: 'sync', // Default to sync message
|
|
667
|
+
y: messageY,
|
|
668
|
+
},
|
|
669
|
+
animated: false,
|
|
670
|
+
};
|
|
671
|
+
|
|
672
|
+
setEdges((eds) => [...eds, newEdge]);
|
|
673
|
+
|
|
674
|
+
setNodes((nds) => {
|
|
675
|
+
return nds.map((node) => {
|
|
676
|
+
if (
|
|
677
|
+
node.type === 'sequenceEntity' &&
|
|
678
|
+
(node.id === params.source || node.id === params.target)
|
|
679
|
+
) {
|
|
680
|
+
const nodeData = node.data as SequenceEntityData;
|
|
681
|
+
// Find all messages (edges) connected to this node, including the new one
|
|
682
|
+
const connectedMessages = [...edges, newEdge].filter(
|
|
683
|
+
(edge) => edge.source === node.id || edge.target === node.id,
|
|
684
|
+
);
|
|
685
|
+
// Find the max Y position among these messages
|
|
686
|
+
const maxY = Math.max(
|
|
687
|
+
...connectedMessages.map((edge) => (edge.data as SequenceMessageData)?.y || 0),
|
|
688
|
+
nodeData.lifelineLength || 0, // at least current length
|
|
689
|
+
);
|
|
690
|
+
const newLength = Math.max(maxY + 40, nodeData.lifelineLength || 0);
|
|
691
|
+
// Only update if needed
|
|
692
|
+
if (newLength > (nodeData.lifelineLength || 0)) {
|
|
693
|
+
return {
|
|
694
|
+
...node,
|
|
695
|
+
data: {
|
|
696
|
+
...nodeData,
|
|
697
|
+
lifelineLength: newLength,
|
|
698
|
+
},
|
|
699
|
+
};
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return node;
|
|
703
|
+
});
|
|
704
|
+
});
|
|
705
|
+
},
|
|
706
|
+
[nodes, edges, setEdges, setNodes],
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
// Handle node click events - use ReactFlow's NodeMouseHandler type
|
|
710
|
+
const onNodeClick: NodeMouseHandler = useCallback(
|
|
711
|
+
(event, node) => {
|
|
712
|
+
// If the node is a combined fragment, select it for editing
|
|
713
|
+
if (node.type === 'combinedFragment') {
|
|
714
|
+
setSelectedFragmentId(node.id);
|
|
715
|
+
|
|
716
|
+
// Set current fragment dimensions
|
|
717
|
+
const width = node.style?.width || 400;
|
|
718
|
+
const height = node.style?.height || 150;
|
|
719
|
+
setFragmentSettings({ width: Number(width), height: Number(height) });
|
|
720
|
+
|
|
721
|
+
// Show the settings panel
|
|
722
|
+
setShowFragmentSettings(true);
|
|
723
|
+
setShowEntitySettings(false);
|
|
724
|
+
}
|
|
725
|
+
// If the node is an entity, select it for settings
|
|
726
|
+
else if (node.type === 'sequenceEntity') {
|
|
727
|
+
setSelectedEntityId(node.id);
|
|
728
|
+
setShowEntitySettings(true);
|
|
729
|
+
setShowFragmentSettings(false);
|
|
730
|
+
} else {
|
|
731
|
+
// If clicking on a non-fragment node while settings are open, close them
|
|
732
|
+
if (showFragmentSettings) {
|
|
733
|
+
setShowFragmentSettings(false);
|
|
734
|
+
setSelectedFragmentId(null);
|
|
735
|
+
}
|
|
736
|
+
if (showEntitySettings) {
|
|
737
|
+
setShowEntitySettings(false);
|
|
738
|
+
setSelectedEntityId(null);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
},
|
|
742
|
+
[showFragmentSettings, showEntitySettings],
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
// Handle node double-click for label editing - use ReactFlow's NodeMouseHandler type
|
|
746
|
+
const onNodeDoubleClick: NodeMouseHandler = useCallback(
|
|
747
|
+
(event, node) => {
|
|
748
|
+
event.preventDefault();
|
|
749
|
+
event.stopPropagation();
|
|
750
|
+
|
|
751
|
+
if (node.type === 'sequenceEntity') {
|
|
752
|
+
const nodeData = node.data as SequenceEntityData;
|
|
753
|
+
setEditingNode(node.id);
|
|
754
|
+
setEditingEdge(null);
|
|
755
|
+
setEditingLabel(nodeData.label || '');
|
|
756
|
+
setEditingType('node');
|
|
757
|
+
} else if (node.type === 'combinedFragment') {
|
|
758
|
+
const nodeData = node.data as CombinedFragmentData;
|
|
759
|
+
setEditingNode(node.id);
|
|
760
|
+
setEditingEdge(null);
|
|
761
|
+
setEditingLabel(nodeData.label || '');
|
|
762
|
+
setEditingType('fragment');
|
|
763
|
+
setEditingConditions(nodeData.conditions || []);
|
|
764
|
+
}
|
|
765
|
+
},
|
|
766
|
+
[],
|
|
767
|
+
);
|
|
768
|
+
|
|
769
|
+
// Handle edge double-click for label editing - use ReactFlow's EdgeMouseHandler type
|
|
770
|
+
const onEdgeDoubleClick: EdgeMouseHandler = useCallback(
|
|
771
|
+
(event, edge) => {
|
|
772
|
+
event.preventDefault();
|
|
773
|
+
event.stopPropagation();
|
|
774
|
+
|
|
775
|
+
if (edge.type === 'sequenceMessage') {
|
|
776
|
+
const edgeData = edge.data as SequenceMessageData;
|
|
777
|
+
setEditingEdge(edge.id);
|
|
778
|
+
setEditingNode(null);
|
|
779
|
+
setEditingLabel(edgeData?.label || '');
|
|
780
|
+
setEditingType('edge');
|
|
781
|
+
}
|
|
782
|
+
},
|
|
783
|
+
[],
|
|
784
|
+
);
|
|
785
|
+
|
|
786
|
+
// Handle label input change
|
|
787
|
+
const handleLabelChange = useCallback(
|
|
788
|
+
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
789
|
+
setEditingLabel(e.target.value);
|
|
790
|
+
},
|
|
791
|
+
[],
|
|
792
|
+
);
|
|
793
|
+
|
|
794
|
+
// Handle condition change
|
|
795
|
+
const handleConditionChange = (idx: number, value: string) => {
|
|
796
|
+
const updated = [...editingConditions];
|
|
797
|
+
updated[idx] = value;
|
|
798
|
+
setEditingConditions(updated);
|
|
799
|
+
};
|
|
800
|
+
|
|
801
|
+
// Handle add condition
|
|
802
|
+
const handleAddCondition = () => {
|
|
803
|
+
setEditingConditions([...editingConditions, '']);
|
|
804
|
+
};
|
|
805
|
+
|
|
806
|
+
// Handle remove condition
|
|
807
|
+
const handleRemoveCondition = (idx: number) => {
|
|
808
|
+
setEditingConditions(editingConditions.filter((_, i) => i !== idx));
|
|
809
|
+
};
|
|
810
|
+
|
|
811
|
+
// Save the edited label
|
|
812
|
+
const saveLabel = useCallback(() => {
|
|
813
|
+
if (editingNode && editingType === 'node') {
|
|
814
|
+
setNodes((nds) =>
|
|
815
|
+
nds.map((node) => {
|
|
816
|
+
if (node.id === editingNode) {
|
|
817
|
+
return {
|
|
818
|
+
...node,
|
|
819
|
+
data: {
|
|
820
|
+
...node.data,
|
|
821
|
+
label: editingLabel,
|
|
822
|
+
},
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
return node;
|
|
826
|
+
}),
|
|
827
|
+
);
|
|
828
|
+
setEditingNode(null);
|
|
829
|
+
} else if (editingNode && editingType === 'fragment') {
|
|
830
|
+
setNodes((nds) =>
|
|
831
|
+
nds.map((node) => {
|
|
832
|
+
if (node.id === editingNode) {
|
|
833
|
+
return {
|
|
834
|
+
...node,
|
|
835
|
+
data: {
|
|
836
|
+
...node.data,
|
|
837
|
+
label: editingLabel,
|
|
838
|
+
conditions: editingConditions,
|
|
839
|
+
},
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
return node;
|
|
843
|
+
}),
|
|
844
|
+
);
|
|
845
|
+
setEditingNode(null);
|
|
846
|
+
setEditingType('node');
|
|
847
|
+
} else if (editingEdge && editingType === 'edge') {
|
|
848
|
+
setEdges((eds) =>
|
|
849
|
+
eds.map((edge) => {
|
|
850
|
+
if (edge.id === editingEdge) {
|
|
851
|
+
return {
|
|
852
|
+
...edge,
|
|
853
|
+
data: {
|
|
854
|
+
...edge.data,
|
|
855
|
+
label: editingLabel,
|
|
856
|
+
},
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
return edge;
|
|
860
|
+
}),
|
|
861
|
+
);
|
|
862
|
+
setEditingEdge(null);
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Reset editing state
|
|
866
|
+
setEditingType('node');
|
|
867
|
+
}, [
|
|
868
|
+
editingNode,
|
|
869
|
+
editingEdge,
|
|
870
|
+
editingType,
|
|
871
|
+
editingLabel,
|
|
872
|
+
editingConditions,
|
|
873
|
+
setNodes,
|
|
874
|
+
setEdges,
|
|
875
|
+
]);
|
|
876
|
+
|
|
877
|
+
// Handle keyboard events for the input field
|
|
878
|
+
const handleKeyDown = useCallback(
|
|
879
|
+
(e: React.KeyboardEvent) => {
|
|
880
|
+
if (e.key === 'Enter') {
|
|
881
|
+
saveLabel();
|
|
882
|
+
} else if (e.key === 'Escape') {
|
|
883
|
+
setEditingNode(null);
|
|
884
|
+
setEditingEdge(null);
|
|
885
|
+
}
|
|
886
|
+
},
|
|
887
|
+
[saveLabel],
|
|
888
|
+
);
|
|
889
|
+
|
|
890
|
+
// Cancel editing
|
|
891
|
+
const cancelEditing = useCallback(() => {
|
|
892
|
+
setEditingNode(null);
|
|
893
|
+
setEditingEdge(null);
|
|
894
|
+
setEditingType('node');
|
|
895
|
+
}, []);
|
|
896
|
+
|
|
897
|
+
// Handler to add a new alt fragment
|
|
898
|
+
const handleAddAltFragment = useCallback(
|
|
899
|
+
(fragmentType: CombinedFragmentType = 'alt') => {
|
|
900
|
+
console.log(`Adding new ${fragmentType} fragment...`);
|
|
901
|
+
|
|
902
|
+
// Find all fragment nodes
|
|
903
|
+
const fragmentNodes = getCombinedFragmentNodes();
|
|
904
|
+
const fragmentCount = fragmentNodes.length;
|
|
905
|
+
const newFragmentId = `${fragmentType}-fragment-${fragmentCount + 1}`;
|
|
906
|
+
|
|
907
|
+
// Find the lowest y position among fragments
|
|
908
|
+
const defaultX = 250; // Fixed x position for all fragments
|
|
909
|
+
const defaultY = 300; // Default starting y for fragments
|
|
910
|
+
|
|
911
|
+
const lowestY =
|
|
912
|
+
fragmentNodes.length > 0
|
|
913
|
+
? Math.max(...fragmentNodes.map((node) => node.position.y))
|
|
914
|
+
: defaultY;
|
|
915
|
+
|
|
916
|
+
const yOffset = 200; // Space between fragments vertically
|
|
917
|
+
const centerX = defaultX;
|
|
918
|
+
const centerY = fragmentNodes.length > 0 ? lowestY + yOffset : lowestY;
|
|
919
|
+
|
|
920
|
+
// Set up conditions based on fragment type
|
|
921
|
+
let conditions: string[] = [];
|
|
922
|
+
switch (fragmentType) {
|
|
923
|
+
case 'alt':
|
|
924
|
+
conditions = ['[condition]', '[else]'];
|
|
925
|
+
break;
|
|
926
|
+
case 'opt':
|
|
927
|
+
conditions = ['[condition]'];
|
|
928
|
+
break;
|
|
929
|
+
case 'loop':
|
|
930
|
+
conditions = ['[loop condition]'];
|
|
931
|
+
break;
|
|
932
|
+
case 'par':
|
|
933
|
+
conditions = ['[parallel 1]', '[parallel 2]'];
|
|
934
|
+
break;
|
|
935
|
+
default:
|
|
936
|
+
conditions = ['[condition]'];
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
// Create a new fragment node
|
|
940
|
+
const newFragment: Node<CombinedFragmentData> = {
|
|
941
|
+
id: newFragmentId,
|
|
942
|
+
type: 'combinedFragment',
|
|
943
|
+
position: { x: centerX, y: centerY },
|
|
944
|
+
style: {
|
|
945
|
+
width: fragmentSettings.width,
|
|
946
|
+
height: fragmentSettings.height,
|
|
947
|
+
},
|
|
948
|
+
data: {
|
|
949
|
+
label: fragmentType,
|
|
950
|
+
conditions: conditions,
|
|
951
|
+
style: {
|
|
952
|
+
width: fragmentSettings.width,
|
|
953
|
+
height: fragmentSettings.height,
|
|
954
|
+
},
|
|
955
|
+
color: '#000000',
|
|
956
|
+
backgroundColor: '#ffffff',
|
|
957
|
+
lifelineLength: 0,
|
|
958
|
+
},
|
|
959
|
+
draggable: true,
|
|
960
|
+
zIndex: 0, // Lower z-index to appear behind other elements
|
|
961
|
+
};
|
|
962
|
+
|
|
963
|
+
setNodes((nds) => [...nds, newFragment]);
|
|
964
|
+
setSelectedFragmentId(newFragmentId);
|
|
965
|
+
setShowFragmentSettings(true);
|
|
966
|
+
},
|
|
967
|
+
[fragmentSettings, getCombinedFragmentNodes, setNodes],
|
|
968
|
+
);
|
|
969
|
+
|
|
970
|
+
// Function to update selected fragment
|
|
971
|
+
const updateSelectedFragment = useCallback(
|
|
972
|
+
(fragmentId: string, updates: FragmentSettingsUpdateData) => {
|
|
973
|
+
setNodes((nds) =>
|
|
974
|
+
nds.map((node) => {
|
|
975
|
+
if (node.id === fragmentId) {
|
|
976
|
+
// Safely handle the style spread by ensuring the fragment data has a style property
|
|
977
|
+
const fragmentData = node.data;
|
|
978
|
+
const currentStyle = fragmentData.style || {};
|
|
979
|
+
// Apply updates to both style and data.style to ensure consistency
|
|
980
|
+
const updatedNode = {
|
|
981
|
+
...node,
|
|
982
|
+
style: { ...node.style, ...updates },
|
|
983
|
+
data: {
|
|
984
|
+
...fragmentData,
|
|
985
|
+
style: { ...currentStyle, ...updates },
|
|
986
|
+
},
|
|
987
|
+
};
|
|
988
|
+
return updatedNode;
|
|
989
|
+
}
|
|
990
|
+
return node;
|
|
991
|
+
}),
|
|
992
|
+
);
|
|
993
|
+
},
|
|
994
|
+
[setNodes],
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
const handleFragmentWidthChange = useCallback(
|
|
998
|
+
(width: number) => {
|
|
999
|
+
setFragmentSettings((prev) => ({ ...prev, width }));
|
|
1000
|
+
|
|
1001
|
+
if (selectedFragmentId) {
|
|
1002
|
+
updateSelectedFragment(selectedFragmentId, { width });
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
1005
|
+
[selectedFragmentId, updateSelectedFragment],
|
|
1006
|
+
);
|
|
1007
|
+
|
|
1008
|
+
const handleFragmentHeightChange = useCallback(
|
|
1009
|
+
(height: number) => {
|
|
1010
|
+
setFragmentSettings((prev) => ({ ...prev, height }));
|
|
1011
|
+
|
|
1012
|
+
if (selectedFragmentId) {
|
|
1013
|
+
updateSelectedFragment(selectedFragmentId, { height });
|
|
1014
|
+
}
|
|
1015
|
+
},
|
|
1016
|
+
[selectedFragmentId, updateSelectedFragment],
|
|
1017
|
+
);
|
|
1018
|
+
|
|
1019
|
+
const closeFragmentSettings = useCallback(() => {
|
|
1020
|
+
setShowFragmentSettings(false);
|
|
1021
|
+
setSelectedFragmentId(null);
|
|
1022
|
+
}, []);
|
|
1023
|
+
|
|
1024
|
+
// Function to update selected entity
|
|
1025
|
+
const updateSelectedEntity = useCallback(
|
|
1026
|
+
(updates: EntitySettingsUpdateData) => {
|
|
1027
|
+
if (!selectedEntityId) return;
|
|
1028
|
+
|
|
1029
|
+
setNodes((nds) =>
|
|
1030
|
+
nds.map((node) => {
|
|
1031
|
+
if (node.id === selectedEntityId) {
|
|
1032
|
+
return {
|
|
1033
|
+
...node,
|
|
1034
|
+
data: {
|
|
1035
|
+
...node.data,
|
|
1036
|
+
...updates,
|
|
1037
|
+
},
|
|
1038
|
+
};
|
|
1039
|
+
}
|
|
1040
|
+
return node;
|
|
1041
|
+
}),
|
|
1042
|
+
);
|
|
1043
|
+
},
|
|
1044
|
+
[selectedEntityId, setNodes],
|
|
1045
|
+
);
|
|
1046
|
+
|
|
1047
|
+
// Close entity settings
|
|
1048
|
+
const closeEntitySettings = useCallback(() => {
|
|
1049
|
+
setShowEntitySettings(false);
|
|
1050
|
+
setSelectedEntityId(null);
|
|
1051
|
+
}, []);
|
|
1052
|
+
|
|
1053
|
+
// Handler to load a template diagram
|
|
1054
|
+
const loadTemplateData = useCallback(
|
|
1055
|
+
(templateType: string) => {
|
|
1056
|
+
console.log(`Loading template: ${templateType}`);
|
|
1057
|
+
|
|
1058
|
+
const template = getTemplateData(templateType);
|
|
1059
|
+
|
|
1060
|
+
if (template.nodes.length > 0) {
|
|
1061
|
+
setNodes(template.nodes);
|
|
1062
|
+
setEdges(template.edges);
|
|
1063
|
+
}
|
|
1064
|
+
},
|
|
1065
|
+
[setNodes, setEdges],
|
|
1066
|
+
);
|
|
1067
|
+
|
|
1068
|
+
return (
|
|
1069
|
+
<>
|
|
1070
|
+
<Box sx={{ display: 'flex', width: '100%', height: '100vh' }}>
|
|
1071
|
+
<Sidebar>
|
|
1072
|
+
<DiagramControls
|
|
1073
|
+
onAddEntity={handleAddEntity}
|
|
1074
|
+
onAddMessage={handleAddMessage}
|
|
1075
|
+
onAddAltFragment={handleAddAltFragment}
|
|
1076
|
+
onUndo={handleUndo}
|
|
1077
|
+
onRedo={handleRedo}
|
|
1078
|
+
canUndo={historyIndex > 0}
|
|
1079
|
+
canRedo={historyIndex < history.length - 1}
|
|
1080
|
+
onLoadTemplate={loadTemplateData}
|
|
1081
|
+
/>
|
|
1082
|
+
</Sidebar>
|
|
1083
|
+
<Box sx={{ flex: 1, height: '100%', position: 'relative' }}>
|
|
1084
|
+
<div
|
|
1085
|
+
ref={diagramContainerRef}
|
|
1086
|
+
className={`diagram-container ${isFullscreen ? 'fullscreen' : ''}`}
|
|
1087
|
+
style={{
|
|
1088
|
+
width: '100%',
|
|
1089
|
+
height: '100%',
|
|
1090
|
+
border: '1px solid #333',
|
|
1091
|
+
position: 'relative',
|
|
1092
|
+
backgroundColor: '#1a1a1a',
|
|
1093
|
+
color: '#ffffff',
|
|
1094
|
+
}}
|
|
1095
|
+
>
|
|
1096
|
+
<ReactFlow<Node, Edge>
|
|
1097
|
+
nodes={nodes}
|
|
1098
|
+
edges={edges}
|
|
1099
|
+
onNodesChange={onNodesChangeLockedY}
|
|
1100
|
+
onEdgesChange={onEdgesChange}
|
|
1101
|
+
onConnect={onConnect}
|
|
1102
|
+
nodeTypes={sequenceNodeTypes}
|
|
1103
|
+
edgeTypes={sequenceEdgeTypes}
|
|
1104
|
+
fitView
|
|
1105
|
+
attributionPosition="bottom-left"
|
|
1106
|
+
connectOnClick={false}
|
|
1107
|
+
onNodeClick={onNodeClick}
|
|
1108
|
+
onNodeDoubleClick={onNodeDoubleClick}
|
|
1109
|
+
onEdgeDoubleClick={onEdgeDoubleClick}
|
|
1110
|
+
style={{
|
|
1111
|
+
backgroundColor: '#1a1a1a',
|
|
1112
|
+
}}
|
|
1113
|
+
>
|
|
1114
|
+
<Background
|
|
1115
|
+
color="#333"
|
|
1116
|
+
variant={BackgroundVariant.Dots}
|
|
1117
|
+
gap={16}
|
|
1118
|
+
/>
|
|
1119
|
+
</ReactFlow>
|
|
1120
|
+
|
|
1121
|
+
{/* Components outside ReactFlow */}
|
|
1122
|
+
<DiagramPanel
|
|
1123
|
+
historyIndex={historyIndex}
|
|
1124
|
+
historyLength={history.length}
|
|
1125
|
+
onUndo={handleUndo}
|
|
1126
|
+
onRedo={handleRedo}
|
|
1127
|
+
onClear={() => {
|
|
1128
|
+
const clearedState: SequenceHistoryEntry = { nodes: [], edges: [] };
|
|
1129
|
+
const newHistory = history.slice(0, historyIndex + 1);
|
|
1130
|
+
newHistory.push(clearedState);
|
|
1131
|
+
setNodes([]);
|
|
1132
|
+
setEdges([]);
|
|
1133
|
+
setHistory(newHistory);
|
|
1134
|
+
setHistoryIndex(newHistory.length - 1);
|
|
1135
|
+
}}
|
|
1136
|
+
onImport={() => { }}
|
|
1137
|
+
isSettingsOpen={false}
|
|
1138
|
+
onToggleSettings={() => { }}
|
|
1139
|
+
/>
|
|
1140
|
+
|
|
1141
|
+
<AiButton />
|
|
1142
|
+
|
|
1143
|
+
<MiniMapCommon />
|
|
1144
|
+
|
|
1145
|
+
{/* Fragment settings panel */}
|
|
1146
|
+
{showFragmentSettings && (
|
|
1147
|
+
<FragmentSettings
|
|
1148
|
+
fragmentId={selectedFragmentId}
|
|
1149
|
+
width={fragmentSettings.width}
|
|
1150
|
+
height={fragmentSettings.height}
|
|
1151
|
+
onWidthChange={handleFragmentWidthChange}
|
|
1152
|
+
onHeightChange={handleFragmentHeightChange}
|
|
1153
|
+
onClose={closeFragmentSettings}
|
|
1154
|
+
/>
|
|
1155
|
+
)}
|
|
1156
|
+
|
|
1157
|
+
{/* Entity settings panel */}
|
|
1158
|
+
{showEntitySettings && selectedEntityId && (
|
|
1159
|
+
<EntitySettings
|
|
1160
|
+
entityId={selectedEntityId}
|
|
1161
|
+
data={
|
|
1162
|
+
(nodes.find((node) =>
|
|
1163
|
+
node.id === selectedEntityId && node.type === 'sequenceEntity'
|
|
1164
|
+
)?.data as SequenceEntityData) || {
|
|
1165
|
+
label: '',
|
|
1166
|
+
color: '#61dafb',
|
|
1167
|
+
backgroundColor: '#2a2a2a',
|
|
1168
|
+
lifelineLength: 500
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
onUpdate={updateSelectedEntity}
|
|
1172
|
+
onClose={closeEntitySettings}
|
|
1173
|
+
/>
|
|
1174
|
+
)}
|
|
1175
|
+
|
|
1176
|
+
{/* Label editing overlay */}
|
|
1177
|
+
{(editingNode || editingEdge) && (
|
|
1178
|
+
<Box
|
|
1179
|
+
sx={{
|
|
1180
|
+
position: 'absolute',
|
|
1181
|
+
zIndex: 10,
|
|
1182
|
+
top: 10,
|
|
1183
|
+
left: '50%',
|
|
1184
|
+
transform: 'translateX(-50%)',
|
|
1185
|
+
bgcolor: '#242424',
|
|
1186
|
+
border: '1px solid #333',
|
|
1187
|
+
color: '#fff',
|
|
1188
|
+
p: 2,
|
|
1189
|
+
borderRadius: 1,
|
|
1190
|
+
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.3)',
|
|
1191
|
+
minWidth: 320,
|
|
1192
|
+
'& .MuiTextField-root': {
|
|
1193
|
+
'& .MuiInputLabel-root': {
|
|
1194
|
+
color: '#999',
|
|
1195
|
+
},
|
|
1196
|
+
'& .MuiOutlinedInput-root': {
|
|
1197
|
+
color: '#fff',
|
|
1198
|
+
'& fieldset': {
|
|
1199
|
+
borderColor: '#444',
|
|
1200
|
+
},
|
|
1201
|
+
'&:hover fieldset': {
|
|
1202
|
+
borderColor: '#61dafb',
|
|
1203
|
+
},
|
|
1204
|
+
'&.Mui-focused fieldset': {
|
|
1205
|
+
borderColor: '#61dafb',
|
|
1206
|
+
},
|
|
1207
|
+
},
|
|
1208
|
+
},
|
|
1209
|
+
'& .MuiButton-contained': {
|
|
1210
|
+
bgcolor: '#61dafb',
|
|
1211
|
+
color: '#000',
|
|
1212
|
+
'&:hover': {
|
|
1213
|
+
bgcolor: '#4db6d6',
|
|
1214
|
+
},
|
|
1215
|
+
},
|
|
1216
|
+
'& .MuiButton-outlined': {
|
|
1217
|
+
borderColor: '#333',
|
|
1218
|
+
color: '#fff',
|
|
1219
|
+
'&:hover': {
|
|
1220
|
+
borderColor: '#61dafb',
|
|
1221
|
+
bgcolor: 'rgba(97, 218, 251, 0.1)',
|
|
1222
|
+
},
|
|
1223
|
+
},
|
|
1224
|
+
'& .MuiIconButton-root': {
|
|
1225
|
+
color: '#999',
|
|
1226
|
+
'&:hover': {
|
|
1227
|
+
color: '#fff',
|
|
1228
|
+
bgcolor: 'rgba(255, 255, 255, 0.1)',
|
|
1229
|
+
},
|
|
1230
|
+
},
|
|
1231
|
+
}}
|
|
1232
|
+
>
|
|
1233
|
+
<Typography sx={{ mb: 1, fontWeight: 'bold', color: '#fff' }}>
|
|
1234
|
+
{editingType === 'node'
|
|
1235
|
+
? t('sequenceFlow.editLabels.entityLabel')
|
|
1236
|
+
: editingType === 'edge'
|
|
1237
|
+
? t('sequenceFlow.editLabels.messageLabel')
|
|
1238
|
+
: t('sequenceFlow.editLabels.editFragment')}
|
|
1239
|
+
</Typography>
|
|
1240
|
+
{editingType === 'fragment' ? (
|
|
1241
|
+
<>
|
|
1242
|
+
<Box display="flex" alignItems="center" gap={2} mb={2}>
|
|
1243
|
+
<TextField
|
|
1244
|
+
type="text"
|
|
1245
|
+
value={editingLabel}
|
|
1246
|
+
onChange={handleLabelChange}
|
|
1247
|
+
onKeyDown={handleKeyDown}
|
|
1248
|
+
autoFocus
|
|
1249
|
+
size="small"
|
|
1250
|
+
sx={{ width: 200 }}
|
|
1251
|
+
label={t('fragmentSettings.fragmentName')}
|
|
1252
|
+
/>
|
|
1253
|
+
</Box>
|
|
1254
|
+
{/* Conditions editing */}
|
|
1255
|
+
<Box mb={2}>
|
|
1256
|
+
<Typography
|
|
1257
|
+
variant="body2"
|
|
1258
|
+
fontWeight="bold"
|
|
1259
|
+
mb={0.5}
|
|
1260
|
+
sx={{ color: '#61dafb' }}
|
|
1261
|
+
>
|
|
1262
|
+
{t('fragmentSettings.conditions')}
|
|
1263
|
+
</Typography>
|
|
1264
|
+
{editingConditions.map((cond, idx) => (
|
|
1265
|
+
<Box
|
|
1266
|
+
key={idx}
|
|
1267
|
+
display="flex"
|
|
1268
|
+
alignItems="center"
|
|
1269
|
+
mb={1}
|
|
1270
|
+
>
|
|
1271
|
+
<TextField
|
|
1272
|
+
value={cond}
|
|
1273
|
+
onChange={(e) =>
|
|
1274
|
+
handleConditionChange(idx, e.target.value)
|
|
1275
|
+
}
|
|
1276
|
+
size="small"
|
|
1277
|
+
fullWidth
|
|
1278
|
+
sx={{ mr: 1 }}
|
|
1279
|
+
/>
|
|
1280
|
+
<IconButton
|
|
1281
|
+
onClick={() => handleRemoveCondition(idx)}
|
|
1282
|
+
size="small"
|
|
1283
|
+
>
|
|
1284
|
+
<CloseIcon fontSize="small" />
|
|
1285
|
+
</IconButton>
|
|
1286
|
+
</Box>
|
|
1287
|
+
))}
|
|
1288
|
+
<Button
|
|
1289
|
+
onClick={handleAddCondition}
|
|
1290
|
+
size="small"
|
|
1291
|
+
variant="outlined"
|
|
1292
|
+
>
|
|
1293
|
+
{t('fragmentSettings.addCondition')}
|
|
1294
|
+
</Button>
|
|
1295
|
+
</Box>
|
|
1296
|
+
<Box display="flex" gap={2}>
|
|
1297
|
+
<Button
|
|
1298
|
+
variant="contained"
|
|
1299
|
+
size="small"
|
|
1300
|
+
onClick={saveLabel}
|
|
1301
|
+
sx={{ textTransform: 'none' }}
|
|
1302
|
+
>
|
|
1303
|
+
{t('common.save')}
|
|
1304
|
+
</Button>
|
|
1305
|
+
<Button
|
|
1306
|
+
variant="outlined"
|
|
1307
|
+
size="small"
|
|
1308
|
+
onClick={cancelEditing}
|
|
1309
|
+
sx={{ textTransform: 'none' }}
|
|
1310
|
+
>
|
|
1311
|
+
{t('common.cancel')}
|
|
1312
|
+
</Button>
|
|
1313
|
+
</Box>
|
|
1314
|
+
</>
|
|
1315
|
+
) : (
|
|
1316
|
+
<Box display="flex" alignItems="center" gap={2}>
|
|
1317
|
+
<TextField
|
|
1318
|
+
type="text"
|
|
1319
|
+
value={editingLabel}
|
|
1320
|
+
onChange={handleLabelChange}
|
|
1321
|
+
onKeyDown={handleKeyDown}
|
|
1322
|
+
autoFocus
|
|
1323
|
+
size="small"
|
|
1324
|
+
sx={{ width: 200 }}
|
|
1325
|
+
/>
|
|
1326
|
+
<Button
|
|
1327
|
+
variant="contained"
|
|
1328
|
+
size="small"
|
|
1329
|
+
onClick={saveLabel}
|
|
1330
|
+
sx={{ textTransform: 'none' }}
|
|
1331
|
+
>
|
|
1332
|
+
{t('common.save')}
|
|
1333
|
+
</Button>
|
|
1334
|
+
<Button
|
|
1335
|
+
variant="outlined"
|
|
1336
|
+
size="small"
|
|
1337
|
+
onClick={cancelEditing}
|
|
1338
|
+
sx={{ textTransform: 'none' }}
|
|
1339
|
+
>
|
|
1340
|
+
{t('common.cancel')}
|
|
1341
|
+
</Button>
|
|
1342
|
+
</Box>
|
|
1343
|
+
)}
|
|
1344
|
+
</Box>
|
|
1345
|
+
)}
|
|
1346
|
+
|
|
1347
|
+
{/* Add Message Dialog */}
|
|
1348
|
+
<AddMessageDialog
|
|
1349
|
+
open={showMessageDialog}
|
|
1350
|
+
onClose={() => setShowMessageDialog(false)}
|
|
1351
|
+
onAddMessage={handleAddCustomMessage}
|
|
1352
|
+
entities={sequenceEntities()}
|
|
1353
|
+
/>
|
|
1354
|
+
</div>
|
|
1355
|
+
</Box>
|
|
1356
|
+
</Box>
|
|
1357
|
+
</>
|
|
1358
|
+
);
|
|
1359
|
+
};
|
|
1360
|
+
|
|
1361
|
+
export default SequenceDiagramFlow;
|