@flowuent-org/diagramming-core 1.0.5
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 -0
- package/.eslintignore +1 -0
- package/.eslintrc.json +35 -0
- package/.prettierignore +4 -0
- package/.prettierrc +3 -0
- package/.vscode/settings.json +6 -0
- package/README.md +95 -0
- package/apps/authentication/tsconfig.app.json +23 -0
- package/apps/authentication/tsconfig.json +21 -0
- package/apps/authentication/tsconfig.spec.json +28 -0
- package/apps/authentication/vite.config.ts +0 -0
- package/apps/diagramming/.eslintrc.json +18 -0
- package/apps/diagramming/index.html +16 -0
- package/apps/diagramming/project.json +53 -0
- package/apps/diagramming/public/aws-icons/compute/App Runner.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Application Auto Scaling.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Batch.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Bottlerocket.svg +20 -0
- package/apps/diagramming/public/aws-icons/compute/Compute Optimizer.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/EC2 Auto Scaling.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/EC2 Image Builder.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/EC2.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Elastic Beanstalk.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Elastic Fabric Adapter.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Fargate.svg +20 -0
- package/apps/diagramming/public/aws-icons/compute/Genomics CLI.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Lambda.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Local Zones.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/NICE DCV.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/NICE EnginFrame.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Nitro Enclaves.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Outposts family.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Outposts rack.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Outposts servers.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/ParallelCluster.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Serverless Application Repository.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/SimSpace Weaver.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Deadline.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Frost.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Sequoia.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Stoke.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Thinkbox XMesh.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/VMware Cloud on AWS.svg +18 -0
- package/apps/diagramming/public/aws-icons/compute/Wavelength.svg +18 -0
- package/apps/diagramming/public/aws-icons/container/ECS Anywhere.svg +18 -0
- package/apps/diagramming/public/aws-icons/container/EKS Anywhere.svg +18 -0
- package/apps/diagramming/public/aws-icons/container/EKS Cloud.svg +18 -0
- package/apps/diagramming/public/aws-icons/container/EKS Distro.svg +18 -0
- package/apps/diagramming/public/aws-icons/container/Elastic Container Registry.svg +18 -0
- package/apps/diagramming/public/aws-icons/container/Elastic Container Service.svg +18 -0
- package/apps/diagramming/public/aws-icons/container/Elastic Kubernetes Service.svg +18 -0
- package/apps/diagramming/public/aws-icons/container/Fargate.svg +20 -0
- package/apps/diagramming/public/aws-icons/container/Red Hat OpenShift Service on AWS.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/Aurora.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/Database Migration Service.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/DocumentDB.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/ElastiCache.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/Keyspaces.svg +16 -0
- package/apps/diagramming/public/aws-icons/database/MemoryDB for Redis.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/Neptune.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/RDS on VMware.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/RDS.svg +18 -0
- package/apps/diagramming/public/aws-icons/database/Timestream.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/App Mesh.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Client VPN.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Cloud Directory.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Cloud Map.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Cloud WAN.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/CloudFront.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Direct Connect.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Elastic Load Balancing.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Global Accelerator.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Private 5G.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/PrivateLink.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Route 53.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Site to Site VPN.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Transit Gateway.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/VPC Lattice.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Verified Access.svg +18 -0
- package/apps/diagramming/public/aws-icons/networking/Virtual Private Cloud.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Artifact.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Audit Manager.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Certificate Manager.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/CloudHSM.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Cognito.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Detective.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Directory Service.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Firewall Manager.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/GuardDuty.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/IAM Identity Center.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Identity and Access Management.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Inspector.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Key Management Service.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Macie.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Network Firewall.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Private Certificate Authority.svg +1 -0
- package/apps/diagramming/public/aws-icons/security/Resource Access Manager.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Secrets Manager.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Security Hub.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Security Lake.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Shield.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Signer.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/Verified Permissions.svg +18 -0
- package/apps/diagramming/public/aws-icons/security/WAF.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Backup.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/EFS.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Elastic Block Store.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Elastic Disaster Recovery.svg +16 -0
- package/apps/diagramming/public/aws-icons/storage/FSx for Lustre.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/FSx for NetApp ONTAP.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/FSx for OpenZFS.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/FSx for WFS.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/FSx.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/File Cache.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/S3 on Outposts.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Simple Storage Service Glacier.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Simple Storage Service.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Snowball Edge.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Snowball.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Snowcone.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Snowmobile.svg +18 -0
- package/apps/diagramming/public/aws-icons/storage/Storage Gateway.svg +18 -0
- package/apps/diagramming/public/azure-icons/compute/00195-icon-service-Maintenance-Configuration.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/00328-icon-service-Host-Pools.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/00329-icon-service-Application-Group.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/00330-icon-service-Workspaces.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/00398-icon-service-Disk-Encryption-Sets.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/00400-icon-service-Workspaces.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/02112-icon-service-Automanaged-VM.svg +2 -0
- package/apps/diagramming/public/azure-icons/compute/02370-icon-service-Managed-Service-Fabric.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/02409-icon-service-Metrics-Advisor.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/02634-icon-service-Image-Templates.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/02817-icon-service-Restore-Points.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/02818-icon-service-Restore-Points-Collections.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/02864-icon-service-Azure-Compute-Galleries.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/03487-icon-service-Compute-Fleet.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/03543-icon-service-AKS-Automatic.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10021-icon-service-Virtual-Machine.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10023-icon-service-Kubernetes-Services.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10024-icon-service-Mesh-Applications.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10025-icon-service-Availability-Sets.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10026-icon-service-Disks-Snapshots.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10027-icon-service-OS-Images-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10028-icon-service-Virtual-Machines-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10029-icon-service-Function-Apps.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10030-icon-service-Cloud-Services-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10031-icon-service-Batch-Accounts.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10032-icon-service-Disks.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10033-icon-service-Images.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10034-icon-service-VM-Scale-Sets.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10035-icon-service-App-Services.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10036-icon-service-Service-Fabric-Clusters.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10037-icon-service-Image-Definitions.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10038-icon-service-Image-Versions.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10039-icon-service-Shared-Image-Galleries.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10040-icon-service-VM-Images-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10041-icon-service-Disks-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10049-icon-service-Container-Services-(Deprecated).svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10104-icon-service-Container-Instances.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10346-icon-service-Host-Groups.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10347-icon-service-Hosts.svg +1 -0
- package/apps/diagramming/public/azure-icons/compute/10370-icon-service-Azure-Spring-Apps.svg +1 -0
- package/apps/diagramming/public/azure-icons/containers/03331-icon-service-Azure-Red-Hat-OpenShift.svg +1 -0
- package/apps/diagramming/public/azure-icons/containers/10023-icon-service-Kubernetes-Services.svg +1 -0
- package/apps/diagramming/public/azure-icons/containers/10031-icon-service-Batch-Accounts.svg +1 -0
- package/apps/diagramming/public/azure-icons/containers/10035-icon-service-App-Services.svg +1 -0
- package/apps/diagramming/public/azure-icons/containers/10036-icon-service-Service-Fabric-Clusters.svg +1 -0
- package/apps/diagramming/public/azure-icons/containers/10104-icon-service-Container-Instances.svg +1 -0
- package/apps/diagramming/public/azure-icons/containers/10105-icon-service-Container-Registries.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/00036-icon-service-SQL-Data-Warehouses.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/00606-icon-service-Azure-Synapse-Analytics.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/02390-icon-service-Azure-SQL.svg +2 -0
- package/apps/diagramming/public/azure-icons/databases/02392-icon-service-SSIS-Lift-And-Shift-IR.svg +2 -0
- package/apps/diagramming/public/azure-icons/databases/02517-icon-service-Azure-Purview-Accounts.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/02750-icon-service-Azure-SQL-Edge.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/02827-icon-service-Azure-Database-PostgreSQL-Server-Group.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/03490-icon-service-Oracle-Database.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10121-icon-service-Azure-Cosmos-DB.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10122-icon-service-Azure-Database-MySQL-Server.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10123-icon-service-Azure-Database-MariaDB-Server.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10124-icon-service-Azure-SQL-VM.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10126-icon-service-Data-Factories.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10127-icon-service-Virtual-Clusters.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10128-icon-service-Elastic-Job-Agents.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10130-icon-service-SQL-Database.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10131-icon-service-Azure-Database-PostgreSQL-Server.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10132-icon-service-SQL-Server.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10133-icon-service-Azure-Database-Migration-Services.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10134-icon-service-SQL-Elastic-Pools.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10135-icon-service-Managed-Database.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10136-icon-service-SQL-Managed-Instance.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10137-icon-service-Azure-SQL-Server-Stretch-Databases.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10137-icon-service-Cache-Redis.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10139-icon-service-Instance-Pools.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10145-icon-service-Azure-Data-Explorer-Clusters.svg +1 -0
- package/apps/diagramming/public/azure-icons/databases/10351-icon-service-SQL-Server-Registries.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/00056-icon-service-CDN-Profiles.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/00271-icon-service-Azure-Firewall-Manager.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/00272-icon-service-Azure-Firewall-Policy.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/00427-icon-service-Private-Link.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/00701-icon-service-IP-Groups.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/00860-icon-service-Virtual-WAN-Hub.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/01105-icon-service-Private-Link-Service.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02145-icon-service-Resource-Management-Private-Link.svg +2 -0
- package/apps/diagramming/public/azure-icons/networking/02209-icon-service-Private-Link-Services.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02302-icon-service-Load-Balancer-Hub.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02422-icon-service-Bastions.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02496-icon-service-Virtual-Router.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02509-icon-service-Connected-Cache.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02692-icon-service-Spot-VMSS.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02695-icon-service-Spot-VM.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02742-icon-service-Subnet.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/02882-icon-service-DNS-Private-Resolver.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/03311-icon-service-Azure-Communications-Gateway.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/03328-icon-service-Application-Gateway-Containers.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/03368-icon-service-DNS-Security-Policy.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/03459-icon-service-DNS-Multistack.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/03460-icon-service-ATM-Multistack.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/03461-icon-service-IP-Address-manager.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10061-icon-service-Virtual-Networks.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10062-icon-service-Load-Balancers.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10063-icon-service-Virtual-Network-Gateways.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10064-icon-service-DNS-Zones.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10065-icon-service-Traffic-Manager-Profiles.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10066-icon-service-Network-Watcher.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10067-icon-service-Network-Security-Groups.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10068-icon-service-Public-IP-Addresses-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10069-icon-service-Public-IP-Addresses.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10070-icon-service-On-Premises-Data-Gateways.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10071-icon-service-Route-Filters.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10072-icon-service-DDoS-Protection-Plans.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10073-icon-service-Front-Door-and-CDN-Profiles.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10075-icon-service-Virtual-Networks-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10076-icon-service-Application-Gateways.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10077-icon-service-Local-Network-Gateways.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10079-icon-service-ExpressRoute-Circuits.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10080-icon-service-Network-Interfaces.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10081-icon-service-Connections.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10082-icon-service-Route-Tables.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10084-icon-service-Firewalls.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10085-icon-service-Service-Endpoint-Policies.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10310-icon-service-NAT.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10353-icon-service-Virtual-WANs.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10362-icon-service-Web-Application-Firewall-Policies(WAF).svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10365-icon-service-Proximity-Placement-Groups.svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10371-icon-service-Reserved-IP-Addresses-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/networking/10372-icon-service-Public-IP-Prefixes.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/00378-icon-service-Detonation.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/02247-icon-service-Microsoft-Defender-for-IoT.svg +2 -0
- package/apps/diagramming/public/azure-icons/security/03336-icon-service-Microsoft-Defender-EASM.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/03340-icon-service-Identity-Secure-Score.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/03341-icon-service-Entra-Identity-Risky-Signins.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/03342-icon-service-Entra-Identity-Risky-Users.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/03344-icon-service-Multifactor-Authentication.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/10229-icon-service-Azure-Information-Protection.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/10233-icon-service-Conditional-Access.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/10241-icon-service-Microsoft-Defender-for-Cloud.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/10244-icon-service-Application-Security-Groups.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/10245-icon-service-Key-Vaults.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/10248-icon-service-Azure-Sentinel.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/10433-icon-service-User-Settings.svg +1 -0
- package/apps/diagramming/public/azure-icons/security/10572-icon-service-ExtendedSecurityUpdates.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/00017-icon-service-Recovery-Services-Vaults.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/00691-icon-service-Azure-Databox-Gateway.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/00776-icon-service-Azure-HCP-Cache.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/03502-icon-service-Storage-Actions.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/03549-icon-service-Managed-File-Shares.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10086-icon-service-Storage-Accounts.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10087-icon-service-Storage-Accounts-(Classic).svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10089-icon-service-StorSimple-Device-Managers.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10090-icon-service-Data-Lake-Storage-Gen1.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10091-icon-service-Storage-Explorer.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10092-icon-service-StorSimple-Data-Managers.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10093-icon-service-Storage-Sync-Services.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10094-icon-service-Data-Box.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10095-icon-service-Azure-Stack-Edge.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10096-icon-service-Azure-NetApp-Files.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10097-icon-service-Data-Share-Invitations.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10098-icon-service-Data-Shares.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10100-icon-service-Import-Export-Jobs.svg +1 -0
- package/apps/diagramming/public/azure-icons/storage/10400-icon-service-Azure-Fileshares.svg +1 -0
- package/apps/diagramming/public/block.png +0 -0
- package/apps/diagramming/public/common/users.svg +7 -0
- package/apps/diagramming/public/favicon.ico +0 -0
- package/apps/diagramming/public/gcp-icons/compute/appengine.svg +1 -0
- package/apps/diagramming/public/gcp-icons/compute/cloud-functions.svg +1 -0
- package/apps/diagramming/public/gcp-icons/compute/cloud-gpu.svg +1 -0
- package/apps/diagramming/public/gcp-icons/compute/compute-engine.svg +1 -0
- package/apps/diagramming/public/gcp-icons/compute/container-optimized-os.svg +1 -0
- package/apps/diagramming/public/gcp-icons/database&storage/cloud-bigtable.svg +1 -0
- package/apps/diagramming/public/gcp-icons/database&storage/cloud-spanner.svg +1 -0
- package/apps/diagramming/public/gcp-icons/database&storage/cloud-sql.svg +1 -0
- package/apps/diagramming/public/gcp-icons/database&storage/cloud-storage.svg +1 -0
- package/apps/diagramming/public/gcp-icons/database&storage/filestore.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-cdn.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-dns.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-firewall-rules.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-interconnect.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-load-balancing.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-network.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-router.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-routes.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/cloud-vpn.svg +1 -0
- package/apps/diagramming/public/gcp-icons/networking/virtual-private-cloud.svg +1 -0
- package/apps/diagramming/public/gcp-icons/security/beyondcorp.svg +1 -0
- package/apps/diagramming/public/gcp-icons/security/data-loss-prevention-api.svg +1 -0
- package/apps/diagramming/public/gcp-icons/security/iam.svg +1 -0
- package/apps/diagramming/public/gcp-icons/security/key-access-justifications.svg +1 -0
- package/apps/diagramming/public/gcp-icons/security/security-command-center.svg +1 -0
- package/apps/diagramming/public/gcp-icons/security/web-security-scanner.svg +1 -0
- package/apps/diagramming/public/sample-node-complete.json +63 -0
- package/apps/diagramming/public/sample-node-data.json +63 -0
- package/apps/diagramming/public/text.png +0 -0
- package/apps/diagramming/src/AutomationDiagramData.ts +541 -0
- package/apps/diagramming/src/CollaborationDiagramData.ts +127 -0
- package/apps/diagramming/src/CustomIconExamples.ts +233 -0
- package/apps/diagramming/src/DiagramTabs.tsx +205 -0
- package/apps/diagramming/src/Pages/DevPlayground.tsx +3 -0
- package/apps/diagramming/src/Pages/Home.tsx +112 -0
- package/apps/diagramming/src/SequenceDiagramData.ts +215 -0
- package/apps/diagramming/src/StateMachineDiagramData.ts +64 -0
- package/apps/diagramming/src/UseCaseDiagramData.ts +52 -0
- package/apps/diagramming/src/cloud-data.ts +371 -0
- package/apps/diagramming/src/components/AddNodeView.tsx +252 -0
- package/apps/diagramming/src/createSelecors.tsx +17 -0
- package/apps/diagramming/src/index.scss +13 -0
- package/apps/diagramming/src/main.tsx +463 -0
- package/apps/diagramming/src/node-data.ts +664 -0
- package/apps/diagramming/src/sample-workflow-content.ts +54 -0
- package/apps/diagramming/src/stencil-items.ts +31 -0
- package/apps/diagramming/src/style.css +13 -0
- package/apps/diagramming/src/tabsStore.tsx +152 -0
- package/apps/diagramming/src/vite-env.d.ts +1 -0
- package/apps/diagramming/tsconfig.app.json +23 -0
- package/apps/diagramming/tsconfig.json +21 -0
- package/apps/diagramming/tsconfig.spec.json +28 -0
- package/apps/diagramming/vite.config.ts +34 -0
- package/apps/diagramming/webpack.config.js +13 -0
- package/apps/workflow/.eslintrc.json +18 -0
- package/apps/workflow/group/group-form-hooks/index.ts +3 -0
- package/apps/workflow/group/group-form-hooks/useFormValidation.ts +20 -0
- package/apps/workflow/group/group-form-schema/SettingsTabFormSchema.ts +7 -0
- package/apps/workflow/group/group-form-schema/index.ts +3 -0
- package/apps/workflow/i18n.ts +19 -0
- package/apps/workflow/index.html +16 -0
- package/apps/workflow/locales/en/translation.json +713 -0
- package/apps/workflow/project.json +8 -0
- package/apps/workflow/public/favicon.ico +0 -0
- package/apps/workflow/src/app/app.module.css +1 -0
- package/apps/workflow/src/app/app.spec.tsx +15 -0
- package/apps/workflow/src/app/app.tsx +370 -0
- package/apps/workflow/src/assets/.gitkeep +0 -0
- package/apps/workflow/src/main.tsx +13 -0
- package/apps/workflow/src/styles.css +1 -0
- package/apps/workflow/src/theme/index.ts +546 -0
- package/apps/workflow/tsconfig.app.json +23 -0
- package/apps/workflow/tsconfig.json +21 -0
- package/apps/workflow/tsconfig.spec.json +28 -0
- package/apps/workflow/vite.config.ts +51 -0
- package/nx.json +58 -0
- package/package.json +116 -0
- package/packages/api/.babelrc +12 -0
- package/packages/api/.eslintrc.json +18 -0
- package/packages/api/README.md +7 -0
- package/packages/api/project.json +8 -0
- package/packages/api/src/index.ts +3 -0
- package/packages/api/src/lib/jsonPlaceholderService/endpoints.ts +3 -0
- package/packages/api/src/lib/jsonPlaceholderService/service.ts +13 -0
- package/packages/api/tsconfig.json +17 -0
- package/packages/api/tsconfig.lib.json +24 -0
- package/packages/atoms/.babelrc +12 -0
- package/packages/atoms/.eslintrc.json +18 -0
- package/packages/atoms/README.md +7 -0
- package/packages/atoms/forms/AuthenticationTabForm/authenticationTabForm.json +224 -0
- package/packages/atoms/forms/AuthenticationTabForm/index.tsx +255 -0
- package/packages/atoms/forms/SettingsTabForm/index.tsx +85 -0
- package/packages/atoms/forms/SettingsTabForm/settingTabForm.json +27 -0
- package/packages/atoms/forms/index.ts +4 -0
- package/packages/atoms/project.json +8 -0
- package/packages/atoms/src/index.ts +33 -0
- package/packages/atoms/src/lib/Accordion/Accordion.module.css +7 -0
- package/packages/atoms/src/lib/Accordion/Accordion.spec.tsx +13 -0
- package/packages/atoms/src/lib/Accordion/Accordion.tsx +29 -0
- package/packages/atoms/src/lib/Alert/Alert.module.css +7 -0
- package/packages/atoms/src/lib/Alert/Alert.spec.tsx +10 -0
- package/packages/atoms/src/lib/Alert/Alert.tsx +19 -0
- package/packages/atoms/src/lib/Avatar/Avatar.module.css +7 -0
- package/packages/atoms/src/lib/Avatar/Avatar.spec.tsx +10 -0
- package/packages/atoms/src/lib/Avatar/Avatar.tsx +22 -0
- package/packages/atoms/src/lib/Box/Box.module.css +7 -0
- package/packages/atoms/src/lib/Box/Box.spec.tsx +10 -0
- package/packages/atoms/src/lib/Box/Box.tsx +19 -0
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.module.css +7 -0
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.spec.tsx +10 -0
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.tsx +15 -0
- package/packages/atoms/src/lib/Button/Button.module.css +7 -0
- package/packages/atoms/src/lib/Button/Button.spec.tsx +10 -0
- package/packages/atoms/src/lib/Button/Button.tsx +78 -0
- package/packages/atoms/src/lib/Card/Card.module.css +7 -0
- package/packages/atoms/src/lib/Card/Card.spec.tsx +10 -0
- package/packages/atoms/src/lib/Card/Card.tsx +19 -0
- package/packages/atoms/src/lib/CardContent/CardContent.module.css +7 -0
- package/packages/atoms/src/lib/CardContent/CardContent.spec.tsx +10 -0
- package/packages/atoms/src/lib/CardContent/CardContent.tsx +22 -0
- package/packages/atoms/src/lib/FormTextField/FormTextField.module.css +7 -0
- package/packages/atoms/src/lib/FormTextField/FormTextField.spec.tsx +10 -0
- package/packages/atoms/src/lib/FormTextField/FormTextField.tsx +35 -0
- package/packages/atoms/src/lib/Grid/Grid.module.css +7 -0
- package/packages/atoms/src/lib/Grid/Grid.spec.tsx +10 -0
- package/packages/atoms/src/lib/Grid/Grid.tsx +19 -0
- package/packages/atoms/src/lib/Stack/Stack.module.css +7 -0
- package/packages/atoms/src/lib/Stack/Stack.spec.tsx +10 -0
- package/packages/atoms/src/lib/Stack/Stack.tsx +19 -0
- package/packages/atoms/src/lib/Switch/Switch.module.css +7 -0
- package/packages/atoms/src/lib/Switch/Switch.spec.tsx +10 -0
- package/packages/atoms/src/lib/Switch/Switch.tsx +46 -0
- package/packages/atoms/src/lib/TableView/TableView.module.css +7 -0
- package/packages/atoms/src/lib/TableView/TableView.spec.tsx +10 -0
- package/packages/atoms/src/lib/TableView/TableView.tsx +88 -0
- package/packages/atoms/src/lib/TextField/TextField.module.css +7 -0
- package/packages/atoms/src/lib/TextField/TextField.spec.tsx +10 -0
- package/packages/atoms/src/lib/TextField/TextField.tsx +17 -0
- package/packages/atoms/src/lib/Typography/Typography.module.css +7 -0
- package/packages/atoms/src/lib/Typography/Typography.spec.tsx +10 -0
- package/packages/atoms/src/lib/Typography/Typography.tsx +22 -0
- package/packages/atoms/tsconfig.json +17 -0
- package/packages/atoms/tsconfig.lib.json +30 -0
- package/packages/common/.babelrc +12 -0
- package/packages/common/.eslintrc.json +18 -0
- package/packages/common/README.md +7 -0
- package/packages/common/project.json +8 -0
- package/packages/common/src/index.ts +4 -0
- package/packages/common/src/lib/generateRandomProfilePic.ts +3 -0
- package/packages/common/src/lib/generateValidationSchema.ts +46 -0
- package/packages/common/tsconfig.json +17 -0
- package/packages/common/tsconfig.lib.json +24 -0
- package/packages/contexts/.babelrc +12 -0
- package/packages/contexts/.eslintrc.json +18 -0
- package/packages/contexts/README.md +7 -0
- package/packages/contexts/project.json +8 -0
- package/packages/contexts/src/index.ts +2 -0
- package/packages/contexts/tsconfig.json +17 -0
- package/packages/contexts/tsconfig.lib.json +24 -0
- package/packages/diagrams/.babelrc +12 -0
- package/packages/diagrams/.ctirc +208 -0
- package/packages/diagrams/.eslintrc.json +17 -0
- package/packages/diagrams/CUSTOM_ICONS.md +232 -0
- package/packages/diagrams/GOOGLE_SHEETS_IMPLEMENTATION.md +233 -0
- package/packages/diagrams/NODE_DATA_UPDATE_API.md +430 -0
- package/packages/diagrams/README.md +7 -0
- package/packages/diagrams/UNDO_REDO_API.md +306 -0
- package/packages/diagrams/package.json +45 -0
- package/packages/diagrams/project.json +38 -0
- package/packages/diagrams/rollup.config.js +31 -0
- package/packages/diagrams/src/DiagramFlow.tsx +7 -0
- package/packages/diagrams/src/declarations.d.ts +7 -0
- package/packages/diagrams/src/index.ts +113 -0
- package/packages/diagrams/src/index.ts.bak +99 -0
- package/packages/diagrams/src/lib/assets/markers/markers.param.tsx +101 -0
- package/packages/diagrams/src/lib/assets/markers/markers.type.ts +10 -0
- package/packages/diagrams/src/lib/atoms/ActorNode.tsx +124 -0
- package/packages/diagrams/src/lib/atoms/AddNodeAnchor.tsx +97 -0
- package/packages/diagrams/src/lib/atoms/AddParallelColButton.tsx +26 -0
- package/packages/diagrams/src/lib/atoms/BendpointNode.tsx +71 -0
- package/packages/diagrams/src/lib/atoms/CardBlockTypeSelector.tsx +35 -0
- package/packages/diagrams/src/lib/atoms/CardEditableTitle.tsx +76 -0
- package/packages/diagrams/src/lib/atoms/CardMainContent.tsx +19 -0
- package/packages/diagrams/src/lib/atoms/ContentAssist.tsx +341 -0
- package/packages/diagrams/src/lib/atoms/DiagramControls.tsx +57 -0
- package/packages/diagrams/src/lib/atoms/ExpressionInput.tsx +437 -0
- package/packages/diagrams/src/lib/atoms/FloatingConnectionLine.tsx +73 -0
- package/packages/diagrams/src/lib/atoms/LazyIcon.tsx +31 -0
- package/packages/diagrams/src/lib/atoms/MarkerSelector.tsx +106 -0
- package/packages/diagrams/src/lib/atoms/MultiSelectInput.tsx +87 -0
- package/packages/diagrams/src/lib/atoms/ParameterInput.tsx +199 -0
- package/packages/diagrams/src/lib/atoms/PropertyInput.tsx +114 -0
- package/packages/diagrams/src/lib/atoms/StyledBox.tsx +69 -0
- package/packages/diagrams/src/lib/atoms/ValidationError.tsx +37 -0
- package/packages/diagrams/src/lib/atoms/ValidationMessage.tsx +63 -0
- package/packages/diagrams/src/lib/components/AiButton.tsx +52 -0
- package/packages/diagrams/src/lib/components/ControlEdgeList.tsx +304 -0
- package/packages/diagrams/src/lib/components/CreateFunctionModal.tsx +265 -0
- package/packages/diagrams/src/lib/components/CustomCodeModal.tsx +288 -0
- package/packages/diagrams/src/lib/components/DiagramPanel.tsx +331 -0
- package/packages/diagrams/src/lib/components/FunctionModalTooltip.tsx +31 -0
- package/packages/diagrams/src/lib/components/GraphQLCustomCodeModal.tsx +210 -0
- package/packages/diagrams/src/lib/components/Header.tsx +143 -0
- package/packages/diagrams/src/lib/components/MiniMapCommon.tsx +24 -0
- package/packages/diagrams/src/lib/components/ResizeIcon.tsx +82 -0
- package/packages/diagrams/src/lib/components/SideBar.tsx +40 -0
- package/packages/diagrams/src/lib/components/automation/AutomationAISuggestionNode.tsx +241 -0
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +519 -0
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +318 -0
- package/packages/diagrams/src/lib/components/automation/AutomationExecutionPanel.tsx +439 -0
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +557 -0
- package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +168 -0
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +831 -0
- package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +319 -0
- package/packages/diagrams/src/lib/components/automation/index.ts +8 -0
- package/packages/diagrams/src/lib/contexts/CardDataProvider.tsx +111 -0
- package/packages/diagrams/src/lib/contexts/DiagramProvider.tsx +375 -0
- package/packages/diagrams/src/lib/contexts/diagramStoreTypes.tsx +132 -0
- package/packages/diagrams/src/lib/contexts/onConnect.ts +39 -0
- package/packages/diagrams/src/lib/contexts/onDragStart.ts +28 -0
- package/packages/diagrams/src/lib/contexts/onNodeDragEnd.ts +85 -0
- package/packages/diagrams/src/lib/contexts/onNodesChange.ts +385 -0
- package/packages/diagrams/src/lib/contexts/onWorkflowNodeDelete.ts +65 -0
- package/packages/diagrams/src/lib/contexts/setContentHeight.ts +14 -0
- package/packages/diagrams/src/lib/contexts/setDiagramType.ts +10 -0
- package/packages/diagrams/src/lib/contexts/setEdgeShapeType.ts +7 -0
- package/packages/diagrams/src/lib/contexts/setEdges.ts +9 -0
- package/packages/diagrams/src/lib/contexts/setLayoutDirection.ts +9 -0
- package/packages/diagrams/src/lib/contexts/setNodeSetting.ts +17 -0
- package/packages/diagrams/src/lib/contexts/setNodes.ts +10 -0
- package/packages/diagrams/src/lib/contexts/setPannable.ts +7 -0
- package/packages/diagrams/src/lib/contexts/setSelectedEdge.ts +7 -0
- package/packages/diagrams/src/lib/contexts/setSelectedNode.ts +7 -0
- package/packages/diagrams/src/lib/contexts/undo.ts +163 -0
- package/packages/diagrams/src/lib/contexts/updateNodeSetting.ts +17 -0
- package/packages/diagrams/src/lib/examples/GoogleSheetsConfigurationExamples.ts +306 -0
- package/packages/diagrams/src/lib/examples/TwilioWhatsAppConfigExample.ts +128 -0
- package/packages/diagrams/src/lib/externals.ts +4 -0
- package/packages/diagrams/src/lib/hooks/customUseReactFlow.tsx +90 -0
- package/packages/diagrams/src/lib/hooks/updateNodes.ts +45 -0
- package/packages/diagrams/src/lib/hooks/useAddChildButton.tsx +61 -0
- package/packages/diagrams/src/lib/hooks/useAddSwitchCase.ts +75 -0
- package/packages/diagrams/src/lib/hooks/useAutoRegisterFunctions.ts +135 -0
- package/packages/diagrams/src/lib/hooks/useAutoRegisterVariables.ts +286 -0
- package/packages/diagrams/src/lib/hooks/useDeleteNodeByEvent.ts +64 -0
- package/packages/diagrams/src/lib/hooks/useDragCallbacks.tsx +122 -0
- package/packages/diagrams/src/lib/hooks/useFormValidation.ts +349 -0
- package/packages/diagrams/src/lib/hooks/useFunctionFormCallbacks.ts +136 -0
- package/packages/diagrams/src/lib/hooks/useIsSelectedNode.ts +11 -0
- package/packages/diagrams/src/lib/hooks/useModalControls.ts +13 -0
- package/packages/diagrams/src/lib/hooks/useNodeDragHandlers.ts +43 -0
- package/packages/diagrams/src/lib/hooks/useNodeSelection.ts +55 -0
- package/packages/diagrams/src/lib/hooks/useRenderDividers.tsx +20 -0
- package/packages/diagrams/src/lib/hooks/useRenderPins.tsx +75 -0
- package/packages/diagrams/src/lib/hooks/useResizeObserver.ts +21 -0
- package/packages/diagrams/src/lib/hooks/useTreeChildNode.ts +536 -0
- package/packages/diagrams/src/lib/hooks/useWorkflowNodeActiont.ts +384 -0
- package/packages/diagrams/src/lib/molecules/AddingBlock.tsx +147 -0
- package/packages/diagrams/src/lib/molecules/AvailableVariablesDisplay.tsx +57 -0
- package/packages/diagrams/src/lib/molecules/Block.tsx +143 -0
- package/packages/diagrams/src/lib/molecules/BlockProvider.tsx +28 -0
- package/packages/diagrams/src/lib/molecules/BlockWrapper.tsx +14 -0
- package/packages/diagrams/src/lib/molecules/ConditionRule.tsx +132 -0
- package/packages/diagrams/src/lib/molecules/DraggablePane.tsx +198 -0
- package/packages/diagrams/src/lib/molecules/EntityNodeBlocks.tsx +66 -0
- package/packages/diagrams/src/lib/molecules/SideHandles.tsx +50 -0
- package/packages/diagrams/src/lib/molecules/StencilItem.tsx +85 -0
- package/packages/diagrams/src/lib/molecules/WorkflowNodeActionButtons.tsx +62 -0
- package/packages/diagrams/src/lib/molecules/animated-add-button.tsx +83 -0
- package/packages/diagrams/src/lib/molecules/json-viewer.tsx +107 -0
- package/packages/diagrams/src/lib/organisms/AddNodeView.tsx +102 -0
- package/packages/diagrams/src/lib/organisms/Card/EntityNode.tsx +259 -0
- package/packages/diagrams/src/lib/organisms/Card/card.params.ts +29 -0
- package/packages/diagrams/src/lib/organisms/Card/card.types.ts +5 -0
- package/packages/diagrams/src/lib/organisms/CodeModal.tsx +105 -0
- package/packages/diagrams/src/lib/organisms/ConditionRuleGroup.tsx +139 -0
- package/packages/diagrams/src/lib/organisms/CustomEdge/EdgeMarkers.tsx +51 -0
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge-generator.tsx +130 -0
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge.params.ts +6 -0
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge.type.ts +15 -0
- package/packages/diagrams/src/lib/organisms/CustomEdge/useCreateBendPoint.tsx +119 -0
- package/packages/diagrams/src/lib/organisms/CustomEdge/useEdgeModal.ts +37 -0
- package/packages/diagrams/src/lib/organisms/CustomEdge/useEdgePath.tsx +99 -0
- package/packages/diagrams/src/lib/organisms/DownloadPanel.tsx +345 -0
- package/packages/diagrams/src/lib/organisms/EdgeModal.tsx +243 -0
- package/packages/diagrams/src/lib/organisms/HistoryPane.tsx +118 -0
- package/packages/diagrams/src/lib/organisms/NodeContextMenu.tsx +259 -0
- package/packages/diagrams/src/lib/organisms/PropertiesPane.tsx +276 -0
- package/packages/diagrams/src/lib/organisms/SmartDynamicForm.tsx +226 -0
- package/packages/diagrams/src/lib/organisms/Stencil.tsx +60 -0
- package/packages/diagrams/src/lib/organisms/UseDiagramStore.tsx +4 -0
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeActionButtons.tsx +45 -0
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeTypeDisplay.tsx +26 -0
- package/packages/diagrams/src/lib/organisms/WorkflowNode.tsx +484 -0
- package/packages/diagrams/src/lib/services/GoogleSheetsService.ts +961 -0
- package/packages/diagrams/src/lib/services/SlackService.ts +134 -0
- package/packages/diagrams/src/lib/services/TwilioWhatsAppService.ts +273 -0
- package/packages/diagrams/src/lib/styles.css +3 -0
- package/packages/diagrams/src/lib/templates/DiagramContainer.tsx +17 -0
- package/packages/diagrams/src/lib/templates/DiagramContent.tsx +310 -0
- package/packages/diagrams/src/lib/templates/Diagramming.tsx +178 -0
- package/packages/diagrams/src/lib/templates/PageLinks.tsx +13 -0
- package/packages/diagrams/src/lib/templates/arch/ArchDiagram.tsx +793 -0
- package/packages/diagrams/src/lib/templates/arch/components/EdgeDialog.tsx +83 -0
- package/packages/diagrams/src/lib/templates/arch/components/EdgeSettings.tsx +82 -0
- package/packages/diagrams/src/lib/templates/arch/components/GroupControls.tsx +60 -0
- package/packages/diagrams/src/lib/templates/arch/components/GroupSizeDialog.tsx +82 -0
- package/packages/diagrams/src/lib/templates/arch/components/NodeDialog.tsx +215 -0
- package/packages/diagrams/src/lib/templates/arch/components/nodeTypes.ts +92 -0
- package/packages/diagrams/src/lib/templates/arch/controls/ArchDiagramControls.tsx +350 -0
- package/packages/diagrams/src/lib/templates/arch/data/templatee-data.ts +213 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/CloudArchitectureDiagram.tsx +3912 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/FormatConverter.tsx +810 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/FragmentImporter.tsx +277 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/PropertiesPanel.tsx +1063 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/ServiceIconPicker.tsx +138 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/SettingsSidebar.tsx +657 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/SubnetSelector.tsx +469 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/ServiceNode.tsx +310 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/SubnetNode.tsx +475 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/UsersNode.tsx +278 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/data/diagramManager.ts +365 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/data/types.ts +184 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/integration/diagramValidator.ts +379 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/messages/errorMessages.json +96 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/types/localization.ts +369 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/types/validation.ts +73 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/aws-icons.ts +558 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/awsValidation.ts +447 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/azure-icons.ts +803 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/azureValidation.ts +496 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/cloudValidation.ts +930 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/errorMessages.ts +284 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/gcp-icons.ts +187 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/gcpValidation.ts +312 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/genericValidation.ts +690 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/iconMapping.ts +384 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/idGenerator.ts +221 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/localizationManager.ts +301 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/restrictionEngine.ts +551 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/restrictionValidator.ts +401 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/serviceIcons.tsx +129 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/serviceMapping.ts +331 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/translations.ts +55 -0
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/validationExtractor.ts +342 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/CollaborationDiagram.tsx +16 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/AddCollabEdgeDialog.tsx +182 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollabLayout.ts +182 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollabObjectNodeSettings.tsx +87 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollaborationDiagramFlow.tsx +731 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/DraggableEdgeLabel.tsx +124 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/controls/CollaborationControls.tsx +132 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/nodes/CollabEdge.tsx +171 -0
- package/packages/diagrams/src/lib/templates/collaborationDiagram/nodes/CollabObjectNode.tsx +179 -0
- package/packages/diagrams/src/lib/templates/home.style.css +16 -0
- package/packages/diagrams/src/lib/templates/node-forms/ApiForm.tsx +416 -0
- package/packages/diagrams/src/lib/templates/node-forms/CallForm.tsx +370 -0
- package/packages/diagrams/src/lib/templates/node-forms/ForLoopNode.tsx +313 -0
- package/packages/diagrams/src/lib/templates/node-forms/FunctionForm.tsx +211 -0
- package/packages/diagrams/src/lib/templates/node-forms/GraphQLForm.tsx +679 -0
- package/packages/diagrams/src/lib/templates/node-forms/IfNodeForm.tsx +97 -0
- package/packages/diagrams/src/lib/templates/node-forms/LetNodeForm.tsx +533 -0
- package/packages/diagrams/src/lib/templates/node-forms/LoopNode.tsx +149 -0
- package/packages/diagrams/src/lib/templates/node-forms/NodeForm.tsx +383 -0
- package/packages/diagrams/src/lib/templates/node-forms/ReturnNodeForm.tsx +96 -0
- package/packages/diagrams/src/lib/templates/node-forms/SetNodeForm.tsx +141 -0
- package/packages/diagrams/src/lib/templates/node-forms/SwitchNodeForm.tsx +249 -0
- package/packages/diagrams/src/lib/templates/node-forms/TryCatchForm.tsx +167 -0
- package/packages/diagrams/src/lib/templates/nodeFormSchema.json +84 -0
- package/packages/diagrams/src/lib/templates/sequence/SequenceDiagram.tsx +22 -0
- package/packages/diagrams/src/lib/templates/sequence/components/ActivationBarNode.tsx +33 -0
- package/packages/diagrams/src/lib/templates/sequence/components/AddMessageDialog.tsx +302 -0
- package/packages/diagrams/src/lib/templates/sequence/components/CombinedFragmentNode.tsx +65 -0
- package/packages/diagrams/src/lib/templates/sequence/components/EntitySettings.tsx +559 -0
- package/packages/diagrams/src/lib/templates/sequence/components/FragmentSettings.tsx +116 -0
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceComponents.tsx +14 -0
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceDiagramFlow.tsx +1361 -0
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceEntityNode.tsx +394 -0
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceMessageEdge.tsx +272 -0
- package/packages/diagrams/src/lib/templates/sequence/components/SimpleEntitySettings.tsx +90 -0
- package/packages/diagrams/src/lib/templates/sequence/controls/DiagramControls.tsx +707 -0
- package/packages/diagrams/src/lib/templates/sequence/data/template-data.ts +635 -0
- package/packages/diagrams/src/lib/templates/stateMachine/StateMachineDiagram.tsx +67 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/CompositeStateNodeSettings.tsx +639 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/CustomEdge.tsx +221 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/EdgeSettings.tsx +194 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/SelfConnectingEdge.tsx +42 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineComponents.tsx +50 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineDiagramFlow.tsx +1238 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineLayout.ts +350 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateNodeSettings.tsx +189 -0
- package/packages/diagrams/src/lib/templates/stateMachine/components/useSelfTransition.ts +114 -0
- package/packages/diagrams/src/lib/templates/stateMachine/controls/StateDiagramControls.tsx +488 -0
- package/packages/diagrams/src/lib/templates/stateMachine/data/template-data.ts +4 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ChoicePointNode.tsx +54 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/CompositeStateNode.tsx +225 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/EntryPointNode.tsx +31 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ExitPointNode.tsx +44 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/FinalStateNode.tsx +41 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ForkJoinNode.tsx +53 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/HistoryStateNode.tsx +36 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/InitialStateNode.tsx +29 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/JunctionPointNode.tsx +33 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/PinPointNode.tsx +112 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/StateNode.tsx +124 -0
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/bendPoint.tsx +71 -0
- package/packages/diagrams/src/lib/templates/systemFlow/SystemFlowDiagram.tsx +67 -0
- package/packages/diagrams/src/lib/templates/systemFlow/components/EdgeSettings.tsx +330 -0
- package/packages/diagrams/src/lib/templates/systemFlow/components/FloatingEdge.tsx +219 -0
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowComponents.tsx +28 -0
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowDiagramFlow.tsx +1179 -0
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowNodeSettings.tsx +151 -0
- package/packages/diagrams/src/lib/templates/systemFlow/controls/SystemFlowControls.tsx +435 -0
- package/packages/diagrams/src/lib/templates/systemFlow/data/template-data.ts +308 -0
- package/packages/diagrams/src/lib/templates/systemFlow/demo.tsx +91 -0
- package/packages/diagrams/src/lib/templates/systemFlow/index.ts +5 -0
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/BendpointNode.tsx +80 -0
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/SystemNode.tsx +92 -0
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/TreeSystemNode.tsx +755 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/UseCaseDiagram.tsx +16 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/ActorNodeSettings.tsx +104 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/AddRelationshipDialog.tsx +187 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/PackageNodeSettings.tsx +206 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/UseCaseDiagramFlow.tsx +637 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/UseCaseNodeSettings.tsx +121 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/controls/UseCaseControls.tsx +289 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/PackageNode.tsx +84 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/UseCaseEdge.tsx +116 -0
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/UseCaseNode.tsx +231 -0
- package/packages/diagrams/src/lib/templates/validationSchema.json +277 -0
- package/packages/diagrams/src/lib/theme.ts +439 -0
- package/packages/diagrams/src/lib/types/FunctionSignature.ts +11 -0
- package/packages/diagrams/src/lib/types/SmartDynamicFormField.ts +37 -0
- package/packages/diagrams/src/lib/types/automation-node-data-types.ts +295 -0
- package/packages/diagrams/src/lib/types/available-variables.ts +18 -0
- package/packages/diagrams/src/lib/types/card-node.ts +68 -0
- package/packages/diagrams/src/lib/types/collaboration-types.ts +114 -0
- package/packages/diagrams/src/lib/types/colors.ts +25 -0
- package/packages/diagrams/src/lib/types/condition-builder.ts +19 -0
- package/packages/diagrams/src/lib/types/diagram-types.ts +14 -0
- package/packages/diagrams/src/lib/types/edge-types.ts +22 -0
- package/packages/diagrams/src/lib/types/function-execution.ts +35 -0
- package/packages/diagrams/src/lib/types/hooks.types.ts +6 -0
- package/packages/diagrams/src/lib/types/ndoe-form-types.ts +139 -0
- package/packages/diagrams/src/lib/types/node-types.ts +29 -0
- package/packages/diagrams/src/lib/types/sequence-types.ts +178 -0
- package/packages/diagrams/src/lib/types/shared-node-types.ts +21 -0
- package/packages/diagrams/src/lib/types/state-machine-types.ts +225 -0
- package/packages/diagrams/src/lib/types/stencil-item.ts +7 -0
- package/packages/diagrams/src/lib/types/structures.ts +80 -0
- package/packages/diagrams/src/lib/types/system-flow-types.ts +129 -0
- package/packages/diagrams/src/lib/types/usecase-types.ts +84 -0
- package/packages/diagrams/src/lib/types/validation-types.ts +134 -0
- package/packages/diagrams/src/lib/types/workflow-content-dynamic-form-type.ts +23 -0
- package/packages/diagrams/src/lib/types/workflow-node-data-types.ts +172 -0
- package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +1162 -0
- package/packages/diagrams/src/lib/utils/add-new-block.ts +34 -0
- package/packages/diagrams/src/lib/utils/add-new-node.ts +52 -0
- package/packages/diagrams/src/lib/utils/addToHistory.ts +15 -0
- package/packages/diagrams/src/lib/utils/automation-flow-processor.ts +619 -0
- package/packages/diagrams/src/lib/utils/closestPoint.ts +45 -0
- package/packages/diagrams/src/lib/utils/color-options.tsx +26 -0
- package/packages/diagrams/src/lib/utils/compress-img.ts +59 -0
- package/packages/diagrams/src/lib/utils/configLoader.ts +329 -0
- package/packages/diagrams/src/lib/utils/constant-lengths.ts +8 -0
- package/packages/diagrams/src/lib/utils/create-updated.tsx +40 -0
- package/packages/diagrams/src/lib/utils/createHistoryChange.ts +3 -0
- package/packages/diagrams/src/lib/utils/dividerUtils.tsx +60 -0
- package/packages/diagrams/src/lib/utils/edge-hooks.ts +53 -0
- package/packages/diagrams/src/lib/utils/elkLayout.ts +297 -0
- package/packages/diagrams/src/lib/utils/event-hooks.ts +69 -0
- package/packages/diagrams/src/lib/utils/event-store.ts +58 -0
- package/packages/diagrams/src/lib/utils/flow-node-hooks.ts +28 -0
- package/packages/diagrams/src/lib/utils/flow-to-js.ts +1465 -0
- package/packages/diagrams/src/lib/utils/functionGenerator.ts +38 -0
- package/packages/diagrams/src/lib/utils/helpers.types.ts +10 -0
- package/packages/diagrams/src/lib/utils/iconMapper.tsx +76 -0
- package/packages/diagrams/src/lib/utils/logger.ts +5 -0
- package/packages/diagrams/src/lib/utils/model-hooks.ts +129 -0
- package/packages/diagrams/src/lib/utils/node-hooks.ts +78 -0
- package/packages/diagrams/src/lib/utils/nodeutils.ts +242 -0
- package/packages/diagrams/src/lib/utils/object.ts +13 -0
- package/packages/diagrams/src/lib/utils/useDebounce.ts +19 -0
- package/packages/diagrams/src/lib/utils/utilities.ts +105 -0
- package/packages/diagrams/src/lib/utils/validationEngine.ts +610 -0
- package/packages/diagrams/src/lib/utils/vhToPixels.ts +9 -0
- package/packages/diagrams/tsconfig.json +21 -0
- package/packages/diagrams/tsconfig.lib.json +27 -0
- package/packages/diagrams/webpack.config.js +17 -0
- package/packages/interfaces/.babelrc +12 -0
- package/packages/interfaces/.eslintrc.json +18 -0
- package/packages/interfaces/README.md +7 -0
- package/packages/interfaces/project.json +8 -0
- package/packages/interfaces/src/index.ts +21 -0
- package/packages/interfaces/src/lib/Button.tsx +1 -0
- package/packages/interfaces/src/lib/DataGrid.ts +14 -0
- package/packages/interfaces/src/lib/StyledBox.tsx +1 -0
- package/packages/interfaces/src/lib/ToolbarItem.ts +7 -0
- package/packages/interfaces/src/lib/UserCard.ts +8 -0
- package/packages/interfaces/src/lib/yup.ts +3 -0
- package/packages/interfaces/tsconfig.json +17 -0
- package/packages/interfaces/tsconfig.lib.json +24 -0
- package/packages/molecules/.babelrc +12 -0
- package/packages/molecules/.eslintrc.json +18 -0
- package/packages/molecules/README.md +7 -0
- package/packages/molecules/project.json +8 -0
- package/packages/molecules/src/index.ts +22 -0
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.module.css +7 -0
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.spec.tsx +10 -0
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.tsx +33 -0
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.module.css +7 -0
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.spec.tsx +11 -0
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.tsx +69 -0
- package/packages/molecules/src/lib/EditorTab/EditorTab.module.css +7 -0
- package/packages/molecules/src/lib/EditorTab/EditorTab.spec.tsx +10 -0
- package/packages/molecules/src/lib/EditorTab/EditorTab.tsx +42 -0
- package/packages/molecules/src/lib/EditorTab/TabPanel.tsx +25 -0
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.module.css +7 -0
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.spec.tsx +10 -0
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.tsx +24 -0
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.module.css +7 -0
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.spec.tsx +10 -0
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.tsx +47 -0
- package/packages/molecules/src/lib/UserCard/UserCard.module.css +7 -0
- package/packages/molecules/src/lib/UserCard/UserCard.spec.tsx +11 -0
- package/packages/molecules/src/lib/UserCard/UserCard.tsx +22 -0
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.module.css +7 -0
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.spec.tsx +10 -0
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.tsx +72 -0
- package/packages/molecules/tsconfig.json +17 -0
- package/packages/molecules/tsconfig.lib.json +30 -0
- package/packages/organisms/.babelrc +12 -0
- package/packages/organisms/.eslintrc.json +18 -0
- package/packages/organisms/README.md +7 -0
- package/packages/organisms/project.json +8 -0
- package/packages/organisms/src/index.ts +4 -0
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.module.css +7 -0
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.spec.tsx +10 -0
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.tsx +117 -0
- package/packages/organisms/src/lib/EditorToolbar/SortableItem.tsx +51 -0
- package/packages/organisms/src/lib/UserList/UserList.module.css +7 -0
- package/packages/organisms/src/lib/UserList/UserList.spec.tsx +11 -0
- package/packages/organisms/src/lib/UserList/UserList.tsx +48 -0
- package/packages/organisms/src/lib/UserList/store.ts +49 -0
- package/packages/organisms/tsconfig.json +17 -0
- package/packages/organisms/tsconfig.lib.json +24 -0
- package/packages/zustand/.babelrc +12 -0
- package/packages/zustand/.eslintrc.json +18 -0
- package/packages/zustand/README.md +7 -0
- package/packages/zustand/project.json +8 -0
- package/packages/zustand/src/index.ts +4 -0
- package/packages/zustand/tsconfig.json +17 -0
- package/packages/zustand/tsconfig.lib.json +24 -0
- package/tsconfig.base.json +30 -0
package/packages/diagrams/src/lib/templates/cloud-arch/components/CloudArchitectureDiagram.tsx
ADDED
|
@@ -0,0 +1,3912 @@
|
|
|
1
|
+
import React, { useCallback, useState } from 'react';
|
|
2
|
+
import { Alert, Box, Button, Snackbar } from '@mui/material';
|
|
3
|
+
import { generateMeaningfulId, generateUsersId } from '../utils/idGenerator';
|
|
4
|
+
import ReactFlow, {
|
|
5
|
+
Node,
|
|
6
|
+
Edge,
|
|
7
|
+
useNodesState,
|
|
8
|
+
useEdgesState,
|
|
9
|
+
ConnectionLineType,
|
|
10
|
+
addEdge,
|
|
11
|
+
Connection,
|
|
12
|
+
XYPosition,
|
|
13
|
+
NodeMouseHandler,
|
|
14
|
+
MarkerType,
|
|
15
|
+
Panel,
|
|
16
|
+
applyNodeChanges,
|
|
17
|
+
applyEdgeChanges,
|
|
18
|
+
NodeChange,
|
|
19
|
+
EdgeChange,
|
|
20
|
+
ReactFlowProvider,
|
|
21
|
+
} from 'reactflow';
|
|
22
|
+
import 'reactflow/dist/style.css';
|
|
23
|
+
import { Typography } from '@mui/material';
|
|
24
|
+
|
|
25
|
+
import ServiceNode from './nodes/ServiceNode';
|
|
26
|
+
import SubnetNode from './nodes/SubnetNode';
|
|
27
|
+
import UsersNode from './nodes/UsersNode';
|
|
28
|
+
import PropertiesPanel from './PropertiesPanel';
|
|
29
|
+
import SettingsSidebar from './SettingsSidebar';
|
|
30
|
+
// import SubnetSelector from './SubnetSelector'; // No longer needed
|
|
31
|
+
import { getServiceInfo, labelMap } from '../utils/iconMapping';
|
|
32
|
+
// Note: Old validation imports removed - now using generic validation system
|
|
33
|
+
import {
|
|
34
|
+
validateCloudProviderPlacement,
|
|
35
|
+
findAutoParent,
|
|
36
|
+
autoAssignParentOnDrop,
|
|
37
|
+
} from '../utils/cloudValidation';
|
|
38
|
+
// Note: Using direct validation in cloudValidation.ts instead of generic system
|
|
39
|
+
import {
|
|
40
|
+
extractNodeSemantics,
|
|
41
|
+
extractNodeRestrictions,
|
|
42
|
+
enhanceNodesWithValidation
|
|
43
|
+
} from '../utils/validationExtractor';
|
|
44
|
+
import Sidebar from '../../../components/SideBar';
|
|
45
|
+
import AiButton from '../../../components/AiButton';
|
|
46
|
+
import MiniMapCommon from '../../../components/MiniMapCommon';
|
|
47
|
+
import DiagramPanel from '../../../components/DiagramPanel';
|
|
48
|
+
|
|
49
|
+
// Localization Schema and Interfaces
|
|
50
|
+
interface LocalizationSchema {
|
|
51
|
+
locale: {
|
|
52
|
+
core: {
|
|
53
|
+
coordinate: {
|
|
54
|
+
x: string;
|
|
55
|
+
y: string;
|
|
56
|
+
};
|
|
57
|
+
size: {
|
|
58
|
+
width: string;
|
|
59
|
+
height: string;
|
|
60
|
+
};
|
|
61
|
+
common: {
|
|
62
|
+
title: string;
|
|
63
|
+
description: string;
|
|
64
|
+
cloudProvider: string;
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
ui: {
|
|
68
|
+
buttons: {
|
|
69
|
+
undo: string;
|
|
70
|
+
redo: string;
|
|
71
|
+
clear: string;
|
|
72
|
+
import: string;
|
|
73
|
+
export: string;
|
|
74
|
+
autoLayout: string;
|
|
75
|
+
refreshValidation: string;
|
|
76
|
+
clearSelection: string;
|
|
77
|
+
};
|
|
78
|
+
messages: {
|
|
79
|
+
success: {
|
|
80
|
+
nodeCreated: string;
|
|
81
|
+
nodePlaced: string;
|
|
82
|
+
nodesArranged: string;
|
|
83
|
+
validationRefreshed: string;
|
|
84
|
+
};
|
|
85
|
+
error: {
|
|
86
|
+
invalidPlacement: string;
|
|
87
|
+
invalidDrop: string;
|
|
88
|
+
validationFailed: string;
|
|
89
|
+
dropBlocked: string;
|
|
90
|
+
};
|
|
91
|
+
info: {
|
|
92
|
+
dropToPlace: string;
|
|
93
|
+
selectContainer: string;
|
|
94
|
+
willBePlacedIn: string;
|
|
95
|
+
willBePlacedAtRoot: string;
|
|
96
|
+
};
|
|
97
|
+
};
|
|
98
|
+
labels: {
|
|
99
|
+
selectedContainer: string;
|
|
100
|
+
dragState: string;
|
|
101
|
+
service: string;
|
|
102
|
+
cloud: string;
|
|
103
|
+
hoveredNode: string;
|
|
104
|
+
invalidOverlay: string;
|
|
105
|
+
message: string;
|
|
106
|
+
target: string;
|
|
107
|
+
servicesWillBePlacedHere: string;
|
|
108
|
+
};
|
|
109
|
+
containers: {
|
|
110
|
+
aws: {
|
|
111
|
+
account: string;
|
|
112
|
+
vpc: string;
|
|
113
|
+
subnet: string;
|
|
114
|
+
availabilityZone: string;
|
|
115
|
+
securityGroup: string;
|
|
116
|
+
};
|
|
117
|
+
azure: {
|
|
118
|
+
subscription: string;
|
|
119
|
+
resourceGroup: string;
|
|
120
|
+
virtualNetwork: string;
|
|
121
|
+
subnet: string;
|
|
122
|
+
networkSecurityGroup: string;
|
|
123
|
+
};
|
|
124
|
+
gcp: {
|
|
125
|
+
project: string;
|
|
126
|
+
folder: string;
|
|
127
|
+
vpc: string;
|
|
128
|
+
subnet: string;
|
|
129
|
+
};
|
|
130
|
+
};
|
|
131
|
+
};
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Default localization (English)
|
|
136
|
+
const defaultLocalization: LocalizationSchema = {
|
|
137
|
+
locale: {
|
|
138
|
+
core: {
|
|
139
|
+
coordinate: {
|
|
140
|
+
x: "X Coordinate",
|
|
141
|
+
y: "Y Coordinate"
|
|
142
|
+
},
|
|
143
|
+
size: {
|
|
144
|
+
width: "Width",
|
|
145
|
+
height: "Height"
|
|
146
|
+
},
|
|
147
|
+
common: {
|
|
148
|
+
title: "Title",
|
|
149
|
+
description: "Description",
|
|
150
|
+
cloudProvider: "Cloud Provider"
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
ui: {
|
|
154
|
+
buttons: {
|
|
155
|
+
undo: "Undo",
|
|
156
|
+
redo: "Redo",
|
|
157
|
+
clear: "Clear",
|
|
158
|
+
import: "Import",
|
|
159
|
+
export: "Export",
|
|
160
|
+
autoLayout: "Auto Layout",
|
|
161
|
+
refreshValidation: "Refresh Validation",
|
|
162
|
+
clearSelection: "Clear Selection"
|
|
163
|
+
},
|
|
164
|
+
messages: {
|
|
165
|
+
success: {
|
|
166
|
+
nodeCreated: "Node created successfully",
|
|
167
|
+
nodePlaced: "Service placed in: {container}",
|
|
168
|
+
nodesArranged: "{count} nodes arranged with proper spacing",
|
|
169
|
+
validationRefreshed: "Validation properties refreshed for all nodes!"
|
|
170
|
+
},
|
|
171
|
+
error: {
|
|
172
|
+
invalidPlacement: "Invalid placement - drop blocked",
|
|
173
|
+
invalidDrop: "Invalid drop location - drop blocked",
|
|
174
|
+
validationFailed: "Validation failed - invalid drop target",
|
|
175
|
+
dropBlocked: "DROP BLOCKED"
|
|
176
|
+
},
|
|
177
|
+
info: {
|
|
178
|
+
dropToPlace: "Drop to place service",
|
|
179
|
+
selectContainer: "Drop to select container",
|
|
180
|
+
willBePlacedIn: "Will be placed in: {container}",
|
|
181
|
+
willBePlacedAtRoot: "Will be placed at root level"
|
|
182
|
+
}
|
|
183
|
+
},
|
|
184
|
+
labels: {
|
|
185
|
+
selectedContainer: "Selected Container",
|
|
186
|
+
dragState: "Debug: Drag State",
|
|
187
|
+
service: "Service",
|
|
188
|
+
cloud: "Cloud",
|
|
189
|
+
hoveredNode: "Hovered Node",
|
|
190
|
+
invalidOverlay: "Invalid Overlay",
|
|
191
|
+
message: "Message",
|
|
192
|
+
target: "Target",
|
|
193
|
+
servicesWillBePlacedHere: "Services will be placed here"
|
|
194
|
+
},
|
|
195
|
+
containers: {
|
|
196
|
+
aws: {
|
|
197
|
+
account: "AWS Account",
|
|
198
|
+
vpc: "Virtual Private Cloud",
|
|
199
|
+
subnet: "Subnet",
|
|
200
|
+
availabilityZone: "Availability Zone",
|
|
201
|
+
securityGroup: "Security Group"
|
|
202
|
+
},
|
|
203
|
+
azure: {
|
|
204
|
+
subscription: "Azure Subscription",
|
|
205
|
+
resourceGroup: "Resource Group",
|
|
206
|
+
virtualNetwork: "Virtual Network",
|
|
207
|
+
subnet: "Subnet",
|
|
208
|
+
networkSecurityGroup: "Network Security Group"
|
|
209
|
+
},
|
|
210
|
+
gcp: {
|
|
211
|
+
project: "GCP Project",
|
|
212
|
+
folder: "Folder",
|
|
213
|
+
vpc: "VPC Network",
|
|
214
|
+
subnet: "Subnet"
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
// Custom hook for localization
|
|
222
|
+
const useLocalization = (customLocalization?: Partial<LocalizationSchema>) => {
|
|
223
|
+
const localization = React.useMemo(() => {
|
|
224
|
+
if (!customLocalization) return defaultLocalization;
|
|
225
|
+
|
|
226
|
+
// Deep merge custom localization with default
|
|
227
|
+
const mergeDeep = (target: any, source: any): any => {
|
|
228
|
+
const result = { ...target };
|
|
229
|
+
for (const key in source) {
|
|
230
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
231
|
+
result[key] = mergeDeep(target[key] || {}, source[key]);
|
|
232
|
+
} else {
|
|
233
|
+
result[key] = source[key];
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return result;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
return mergeDeep(defaultLocalization, customLocalization);
|
|
240
|
+
}, [customLocalization]);
|
|
241
|
+
|
|
242
|
+
const t = useCallback((path: string, replacements?: Record<string, string | number>) => {
|
|
243
|
+
const keys = path.split('.');
|
|
244
|
+
let value: any = localization;
|
|
245
|
+
|
|
246
|
+
for (const key of keys) {
|
|
247
|
+
value = value?.[key];
|
|
248
|
+
if (value === undefined) {
|
|
249
|
+
console.warn(`Localization key not found: ${path}`);
|
|
250
|
+
return path;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (typeof value !== 'string') {
|
|
255
|
+
console.warn(`Localization value is not a string: ${path}`);
|
|
256
|
+
return path;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Replace placeholders like {container}, {count}, etc.
|
|
260
|
+
if (replacements) {
|
|
261
|
+
return Object.entries(replacements).reduce((str, [key, val]) => {
|
|
262
|
+
return str.replace(new RegExp(`\\{${key}\\}`, 'g'), String(val));
|
|
263
|
+
}, value);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return value;
|
|
267
|
+
}, [localization]);
|
|
268
|
+
|
|
269
|
+
return { t, localization };
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
// Interface for component props
|
|
273
|
+
interface CloudArchitectureDiagramProps {
|
|
274
|
+
initialNodes: Node[];
|
|
275
|
+
initialEdges: Edge[];
|
|
276
|
+
localization?: Partial<LocalizationSchema>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// CSS keyframes for invalid drop animation with enhanced UX
|
|
280
|
+
const invalidDropShakeKeyframes = `
|
|
281
|
+
@keyframes invalidDropShake {
|
|
282
|
+
0%, 100% { transform: translateX(0) scale(1); }
|
|
283
|
+
10%, 30%, 50%, 70%, 90% { transform: translateX(-3px) scale(1.02); }
|
|
284
|
+
20%, 40%, 60%, 80% { transform: translateX(3px) scale(1.02); }
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
@keyframes invalidDropPulse {
|
|
288
|
+
0%, 100% {
|
|
289
|
+
transform: scale(1);
|
|
290
|
+
box-shadow: 0 4px 15px rgba(220, 53, 69, 0.4), 0 0 0 1px rgba(220, 53, 69, 0.3);
|
|
291
|
+
background: rgba(220, 53, 69, 0.05);
|
|
292
|
+
}
|
|
293
|
+
50% {
|
|
294
|
+
transform: scale(1.05);
|
|
295
|
+
box-shadow: 0 6px 25px rgba(220, 53, 69, 0.7), 0 0 0 3px rgba(220, 53, 69, 0.6);
|
|
296
|
+
background: rgba(220, 53, 69, 0.15);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
@keyframes invalidDropGlow {
|
|
301
|
+
0%, 100% {
|
|
302
|
+
box-shadow:
|
|
303
|
+
0 0 10px rgba(220, 53, 69, 0.6),
|
|
304
|
+
0 0 20px rgba(220, 53, 69, 0.4),
|
|
305
|
+
0 0 30px rgba(220, 53, 69, 0.2),
|
|
306
|
+
inset 0 0 10px rgba(220, 53, 69, 0.1);
|
|
307
|
+
}
|
|
308
|
+
50% {
|
|
309
|
+
box-shadow:
|
|
310
|
+
0 0 15px rgba(220, 53, 69, 0.8),
|
|
311
|
+
0 0 30px rgba(220, 53, 69, 0.6),
|
|
312
|
+
0 0 45px rgba(220, 53, 69, 0.4),
|
|
313
|
+
inset 0 0 15px rgba(220, 53, 69, 0.2);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@keyframes validDropGlow {
|
|
318
|
+
0%, 100% {
|
|
319
|
+
box-shadow:
|
|
320
|
+
0 0 10px rgba(76, 175, 80, 0.5),
|
|
321
|
+
0 0 20px rgba(76, 175, 80, 0.3),
|
|
322
|
+
inset 0 0 10px rgba(76, 175, 80, 0.1);
|
|
323
|
+
}
|
|
324
|
+
50% {
|
|
325
|
+
box-shadow:
|
|
326
|
+
0 0 15px rgba(76, 175, 80, 0.7),
|
|
327
|
+
0 0 30px rgba(76, 175, 80, 0.5),
|
|
328
|
+
inset 0 0 15px rgba(76, 175, 80, 0.2);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/* Enhanced invalid drop target styling */
|
|
333
|
+
.invalid-drop-target {
|
|
334
|
+
cursor: not-allowed !important;
|
|
335
|
+
position: relative;
|
|
336
|
+
transition: all 0.2s ease !important;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
.invalid-drop-target::before {
|
|
340
|
+
content: '';
|
|
341
|
+
position: absolute;
|
|
342
|
+
top: -2px;
|
|
343
|
+
left: -2px;
|
|
344
|
+
right: -2px;
|
|
345
|
+
bottom: -2px;
|
|
346
|
+
background: linear-gradient(45deg,
|
|
347
|
+
rgba(220, 53, 69, 0.1) 0%,
|
|
348
|
+
rgba(220, 53, 69, 0.05) 50%,
|
|
349
|
+
rgba(220, 53, 69, 0.1) 100%);
|
|
350
|
+
pointer-events: none;
|
|
351
|
+
z-index: 1000;
|
|
352
|
+
animation: invalidDropPulse 1s ease-in-out infinite;
|
|
353
|
+
border: 2px dashed rgba(220, 53, 69, 0.8);
|
|
354
|
+
border-radius: 8px;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.invalid-drop-target * {
|
|
358
|
+
cursor: not-allowed !important;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/* Enhanced valid drop target styling */
|
|
362
|
+
.valid-drop-target {
|
|
363
|
+
cursor: copy !important;
|
|
364
|
+
position: relative;
|
|
365
|
+
transition: all 0.2s ease !important;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.valid-drop-target::before {
|
|
369
|
+
content: '';
|
|
370
|
+
position: absolute;
|
|
371
|
+
top: -2px;
|
|
372
|
+
left: -2px;
|
|
373
|
+
right: -2px;
|
|
374
|
+
bottom: -2px;
|
|
375
|
+
background: linear-gradient(45deg,
|
|
376
|
+
rgba(76, 175, 80, 0.08) 0%,
|
|
377
|
+
rgba(76, 175, 80, 0.03) 50%,
|
|
378
|
+
rgba(76, 175, 80, 0.08) 100%);
|
|
379
|
+
pointer-events: none;
|
|
380
|
+
z-index: 1000;
|
|
381
|
+
animation: validDropGlow 1.5s ease-in-out infinite;
|
|
382
|
+
border: 2px dashed rgba(76, 175, 80, 0.6);
|
|
383
|
+
border-radius: 8px;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/* Force cursor override globally during drag */
|
|
387
|
+
body.dragging-invalid {
|
|
388
|
+
cursor: not-allowed !important;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
body.dragging-invalid * {
|
|
392
|
+
cursor: not-allowed !important;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
body.dragging-valid {
|
|
396
|
+
cursor: copy !important;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
/* Enhanced node drag hover states with immediate feedback */
|
|
400
|
+
.react-flow__node[data-drag-hover="true"] {
|
|
401
|
+
transition: all 0.1s ease !important;
|
|
402
|
+
z-index: 1000 !important;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
.react-flow__node[data-drag-hover="true"][data-drag-hover-valid="true"] {
|
|
406
|
+
outline: 3px solid rgba(76, 175, 80, 0.9) !important;
|
|
407
|
+
outline-offset: 3px;
|
|
408
|
+
box-shadow:
|
|
409
|
+
0 0 20px rgba(76, 175, 80, 0.6) !important,
|
|
410
|
+
0 0 40px rgba(76, 175, 80, 0.3) !important,
|
|
411
|
+
inset 0 0 20px rgba(76, 175, 80, 0.1) !important;
|
|
412
|
+
background: linear-gradient(135deg,
|
|
413
|
+
rgba(76, 175, 80, 0.15) 0%,
|
|
414
|
+
rgba(76, 175, 80, 0.05) 100%) !important;
|
|
415
|
+
cursor: copy !important;
|
|
416
|
+
transform: scale(1.02) !important;
|
|
417
|
+
animation: validDropGlow 1s ease-in-out infinite;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
.react-flow__node[data-drag-hover="true"][data-drag-hover-valid="false"] {
|
|
421
|
+
outline: 3px solid rgba(220, 53, 69, 0.9) !important;
|
|
422
|
+
outline-offset: 3px;
|
|
423
|
+
box-shadow:
|
|
424
|
+
0 0 20px rgba(220, 53, 69, 0.6) !important,
|
|
425
|
+
0 0 40px rgba(220, 53, 69, 0.3) !important,
|
|
426
|
+
inset 0 0 20px rgba(220, 53, 69, 0.1) !important;
|
|
427
|
+
background: linear-gradient(135deg,
|
|
428
|
+
rgba(220, 53, 69, 0.15) 0%,
|
|
429
|
+
rgba(220, 53, 69, 0.05) 100%) !important;
|
|
430
|
+
cursor: not-allowed !important;
|
|
431
|
+
transform: scale(1.02) !important;
|
|
432
|
+
animation: invalidDropShake 0.8s ease-in-out infinite, invalidDropGlow 1s ease-in-out infinite;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
.react-flow__node[data-drag-hover="true"][data-drag-hover-valid="false"] * {
|
|
436
|
+
cursor: not-allowed !important;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
.react-flow__node[data-drag-hover="true"][data-drag-hover-valid="true"] * {
|
|
440
|
+
cursor: copy !important;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/* Immediate cursor feedback for drag operations */
|
|
444
|
+
.react-flow__renderer {
|
|
445
|
+
transition: cursor 0.1s ease !important;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
.react-flow.drag-active {
|
|
449
|
+
cursor: grabbing !important;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
.react-flow.drag-invalid {
|
|
453
|
+
cursor: not-allowed !important;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
.react-flow.drag-valid {
|
|
457
|
+
cursor: copy !important;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/* Enhanced overlay animations */
|
|
461
|
+
@keyframes invalidDropOverlayAppear {
|
|
462
|
+
0% {
|
|
463
|
+
opacity: 0;
|
|
464
|
+
transform: scale(0.5) rotate(-5deg);
|
|
465
|
+
}
|
|
466
|
+
50% {
|
|
467
|
+
opacity: 0.8;
|
|
468
|
+
transform: scale(1.1) rotate(2deg);
|
|
469
|
+
}
|
|
470
|
+
100% {
|
|
471
|
+
opacity: 1;
|
|
472
|
+
transform: scale(1) rotate(0deg);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
@keyframes invalidDropTooltipAppear {
|
|
477
|
+
0% {
|
|
478
|
+
opacity: 0;
|
|
479
|
+
transform: translateX(-50%) translateY(-20px) scale(0.8);
|
|
480
|
+
}
|
|
481
|
+
100% {
|
|
482
|
+
opacity: 1;
|
|
483
|
+
transform: translateX(-50%) translateY(0) scale(1);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
@keyframes stopIconRotate {
|
|
488
|
+
0% { transform: rotate(0deg); }
|
|
489
|
+
25% { transform: rotate(-5deg); }
|
|
490
|
+
75% { transform: rotate(5deg); }
|
|
491
|
+
100% { transform: rotate(0deg); }
|
|
492
|
+
}
|
|
493
|
+
`;
|
|
494
|
+
|
|
495
|
+
// Inject the keyframes into the document head
|
|
496
|
+
if (typeof document !== 'undefined') {
|
|
497
|
+
const styleElement = document.createElement('style');
|
|
498
|
+
styleElement.textContent = invalidDropShakeKeyframes;
|
|
499
|
+
if (!document.head.querySelector('style[data-drag-preview="true"]')) {
|
|
500
|
+
styleElement.setAttribute('data-drag-preview', 'true');
|
|
501
|
+
document.head.appendChild(styleElement);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Update default edge options with additional styles
|
|
506
|
+
const defaultEdgeOptions = {
|
|
507
|
+
type: 'smoothstep',
|
|
508
|
+
animated: false,
|
|
509
|
+
style: {
|
|
510
|
+
stroke: '#61dafb',
|
|
511
|
+
strokeWidth: 2,
|
|
512
|
+
zIndex: 5000,
|
|
513
|
+
pointerEvents: 'all' as const,
|
|
514
|
+
cursor: 'pointer',
|
|
515
|
+
},
|
|
516
|
+
markerEnd: {
|
|
517
|
+
type: MarkerType.ArrowClosed,
|
|
518
|
+
width: 20,
|
|
519
|
+
height: 20,
|
|
520
|
+
color: '#61dafb',
|
|
521
|
+
},
|
|
522
|
+
className: 'edge-path-selector',
|
|
523
|
+
labelStyle: {
|
|
524
|
+
fill: '#fff',
|
|
525
|
+
fontWeight: 700,
|
|
526
|
+
fontSize: 12,
|
|
527
|
+
},
|
|
528
|
+
labelBgStyle: {
|
|
529
|
+
fill: '#242424',
|
|
530
|
+
fillOpacity: 0.7,
|
|
531
|
+
rx: 4,
|
|
532
|
+
ry: 4,
|
|
533
|
+
},
|
|
534
|
+
};
|
|
535
|
+
|
|
536
|
+
// Register custom node types
|
|
537
|
+
const nodeTypes = {
|
|
538
|
+
serviceNode: ServiceNode,
|
|
539
|
+
subnetNode: SubnetNode,
|
|
540
|
+
internetNode: ServiceNode,
|
|
541
|
+
usersNode: UsersNode,
|
|
542
|
+
};
|
|
543
|
+
|
|
544
|
+
export interface NodeLabel {
|
|
545
|
+
label: string;
|
|
546
|
+
sublabel?: string;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Helper function to format node labels
|
|
550
|
+
const formatNodeLabel = (
|
|
551
|
+
service: string,
|
|
552
|
+
cloudProvider?: 'aws' | 'azure' | 'gcp',
|
|
553
|
+
): NodeLabel => {
|
|
554
|
+
// Special case for users node
|
|
555
|
+
if (service === 'users') {
|
|
556
|
+
return {
|
|
557
|
+
label: 'Users',
|
|
558
|
+
sublabel: 'External',
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// Get service info from iconMapping
|
|
563
|
+
const serviceInfo = getServiceInfo(service);
|
|
564
|
+
if (serviceInfo) {
|
|
565
|
+
// Split the name into label and sublabel if it contains spaces
|
|
566
|
+
const parts = serviceInfo.name.split(' ');
|
|
567
|
+
if (parts.length > 1) {
|
|
568
|
+
return {
|
|
569
|
+
label: parts[0],
|
|
570
|
+
sublabel: parts.slice(1).join(' '),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
return {
|
|
574
|
+
label: serviceInfo.name,
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
return (
|
|
579
|
+
labelMap[service] || {
|
|
580
|
+
label: service
|
|
581
|
+
.split('-')
|
|
582
|
+
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
583
|
+
.join(' '),
|
|
584
|
+
}
|
|
585
|
+
);
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const getNodePosition = (position?: XYPosition, nodes?: Node[]): XYPosition => {
|
|
589
|
+
// If position is provided, use it directly
|
|
590
|
+
if (position) {
|
|
591
|
+
return position;
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
// Otherwise, return a default position
|
|
595
|
+
return { x: 100, y: 100 };
|
|
596
|
+
};
|
|
597
|
+
|
|
598
|
+
// Add type guard for position changes
|
|
599
|
+
const isPositionChange = (
|
|
600
|
+
change: NodeChange,
|
|
601
|
+
): change is NodeChange & { dragging?: boolean } => {
|
|
602
|
+
return change.type === 'position';
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
// Add a new type for resize events
|
|
606
|
+
interface ResizeEvent {
|
|
607
|
+
id: string;
|
|
608
|
+
width: number;
|
|
609
|
+
height: number;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
const CloudArchitectureDiagramContent: React.FC<CloudArchitectureDiagramProps> = ({ initialNodes, initialEdges, localization }) => {
|
|
613
|
+
const { t } = useLocalization(localization);
|
|
614
|
+
const [nodes, setNodes] = useNodesState(initialNodes);
|
|
615
|
+
const [edges, setEdges] = useEdgesState(initialEdges);
|
|
616
|
+
const [selectedNode, setSelectedNode] = useState<Node | null>(null);
|
|
617
|
+
const [selectedEdge, setSelectedEdge] = useState<Edge | null>(null);
|
|
618
|
+
const [isSettingsSidebarOpen, setIsSettingsSidebarOpen] = useState(false);
|
|
619
|
+
const [validationError, setValidationError] = useState<string | null>(null);
|
|
620
|
+
const [successMessage, setSuccessMessage] = useState<string | null>(null);
|
|
621
|
+
const [history, setHistory] = useState<
|
|
622
|
+
Array<{ nodes: Node[]; edges: Edge[] }>
|
|
623
|
+
>([{ nodes: initialNodes, edges: initialEdges }]);
|
|
624
|
+
const [historyIndex, setHistoryIndex] = useState(0);
|
|
625
|
+
|
|
626
|
+
// Add reactFlowInstance state to track the flow instance
|
|
627
|
+
const [reactFlowInstance, setReactFlowInstance] = useState<any>(null);
|
|
628
|
+
|
|
629
|
+
// Add selected container state for drag and drop
|
|
630
|
+
const [selectedContainer, setSelectedContainer] = useState<Node | null>(null);
|
|
631
|
+
|
|
632
|
+
// Add drag preview state
|
|
633
|
+
const [dragPreview, setDragPreview] = useState<{
|
|
634
|
+
isVisible: boolean;
|
|
635
|
+
service: string;
|
|
636
|
+
cloudProvider: string;
|
|
637
|
+
position: { x: number; y: number };
|
|
638
|
+
isValid: boolean;
|
|
639
|
+
validationMessage: string;
|
|
640
|
+
targetContainer: Node | null;
|
|
641
|
+
} | null>(null);
|
|
642
|
+
|
|
643
|
+
// Add state to track current drag operation and hovered node
|
|
644
|
+
const [currentDragData, setCurrentDragData] = useState<{
|
|
645
|
+
service: string;
|
|
646
|
+
nodeType: string;
|
|
647
|
+
cloudProvider: string;
|
|
648
|
+
label: string;
|
|
649
|
+
} | null>(null);
|
|
650
|
+
|
|
651
|
+
// Add state to track which node is currently being hovered during drag
|
|
652
|
+
const [hoveredNodeDuringDrag, setHoveredNodeDuringDrag] = useState<
|
|
653
|
+
string | null
|
|
654
|
+
>(null);
|
|
655
|
+
|
|
656
|
+
// Add state to track invalid drop overlay
|
|
657
|
+
const [invalidDropOverlay, setInvalidDropOverlay] = useState<{
|
|
658
|
+
nodeId: string;
|
|
659
|
+
position: { x: number; y: number };
|
|
660
|
+
message: string;
|
|
661
|
+
} | null>(null);
|
|
662
|
+
|
|
663
|
+
// Add state for overlay auto-hide timing
|
|
664
|
+
const [overlayAutoHideTimer, setOverlayAutoHideTimer] = useState<NodeJS.Timeout | null>(null);
|
|
665
|
+
|
|
666
|
+
// Configuration for overlay timing (you can adjust these values)
|
|
667
|
+
const OVERLAY_TIMING = {
|
|
668
|
+
appearDuration: 0.3, // seconds for initial appearance
|
|
669
|
+
pulseDuration: 1.0, // seconds for pulse cycle
|
|
670
|
+
tooltipDelay: 0.2, // seconds before tooltip appears
|
|
671
|
+
tooltipDuration: 0.4, // seconds for tooltip animation
|
|
672
|
+
autoHideDuration: 3.0, // seconds before auto-hide (set to 0 to disable)
|
|
673
|
+
iconRotationDuration: 2.0, // seconds for stop icon rotation
|
|
674
|
+
};
|
|
675
|
+
|
|
676
|
+
// Helper function to check if a node is a container node
|
|
677
|
+
const isContainerNode = useCallback((node: Node) => {
|
|
678
|
+
return (
|
|
679
|
+
node.type === 'subnetNode' ||
|
|
680
|
+
// AWS containers
|
|
681
|
+
node.data?.service === 'account' ||
|
|
682
|
+
node.data?.service === 'vpc' ||
|
|
683
|
+
node.data?.service === 'subnet' ||
|
|
684
|
+
node.data?.service === 'availability-zone' ||
|
|
685
|
+
node.data?.service === 'local-zone' ||
|
|
686
|
+
node.data?.service === 'security-group' ||
|
|
687
|
+
// Azure containers
|
|
688
|
+
node.data?.service === 'subscription' ||
|
|
689
|
+
node.data?.service === 'resource-group' ||
|
|
690
|
+
node.data?.service === 'virtual-network' ||
|
|
691
|
+
node.data?.service === 'network-security-group' ||
|
|
692
|
+
// GCP containers
|
|
693
|
+
node.data?.service === 'project' ||
|
|
694
|
+
node.data?.service === 'folder'
|
|
695
|
+
);
|
|
696
|
+
}, []);
|
|
697
|
+
|
|
698
|
+
// Helper function to check if a service is a container service
|
|
699
|
+
const isContainerService = useCallback((service: string) => {
|
|
700
|
+
return (
|
|
701
|
+
// AWS containers
|
|
702
|
+
service === 'account' ||
|
|
703
|
+
service === 'vpc' ||
|
|
704
|
+
service === 'subnet' ||
|
|
705
|
+
service === 'availability-zone' ||
|
|
706
|
+
service === 'local-zone' ||
|
|
707
|
+
service === 'security-group' ||
|
|
708
|
+
// Azure containers
|
|
709
|
+
service === 'subscription' ||
|
|
710
|
+
service === 'resource-group' ||
|
|
711
|
+
service === 'virtual-network' ||
|
|
712
|
+
service === 'network-security-group' ||
|
|
713
|
+
// GCP containers
|
|
714
|
+
service === 'project' ||
|
|
715
|
+
service === 'folder'
|
|
716
|
+
);
|
|
717
|
+
}, []);
|
|
718
|
+
|
|
719
|
+
// Helper function to enhance a node with semantic and restriction properties
|
|
720
|
+
const enhanceNodeWithValidation = useCallback((node: Node, allNodes: Node[] = nodes): Node => {
|
|
721
|
+
const semantics = extractNodeSemantics(node);
|
|
722
|
+
const restrictions = extractNodeRestrictions(node, allNodes);
|
|
723
|
+
|
|
724
|
+
return {
|
|
725
|
+
...node,
|
|
726
|
+
data: {
|
|
727
|
+
...node.data,
|
|
728
|
+
semantics,
|
|
729
|
+
restrictions,
|
|
730
|
+
validationStatus: {
|
|
731
|
+
isValid: true,
|
|
732
|
+
message: 'Node created successfully',
|
|
733
|
+
suggestions: [],
|
|
734
|
+
lastValidated: new Date().toISOString()
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
};
|
|
738
|
+
}, [nodes]);
|
|
739
|
+
|
|
740
|
+
// Add container selection handler
|
|
741
|
+
const handleContainerSelection = useCallback(
|
|
742
|
+
(node: Node) => {
|
|
743
|
+
if (isContainerNode(node)) {
|
|
744
|
+
setSelectedContainer(node);
|
|
745
|
+
// Update nodes to show selection visually
|
|
746
|
+
const updatedNodes = nodes.map((n) => ({
|
|
747
|
+
...n,
|
|
748
|
+
data: {
|
|
749
|
+
...n.data,
|
|
750
|
+
isSelected: n.id === node.id,
|
|
751
|
+
},
|
|
752
|
+
}));
|
|
753
|
+
setNodes(updatedNodes);
|
|
754
|
+
}
|
|
755
|
+
},
|
|
756
|
+
[nodes, isContainerNode],
|
|
757
|
+
);
|
|
758
|
+
|
|
759
|
+
// Function to detect overlapping nodes
|
|
760
|
+
const detectOverlapping = useCallback(
|
|
761
|
+
(nodes: Node[]): { node1: Node; node2: Node }[] => {
|
|
762
|
+
const overlaps: { node1: Node; node2: Node }[] = [];
|
|
763
|
+
|
|
764
|
+
for (let i = 0; i < nodes.length; i++) {
|
|
765
|
+
for (let j = i + 1; j < nodes.length; j++) {
|
|
766
|
+
const node1 = nodes[i];
|
|
767
|
+
const node2 = nodes[j];
|
|
768
|
+
|
|
769
|
+
// Skip if nodes are parent-child (they should overlap)
|
|
770
|
+
if (node1.parentNode === node2.id || node2.parentNode === node1.id) {
|
|
771
|
+
continue;
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
// Skip if nodes have different parents (they're in different containers)
|
|
775
|
+
if (node1.parentNode !== node2.parentNode) {
|
|
776
|
+
continue;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
// Get node dimensions based on node type
|
|
780
|
+
const getNodeDimensions = (node: Node) => {
|
|
781
|
+
if (isContainerNode(node)) {
|
|
782
|
+
return {
|
|
783
|
+
width: (node.style?.width as number) || node.data?.width || 300,
|
|
784
|
+
height:
|
|
785
|
+
(node.style?.height as number) || node.data?.height || 200,
|
|
786
|
+
};
|
|
787
|
+
} else {
|
|
788
|
+
return {
|
|
789
|
+
width: (node.style?.width as number) || node.data?.width || 120,
|
|
790
|
+
height:
|
|
791
|
+
(node.style?.height as number) || node.data?.height || 80,
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
};
|
|
795
|
+
|
|
796
|
+
const { width: node1Width, height: node1Height } =
|
|
797
|
+
getNodeDimensions(node1);
|
|
798
|
+
const { width: node2Width, height: node2Height } =
|
|
799
|
+
getNodeDimensions(node2);
|
|
800
|
+
|
|
801
|
+
// Check for overlap with a small margin
|
|
802
|
+
const margin = 10;
|
|
803
|
+
const overlap =
|
|
804
|
+
node1.position.x < node2.position.x + node2Width + margin &&
|
|
805
|
+
node1.position.x + node1Width + margin > node2.position.x &&
|
|
806
|
+
node1.position.y < node2.position.y + node2Height + margin &&
|
|
807
|
+
node1.position.y + node1Height + margin > node2.position.y;
|
|
808
|
+
|
|
809
|
+
if (overlap) {
|
|
810
|
+
overlaps.push({ node1, node2 });
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
return overlaps;
|
|
816
|
+
},
|
|
817
|
+
[],
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
// Function to automatically arrange nodes with proper spacing
|
|
821
|
+
const autoLayout = useCallback(() => {
|
|
822
|
+
const newNodes = [...nodes];
|
|
823
|
+
|
|
824
|
+
// Group nodes by their parent containers
|
|
825
|
+
const nodesByParent = new Map<string | undefined, Node[]>();
|
|
826
|
+
|
|
827
|
+
newNodes.forEach((node) => {
|
|
828
|
+
const parentId = node.parentNode;
|
|
829
|
+
if (!nodesByParent.has(parentId)) {
|
|
830
|
+
nodesByParent.set(parentId, []);
|
|
831
|
+
}
|
|
832
|
+
nodesByParent.get(parentId)?.push(node);
|
|
833
|
+
});
|
|
834
|
+
|
|
835
|
+
// Process each group separately
|
|
836
|
+
nodesByParent.forEach((groupNodes, parentId) => {
|
|
837
|
+
const containerNodes = groupNodes.filter((node) => isContainerNode(node));
|
|
838
|
+
const serviceNodes = groupNodes.filter((node) => !isContainerNode(node));
|
|
839
|
+
|
|
840
|
+
if (parentId) {
|
|
841
|
+
// This is a group within a container
|
|
842
|
+
const parentNode = newNodes.find((n) => n.id === parentId);
|
|
843
|
+
if (parentNode) {
|
|
844
|
+
// Arrange both container nodes (subnets) and service nodes within the parent
|
|
845
|
+
arrangeNodesInContainer(
|
|
846
|
+
[...containerNodes, ...serviceNodes],
|
|
847
|
+
parentNode,
|
|
848
|
+
newNodes,
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
} else {
|
|
852
|
+
// This is the root level
|
|
853
|
+
arrangeNodesInRoot(containerNodes, newNodes);
|
|
854
|
+
arrangeNodesInRoot(serviceNodes, newNodes);
|
|
855
|
+
}
|
|
856
|
+
});
|
|
857
|
+
|
|
858
|
+
setNodes(newNodes);
|
|
859
|
+
// Add to history after setting nodes
|
|
860
|
+
setTimeout(() => {
|
|
861
|
+
const newHistory = history.slice(0, historyIndex + 1);
|
|
862
|
+
newHistory.push({ nodes: newNodes, edges });
|
|
863
|
+
setHistory(newHistory);
|
|
864
|
+
setHistoryIndex(newHistory.length - 1);
|
|
865
|
+
}, 0);
|
|
866
|
+
|
|
867
|
+
// Count different node types for success message
|
|
868
|
+
const subnetCount = newNodes.filter(
|
|
869
|
+
(node) => node.data?.service === 'subnet',
|
|
870
|
+
).length;
|
|
871
|
+
const totalNodes = newNodes.length;
|
|
872
|
+
|
|
873
|
+
let message = t('locale.ui.messages.success.nodesArranged', { count: totalNodes });
|
|
874
|
+
if (subnetCount > 1) {
|
|
875
|
+
message = t('locale.ui.messages.success.nodesArranged', { count: totalNodes }) + ` (${subnetCount} subnets spaced correctly)`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
setSuccessMessage(message);
|
|
879
|
+
setTimeout(() => setSuccessMessage(null), 3000);
|
|
880
|
+
}, [nodes, edges, history, historyIndex, isContainerNode]);
|
|
881
|
+
|
|
882
|
+
// Function to arrange nodes within a container
|
|
883
|
+
const arrangeNodesInContainer = useCallback(
|
|
884
|
+
(nodesToArrange: Node[], container: Node, allNodes: Node[]) => {
|
|
885
|
+
if (nodesToArrange.length === 0) return;
|
|
886
|
+
|
|
887
|
+
// Get container dimensions
|
|
888
|
+
const containerWidth =
|
|
889
|
+
(container.style?.width as number) || container.data?.width || 500;
|
|
890
|
+
const containerHeight =
|
|
891
|
+
(container.style?.height as number) || container.data?.height || 300;
|
|
892
|
+
|
|
893
|
+
// Helper function to get node dimensions
|
|
894
|
+
const getNodeDimensions = (node: Node) => {
|
|
895
|
+
if (isContainerNode(node)) {
|
|
896
|
+
// For container nodes like subnets
|
|
897
|
+
return {
|
|
898
|
+
width: (node.style?.width as number) || node.data?.width || 300,
|
|
899
|
+
height: (node.style?.height as number) || node.data?.height || 200,
|
|
900
|
+
};
|
|
901
|
+
} else {
|
|
902
|
+
// For service nodes
|
|
903
|
+
return {
|
|
904
|
+
width: 120,
|
|
905
|
+
height: 80,
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
const padding = 30;
|
|
911
|
+
const baseSpacing = 20;
|
|
912
|
+
|
|
913
|
+
// Helper function to get spacing based on node types
|
|
914
|
+
const getSpacing = (currentNode: Node, nextNode?: Node) => {
|
|
915
|
+
const currentIsContainer = isContainerNode(currentNode);
|
|
916
|
+
const nextIsContainer = nextNode ? isContainerNode(nextNode) : false;
|
|
917
|
+
|
|
918
|
+
if (currentIsContainer && nextIsContainer) {
|
|
919
|
+
return baseSpacing * 2; // Extra spacing between container nodes (subnets)
|
|
920
|
+
} else if (currentIsContainer || nextIsContainer) {
|
|
921
|
+
return baseSpacing * 1.5; // Medium spacing between container and service
|
|
922
|
+
} else {
|
|
923
|
+
return baseSpacing; // Standard spacing between services
|
|
924
|
+
}
|
|
925
|
+
};
|
|
926
|
+
|
|
927
|
+
// Sort nodes to arrange subnets first, then services
|
|
928
|
+
const sortedNodes = [...nodesToArrange].sort((a, b) => {
|
|
929
|
+
const aIsContainer = isContainerNode(a);
|
|
930
|
+
const bIsContainer = isContainerNode(b);
|
|
931
|
+
|
|
932
|
+
if (aIsContainer && !bIsContainer) return -1; // Subnets first
|
|
933
|
+
if (!aIsContainer && bIsContainer) return 1; // Services second
|
|
934
|
+
return 0; // Same type, maintain order
|
|
935
|
+
});
|
|
936
|
+
|
|
937
|
+
// Use a simple row-based layout that works for both container and service nodes
|
|
938
|
+
let currentX = padding;
|
|
939
|
+
let currentY = padding;
|
|
940
|
+
let maxRowHeight = 0;
|
|
941
|
+
const maxRowWidth = containerWidth - padding * 2;
|
|
942
|
+
|
|
943
|
+
sortedNodes.forEach((node: Node, index: number) => {
|
|
944
|
+
const { width: nodeWidth, height: nodeHeight } =
|
|
945
|
+
getNodeDimensions(node);
|
|
946
|
+
const nextNode = sortedNodes[index + 1];
|
|
947
|
+
const currentSpacing = getSpacing(node, nextNode);
|
|
948
|
+
|
|
949
|
+
// Check if we need to wrap to the next row
|
|
950
|
+
if (currentX + nodeWidth > maxRowWidth && index > 0) {
|
|
951
|
+
currentX = padding;
|
|
952
|
+
currentY += maxRowHeight + currentSpacing;
|
|
953
|
+
maxRowHeight = 0;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// Position the node
|
|
957
|
+
node.position = {
|
|
958
|
+
x: Math.max(
|
|
959
|
+
padding,
|
|
960
|
+
Math.min(currentX, containerWidth - nodeWidth - padding),
|
|
961
|
+
),
|
|
962
|
+
y: Math.max(
|
|
963
|
+
padding,
|
|
964
|
+
Math.min(currentY, containerHeight - nodeHeight - padding),
|
|
965
|
+
),
|
|
966
|
+
};
|
|
967
|
+
|
|
968
|
+
// Update position for next node
|
|
969
|
+
currentX += nodeWidth + currentSpacing;
|
|
970
|
+
maxRowHeight = Math.max(maxRowHeight, nodeHeight);
|
|
971
|
+
});
|
|
972
|
+
},
|
|
973
|
+
[],
|
|
974
|
+
);
|
|
975
|
+
|
|
976
|
+
// Function to arrange nodes at root level
|
|
977
|
+
const arrangeNodesInRoot = useCallback(
|
|
978
|
+
(nodesToArrange: Node[], allNodes: Node[]) => {
|
|
979
|
+
if (nodesToArrange.length === 0) return;
|
|
980
|
+
|
|
981
|
+
// Sort by node type to group similar nodes together
|
|
982
|
+
const sortedNodes = [...nodesToArrange].sort((a, b) => {
|
|
983
|
+
const getNodePriority = (node: Node) => {
|
|
984
|
+
// Root level containers (accounts, subscriptions, projects)
|
|
985
|
+
if (node.data?.service === 'account') return 1;
|
|
986
|
+
if (node.data?.service === 'subscription') return 1;
|
|
987
|
+
if (node.data?.service === 'project') return 1;
|
|
988
|
+
if (node.data?.service === 'folder') return 1;
|
|
989
|
+
|
|
990
|
+
// Network level containers (VPCs, Virtual Networks)
|
|
991
|
+
if (node.data?.service === 'vpc') return 2;
|
|
992
|
+
if (node.data?.service === 'virtual-network') return 2;
|
|
993
|
+
if (node.data?.service === 'resource-group') return 2;
|
|
994
|
+
|
|
995
|
+
// Subnet level containers
|
|
996
|
+
if (node.data?.service === 'subnet') return 3;
|
|
997
|
+
if (node.data?.service === 'availability-zone') return 3;
|
|
998
|
+
if (node.data?.service === 'local-zone') return 3;
|
|
999
|
+
|
|
1000
|
+
// Security containers
|
|
1001
|
+
if (node.data?.service === 'security-group') return 4;
|
|
1002
|
+
if (node.data?.service === 'network-security-group') return 4;
|
|
1003
|
+
|
|
1004
|
+
// Users and external
|
|
1005
|
+
if (node.data?.service === 'users') return 5;
|
|
1006
|
+
|
|
1007
|
+
return 6; // Other services
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
return getNodePriority(a) - getNodePriority(b);
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
// Calculate spacing based on node types
|
|
1014
|
+
const getNodeSpacing = (node: Node) => {
|
|
1015
|
+
if (isContainerNode(node)) {
|
|
1016
|
+
const width =
|
|
1017
|
+
(node.style?.width as number) || node.data?.width || 500;
|
|
1018
|
+
const height =
|
|
1019
|
+
(node.style?.height as number) || node.data?.height || 300;
|
|
1020
|
+
return { width: width + 50, height: height + 50 }; // Add margin
|
|
1021
|
+
}
|
|
1022
|
+
return { width: 150, height: 100 }; // Standard service node spacing
|
|
1023
|
+
};
|
|
1024
|
+
|
|
1025
|
+
// Arrange in a grid pattern
|
|
1026
|
+
let currentX = 50;
|
|
1027
|
+
let currentY = 50;
|
|
1028
|
+
let maxRowHeight = 0;
|
|
1029
|
+
let rowWidth = 0;
|
|
1030
|
+
const maxRowWidth = 1500; // Maximum width before wrapping to next row
|
|
1031
|
+
|
|
1032
|
+
sortedNodes.forEach((node, index) => {
|
|
1033
|
+
const spacing = getNodeSpacing(node);
|
|
1034
|
+
|
|
1035
|
+
// Check if we need to wrap to next row
|
|
1036
|
+
if (rowWidth + spacing.width > maxRowWidth && index > 0) {
|
|
1037
|
+
currentX = 50;
|
|
1038
|
+
currentY += maxRowHeight + 30; // Add row spacing
|
|
1039
|
+
maxRowHeight = 0;
|
|
1040
|
+
rowWidth = 0;
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Position the node
|
|
1044
|
+
node.position = { x: currentX, y: currentY };
|
|
1045
|
+
|
|
1046
|
+
// Update position for next node
|
|
1047
|
+
currentX += spacing.width + 20;
|
|
1048
|
+
rowWidth += spacing.width + 20;
|
|
1049
|
+
maxRowHeight = Math.max(maxRowHeight, spacing.height);
|
|
1050
|
+
});
|
|
1051
|
+
},
|
|
1052
|
+
[isContainerNode],
|
|
1053
|
+
);
|
|
1054
|
+
|
|
1055
|
+
// Function to prevent overlapping during drag
|
|
1056
|
+
const preventOverlap = useCallback(
|
|
1057
|
+
(
|
|
1058
|
+
draggedNode: Node,
|
|
1059
|
+
newPosition: { x: number; y: number },
|
|
1060
|
+
allNodes: Node[],
|
|
1061
|
+
): { x: number; y: number } => {
|
|
1062
|
+
// Get node dimensions based on type
|
|
1063
|
+
const getNodeDimensions = (node: Node) => {
|
|
1064
|
+
if (isContainerNode(node)) {
|
|
1065
|
+
return {
|
|
1066
|
+
width: (node.style?.width as number) || node.data?.width || 300,
|
|
1067
|
+
height: (node.style?.height as number) || node.data?.height || 200,
|
|
1068
|
+
};
|
|
1069
|
+
} else {
|
|
1070
|
+
return {
|
|
1071
|
+
width: (node.style?.width as number) || node.data?.width || 120,
|
|
1072
|
+
height: (node.style?.height as number) || node.data?.height || 80,
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
};
|
|
1076
|
+
|
|
1077
|
+
const { width: nodeWidth, height: nodeHeight } =
|
|
1078
|
+
getNodeDimensions(draggedNode);
|
|
1079
|
+
const margin = isContainerNode(draggedNode) ? 25 : 15; // Larger margin for container nodes
|
|
1080
|
+
|
|
1081
|
+
// Get sibling nodes (nodes with same parent)
|
|
1082
|
+
const siblingNodes = allNodes.filter(
|
|
1083
|
+
(node) =>
|
|
1084
|
+
node.id !== draggedNode.id &&
|
|
1085
|
+
node.parentNode === draggedNode.parentNode,
|
|
1086
|
+
);
|
|
1087
|
+
|
|
1088
|
+
let adjustedPosition = { ...newPosition };
|
|
1089
|
+
|
|
1090
|
+
// Check for conflicts and adjust position
|
|
1091
|
+
let attempts = 0;
|
|
1092
|
+
const maxAttempts = 20;
|
|
1093
|
+
|
|
1094
|
+
while (attempts < maxAttempts) {
|
|
1095
|
+
let hasConflict = false;
|
|
1096
|
+
|
|
1097
|
+
for (const sibling of siblingNodes) {
|
|
1098
|
+
const { width: siblingWidth, height: siblingHeight } =
|
|
1099
|
+
getNodeDimensions(sibling);
|
|
1100
|
+
|
|
1101
|
+
// Check for overlap
|
|
1102
|
+
const overlap =
|
|
1103
|
+
adjustedPosition.x < sibling.position.x + siblingWidth + margin &&
|
|
1104
|
+
adjustedPosition.x + nodeWidth + margin > sibling.position.x &&
|
|
1105
|
+
adjustedPosition.y < sibling.position.y + siblingHeight + margin &&
|
|
1106
|
+
adjustedPosition.y + nodeHeight + margin > sibling.position.y;
|
|
1107
|
+
|
|
1108
|
+
if (overlap) {
|
|
1109
|
+
hasConflict = true;
|
|
1110
|
+
// Try to move to the right first, then down
|
|
1111
|
+
if (
|
|
1112
|
+
adjustedPosition.x + nodeWidth + margin <
|
|
1113
|
+
sibling.position.x + siblingWidth + 200
|
|
1114
|
+
) {
|
|
1115
|
+
adjustedPosition.x = sibling.position.x + siblingWidth + margin;
|
|
1116
|
+
} else {
|
|
1117
|
+
adjustedPosition.y = sibling.position.y + siblingHeight + margin;
|
|
1118
|
+
adjustedPosition.x = newPosition.x; // Reset X to original
|
|
1119
|
+
}
|
|
1120
|
+
break;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
if (!hasConflict) {
|
|
1125
|
+
break;
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
attempts++;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
return adjustedPosition;
|
|
1132
|
+
},
|
|
1133
|
+
[],
|
|
1134
|
+
);
|
|
1135
|
+
|
|
1136
|
+
// Add function to clear container selection
|
|
1137
|
+
const clearContainerSelection = useCallback(() => {
|
|
1138
|
+
setSelectedContainer(null);
|
|
1139
|
+
const updatedNodes = nodes.map((n) => ({
|
|
1140
|
+
...n,
|
|
1141
|
+
data: {
|
|
1142
|
+
...n.data,
|
|
1143
|
+
isSelected: false,
|
|
1144
|
+
},
|
|
1145
|
+
}));
|
|
1146
|
+
setNodes(updatedNodes);
|
|
1147
|
+
}, [nodes]);
|
|
1148
|
+
|
|
1149
|
+
// Add onInit callback to store the flow instance
|
|
1150
|
+
const onInit = useCallback((reactFlowInstance: any) => {
|
|
1151
|
+
setReactFlowInstance(reactFlowInstance);
|
|
1152
|
+
}, []);
|
|
1153
|
+
|
|
1154
|
+
// Function to add state to history with enhanced validation
|
|
1155
|
+
const addToHistory = useCallback(
|
|
1156
|
+
(newNodes: Node[], newEdges: Edge[]) => {
|
|
1157
|
+
// Enhance nodes with validation properties before storing in history
|
|
1158
|
+
const enhancedNodes = enhanceNodesWithValidation(newNodes);
|
|
1159
|
+
|
|
1160
|
+
const newState = {
|
|
1161
|
+
nodes: JSON.parse(JSON.stringify(enhancedNodes)),
|
|
1162
|
+
edges: JSON.parse(JSON.stringify(newEdges)),
|
|
1163
|
+
};
|
|
1164
|
+
|
|
1165
|
+
// Trim any future history if not at the end
|
|
1166
|
+
const newHistory = history.slice(0, historyIndex + 1);
|
|
1167
|
+
newHistory.push(newState);
|
|
1168
|
+
setHistory(newHistory);
|
|
1169
|
+
setHistoryIndex(newHistory.length - 1);
|
|
1170
|
+
},
|
|
1171
|
+
[history, historyIndex],
|
|
1172
|
+
);
|
|
1173
|
+
|
|
1174
|
+
// Function to handle node deletion
|
|
1175
|
+
const handleDeleteNodes = useCallback(
|
|
1176
|
+
(nodeIds: string[]) => {
|
|
1177
|
+
if (nodeIds.length === 0) return;
|
|
1178
|
+
|
|
1179
|
+
// Get nodes to delete
|
|
1180
|
+
const nodesToDelete = nodes.filter((node) => nodeIds.includes(node.id));
|
|
1181
|
+
|
|
1182
|
+
// Find all child nodes that should be deleted along with their parents
|
|
1183
|
+
const allNodesToDelete = new Set<string>();
|
|
1184
|
+
|
|
1185
|
+
// Add selected nodes to deletion set
|
|
1186
|
+
nodeIds.forEach((id) => allNodesToDelete.add(id));
|
|
1187
|
+
|
|
1188
|
+
// Recursively find all child nodes
|
|
1189
|
+
const findChildNodes = (parentId: string) => {
|
|
1190
|
+
const children = nodes.filter((node) => node.parentNode === parentId);
|
|
1191
|
+
children.forEach((child) => {
|
|
1192
|
+
allNodesToDelete.add(child.id);
|
|
1193
|
+
findChildNodes(child.id); // Recursively find grandchildren
|
|
1194
|
+
});
|
|
1195
|
+
};
|
|
1196
|
+
|
|
1197
|
+
// Find children for each node to delete
|
|
1198
|
+
nodeIds.forEach((nodeId) => {
|
|
1199
|
+
findChildNodes(nodeId);
|
|
1200
|
+
});
|
|
1201
|
+
|
|
1202
|
+
// Remove nodes and their associated edges
|
|
1203
|
+
const remainingNodes = nodes.filter(
|
|
1204
|
+
(node) => !allNodesToDelete.has(node.id),
|
|
1205
|
+
);
|
|
1206
|
+
const remainingEdges = edges.filter(
|
|
1207
|
+
(edge) =>
|
|
1208
|
+
!allNodesToDelete.has(edge.source) &&
|
|
1209
|
+
!allNodesToDelete.has(edge.target),
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
// Update state
|
|
1213
|
+
setNodes(remainingNodes);
|
|
1214
|
+
setEdges(remainingEdges);
|
|
1215
|
+
|
|
1216
|
+
// Add to history
|
|
1217
|
+
addToHistory(remainingNodes, remainingEdges);
|
|
1218
|
+
|
|
1219
|
+
// Clear selections
|
|
1220
|
+
setSelectedNode(null);
|
|
1221
|
+
setSelectedEdge(null);
|
|
1222
|
+
setSelectedContainer(null);
|
|
1223
|
+
|
|
1224
|
+
// Show success message with detailed info
|
|
1225
|
+
const deleteCount = allNodesToDelete.size;
|
|
1226
|
+
const containerNodes = nodesToDelete.filter((n) => isContainerNode(n));
|
|
1227
|
+
let message = `Deleted ${deleteCount} ${deleteCount === 1 ? 'node' : 'nodes'}`;
|
|
1228
|
+
|
|
1229
|
+
if (deleteCount > nodeIds.length) {
|
|
1230
|
+
const childCount = deleteCount - nodeIds.length;
|
|
1231
|
+
message += ` (including ${childCount} child ${childCount === 1 ? 'node' : 'nodes'})`;
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1234
|
+
// Add details about what was deleted
|
|
1235
|
+
if (containerNodes.length > 0) {
|
|
1236
|
+
const containerTypes = containerNodes
|
|
1237
|
+
.map((n) => n.data?.service || 'container')
|
|
1238
|
+
.join(', ');
|
|
1239
|
+
message += ` - Containers: ${containerTypes}`;
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
setSuccessMessage(message);
|
|
1243
|
+
setTimeout(() => setSuccessMessage(null), 4000);
|
|
1244
|
+
},
|
|
1245
|
+
[nodes, edges, addToHistory, isContainerNode],
|
|
1246
|
+
);
|
|
1247
|
+
|
|
1248
|
+
// Function to handle single node deletion (for right-click context menu or programmatic deletion)
|
|
1249
|
+
const handleDeleteSingleNode = useCallback(
|
|
1250
|
+
(nodeId: string) => {
|
|
1251
|
+
handleDeleteNodes([nodeId]);
|
|
1252
|
+
},
|
|
1253
|
+
[handleDeleteNodes],
|
|
1254
|
+
);
|
|
1255
|
+
|
|
1256
|
+
// Add keyboard event handler for node deletion
|
|
1257
|
+
React.useEffect(() => {
|
|
1258
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
1259
|
+
// Check if delete or backspace key is pressed
|
|
1260
|
+
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
1261
|
+
// Don't trigger deletion if user is typing in an input field
|
|
1262
|
+
if (
|
|
1263
|
+
event.target instanceof HTMLInputElement ||
|
|
1264
|
+
event.target instanceof HTMLTextAreaElement ||
|
|
1265
|
+
(event.target as any)?.isContentEditable
|
|
1266
|
+
) {
|
|
1267
|
+
return;
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Get currently selected nodes from ReactFlow
|
|
1271
|
+
const selectedNodeIds = nodes
|
|
1272
|
+
.filter((node) => node.selected)
|
|
1273
|
+
.map((node) => node.id);
|
|
1274
|
+
|
|
1275
|
+
if (selectedNodeIds.length > 0) {
|
|
1276
|
+
event.preventDefault();
|
|
1277
|
+
handleDeleteNodes(selectedNodeIds);
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
};
|
|
1281
|
+
|
|
1282
|
+
// Add event listener
|
|
1283
|
+
document.addEventListener('keydown', handleKeyDown);
|
|
1284
|
+
|
|
1285
|
+
// Cleanup on unmount
|
|
1286
|
+
return () => {
|
|
1287
|
+
document.removeEventListener('keydown', handleKeyDown);
|
|
1288
|
+
};
|
|
1289
|
+
}, [nodes, handleDeleteNodes]); // Re-run when nodes or handleDeleteNodes change
|
|
1290
|
+
|
|
1291
|
+
// Add handler for node resize
|
|
1292
|
+
const onNodeResize = useCallback(
|
|
1293
|
+
(event: ResizeEvent) => {
|
|
1294
|
+
const newNodes = nodes.map((node) => {
|
|
1295
|
+
if (node.id === event.id) {
|
|
1296
|
+
return {
|
|
1297
|
+
...node,
|
|
1298
|
+
style: {
|
|
1299
|
+
...node.style,
|
|
1300
|
+
width: event.width,
|
|
1301
|
+
height: event.height,
|
|
1302
|
+
},
|
|
1303
|
+
data: {
|
|
1304
|
+
...node.data,
|
|
1305
|
+
width: event.width,
|
|
1306
|
+
height: event.height,
|
|
1307
|
+
},
|
|
1308
|
+
};
|
|
1309
|
+
}
|
|
1310
|
+
return node;
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
setNodes(newNodes);
|
|
1314
|
+
addToHistory(newNodes, edges);
|
|
1315
|
+
},
|
|
1316
|
+
[nodes, edges, addToHistory],
|
|
1317
|
+
);
|
|
1318
|
+
|
|
1319
|
+
// Update onAddNode with automatic parent assignment
|
|
1320
|
+
const onAddNode = useCallback(
|
|
1321
|
+
(
|
|
1322
|
+
nodeType: string,
|
|
1323
|
+
service: string,
|
|
1324
|
+
position?: XYPosition,
|
|
1325
|
+
cloudProvider?: 'aws' | 'azure' | 'gcp',
|
|
1326
|
+
) => {
|
|
1327
|
+
// Force GCP Projects to be created at root level
|
|
1328
|
+
if (cloudProvider === 'gcp' && service === 'project') {
|
|
1329
|
+
const baseNode: Node = createProjectNode(
|
|
1330
|
+
service,
|
|
1331
|
+
position || { x: 100, y: 100 },
|
|
1332
|
+
);
|
|
1333
|
+
const enhancedNode = enhanceNodeWithValidation(baseNode);
|
|
1334
|
+
const newNodes = [...nodes, enhancedNode];
|
|
1335
|
+
setNodes(newNodes);
|
|
1336
|
+
addToHistory(newNodes, edges);
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
// Special case for users node
|
|
1341
|
+
if (service === 'users') {
|
|
1342
|
+
const baseNode: Node = createUsersNode(position || { x: 100, y: 100 });
|
|
1343
|
+
const enhancedNode = enhanceNodeWithValidation(baseNode);
|
|
1344
|
+
const newNodes = [...nodes, enhancedNode];
|
|
1345
|
+
setNodes(newNodes);
|
|
1346
|
+
addToHistory(newNodes, edges);
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
// Get node labels
|
|
1351
|
+
const { label, sublabel } = formatNodeLabel(service);
|
|
1352
|
+
const nodePosition = getNodePosition(position, nodes);
|
|
1353
|
+
|
|
1354
|
+
// Automatically find the best parent based on position and schema
|
|
1355
|
+
const bestParent = cloudProvider
|
|
1356
|
+
? findAutoParent(nodePosition, service, cloudProvider, nodes)
|
|
1357
|
+
: null;
|
|
1358
|
+
|
|
1359
|
+
// Create temporary node for validation
|
|
1360
|
+
const tempNodeId = cloudProvider ? generateMeaningfulId(cloudProvider, service) : `${service}-temp`;
|
|
1361
|
+
const tempNode: Node = {
|
|
1362
|
+
id: tempNodeId,
|
|
1363
|
+
type: nodeType,
|
|
1364
|
+
position: nodePosition,
|
|
1365
|
+
data: {
|
|
1366
|
+
service,
|
|
1367
|
+
label,
|
|
1368
|
+
sublabel,
|
|
1369
|
+
handles: ['left', 'right'],
|
|
1370
|
+
cloudProvider,
|
|
1371
|
+
infiniteResize: true,
|
|
1372
|
+
},
|
|
1373
|
+
style: { zIndex: 3 },
|
|
1374
|
+
parentNode: bestParent?.id,
|
|
1375
|
+
extent: bestParent ? 'parent' : undefined,
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1378
|
+
// Validate placement
|
|
1379
|
+
const validation = validateCloudProviderPlacement(
|
|
1380
|
+
tempNode,
|
|
1381
|
+
bestParent,
|
|
1382
|
+
nodes,
|
|
1383
|
+
cloudProvider,
|
|
1384
|
+
);
|
|
1385
|
+
|
|
1386
|
+
if (!validation.isValid && validation.message) {
|
|
1387
|
+
setValidationError(validation.message);
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// Create the actual node with meaningful ID
|
|
1392
|
+
const nodeId = cloudProvider ? generateMeaningfulId(cloudProvider, service) : `${service}-1`;
|
|
1393
|
+
const baseNode: Node = {
|
|
1394
|
+
id: nodeId,
|
|
1395
|
+
type: nodeType,
|
|
1396
|
+
position: nodePosition,
|
|
1397
|
+
data: {
|
|
1398
|
+
service,
|
|
1399
|
+
label,
|
|
1400
|
+
sublabel,
|
|
1401
|
+
handles: ['left', 'right'],
|
|
1402
|
+
cloudProvider,
|
|
1403
|
+
infiniteResize: true,
|
|
1404
|
+
},
|
|
1405
|
+
style: { zIndex: 3 },
|
|
1406
|
+
parentNode: bestParent?.id,
|
|
1407
|
+
extent: bestParent ? 'parent' : undefined,
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
// Handle container nodes
|
|
1411
|
+
if (nodeType === 'subnetNode') {
|
|
1412
|
+
const containerBaseNode = createContainerNode(
|
|
1413
|
+
service,
|
|
1414
|
+
nodePosition,
|
|
1415
|
+
cloudProvider,
|
|
1416
|
+
bestParent?.id,
|
|
1417
|
+
);
|
|
1418
|
+
const enhancedContainerNode = enhanceNodeWithValidation(containerBaseNode);
|
|
1419
|
+
const newNodes = [...nodes, enhancedContainerNode];
|
|
1420
|
+
setNodes(newNodes);
|
|
1421
|
+
addToHistory(newNodes, edges);
|
|
1422
|
+
return;
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
// Enhance regular service node with validation properties
|
|
1426
|
+
const enhancedNode = enhanceNodeWithValidation(baseNode);
|
|
1427
|
+
const newNodes = [...nodes, enhancedNode];
|
|
1428
|
+
setNodes(newNodes);
|
|
1429
|
+
addToHistory(newNodes, edges);
|
|
1430
|
+
},
|
|
1431
|
+
[nodes, edges, addToHistory],
|
|
1432
|
+
);
|
|
1433
|
+
|
|
1434
|
+
// Helper function to create project nodes
|
|
1435
|
+
const createProjectNode = (service: string, position: XYPosition): Node => {
|
|
1436
|
+
// Use the same createContainerNode function for consistency
|
|
1437
|
+
return createContainerNode(service, position, 'gcp');
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
// Helper function to create users nodes
|
|
1441
|
+
const createUsersNode = (position: XYPosition): Node => {
|
|
1442
|
+
const nodeId = generateUsersId();
|
|
1443
|
+
return {
|
|
1444
|
+
id: nodeId,
|
|
1445
|
+
type: 'usersNode',
|
|
1446
|
+
position,
|
|
1447
|
+
data: {
|
|
1448
|
+
label: 'Users',
|
|
1449
|
+
sublabel: 'External',
|
|
1450
|
+
handles: ['top', 'bottom'],
|
|
1451
|
+
},
|
|
1452
|
+
style: { zIndex: 3 },
|
|
1453
|
+
};
|
|
1454
|
+
};
|
|
1455
|
+
|
|
1456
|
+
// Helper function to create container nodes
|
|
1457
|
+
const createContainerNode = (
|
|
1458
|
+
service: string,
|
|
1459
|
+
position: XYPosition,
|
|
1460
|
+
cloudProvider?: 'aws' | 'azure' | 'gcp',
|
|
1461
|
+
parentId?: string,
|
|
1462
|
+
): Node => {
|
|
1463
|
+
// Get cloud-specific dimensions and labels
|
|
1464
|
+
const getContainerConfig = (service: string, cloudProvider?: string) => {
|
|
1465
|
+
// AWS Container Configuration
|
|
1466
|
+
if (cloudProvider === 'aws') {
|
|
1467
|
+
switch (service) {
|
|
1468
|
+
case 'account':
|
|
1469
|
+
return {
|
|
1470
|
+
width: 1200,
|
|
1471
|
+
height: 900,
|
|
1472
|
+
label: t('locale.ui.containers.aws.account'),
|
|
1473
|
+
color: 'rgba(255, 153, 0, 0.1)',
|
|
1474
|
+
borderColor: '#FF9900',
|
|
1475
|
+
zIndex: 0
|
|
1476
|
+
};
|
|
1477
|
+
case 'vpc':
|
|
1478
|
+
return {
|
|
1479
|
+
width: 900,
|
|
1480
|
+
height: 700,
|
|
1481
|
+
label: t('locale.ui.containers.aws.vpc'),
|
|
1482
|
+
color: 'rgba(97, 218, 251, 0.1)',
|
|
1483
|
+
borderColor: '#61dafb',
|
|
1484
|
+
zIndex: 1
|
|
1485
|
+
};
|
|
1486
|
+
case 'subnet':
|
|
1487
|
+
return {
|
|
1488
|
+
width: 400,
|
|
1489
|
+
height: 250,
|
|
1490
|
+
label: t('locale.ui.containers.aws.subnet'),
|
|
1491
|
+
color: 'rgba(97, 218, 251, 0.05)',
|
|
1492
|
+
borderColor: '#61dafb',
|
|
1493
|
+
zIndex: 2
|
|
1494
|
+
};
|
|
1495
|
+
case 'availability-zone':
|
|
1496
|
+
return {
|
|
1497
|
+
width: 700,
|
|
1498
|
+
height: 500,
|
|
1499
|
+
label: t('locale.ui.containers.aws.availabilityZone'),
|
|
1500
|
+
color: 'rgba(156, 39, 176, 0.05)',
|
|
1501
|
+
borderColor: '#9c27b0',
|
|
1502
|
+
zIndex: 1
|
|
1503
|
+
};
|
|
1504
|
+
case 'security-group':
|
|
1505
|
+
return {
|
|
1506
|
+
width: 300,
|
|
1507
|
+
height: 200,
|
|
1508
|
+
label: t('locale.ui.containers.aws.securityGroup'),
|
|
1509
|
+
color: 'rgba(244, 67, 54, 0.05)',
|
|
1510
|
+
borderColor: '#f44336',
|
|
1511
|
+
zIndex: 2
|
|
1512
|
+
};
|
|
1513
|
+
default:
|
|
1514
|
+
return {
|
|
1515
|
+
width: 500,
|
|
1516
|
+
height: 300,
|
|
1517
|
+
label: service.charAt(0).toUpperCase() + service.slice(1),
|
|
1518
|
+
color: 'rgba(97, 218, 251, 0.05)',
|
|
1519
|
+
borderColor: '#61dafb',
|
|
1520
|
+
zIndex: 2
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// Azure Container Configuration
|
|
1526
|
+
if (cloudProvider === 'azure') {
|
|
1527
|
+
switch (service) {
|
|
1528
|
+
case 'subscription':
|
|
1529
|
+
return {
|
|
1530
|
+
width: 1300,
|
|
1531
|
+
height: 950,
|
|
1532
|
+
label: t('locale.ui.containers.azure.subscription'),
|
|
1533
|
+
color: 'rgba(0, 120, 212, 0.08)',
|
|
1534
|
+
borderColor: '#0078D4',
|
|
1535
|
+
zIndex: 0
|
|
1536
|
+
};
|
|
1537
|
+
case 'resource-group':
|
|
1538
|
+
return {
|
|
1539
|
+
width: 1000,
|
|
1540
|
+
height: 750,
|
|
1541
|
+
label: t('locale.ui.containers.azure.resourceGroup'),
|
|
1542
|
+
color: 'rgba(0, 120, 212, 0.12)',
|
|
1543
|
+
borderColor: '#0078D4',
|
|
1544
|
+
zIndex: 1
|
|
1545
|
+
};
|
|
1546
|
+
case 'virtual-network':
|
|
1547
|
+
return {
|
|
1548
|
+
width: 750,
|
|
1549
|
+
height: 550,
|
|
1550
|
+
label: t('locale.ui.containers.azure.virtualNetwork'),
|
|
1551
|
+
color: 'rgba(64, 153, 255, 0.1)',
|
|
1552
|
+
borderColor: '#4099FF',
|
|
1553
|
+
zIndex: 2
|
|
1554
|
+
};
|
|
1555
|
+
case 'subnet':
|
|
1556
|
+
return {
|
|
1557
|
+
width: 350,
|
|
1558
|
+
height: 220,
|
|
1559
|
+
label: t('locale.ui.containers.azure.subnet'),
|
|
1560
|
+
color: 'rgba(64, 153, 255, 0.05)',
|
|
1561
|
+
borderColor: '#4099FF',
|
|
1562
|
+
zIndex: 3
|
|
1563
|
+
};
|
|
1564
|
+
case 'network-security-group':
|
|
1565
|
+
return {
|
|
1566
|
+
width: 300,
|
|
1567
|
+
height: 180,
|
|
1568
|
+
label: t('locale.ui.containers.azure.networkSecurityGroup'),
|
|
1569
|
+
color: 'rgba(255, 99, 71, 0.08)',
|
|
1570
|
+
borderColor: '#FF6347',
|
|
1571
|
+
zIndex: 3
|
|
1572
|
+
};
|
|
1573
|
+
default:
|
|
1574
|
+
return {
|
|
1575
|
+
width: 450,
|
|
1576
|
+
height: 280,
|
|
1577
|
+
label: service.split('-').map(word =>
|
|
1578
|
+
word.charAt(0).toUpperCase() + word.slice(1)
|
|
1579
|
+
).join(' '),
|
|
1580
|
+
color: 'rgba(0, 120, 212, 0.08)',
|
|
1581
|
+
borderColor: '#0078D4',
|
|
1582
|
+
zIndex: 2
|
|
1583
|
+
};
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
// GCP Container Configuration
|
|
1588
|
+
if (cloudProvider === 'gcp') {
|
|
1589
|
+
switch (service) {
|
|
1590
|
+
case 'project':
|
|
1591
|
+
return {
|
|
1592
|
+
width: 1250,
|
|
1593
|
+
height: 900,
|
|
1594
|
+
label: t('locale.ui.containers.gcp.project'),
|
|
1595
|
+
color: 'rgba(66, 133, 244, 0.08)',
|
|
1596
|
+
borderColor: '#4285F4',
|
|
1597
|
+
zIndex: 0
|
|
1598
|
+
};
|
|
1599
|
+
case 'vpc':
|
|
1600
|
+
return {
|
|
1601
|
+
width: 850,
|
|
1602
|
+
height: 650,
|
|
1603
|
+
label: t('locale.ui.containers.gcp.vpc'),
|
|
1604
|
+
color: 'rgba(66, 133, 244, 0.12)',
|
|
1605
|
+
borderColor: '#4285F4',
|
|
1606
|
+
zIndex: 1
|
|
1607
|
+
};
|
|
1608
|
+
case 'subnet':
|
|
1609
|
+
return {
|
|
1610
|
+
width: 380,
|
|
1611
|
+
height: 240,
|
|
1612
|
+
label: t('locale.ui.containers.gcp.subnet'),
|
|
1613
|
+
color: 'rgba(66, 133, 244, 0.05)',
|
|
1614
|
+
borderColor: '#4285F4',
|
|
1615
|
+
zIndex: 2
|
|
1616
|
+
};
|
|
1617
|
+
case 'folder':
|
|
1618
|
+
return {
|
|
1619
|
+
width: 1100,
|
|
1620
|
+
height: 800,
|
|
1621
|
+
label: t('locale.ui.containers.gcp.folder'),
|
|
1622
|
+
color: 'rgba(52, 168, 83, 0.08)',
|
|
1623
|
+
borderColor: '#34A853',
|
|
1624
|
+
zIndex: 0
|
|
1625
|
+
};
|
|
1626
|
+
default:
|
|
1627
|
+
return {
|
|
1628
|
+
width: 500,
|
|
1629
|
+
height: 300,
|
|
1630
|
+
label: service.charAt(0).toUpperCase() + service.slice(1),
|
|
1631
|
+
color: 'rgba(66, 133, 244, 0.08)',
|
|
1632
|
+
borderColor: '#4285F4',
|
|
1633
|
+
zIndex: 2
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// Default configuration
|
|
1639
|
+
return {
|
|
1640
|
+
width: 500,
|
|
1641
|
+
height: 300,
|
|
1642
|
+
label: service.charAt(0).toUpperCase() + service.slice(1),
|
|
1643
|
+
color: 'rgba(97, 218, 251, 0.05)',
|
|
1644
|
+
borderColor: '#61dafb',
|
|
1645
|
+
zIndex: 2
|
|
1646
|
+
};
|
|
1647
|
+
};
|
|
1648
|
+
|
|
1649
|
+
const config = getContainerConfig(service, cloudProvider);
|
|
1650
|
+
const nodeId = cloudProvider ? generateMeaningfulId(cloudProvider, service) : `${service}-1`;
|
|
1651
|
+
|
|
1652
|
+
return {
|
|
1653
|
+
id: nodeId,
|
|
1654
|
+
type: 'subnetNode',
|
|
1655
|
+
position,
|
|
1656
|
+
data: {
|
|
1657
|
+
service,
|
|
1658
|
+
label: config.label,
|
|
1659
|
+
color: config.color,
|
|
1660
|
+
borderColor: config.borderColor,
|
|
1661
|
+
textAlign: 'left',
|
|
1662
|
+
cloudProvider,
|
|
1663
|
+
infiniteResize: true,
|
|
1664
|
+
width: config.width,
|
|
1665
|
+
height: config.height,
|
|
1666
|
+
draggable: true,
|
|
1667
|
+
},
|
|
1668
|
+
style: {
|
|
1669
|
+
width: config.width,
|
|
1670
|
+
height: config.height,
|
|
1671
|
+
zIndex: config.zIndex,
|
|
1672
|
+
},
|
|
1673
|
+
parentNode: parentId,
|
|
1674
|
+
extent: parentId ? 'parent' : undefined,
|
|
1675
|
+
draggable: true,
|
|
1676
|
+
};
|
|
1677
|
+
};
|
|
1678
|
+
|
|
1679
|
+
// Update the onNodesChangeWithHistory function to include automatic parent assignment during drag
|
|
1680
|
+
const onNodesChangeWithHistory = useCallback(
|
|
1681
|
+
(changes: NodeChange[]) => {
|
|
1682
|
+
// Apply changes to get the new nodes state
|
|
1683
|
+
let newNodes = applyNodeChanges(changes, nodes);
|
|
1684
|
+
|
|
1685
|
+
// Handle automatic parent assignment during drag end (only for container nodes)
|
|
1686
|
+
changes.forEach((change) => {
|
|
1687
|
+
if (isPositionChange(change) && !change.dragging) {
|
|
1688
|
+
const positionChange = change as NodeChange & {
|
|
1689
|
+
id: string;
|
|
1690
|
+
position: XYPosition;
|
|
1691
|
+
};
|
|
1692
|
+
const movedNode = newNodes.find(
|
|
1693
|
+
(node) => node.id === positionChange.id,
|
|
1694
|
+
);
|
|
1695
|
+
if (
|
|
1696
|
+
movedNode &&
|
|
1697
|
+
movedNode.data?.cloudProvider &&
|
|
1698
|
+
movedNode.data?.service
|
|
1699
|
+
) {
|
|
1700
|
+
// Only auto-assign parent for container nodes (VPC, Subnet, etc.)
|
|
1701
|
+
if (isContainerNode(movedNode)) {
|
|
1702
|
+
// Use automatic parent assignment for the final position
|
|
1703
|
+
const updatedNode = autoAssignParentOnDrop(
|
|
1704
|
+
movedNode,
|
|
1705
|
+
positionChange.position || movedNode.position,
|
|
1706
|
+
newNodes.filter((n) => n.id !== positionChange.id), // Exclude the moved node from parent search
|
|
1707
|
+
);
|
|
1708
|
+
|
|
1709
|
+
// Update the node in the array
|
|
1710
|
+
newNodes = newNodes.map((node) =>
|
|
1711
|
+
node.id === positionChange.id ? updatedNode : node,
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
});
|
|
1717
|
+
|
|
1718
|
+
// Validate the entire diagram after changes using direct validation system
|
|
1719
|
+
if (
|
|
1720
|
+
changes.some((change) => isPositionChange(change) && !change.dragging)
|
|
1721
|
+
) {
|
|
1722
|
+
// Validate all nodes using the working direct validation system
|
|
1723
|
+
const validationErrors: string[] = [];
|
|
1724
|
+
|
|
1725
|
+
for (const node of newNodes) {
|
|
1726
|
+
if (node.data?.cloudProvider && node.data?.service) {
|
|
1727
|
+
const parentNode = newNodes.find(n => n.id === node.parentNode) || null;
|
|
1728
|
+
|
|
1729
|
+
// Use the working direct validation system instead of broken generic system
|
|
1730
|
+
const validation = validateCloudProviderPlacement(
|
|
1731
|
+
node,
|
|
1732
|
+
parentNode,
|
|
1733
|
+
newNodes,
|
|
1734
|
+
node.data.cloudProvider as 'aws' | 'azure' | 'gcp'
|
|
1735
|
+
);
|
|
1736
|
+
|
|
1737
|
+
if (!validation.isValid && validation.message) {
|
|
1738
|
+
validationErrors.push(validation.message);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1742
|
+
|
|
1743
|
+
if (validationErrors.length > 0) {
|
|
1744
|
+
setValidationError(validationErrors[0]);
|
|
1745
|
+
return;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
// Handle validation during dragging (only for container nodes)
|
|
1750
|
+
if (
|
|
1751
|
+
changes.some((change) => isPositionChange(change) && change.dragging)
|
|
1752
|
+
) {
|
|
1753
|
+
const positionChange = changes.find(
|
|
1754
|
+
(change) => isPositionChange(change) && change.dragging,
|
|
1755
|
+
) as NodeChange & {
|
|
1756
|
+
id: string;
|
|
1757
|
+
position: XYPosition;
|
|
1758
|
+
dragging: boolean;
|
|
1759
|
+
};
|
|
1760
|
+
|
|
1761
|
+
if (positionChange) {
|
|
1762
|
+
const draggedNode = nodes.find(
|
|
1763
|
+
(node) => node.id === positionChange.id,
|
|
1764
|
+
);
|
|
1765
|
+
if (
|
|
1766
|
+
draggedNode &&
|
|
1767
|
+
draggedNode.data?.cloudProvider &&
|
|
1768
|
+
draggedNode.data?.service
|
|
1769
|
+
) {
|
|
1770
|
+
// Only validate container nodes during drag - service nodes will be validated in modal
|
|
1771
|
+
if (isContainerNode(draggedNode)) {
|
|
1772
|
+
// Find potential parent during drag for visual feedback
|
|
1773
|
+
const potentialParent = findAutoParent(
|
|
1774
|
+
positionChange.position || draggedNode.position,
|
|
1775
|
+
draggedNode.data.service,
|
|
1776
|
+
draggedNode.data.cloudProvider,
|
|
1777
|
+
nodes.filter((n) => n.id !== draggedNode.id),
|
|
1778
|
+
);
|
|
1779
|
+
|
|
1780
|
+
const validation = validateCloudProviderPlacement(
|
|
1781
|
+
draggedNode,
|
|
1782
|
+
potentialParent,
|
|
1783
|
+
nodes,
|
|
1784
|
+
draggedNode.data.cloudProvider,
|
|
1785
|
+
);
|
|
1786
|
+
|
|
1787
|
+
if (!validation.isValid && validation.message) {
|
|
1788
|
+
setValidationError(validation.message);
|
|
1789
|
+
} else {
|
|
1790
|
+
setValidationError(null);
|
|
1791
|
+
}
|
|
1792
|
+
} else {
|
|
1793
|
+
// Clear validation errors for service nodes during drag
|
|
1794
|
+
setValidationError(null);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
// Add resize handling
|
|
1801
|
+
const hasResizeChange = changes.some(
|
|
1802
|
+
(change) => change.type === 'dimensions',
|
|
1803
|
+
);
|
|
1804
|
+
|
|
1805
|
+
if (hasResizeChange) {
|
|
1806
|
+
const resizeChange = changes.find(
|
|
1807
|
+
(change) => change.type === 'dimensions',
|
|
1808
|
+
);
|
|
1809
|
+
if (resizeChange && 'dimensions' in resizeChange) {
|
|
1810
|
+
onNodeResize({
|
|
1811
|
+
id: resizeChange.id,
|
|
1812
|
+
width: resizeChange.dimensions?.width || 0,
|
|
1813
|
+
height: resizeChange.dimensions?.height || 0,
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
setNodes(newNodes);
|
|
1819
|
+
|
|
1820
|
+
// Don't add to history for position changes while dragging
|
|
1821
|
+
const hasPositionChange = changes.some(
|
|
1822
|
+
(change) => isPositionChange(change) && !change.dragging,
|
|
1823
|
+
);
|
|
1824
|
+
|
|
1825
|
+
if (
|
|
1826
|
+
!changes.some(
|
|
1827
|
+
(change) => isPositionChange(change) && change.dragging,
|
|
1828
|
+
) &&
|
|
1829
|
+
hasPositionChange
|
|
1830
|
+
) {
|
|
1831
|
+
addToHistory(newNodes, edges);
|
|
1832
|
+
}
|
|
1833
|
+
},
|
|
1834
|
+
[nodes, edges, addToHistory, onNodeResize, isContainerNode],
|
|
1835
|
+
);
|
|
1836
|
+
|
|
1837
|
+
// Add drag over handler to allow dropping
|
|
1838
|
+
const onDragOver = useCallback(
|
|
1839
|
+
(event: React.DragEvent) => {
|
|
1840
|
+
event.preventDefault();
|
|
1841
|
+
|
|
1842
|
+
// Initialize with copy effect
|
|
1843
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
1844
|
+
|
|
1845
|
+
// Try to get drag data if we don't have currentDragData yet
|
|
1846
|
+
if (!currentDragData) {
|
|
1847
|
+
try {
|
|
1848
|
+
const rawData = event.dataTransfer.getData('application/json');
|
|
1849
|
+
console.log('onDragOver: Raw drag data:', rawData);
|
|
1850
|
+
|
|
1851
|
+
// Check if we have valid data before parsing
|
|
1852
|
+
if (rawData && rawData.trim().length > 0) {
|
|
1853
|
+
const dragData = JSON.parse(rawData);
|
|
1854
|
+
console.log('onDragOver: Parsed drag data:', dragData);
|
|
1855
|
+
|
|
1856
|
+
if (dragData && dragData.service && dragData.cloudProvider) {
|
|
1857
|
+
console.log(
|
|
1858
|
+
'onDragOver: Setting currentDragData from dragOver',
|
|
1859
|
+
dragData,
|
|
1860
|
+
);
|
|
1861
|
+
setCurrentDragData({
|
|
1862
|
+
service: dragData.service,
|
|
1863
|
+
nodeType: dragData.nodeType || 'serviceNode',
|
|
1864
|
+
cloudProvider: dragData.cloudProvider,
|
|
1865
|
+
label: dragData.label || dragData.service,
|
|
1866
|
+
});
|
|
1867
|
+
} else {
|
|
1868
|
+
console.log('onDragOver: Invalid drag data structure:', dragData);
|
|
1869
|
+
}
|
|
1870
|
+
} else {
|
|
1871
|
+
console.log('onDragOver: No drag data available or empty string');
|
|
1872
|
+
|
|
1873
|
+
// Try alternative data transfer types
|
|
1874
|
+
const types = Array.from(event.dataTransfer.types);
|
|
1875
|
+
console.log('onDragOver: Available data types:', types);
|
|
1876
|
+
|
|
1877
|
+
// Try text/plain as fallback
|
|
1878
|
+
if (types.includes('text/plain')) {
|
|
1879
|
+
const textData = event.dataTransfer.getData('text/plain');
|
|
1880
|
+
console.log('onDragOver: Text data:', textData);
|
|
1881
|
+
if (textData) {
|
|
1882
|
+
try {
|
|
1883
|
+
const dragData = JSON.parse(textData);
|
|
1884
|
+
if (dragData && dragData.service && dragData.cloudProvider) {
|
|
1885
|
+
console.log(
|
|
1886
|
+
'onDragOver: Setting currentDragData from text fallback',
|
|
1887
|
+
dragData,
|
|
1888
|
+
);
|
|
1889
|
+
setCurrentDragData({
|
|
1890
|
+
service: dragData.service,
|
|
1891
|
+
nodeType: dragData.nodeType || 'serviceNode',
|
|
1892
|
+
cloudProvider: dragData.cloudProvider,
|
|
1893
|
+
label: dragData.label || dragData.service,
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
} catch (textError) {
|
|
1897
|
+
console.log(
|
|
1898
|
+
'onDragOver: Could not parse text data as JSON:',
|
|
1899
|
+
textError,
|
|
1900
|
+
);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
} catch (error) {
|
|
1906
|
+
console.log('onDragOver: Error handling drag data:', error);
|
|
1907
|
+
}
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
// Update cursor based on hover state
|
|
1911
|
+
try {
|
|
1912
|
+
if (hoveredNodeDuringDrag) {
|
|
1913
|
+
const hoveredNode = nodes.find((n) => n.id === hoveredNodeDuringDrag);
|
|
1914
|
+
if (hoveredNode && hoveredNode.data?.dragHoverValid === false) {
|
|
1915
|
+
event.dataTransfer.dropEffect = 'none';
|
|
1916
|
+
document.body.style.cursor = 'not-allowed';
|
|
1917
|
+
document.body.classList.add('dragging-invalid');
|
|
1918
|
+
} else if (hoveredNode && hoveredNode.data?.dragHoverValid === true) {
|
|
1919
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
1920
|
+
document.body.style.cursor = 'copy';
|
|
1921
|
+
document.body.classList.remove('dragging-invalid');
|
|
1922
|
+
}
|
|
1923
|
+
} else {
|
|
1924
|
+
document.body.style.cursor = 'copy';
|
|
1925
|
+
document.body.classList.remove('dragging-invalid');
|
|
1926
|
+
}
|
|
1927
|
+
} catch (error) {
|
|
1928
|
+
console.log('Error updating cursor:', error);
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
// Update drag preview position and validate drop location
|
|
1932
|
+
if (dragPreview && reactFlowInstance) {
|
|
1933
|
+
const reactFlowBounds = (
|
|
1934
|
+
event.currentTarget as HTMLElement
|
|
1935
|
+
).getBoundingClientRect();
|
|
1936
|
+
const dropPosition = {
|
|
1937
|
+
x: event.clientX - reactFlowBounds.left,
|
|
1938
|
+
y: event.clientY - reactFlowBounds.top,
|
|
1939
|
+
};
|
|
1940
|
+
|
|
1941
|
+
// Convert screen coordinates to flow coordinates
|
|
1942
|
+
const flowPosition =
|
|
1943
|
+
reactFlowInstance.screenToFlowPosition(dropPosition);
|
|
1944
|
+
|
|
1945
|
+
// For service nodes, don't auto-assign parent - let user choose via modal
|
|
1946
|
+
let isValidDrop = true;
|
|
1947
|
+
let validationMessage = '';
|
|
1948
|
+
let targetContainer = null;
|
|
1949
|
+
|
|
1950
|
+
try {
|
|
1951
|
+
// For container nodes (VPC, Subnet, etc.), find the best parent
|
|
1952
|
+
if (isContainerService(dragPreview.service)) {
|
|
1953
|
+
const bestParent = findAutoParent(
|
|
1954
|
+
flowPosition,
|
|
1955
|
+
dragPreview.service,
|
|
1956
|
+
dragPreview.cloudProvider as 'aws' | 'azure' | 'gcp',
|
|
1957
|
+
nodes,
|
|
1958
|
+
);
|
|
1959
|
+
|
|
1960
|
+
targetContainer = bestParent;
|
|
1961
|
+
|
|
1962
|
+
// Create temporary node for validation
|
|
1963
|
+
const tempNode = {
|
|
1964
|
+
id: `temp-${Date.now()}`,
|
|
1965
|
+
type: 'subnetNode',
|
|
1966
|
+
position: flowPosition,
|
|
1967
|
+
data: {
|
|
1968
|
+
service: dragPreview.service,
|
|
1969
|
+
cloudProvider: dragPreview.cloudProvider,
|
|
1970
|
+
},
|
|
1971
|
+
parentNode: bestParent?.id,
|
|
1972
|
+
};
|
|
1973
|
+
|
|
1974
|
+
// Validate placement
|
|
1975
|
+
const validation = validateCloudProviderPlacement(
|
|
1976
|
+
tempNode as any,
|
|
1977
|
+
bestParent,
|
|
1978
|
+
nodes,
|
|
1979
|
+
dragPreview.cloudProvider as 'aws' | 'azure' | 'gcp',
|
|
1980
|
+
);
|
|
1981
|
+
|
|
1982
|
+
if (!validation.isValid) {
|
|
1983
|
+
isValidDrop = false;
|
|
1984
|
+
validationMessage = validation.message || 'Invalid placement';
|
|
1985
|
+
event.dataTransfer.dropEffect = 'none'; // Show "not allowed" cursor
|
|
1986
|
+
} else {
|
|
1987
|
+
// Show target container info
|
|
1988
|
+
if (bestParent) {
|
|
1989
|
+
validationMessage = t('locale.ui.messages.info.willBePlacedIn', {
|
|
1990
|
+
container: bestParent.data?.label || bestParent.data?.service || 'Container'
|
|
1991
|
+
});
|
|
1992
|
+
} else {
|
|
1993
|
+
validationMessage = t('locale.ui.messages.info.willBePlacedAtRoot');
|
|
1994
|
+
}
|
|
1995
|
+
event.dataTransfer.dropEffect = 'copy'; // Show "copy" cursor
|
|
1996
|
+
}
|
|
1997
|
+
} else {
|
|
1998
|
+
// For service nodes, just show general message
|
|
1999
|
+
validationMessage = t('locale.ui.messages.info.selectContainer');
|
|
2000
|
+
event.dataTransfer.dropEffect = 'copy'; // Allow dropping for service nodes
|
|
2001
|
+
}
|
|
2002
|
+
} catch (error) {
|
|
2003
|
+
isValidDrop = false;
|
|
2004
|
+
validationMessage = 'Invalid drop location';
|
|
2005
|
+
event.dataTransfer.dropEffect = 'none'; // Show "not allowed" cursor for errors
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
// Update nodes to highlight target container (only for container nodes)
|
|
2009
|
+
const updatedNodes = nodes.map((node) => ({
|
|
2010
|
+
...node,
|
|
2011
|
+
data: {
|
|
2012
|
+
...node.data,
|
|
2013
|
+
highlighted: targetContainer && node.id === targetContainer.id,
|
|
2014
|
+
},
|
|
2015
|
+
}));
|
|
2016
|
+
|
|
2017
|
+
// Only update nodes if highlighting changed
|
|
2018
|
+
const currentHighlighted = nodes.find((n) => n.data?.highlighted);
|
|
2019
|
+
const newHighlighted = updatedNodes.find((n) => n.data?.highlighted);
|
|
2020
|
+
if (
|
|
2021
|
+
(!currentHighlighted && newHighlighted) ||
|
|
2022
|
+
(currentHighlighted && !newHighlighted) ||
|
|
2023
|
+
(currentHighlighted &&
|
|
2024
|
+
newHighlighted &&
|
|
2025
|
+
currentHighlighted.id !== newHighlighted.id)
|
|
2026
|
+
) {
|
|
2027
|
+
setNodes(updatedNodes);
|
|
2028
|
+
}
|
|
2029
|
+
|
|
2030
|
+
setDragPreview((prev) =>
|
|
2031
|
+
prev
|
|
2032
|
+
? {
|
|
2033
|
+
...prev,
|
|
2034
|
+
position: dropPosition,
|
|
2035
|
+
isValid: isValidDrop,
|
|
2036
|
+
validationMessage: validationMessage,
|
|
2037
|
+
targetContainer: targetContainer,
|
|
2038
|
+
}
|
|
2039
|
+
: null,
|
|
2040
|
+
);
|
|
2041
|
+
}
|
|
2042
|
+
},
|
|
2043
|
+
[
|
|
2044
|
+
dragPreview,
|
|
2045
|
+
reactFlowInstance,
|
|
2046
|
+
nodes,
|
|
2047
|
+
isContainerService,
|
|
2048
|
+
currentDragData,
|
|
2049
|
+
],
|
|
2050
|
+
);
|
|
2051
|
+
|
|
2052
|
+
// Add drop handler with validation blocking
|
|
2053
|
+
const onDrop = useCallback(
|
|
2054
|
+
(event: React.DragEvent) => {
|
|
2055
|
+
event.preventDefault();
|
|
2056
|
+
|
|
2057
|
+
// Check if we have valid drag data and target
|
|
2058
|
+
const isValidDrop = dragPreview?.isValid !== false;
|
|
2059
|
+
const targetContainer = dragPreview?.targetContainer;
|
|
2060
|
+
|
|
2061
|
+
// Clear all drag-related state
|
|
2062
|
+
setDragPreview(null);
|
|
2063
|
+
setCurrentDragData(null);
|
|
2064
|
+
setHoveredNodeDuringDrag(null);
|
|
2065
|
+
setInvalidDropOverlay(null);
|
|
2066
|
+
|
|
2067
|
+
// Reset cursor
|
|
2068
|
+
document.body.style.cursor = 'default';
|
|
2069
|
+
document.body.classList.remove('dragging-invalid');
|
|
2070
|
+
|
|
2071
|
+
// Clear all drag-related styling
|
|
2072
|
+
const clearedNodes = nodes.map((node) => ({
|
|
2073
|
+
...node,
|
|
2074
|
+
data: {
|
|
2075
|
+
...node.data,
|
|
2076
|
+
highlighted: false,
|
|
2077
|
+
dragHover: false,
|
|
2078
|
+
dragHoverValid: false,
|
|
2079
|
+
},
|
|
2080
|
+
style: {
|
|
2081
|
+
...node.style,
|
|
2082
|
+
'--drag-hover': undefined,
|
|
2083
|
+
'--drag-hover-valid': undefined,
|
|
2084
|
+
},
|
|
2085
|
+
}));
|
|
2086
|
+
setNodes(clearedNodes);
|
|
2087
|
+
|
|
2088
|
+
// Clear DOM attributes from all nodes
|
|
2089
|
+
setTimeout(() => {
|
|
2090
|
+
document
|
|
2091
|
+
.querySelectorAll('.react-flow__node')
|
|
2092
|
+
.forEach((nodeElement) => {
|
|
2093
|
+
nodeElement.removeAttribute('data-drag-hover');
|
|
2094
|
+
nodeElement.removeAttribute('data-drag-hover-valid');
|
|
2095
|
+
// Reset any inline cursor styles
|
|
2096
|
+
const style = nodeElement.getAttribute('style');
|
|
2097
|
+
if (style && style.includes('cursor:')) {
|
|
2098
|
+
const cleanedStyle = style.replace(/cursor:\s*[^;]+;?\s*/g, '');
|
|
2099
|
+
if (cleanedStyle.trim()) {
|
|
2100
|
+
nodeElement.setAttribute('style', cleanedStyle);
|
|
2101
|
+
} else {
|
|
2102
|
+
nodeElement.removeAttribute('style');
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
});
|
|
2106
|
+
}, 0);
|
|
2107
|
+
|
|
2108
|
+
// Block invalid drops completely
|
|
2109
|
+
if (!isValidDrop) {
|
|
2110
|
+
setValidationError(
|
|
2111
|
+
dragPreview?.validationMessage ||
|
|
2112
|
+
'Invalid drop location - drop blocked',
|
|
2113
|
+
);
|
|
2114
|
+
return;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
try {
|
|
2118
|
+
const dragData = JSON.parse(
|
|
2119
|
+
event.dataTransfer.getData('application/json'),
|
|
2120
|
+
);
|
|
2121
|
+
|
|
2122
|
+
if (!dragData || !dragData.service || !dragData.nodeType) {
|
|
2123
|
+
setValidationError('Invalid drag data');
|
|
2124
|
+
return;
|
|
2125
|
+
}
|
|
2126
|
+
|
|
2127
|
+
const { service, nodeType, cloudProvider, label } = dragData;
|
|
2128
|
+
|
|
2129
|
+
// Get the position where the item was dropped
|
|
2130
|
+
const reactFlowBounds = (
|
|
2131
|
+
event.currentTarget as HTMLElement
|
|
2132
|
+
).getBoundingClientRect();
|
|
2133
|
+
const dropPosition = {
|
|
2134
|
+
x: event.clientX - reactFlowBounds.left,
|
|
2135
|
+
y: event.clientY - reactFlowBounds.top,
|
|
2136
|
+
};
|
|
2137
|
+
|
|
2138
|
+
// Convert screen coordinates to flow coordinates
|
|
2139
|
+
const flowInstance = reactFlowInstance;
|
|
2140
|
+
if (!flowInstance) {
|
|
2141
|
+
setValidationError('Diagram not ready');
|
|
2142
|
+
return;
|
|
2143
|
+
}
|
|
2144
|
+
|
|
2145
|
+
const flowPosition = flowInstance.screenToFlowPosition(dropPosition);
|
|
2146
|
+
|
|
2147
|
+
// Special handling for container nodes (VPC, Subnet, etc.) and users - place immediately
|
|
2148
|
+
if (
|
|
2149
|
+
nodeType === 'subnetNode' ||
|
|
2150
|
+
service === 'users' ||
|
|
2151
|
+
isContainerService(service)
|
|
2152
|
+
) {
|
|
2153
|
+
onAddNode(nodeType, service, flowPosition, cloudProvider);
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2157
|
+
// For service nodes, use the hovered target container or selected container
|
|
2158
|
+
const finalTargetContainer = targetContainer || selectedContainer;
|
|
2159
|
+
|
|
2160
|
+
if (finalTargetContainer) {
|
|
2161
|
+
// Place service in the target container
|
|
2162
|
+
handleServicePlacement(
|
|
2163
|
+
service,
|
|
2164
|
+
nodeType,
|
|
2165
|
+
cloudProvider,
|
|
2166
|
+
label,
|
|
2167
|
+
flowPosition,
|
|
2168
|
+
finalTargetContainer,
|
|
2169
|
+
);
|
|
2170
|
+
} else {
|
|
2171
|
+
// No container selected - place at root level or show error
|
|
2172
|
+
handleServicePlacement(
|
|
2173
|
+
service,
|
|
2174
|
+
nodeType,
|
|
2175
|
+
cloudProvider,
|
|
2176
|
+
label,
|
|
2177
|
+
flowPosition,
|
|
2178
|
+
null,
|
|
2179
|
+
);
|
|
2180
|
+
}
|
|
2181
|
+
} catch (error) {
|
|
2182
|
+
console.error('Error handling drop:', error);
|
|
2183
|
+
setValidationError('Failed to add service');
|
|
2184
|
+
}
|
|
2185
|
+
},
|
|
2186
|
+
[
|
|
2187
|
+
dragPreview,
|
|
2188
|
+
onAddNode,
|
|
2189
|
+
reactFlowInstance,
|
|
2190
|
+
nodes,
|
|
2191
|
+
isContainerService,
|
|
2192
|
+
selectedContainer,
|
|
2193
|
+
],
|
|
2194
|
+
);
|
|
2195
|
+
|
|
2196
|
+
// Handle service placement in selected container
|
|
2197
|
+
const handleServicePlacement = useCallback(
|
|
2198
|
+
(
|
|
2199
|
+
service: string,
|
|
2200
|
+
nodeType: string,
|
|
2201
|
+
cloudProvider: string,
|
|
2202
|
+
label: string,
|
|
2203
|
+
dropPosition: { x: number; y: number },
|
|
2204
|
+
targetContainer: Node | null,
|
|
2205
|
+
) => {
|
|
2206
|
+
// Get node labels
|
|
2207
|
+
const { label: nodeLabel, sublabel } = formatNodeLabel(
|
|
2208
|
+
service,
|
|
2209
|
+
cloudProvider as 'aws' | 'azure' | 'gcp',
|
|
2210
|
+
);
|
|
2211
|
+
|
|
2212
|
+
// Calculate the correct position based on parent selection
|
|
2213
|
+
let finalPosition = dropPosition;
|
|
2214
|
+
let parentNode: Node | null = targetContainer;
|
|
2215
|
+
|
|
2216
|
+
// Define service dimensions (used for overlap prevention)
|
|
2217
|
+
const serviceWidth = 120; // Approximate service node width
|
|
2218
|
+
const serviceHeight = 80; // Approximate service node height
|
|
2219
|
+
|
|
2220
|
+
if (targetContainer) {
|
|
2221
|
+
// Get parent dimensions
|
|
2222
|
+
const parentWidth =
|
|
2223
|
+
(targetContainer.style?.width as number) ||
|
|
2224
|
+
targetContainer.data?.width ||
|
|
2225
|
+
500;
|
|
2226
|
+
const parentHeight =
|
|
2227
|
+
(targetContainer.style?.height as number) ||
|
|
2228
|
+
targetContainer.data?.height ||
|
|
2229
|
+
300;
|
|
2230
|
+
const padding = 50;
|
|
2231
|
+
|
|
2232
|
+
// Convert absolute position to relative position within parent
|
|
2233
|
+
const relativeX = dropPosition.x - targetContainer.position.x;
|
|
2234
|
+
const relativeY = dropPosition.y - targetContainer.position.y;
|
|
2235
|
+
|
|
2236
|
+
// Check if the original drop position is within parent bounds
|
|
2237
|
+
const isWithinParent =
|
|
2238
|
+
relativeX >= 0 &&
|
|
2239
|
+
relativeY >= 0 &&
|
|
2240
|
+
relativeX <= parentWidth &&
|
|
2241
|
+
relativeY <= parentHeight;
|
|
2242
|
+
|
|
2243
|
+
if (isWithinParent) {
|
|
2244
|
+
// Use the relative position but ensure it's within bounds with padding
|
|
2245
|
+
finalPosition = {
|
|
2246
|
+
x: Math.max(
|
|
2247
|
+
padding,
|
|
2248
|
+
Math.min(relativeX, parentWidth - serviceWidth - padding),
|
|
2249
|
+
),
|
|
2250
|
+
y: Math.max(
|
|
2251
|
+
padding,
|
|
2252
|
+
Math.min(relativeY, parentHeight - serviceHeight - padding),
|
|
2253
|
+
),
|
|
2254
|
+
};
|
|
2255
|
+
} else {
|
|
2256
|
+
// Find a good auto-position within the parent
|
|
2257
|
+
// Check for existing child nodes to avoid overlap
|
|
2258
|
+
const childNodes = nodes.filter(
|
|
2259
|
+
(node) => node.parentNode === targetContainer.id,
|
|
2260
|
+
);
|
|
2261
|
+
|
|
2262
|
+
// Try to place the service in a grid pattern
|
|
2263
|
+
const cols = Math.floor(
|
|
2264
|
+
(parentWidth - padding * 2) / (serviceWidth + 20),
|
|
2265
|
+
); // 20px spacing
|
|
2266
|
+
const rows = Math.floor(
|
|
2267
|
+
(parentHeight - padding * 2) / (serviceHeight + 20),
|
|
2268
|
+
);
|
|
2269
|
+
|
|
2270
|
+
let placed = false;
|
|
2271
|
+
|
|
2272
|
+
// Try to find an empty spot in the grid
|
|
2273
|
+
for (let row = 0; row < rows && !placed; row++) {
|
|
2274
|
+
for (let col = 0; col < cols && !placed; col++) {
|
|
2275
|
+
const testX = padding + col * (serviceWidth + 20);
|
|
2276
|
+
const testY = padding + row * (serviceHeight + 20);
|
|
2277
|
+
|
|
2278
|
+
// Check if this position conflicts with existing child nodes
|
|
2279
|
+
const hasConflict = childNodes.some((childNode) => {
|
|
2280
|
+
const childX = childNode.position.x;
|
|
2281
|
+
const childY = childNode.position.y;
|
|
2282
|
+
const childWidth = (childNode.style?.width as number) || 120;
|
|
2283
|
+
const childHeight = (childNode.style?.height as number) || 80;
|
|
2284
|
+
|
|
2285
|
+
return (
|
|
2286
|
+
testX < childX + childWidth + 10 &&
|
|
2287
|
+
testX + serviceWidth + 10 > childX &&
|
|
2288
|
+
testY < childY + childHeight + 10 &&
|
|
2289
|
+
testY + serviceHeight + 10 > childY
|
|
2290
|
+
);
|
|
2291
|
+
});
|
|
2292
|
+
|
|
2293
|
+
if (!hasConflict) {
|
|
2294
|
+
finalPosition = { x: testX, y: testY };
|
|
2295
|
+
placed = true;
|
|
2296
|
+
}
|
|
2297
|
+
}
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
// If no grid spot found, use a fallback position
|
|
2301
|
+
if (!placed) {
|
|
2302
|
+
finalPosition = {
|
|
2303
|
+
x: padding + (childNodes.length % 3) * (serviceWidth + 20),
|
|
2304
|
+
y:
|
|
2305
|
+
padding +
|
|
2306
|
+
Math.floor(childNodes.length / 3) * (serviceHeight + 20),
|
|
2307
|
+
};
|
|
2308
|
+
|
|
2309
|
+
// Ensure it's still within bounds
|
|
2310
|
+
finalPosition = {
|
|
2311
|
+
x: Math.max(
|
|
2312
|
+
padding,
|
|
2313
|
+
Math.min(finalPosition.x, parentWidth - serviceWidth - padding),
|
|
2314
|
+
),
|
|
2315
|
+
y: Math.max(
|
|
2316
|
+
padding,
|
|
2317
|
+
Math.min(
|
|
2318
|
+
finalPosition.y,
|
|
2319
|
+
parentHeight - serviceHeight - padding,
|
|
2320
|
+
),
|
|
2321
|
+
),
|
|
2322
|
+
};
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
// Apply overlap prevention as final step
|
|
2328
|
+
const tempNode = {
|
|
2329
|
+
id: `temp-${Date.now()}`,
|
|
2330
|
+
type: nodeType,
|
|
2331
|
+
position: finalPosition,
|
|
2332
|
+
data: { service, cloudProvider },
|
|
2333
|
+
style: { width: 120, height: 80 },
|
|
2334
|
+
parentNode: targetContainer?.id,
|
|
2335
|
+
};
|
|
2336
|
+
|
|
2337
|
+
finalPosition = preventOverlap(tempNode as Node, finalPosition, nodes);
|
|
2338
|
+
|
|
2339
|
+
// Create the base node with the selected parent using meaningful ID
|
|
2340
|
+
const nodeId = cloudProvider ? generateMeaningfulId(cloudProvider as 'aws' | 'azure' | 'gcp', service) : `${service}-1`;
|
|
2341
|
+
const baseNode: Node = {
|
|
2342
|
+
id: nodeId,
|
|
2343
|
+
type: nodeType,
|
|
2344
|
+
position: finalPosition,
|
|
2345
|
+
data: {
|
|
2346
|
+
service,
|
|
2347
|
+
label: nodeLabel,
|
|
2348
|
+
sublabel,
|
|
2349
|
+
handles: ['left', 'right'],
|
|
2350
|
+
cloudProvider,
|
|
2351
|
+
infiniteResize: true,
|
|
2352
|
+
},
|
|
2353
|
+
style: { zIndex: 3 },
|
|
2354
|
+
parentNode: targetContainer?.id,
|
|
2355
|
+
extent: targetContainer ? 'parent' : undefined,
|
|
2356
|
+
};
|
|
2357
|
+
|
|
2358
|
+
// Validate placement if a parent is selected
|
|
2359
|
+
if (targetContainer) {
|
|
2360
|
+
const validation = validateCloudProviderPlacement(
|
|
2361
|
+
baseNode,
|
|
2362
|
+
parentNode,
|
|
2363
|
+
nodes,
|
|
2364
|
+
cloudProvider as 'aws' | 'azure' | 'gcp',
|
|
2365
|
+
);
|
|
2366
|
+
|
|
2367
|
+
if (!validation.isValid && validation.message) {
|
|
2368
|
+
setValidationError(validation.message);
|
|
2369
|
+
return;
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
// Enhance the node with validation properties
|
|
2374
|
+
const enhancedNode = enhanceNodeWithValidation(baseNode, [...nodes]);
|
|
2375
|
+
|
|
2376
|
+
// Add the enhanced node
|
|
2377
|
+
const newNodes = [...nodes, enhancedNode];
|
|
2378
|
+
setNodes(newNodes);
|
|
2379
|
+
addToHistory(newNodes, edges);
|
|
2380
|
+
|
|
2381
|
+
// Show success message
|
|
2382
|
+
if (targetContainer) {
|
|
2383
|
+
setSuccessMessage(
|
|
2384
|
+
t('locale.ui.messages.success.nodePlaced', {
|
|
2385
|
+
container: targetContainer.data?.label || targetContainer.data?.service
|
|
2386
|
+
}),
|
|
2387
|
+
);
|
|
2388
|
+
setTimeout(() => setSuccessMessage(null), 2000);
|
|
2389
|
+
}
|
|
2390
|
+
},
|
|
2391
|
+
[nodes, edges, addToHistory],
|
|
2392
|
+
);
|
|
2393
|
+
|
|
2394
|
+
// Add drag enter handler to show drag preview
|
|
2395
|
+
const onDragEnter = useCallback((event: React.DragEvent) => {
|
|
2396
|
+
event.preventDefault();
|
|
2397
|
+
|
|
2398
|
+
try {
|
|
2399
|
+
// Try to get drag data from the event
|
|
2400
|
+
let dragData = null;
|
|
2401
|
+
|
|
2402
|
+
// First, try application/json
|
|
2403
|
+
try {
|
|
2404
|
+
const rawData = event.dataTransfer.getData('application/json');
|
|
2405
|
+
console.log('onDragEnter: Raw JSON data:', rawData);
|
|
2406
|
+
|
|
2407
|
+
if (rawData && rawData.trim().length > 0) {
|
|
2408
|
+
dragData = JSON.parse(rawData);
|
|
2409
|
+
console.log('onDragEnter: Parsed JSON data:', dragData);
|
|
2410
|
+
}
|
|
2411
|
+
} catch (e) {
|
|
2412
|
+
console.log('onDragEnter: Could not parse application/json data:', e);
|
|
2413
|
+
|
|
2414
|
+
// Fallback: try text/plain
|
|
2415
|
+
try {
|
|
2416
|
+
const textData = event.dataTransfer.getData('text/plain');
|
|
2417
|
+
console.log('onDragEnter: Raw text data:', textData);
|
|
2418
|
+
|
|
2419
|
+
if (textData && textData.trim().length > 0) {
|
|
2420
|
+
dragData = JSON.parse(textData);
|
|
2421
|
+
console.log('onDragEnter: Parsed text data:', dragData);
|
|
2422
|
+
}
|
|
2423
|
+
} catch (e2) {
|
|
2424
|
+
console.log('onDragEnter: Could not parse text/plain data:', e2);
|
|
2425
|
+
|
|
2426
|
+
// Log available types for debugging
|
|
2427
|
+
const types = Array.from(event.dataTransfer.types);
|
|
2428
|
+
console.log('onDragEnter: Available data types:', types);
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
|
|
2432
|
+
if (dragData && dragData.service && dragData.cloudProvider) {
|
|
2433
|
+
console.log('onDragEnter: Setting currentDragData', dragData);
|
|
2434
|
+
// Store current drag data for validation
|
|
2435
|
+
setCurrentDragData({
|
|
2436
|
+
service: dragData.service,
|
|
2437
|
+
nodeType: dragData.nodeType || 'serviceNode',
|
|
2438
|
+
cloudProvider: dragData.cloudProvider,
|
|
2439
|
+
label: dragData.label || dragData.service,
|
|
2440
|
+
});
|
|
2441
|
+
|
|
2442
|
+
const reactFlowBounds = (
|
|
2443
|
+
event.currentTarget as HTMLElement
|
|
2444
|
+
).getBoundingClientRect();
|
|
2445
|
+
setDragPreview({
|
|
2446
|
+
isVisible: true,
|
|
2447
|
+
service: dragData.service,
|
|
2448
|
+
cloudProvider: dragData.cloudProvider,
|
|
2449
|
+
position: {
|
|
2450
|
+
x: event.clientX - reactFlowBounds.left,
|
|
2451
|
+
y: event.clientY - reactFlowBounds.top,
|
|
2452
|
+
},
|
|
2453
|
+
isValid: true,
|
|
2454
|
+
validationMessage: t('locale.ui.messages.info.dropToPlace'),
|
|
2455
|
+
targetContainer: null, // Reset target container on new drag enter
|
|
2456
|
+
});
|
|
2457
|
+
} else {
|
|
2458
|
+
console.log(
|
|
2459
|
+
'onDragEnter: No valid drag data found - dragData:',
|
|
2460
|
+
dragData,
|
|
2461
|
+
);
|
|
2462
|
+
}
|
|
2463
|
+
} catch (error) {
|
|
2464
|
+
console.error('onDragEnter: Error reading drag data:', error);
|
|
2465
|
+
}
|
|
2466
|
+
}, []);
|
|
2467
|
+
|
|
2468
|
+
// Add node mouse enter handler during drag operations
|
|
2469
|
+
const onNodeMouseEnter = useCallback(
|
|
2470
|
+
(event: React.MouseEvent, node: Node) => {
|
|
2471
|
+
console.log(
|
|
2472
|
+
'onNodeMouseEnter: Processing hover over node',
|
|
2473
|
+
node.id,
|
|
2474
|
+
'with drag data',
|
|
2475
|
+
currentDragData,
|
|
2476
|
+
);
|
|
2477
|
+
|
|
2478
|
+
// Only process if we're currently dragging something
|
|
2479
|
+
if (!currentDragData) {
|
|
2480
|
+
console.log('onNodeMouseEnter: No currentDragData available');
|
|
2481
|
+
return;
|
|
2482
|
+
}
|
|
2483
|
+
|
|
2484
|
+
try {
|
|
2485
|
+
setHoveredNodeDuringDrag(node.id);
|
|
2486
|
+
|
|
2487
|
+
// Validate if this node can be a valid parent for the dragged service
|
|
2488
|
+
let isValidParent = false;
|
|
2489
|
+
let validationMessage = '';
|
|
2490
|
+
|
|
2491
|
+
try {
|
|
2492
|
+
// For container services, validate placement
|
|
2493
|
+
if (isContainerService(currentDragData.service)) {
|
|
2494
|
+
console.log('Validating container service placement');
|
|
2495
|
+
// Create temporary node for validation
|
|
2496
|
+
const tempNode = {
|
|
2497
|
+
id: `temp-${Date.now()}`,
|
|
2498
|
+
type: currentDragData.nodeType || 'serviceNode',
|
|
2499
|
+
position: { x: 0, y: 0 }, // Position doesn't matter for parent validation
|
|
2500
|
+
data: {
|
|
2501
|
+
service: currentDragData.service,
|
|
2502
|
+
cloudProvider: currentDragData.cloudProvider,
|
|
2503
|
+
},
|
|
2504
|
+
parentNode: node.id,
|
|
2505
|
+
};
|
|
2506
|
+
|
|
2507
|
+
const validation = validateCloudProviderPlacement(
|
|
2508
|
+
tempNode as any,
|
|
2509
|
+
node,
|
|
2510
|
+
nodes,
|
|
2511
|
+
currentDragData.cloudProvider as 'aws' | 'azure' | 'gcp',
|
|
2512
|
+
);
|
|
2513
|
+
|
|
2514
|
+
isValidParent = validation.isValid;
|
|
2515
|
+
validationMessage =
|
|
2516
|
+
validation.message || 'Invalid container placement';
|
|
2517
|
+
console.log('Container validation result:', {
|
|
2518
|
+
isValidParent,
|
|
2519
|
+
validationMessage,
|
|
2520
|
+
});
|
|
2521
|
+
} else {
|
|
2522
|
+
console.log('Validating service node placement');
|
|
2523
|
+
// For service nodes, check if the hovered node is a container
|
|
2524
|
+
try {
|
|
2525
|
+
isValidParent = isContainerNode(node);
|
|
2526
|
+
validationMessage = isValidParent
|
|
2527
|
+
? `Can place ${currentDragData.service} in ${node.data?.label || node.data?.service}`
|
|
2528
|
+
: `${node.data?.label || node.data?.service || 'This node'} cannot contain services`;
|
|
2529
|
+
console.log('Service validation result:', {
|
|
2530
|
+
isValidParent,
|
|
2531
|
+
validationMessage,
|
|
2532
|
+
isContainer: isContainerNode(node),
|
|
2533
|
+
});
|
|
2534
|
+
} catch (containerCheckError) {
|
|
2535
|
+
console.error(
|
|
2536
|
+
'Error checking if node is container:',
|
|
2537
|
+
containerCheckError,
|
|
2538
|
+
);
|
|
2539
|
+
isValidParent = false;
|
|
2540
|
+
validationMessage =
|
|
2541
|
+
'Cannot determine if this is a valid container';
|
|
2542
|
+
}
|
|
2543
|
+
}
|
|
2544
|
+
} catch (validationError) {
|
|
2545
|
+
console.error('Validation error:', validationError);
|
|
2546
|
+
isValidParent = false;
|
|
2547
|
+
validationMessage = 'Validation failed - invalid drop target';
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
// IMMEDIATE cursor and body class changes for instant feedback
|
|
2551
|
+
if (!isValidParent) {
|
|
2552
|
+
document.body.style.cursor = 'not-allowed';
|
|
2553
|
+
document.body.classList.add('dragging-invalid');
|
|
2554
|
+
document.body.classList.remove('dragging-valid');
|
|
2555
|
+
|
|
2556
|
+
// Also update ReactFlow container
|
|
2557
|
+
const reactFlowElement = document.querySelector('.react-flow');
|
|
2558
|
+
if (reactFlowElement) {
|
|
2559
|
+
reactFlowElement.classList.add('drag-invalid');
|
|
2560
|
+
reactFlowElement.classList.remove('drag-valid');
|
|
2561
|
+
}
|
|
2562
|
+
} else {
|
|
2563
|
+
document.body.style.cursor = 'copy';
|
|
2564
|
+
document.body.classList.add('dragging-valid');
|
|
2565
|
+
document.body.classList.remove('dragging-invalid');
|
|
2566
|
+
|
|
2567
|
+
// Also update ReactFlow container
|
|
2568
|
+
const reactFlowElement = document.querySelector('.react-flow');
|
|
2569
|
+
if (reactFlowElement) {
|
|
2570
|
+
reactFlowElement.classList.add('drag-valid');
|
|
2571
|
+
reactFlowElement.classList.remove('drag-invalid');
|
|
2572
|
+
}
|
|
2573
|
+
}
|
|
2574
|
+
|
|
2575
|
+
// Update nodes to show visual feedback with immediate DOM updates
|
|
2576
|
+
try {
|
|
2577
|
+
// First, update the hovered node immediately via DOM
|
|
2578
|
+
const hoveredElement = document.querySelector(
|
|
2579
|
+
`[data-id="${node.id}"]`,
|
|
2580
|
+
);
|
|
2581
|
+
if (hoveredElement) {
|
|
2582
|
+
hoveredElement.setAttribute('data-drag-hover', 'true');
|
|
2583
|
+
hoveredElement.setAttribute(
|
|
2584
|
+
'data-drag-hover-valid',
|
|
2585
|
+
isValidParent.toString(),
|
|
2586
|
+
);
|
|
2587
|
+
|
|
2588
|
+
// Apply immediate inline styles for instant feedback
|
|
2589
|
+
if (isValidParent) {
|
|
2590
|
+
hoveredElement.setAttribute(
|
|
2591
|
+
'style',
|
|
2592
|
+
(hoveredElement.getAttribute('style') || '') +
|
|
2593
|
+
'; cursor: copy !important; transition: all 0.1s ease !important;',
|
|
2594
|
+
);
|
|
2595
|
+
} else {
|
|
2596
|
+
hoveredElement.setAttribute(
|
|
2597
|
+
'style',
|
|
2598
|
+
(hoveredElement.getAttribute('style') || '') +
|
|
2599
|
+
'; cursor: not-allowed !important; transition: all 0.1s ease !important;',
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
|
|
2604
|
+
// Then update React state for persistence
|
|
2605
|
+
const updatedNodes = nodes.map((n) => ({
|
|
2606
|
+
...n,
|
|
2607
|
+
data: {
|
|
2608
|
+
...n.data,
|
|
2609
|
+
dragHover: n.id === node.id,
|
|
2610
|
+
dragHoverValid: n.id === node.id ? isValidParent : false,
|
|
2611
|
+
highlighted: false, // Clear previous highlighting
|
|
2612
|
+
},
|
|
2613
|
+
// Add data attributes for CSS styling
|
|
2614
|
+
...(n.id === node.id && {
|
|
2615
|
+
style: {
|
|
2616
|
+
...n.style,
|
|
2617
|
+
'--drag-hover': 'true',
|
|
2618
|
+
'--drag-hover-valid': isValidParent.toString(),
|
|
2619
|
+
},
|
|
2620
|
+
}),
|
|
2621
|
+
}));
|
|
2622
|
+
setNodes(updatedNodes);
|
|
2623
|
+
} catch (nodeUpdateError) {
|
|
2624
|
+
console.error('Error updating nodes:', nodeUpdateError);
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2627
|
+
// Update drag preview with validation result
|
|
2628
|
+
try {
|
|
2629
|
+
if (dragPreview) {
|
|
2630
|
+
setDragPreview((prev) =>
|
|
2631
|
+
prev
|
|
2632
|
+
? {
|
|
2633
|
+
...prev,
|
|
2634
|
+
isValid: isValidParent,
|
|
2635
|
+
validationMessage: validationMessage,
|
|
2636
|
+
targetContainer: isValidParent ? node : null,
|
|
2637
|
+
}
|
|
2638
|
+
: null,
|
|
2639
|
+
);
|
|
2640
|
+
}
|
|
2641
|
+
} catch (previewError) {
|
|
2642
|
+
console.error('Error updating drag preview:', previewError);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
// Show invalid drop overlay with stop icon and tooltip for invalid targets
|
|
2646
|
+
console.log('Validation result:', {
|
|
2647
|
+
isValidParent,
|
|
2648
|
+
nodeId: node.id,
|
|
2649
|
+
service: node.data?.service,
|
|
2650
|
+
message: validationMessage,
|
|
2651
|
+
dragService: currentDragData.service,
|
|
2652
|
+
});
|
|
2653
|
+
|
|
2654
|
+
try {
|
|
2655
|
+
if (!isValidParent) {
|
|
2656
|
+
console.log('Setting invalid drop overlay for node:', node.id);
|
|
2657
|
+
|
|
2658
|
+
// Get the node's screen position with better error handling
|
|
2659
|
+
const nodeElement =
|
|
2660
|
+
document.querySelector(`[data-id="${node.id}"]`) ||
|
|
2661
|
+
document.querySelector(`.react-flow__node[data-id="${node.id}"]`);
|
|
2662
|
+
console.log('Found node element:', !!nodeElement);
|
|
2663
|
+
|
|
2664
|
+
if (nodeElement) {
|
|
2665
|
+
const nodeRect = nodeElement.getBoundingClientRect();
|
|
2666
|
+
const flowElement =
|
|
2667
|
+
document.querySelector('.react-flow') ||
|
|
2668
|
+
document.querySelector('.react-flow__renderer') ||
|
|
2669
|
+
event.currentTarget?.closest('.react-flow');
|
|
2670
|
+
const flowRect = flowElement?.getBoundingClientRect();
|
|
2671
|
+
|
|
2672
|
+
console.log('Positioning data:', {
|
|
2673
|
+
nodeRect: {
|
|
2674
|
+
width: nodeRect.width,
|
|
2675
|
+
height: nodeRect.height,
|
|
2676
|
+
left: nodeRect.left,
|
|
2677
|
+
top: nodeRect.top,
|
|
2678
|
+
},
|
|
2679
|
+
flowRect: flowRect
|
|
2680
|
+
? { left: flowRect.left, top: flowRect.top }
|
|
2681
|
+
: null,
|
|
2682
|
+
});
|
|
2683
|
+
|
|
2684
|
+
if (flowRect) {
|
|
2685
|
+
const centerX =
|
|
2686
|
+
nodeRect.left + nodeRect.width / 2 - flowRect.left;
|
|
2687
|
+
const centerY =
|
|
2688
|
+
nodeRect.top + nodeRect.height / 2 - flowRect.top;
|
|
2689
|
+
|
|
2690
|
+
console.log('Setting overlay at position:', {
|
|
2691
|
+
centerX,
|
|
2692
|
+
centerY,
|
|
2693
|
+
});
|
|
2694
|
+
|
|
2695
|
+
setInvalidDropOverlay({
|
|
2696
|
+
nodeId: node.id,
|
|
2697
|
+
position: { x: centerX, y: centerY },
|
|
2698
|
+
message: validationMessage || 'Invalid drop target',
|
|
2699
|
+
});
|
|
2700
|
+
|
|
2701
|
+
// Set auto-hide timer if configured
|
|
2702
|
+
if (OVERLAY_TIMING.autoHideDuration > 0) {
|
|
2703
|
+
// Clear any existing timer
|
|
2704
|
+
if (overlayAutoHideTimer) {
|
|
2705
|
+
clearTimeout(overlayAutoHideTimer);
|
|
2706
|
+
}
|
|
2707
|
+
|
|
2708
|
+
// Set new timer to auto-hide overlay
|
|
2709
|
+
const timer = setTimeout(() => {
|
|
2710
|
+
setInvalidDropOverlay(null);
|
|
2711
|
+
setOverlayAutoHideTimer(null);
|
|
2712
|
+
}, OVERLAY_TIMING.autoHideDuration * 1000);
|
|
2713
|
+
|
|
2714
|
+
setOverlayAutoHideTimer(timer);
|
|
2715
|
+
}
|
|
2716
|
+
} else {
|
|
2717
|
+
console.warn('Could not find flow container for positioning');
|
|
2718
|
+
// Fallback positioning using mouse coordinates
|
|
2719
|
+
const rect = (
|
|
2720
|
+
event.currentTarget as HTMLElement
|
|
2721
|
+
).getBoundingClientRect();
|
|
2722
|
+
setInvalidDropOverlay({
|
|
2723
|
+
nodeId: node.id,
|
|
2724
|
+
position: {
|
|
2725
|
+
x: event.clientX - rect.left,
|
|
2726
|
+
y: event.clientY - rect.top,
|
|
2727
|
+
},
|
|
2728
|
+
message: validationMessage || 'Invalid drop target',
|
|
2729
|
+
});
|
|
2730
|
+
}
|
|
2731
|
+
} else {
|
|
2732
|
+
console.warn(
|
|
2733
|
+
'Could not find node element for overlay positioning',
|
|
2734
|
+
);
|
|
2735
|
+
}
|
|
2736
|
+
} else {
|
|
2737
|
+
console.log(
|
|
2738
|
+
'Clearing invalid drop overlay - isValidParent:',
|
|
2739
|
+
isValidParent,
|
|
2740
|
+
);
|
|
2741
|
+
// Clear overlay for valid targets
|
|
2742
|
+
setInvalidDropOverlay(null);
|
|
2743
|
+
}
|
|
2744
|
+
} catch (overlayError) {
|
|
2745
|
+
console.error('Error handling invalid drop overlay:', overlayError);
|
|
2746
|
+
// Still try to show a basic overlay even if positioning fails
|
|
2747
|
+
setInvalidDropOverlay({
|
|
2748
|
+
nodeId: node.id,
|
|
2749
|
+
position: { x: 100, y: 100 }, // Fallback position
|
|
2750
|
+
message: validationMessage || 'Invalid drop target',
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
} catch (globalError) {
|
|
2754
|
+
console.error('Global error in onNodeMouseEnter:', globalError);
|
|
2755
|
+
// Ensure we always clear states on error
|
|
2756
|
+
setInvalidDropOverlay(null);
|
|
2757
|
+
setHoveredNodeDuringDrag(null);
|
|
2758
|
+
// Reset cursor on error
|
|
2759
|
+
document.body.style.cursor = 'default';
|
|
2760
|
+
document.body.classList.remove('dragging-invalid', 'dragging-valid');
|
|
2761
|
+
}
|
|
2762
|
+
},
|
|
2763
|
+
[
|
|
2764
|
+
currentDragData,
|
|
2765
|
+
nodes,
|
|
2766
|
+
dragPreview,
|
|
2767
|
+
isContainerService,
|
|
2768
|
+
isContainerNode,
|
|
2769
|
+
reactFlowInstance,
|
|
2770
|
+
],
|
|
2771
|
+
);
|
|
2772
|
+
|
|
2773
|
+
// Add node mouse leave handler during drag operations
|
|
2774
|
+
const onNodeMouseLeave = useCallback(
|
|
2775
|
+
(event: React.MouseEvent, node: Node) => {
|
|
2776
|
+
// Only process if we're currently dragging something
|
|
2777
|
+
if (!currentDragData) return;
|
|
2778
|
+
|
|
2779
|
+
setHoveredNodeDuringDrag(null);
|
|
2780
|
+
|
|
2781
|
+
// Clear hover styling from all nodes
|
|
2782
|
+
const updatedNodes = nodes.map((n) => ({
|
|
2783
|
+
...n,
|
|
2784
|
+
data: {
|
|
2785
|
+
...n.data,
|
|
2786
|
+
dragHover: false,
|
|
2787
|
+
dragHoverValid: false,
|
|
2788
|
+
highlighted: false,
|
|
2789
|
+
},
|
|
2790
|
+
style: {
|
|
2791
|
+
...n.style,
|
|
2792
|
+
'--drag-hover': undefined,
|
|
2793
|
+
'--drag-hover-valid': undefined,
|
|
2794
|
+
},
|
|
2795
|
+
}));
|
|
2796
|
+
setNodes(updatedNodes);
|
|
2797
|
+
|
|
2798
|
+
// Also clear DOM attributes for immediate visual feedback
|
|
2799
|
+
setTimeout(() => {
|
|
2800
|
+
const nodeElement = document.querySelector(`[data-id="${node.id}"]`);
|
|
2801
|
+
if (nodeElement) {
|
|
2802
|
+
nodeElement.removeAttribute('data-drag-hover');
|
|
2803
|
+
nodeElement.removeAttribute('data-drag-hover-valid');
|
|
2804
|
+
}
|
|
2805
|
+
}, 0);
|
|
2806
|
+
|
|
2807
|
+
// Reset drag preview validation
|
|
2808
|
+
if (dragPreview) {
|
|
2809
|
+
setDragPreview((prev) =>
|
|
2810
|
+
prev
|
|
2811
|
+
? {
|
|
2812
|
+
...prev,
|
|
2813
|
+
isValid: true, // Reset to neutral state when not hovering over anything
|
|
2814
|
+
validationMessage: t('locale.ui.messages.info.dropToPlace'),
|
|
2815
|
+
targetContainer: null,
|
|
2816
|
+
}
|
|
2817
|
+
: null,
|
|
2818
|
+
);
|
|
2819
|
+
}
|
|
2820
|
+
|
|
2821
|
+
// Clear invalid drop overlay
|
|
2822
|
+
setInvalidDropOverlay(null);
|
|
2823
|
+
},
|
|
2824
|
+
[currentDragData, nodes, dragPreview],
|
|
2825
|
+
);
|
|
2826
|
+
|
|
2827
|
+
// Add drag leave handler to hide drag preview
|
|
2828
|
+
const onDragLeave = useCallback(
|
|
2829
|
+
(event: React.DragEvent) => {
|
|
2830
|
+
// Only hide if leaving the main container
|
|
2831
|
+
if (
|
|
2832
|
+
!(event.currentTarget as HTMLElement).contains(
|
|
2833
|
+
event.relatedTarget as HTMLElement,
|
|
2834
|
+
)
|
|
2835
|
+
) {
|
|
2836
|
+
setDragPreview(null);
|
|
2837
|
+
setCurrentDragData(null);
|
|
2838
|
+
setHoveredNodeDuringDrag(null);
|
|
2839
|
+
setInvalidDropOverlay(null);
|
|
2840
|
+
|
|
2841
|
+
// Reset cursor
|
|
2842
|
+
document.body.style.cursor = 'default';
|
|
2843
|
+
document.body.classList.remove('dragging-invalid');
|
|
2844
|
+
|
|
2845
|
+
// Clear all drag-related styling
|
|
2846
|
+
const clearedNodes = nodes.map((node) => ({
|
|
2847
|
+
...node,
|
|
2848
|
+
data: {
|
|
2849
|
+
...node.data,
|
|
2850
|
+
highlighted: false,
|
|
2851
|
+
dragHover: false,
|
|
2852
|
+
dragHoverValid: false,
|
|
2853
|
+
},
|
|
2854
|
+
style: {
|
|
2855
|
+
...node.style,
|
|
2856
|
+
'--drag-hover': undefined,
|
|
2857
|
+
'--drag-hover-valid': undefined,
|
|
2858
|
+
},
|
|
2859
|
+
}));
|
|
2860
|
+
setNodes(clearedNodes);
|
|
2861
|
+
|
|
2862
|
+
// Clear DOM attributes from all nodes
|
|
2863
|
+
setTimeout(() => {
|
|
2864
|
+
document
|
|
2865
|
+
.querySelectorAll('.react-flow__node')
|
|
2866
|
+
.forEach((nodeElement) => {
|
|
2867
|
+
nodeElement.removeAttribute('data-drag-hover');
|
|
2868
|
+
nodeElement.removeAttribute('data-drag-hover-valid');
|
|
2869
|
+
nodeElement.removeAttribute('style');
|
|
2870
|
+
});
|
|
2871
|
+
}, 0);
|
|
2872
|
+
}
|
|
2873
|
+
},
|
|
2874
|
+
[nodes],
|
|
2875
|
+
);
|
|
2876
|
+
|
|
2877
|
+
// Modify onEdgesChange to track history
|
|
2878
|
+
const onEdgesChangeWithHistory = useCallback(
|
|
2879
|
+
(changes: EdgeChange[]) => {
|
|
2880
|
+
// Apply changes to get the new edges state
|
|
2881
|
+
const newEdges = applyEdgeChanges(changes, edges);
|
|
2882
|
+
setEdges(newEdges);
|
|
2883
|
+
addToHistory(nodes, newEdges);
|
|
2884
|
+
},
|
|
2885
|
+
[nodes, edges, addToHistory],
|
|
2886
|
+
);
|
|
2887
|
+
|
|
2888
|
+
// Modify onConnect to track history
|
|
2889
|
+
const onConnect = useCallback(
|
|
2890
|
+
(params: Connection) => {
|
|
2891
|
+
const newEdges = addEdge(
|
|
2892
|
+
{
|
|
2893
|
+
...params,
|
|
2894
|
+
...defaultEdgeOptions,
|
|
2895
|
+
},
|
|
2896
|
+
edges,
|
|
2897
|
+
);
|
|
2898
|
+
|
|
2899
|
+
// Validate the connection
|
|
2900
|
+
const sourceNode = nodes.find((n) => n.id === params.source);
|
|
2901
|
+
const targetNode = nodes.find((n) => n.id === params.target);
|
|
2902
|
+
|
|
2903
|
+
if (
|
|
2904
|
+
sourceNode?.data?.cloudProvider === 'aws' &&
|
|
2905
|
+
targetNode?.data?.cloudProvider === 'aws'
|
|
2906
|
+
) {
|
|
2907
|
+
// Add any specific AWS connection validation here
|
|
2908
|
+
// For example, validate EBS to EC2 connections are in the same AZ
|
|
2909
|
+
// This would need to be implemented based on your specific requirements
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
setEdges(newEdges);
|
|
2913
|
+
addToHistory(nodes, newEdges);
|
|
2914
|
+
},
|
|
2915
|
+
[edges, nodes, addToHistory],
|
|
2916
|
+
);
|
|
2917
|
+
|
|
2918
|
+
// Update undo handler
|
|
2919
|
+
const handleUndo = useCallback(() => {
|
|
2920
|
+
if (historyIndex > 0) {
|
|
2921
|
+
const newIndex = historyIndex - 1;
|
|
2922
|
+
const previousState = history[newIndex];
|
|
2923
|
+
setNodes(JSON.parse(JSON.stringify(previousState.nodes)));
|
|
2924
|
+
setEdges(JSON.parse(JSON.stringify(previousState.edges)));
|
|
2925
|
+
setHistoryIndex(newIndex);
|
|
2926
|
+
}
|
|
2927
|
+
}, [history, historyIndex]);
|
|
2928
|
+
|
|
2929
|
+
// Update redo handler
|
|
2930
|
+
const handleRedo = useCallback(() => {
|
|
2931
|
+
if (historyIndex < history.length - 1) {
|
|
2932
|
+
const newIndex = historyIndex + 1;
|
|
2933
|
+
const nextState = history[newIndex];
|
|
2934
|
+
setNodes(JSON.parse(JSON.stringify(nextState.nodes)));
|
|
2935
|
+
setEdges(JSON.parse(JSON.stringify(nextState.edges)));
|
|
2936
|
+
setHistoryIndex(newIndex);
|
|
2937
|
+
}
|
|
2938
|
+
}, [history, historyIndex]);
|
|
2939
|
+
|
|
2940
|
+
// Update handleResize
|
|
2941
|
+
const handleResize = useCallback(
|
|
2942
|
+
(width: number, height: number) => {
|
|
2943
|
+
if (!selectedNode) return;
|
|
2944
|
+
|
|
2945
|
+
const newNodes = nodes.map((node) => {
|
|
2946
|
+
if (node.id === selectedNode.id) {
|
|
2947
|
+
return {
|
|
2948
|
+
...node,
|
|
2949
|
+
style: {
|
|
2950
|
+
...node.style,
|
|
2951
|
+
width,
|
|
2952
|
+
height,
|
|
2953
|
+
},
|
|
2954
|
+
data: {
|
|
2955
|
+
...node.data,
|
|
2956
|
+
width,
|
|
2957
|
+
height,
|
|
2958
|
+
},
|
|
2959
|
+
};
|
|
2960
|
+
}
|
|
2961
|
+
return node;
|
|
2962
|
+
});
|
|
2963
|
+
|
|
2964
|
+
setNodes(newNodes);
|
|
2965
|
+
addToHistory(newNodes, edges);
|
|
2966
|
+
},
|
|
2967
|
+
[selectedNode, nodes, edges, addToHistory],
|
|
2968
|
+
);
|
|
2969
|
+
|
|
2970
|
+
// Update handleLabelSave
|
|
2971
|
+
const handleLabelSave = useCallback(
|
|
2972
|
+
(
|
|
2973
|
+
label: string,
|
|
2974
|
+
sublabel?: string,
|
|
2975
|
+
dimensions?: { width: number; height: number },
|
|
2976
|
+
) => {
|
|
2977
|
+
if (!selectedNode) return;
|
|
2978
|
+
|
|
2979
|
+
const newNodes = nodes.map((node) => {
|
|
2980
|
+
if (node.id === selectedNode.id) {
|
|
2981
|
+
const updatedNode = {
|
|
2982
|
+
...node,
|
|
2983
|
+
data: {
|
|
2984
|
+
...node.data,
|
|
2985
|
+
label,
|
|
2986
|
+
sublabel,
|
|
2987
|
+
...(dimensions && {
|
|
2988
|
+
width: dimensions.width,
|
|
2989
|
+
height: dimensions.height,
|
|
2990
|
+
}),
|
|
2991
|
+
},
|
|
2992
|
+
};
|
|
2993
|
+
|
|
2994
|
+
if (dimensions) {
|
|
2995
|
+
updatedNode.style = {
|
|
2996
|
+
...node.style,
|
|
2997
|
+
width: dimensions.width,
|
|
2998
|
+
height: dimensions.height,
|
|
2999
|
+
};
|
|
3000
|
+
}
|
|
3001
|
+
|
|
3002
|
+
return updatedNode;
|
|
3003
|
+
}
|
|
3004
|
+
return node;
|
|
3005
|
+
});
|
|
3006
|
+
|
|
3007
|
+
setNodes(newNodes);
|
|
3008
|
+
addToHistory(newNodes, edges);
|
|
3009
|
+
},
|
|
3010
|
+
[selectedNode, nodes, edges, addToHistory],
|
|
3011
|
+
);
|
|
3012
|
+
|
|
3013
|
+
const onNodeDoubleClick: NodeMouseHandler = useCallback((event, node) => {
|
|
3014
|
+
setSelectedNode(node);
|
|
3015
|
+
setIsSettingsSidebarOpen(true);
|
|
3016
|
+
}, []);
|
|
3017
|
+
|
|
3018
|
+
// Add single click handler for container selection
|
|
3019
|
+
const onNodeClick: NodeMouseHandler = useCallback(
|
|
3020
|
+
(event, node) => {
|
|
3021
|
+
// Single click for container selection
|
|
3022
|
+
if (isContainerNode(node)) {
|
|
3023
|
+
handleContainerSelection(node);
|
|
3024
|
+
}
|
|
3025
|
+
},
|
|
3026
|
+
[isContainerNode, handleContainerSelection],
|
|
3027
|
+
);
|
|
3028
|
+
|
|
3029
|
+
// Add edge selection handler
|
|
3030
|
+
const onEdgeClick = useCallback((event: React.MouseEvent, edge: Edge) => {
|
|
3031
|
+
setSelectedEdge(edge);
|
|
3032
|
+
setIsSettingsSidebarOpen(true);
|
|
3033
|
+
}, []);
|
|
3034
|
+
|
|
3035
|
+
// Add edge settings handler
|
|
3036
|
+
const handleEdgeSettings = useCallback(
|
|
3037
|
+
(settings: {
|
|
3038
|
+
arrowDirection: 'source' | 'target' | 'both';
|
|
3039
|
+
color?: string;
|
|
3040
|
+
text?: string;
|
|
3041
|
+
}) => {
|
|
3042
|
+
if (!selectedEdge) return;
|
|
3043
|
+
|
|
3044
|
+
const newEdges = edges.map((edge) => {
|
|
3045
|
+
if (edge.id === selectedEdge.id) {
|
|
3046
|
+
const updatedEdge = {
|
|
3047
|
+
...edge,
|
|
3048
|
+
markerStart:
|
|
3049
|
+
settings.arrowDirection === 'source' ||
|
|
3050
|
+
settings.arrowDirection === 'both'
|
|
3051
|
+
? {
|
|
3052
|
+
type: MarkerType.ArrowClosed,
|
|
3053
|
+
width: 20,
|
|
3054
|
+
height: 20,
|
|
3055
|
+
color: settings.color || '#61dafb',
|
|
3056
|
+
}
|
|
3057
|
+
: undefined,
|
|
3058
|
+
markerEnd:
|
|
3059
|
+
settings.arrowDirection === 'target' ||
|
|
3060
|
+
settings.arrowDirection === 'both'
|
|
3061
|
+
? {
|
|
3062
|
+
type: MarkerType.ArrowClosed,
|
|
3063
|
+
width: 20,
|
|
3064
|
+
height: 20,
|
|
3065
|
+
color: settings.color || '#61dafb',
|
|
3066
|
+
}
|
|
3067
|
+
: undefined,
|
|
3068
|
+
label: settings.text || undefined,
|
|
3069
|
+
};
|
|
3070
|
+
|
|
3071
|
+
if (settings.color) {
|
|
3072
|
+
updatedEdge.style = {
|
|
3073
|
+
...edge.style,
|
|
3074
|
+
stroke: settings.color,
|
|
3075
|
+
};
|
|
3076
|
+
}
|
|
3077
|
+
|
|
3078
|
+
return updatedEdge;
|
|
3079
|
+
}
|
|
3080
|
+
return edge;
|
|
3081
|
+
});
|
|
3082
|
+
|
|
3083
|
+
setEdges(newEdges);
|
|
3084
|
+
addToHistory(nodes, newEdges);
|
|
3085
|
+
},
|
|
3086
|
+
[selectedEdge, edges, nodes, addToHistory],
|
|
3087
|
+
);
|
|
3088
|
+
|
|
3089
|
+
// Add effect to manage real-time drag state and ReactFlow classes
|
|
3090
|
+
React.useEffect(() => {
|
|
3091
|
+
const reactFlowElement = document.querySelector('.react-flow');
|
|
3092
|
+
|
|
3093
|
+
if (currentDragData) {
|
|
3094
|
+
// Add drag-active class when dragging starts
|
|
3095
|
+
if (reactFlowElement) {
|
|
3096
|
+
reactFlowElement.classList.add('drag-active');
|
|
3097
|
+
}
|
|
3098
|
+
document.body.classList.add('dragging');
|
|
3099
|
+
} else {
|
|
3100
|
+
// Clean up when dragging stops
|
|
3101
|
+
if (reactFlowElement) {
|
|
3102
|
+
reactFlowElement.classList.remove(
|
|
3103
|
+
'drag-active',
|
|
3104
|
+
'drag-invalid',
|
|
3105
|
+
'drag-valid',
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
document.body.classList.remove(
|
|
3109
|
+
'dragging',
|
|
3110
|
+
'dragging-invalid',
|
|
3111
|
+
'dragging-valid',
|
|
3112
|
+
);
|
|
3113
|
+
document.body.style.cursor = 'default';
|
|
3114
|
+
}
|
|
3115
|
+
|
|
3116
|
+
// Cleanup function
|
|
3117
|
+
return () => {
|
|
3118
|
+
if (reactFlowElement) {
|
|
3119
|
+
reactFlowElement.classList.remove(
|
|
3120
|
+
'drag-active',
|
|
3121
|
+
'drag-invalid',
|
|
3122
|
+
'drag-valid',
|
|
3123
|
+
);
|
|
3124
|
+
}
|
|
3125
|
+
document.body.classList.remove(
|
|
3126
|
+
'dragging',
|
|
3127
|
+
'dragging-invalid',
|
|
3128
|
+
'dragging-valid',
|
|
3129
|
+
);
|
|
3130
|
+
document.body.style.cursor = 'default';
|
|
3131
|
+
};
|
|
3132
|
+
}, [currentDragData]);
|
|
3133
|
+
|
|
3134
|
+
// Add effect to manage hovered node state for immediate feedback
|
|
3135
|
+
React.useEffect(() => {
|
|
3136
|
+
if (hoveredNodeDuringDrag && currentDragData) {
|
|
3137
|
+
const hoveredNode = nodes.find((n) => n.id === hoveredNodeDuringDrag);
|
|
3138
|
+
const isValid = hoveredNode?.data?.dragHoverValid;
|
|
3139
|
+
|
|
3140
|
+
// Update ReactFlow container classes for global feedback
|
|
3141
|
+
const reactFlowElement = document.querySelector('.react-flow');
|
|
3142
|
+
if (reactFlowElement) {
|
|
3143
|
+
if (isValid === false) {
|
|
3144
|
+
reactFlowElement.classList.add('drag-invalid');
|
|
3145
|
+
reactFlowElement.classList.remove('drag-valid');
|
|
3146
|
+
} else if (isValid === true) {
|
|
3147
|
+
reactFlowElement.classList.add('drag-valid');
|
|
3148
|
+
reactFlowElement.classList.remove('drag-invalid');
|
|
3149
|
+
}
|
|
3150
|
+
}
|
|
3151
|
+
}
|
|
3152
|
+
}, [hoveredNodeDuringDrag, nodes, currentDragData]);
|
|
3153
|
+
|
|
3154
|
+
// Add cleanup effect for overlay timer
|
|
3155
|
+
React.useEffect(() => {
|
|
3156
|
+
return () => {
|
|
3157
|
+
// Cleanup timer on unmount
|
|
3158
|
+
if (overlayAutoHideTimer) {
|
|
3159
|
+
clearTimeout(overlayAutoHideTimer);
|
|
3160
|
+
}
|
|
3161
|
+
};
|
|
3162
|
+
}, [overlayAutoHideTimer]);
|
|
3163
|
+
|
|
3164
|
+
// Function to refresh validation properties for all nodes
|
|
3165
|
+
const refreshValidationProperties = useCallback(() => {
|
|
3166
|
+
const enhancedNodes = enhanceNodesWithValidation(nodes);
|
|
3167
|
+
setNodes(enhancedNodes);
|
|
3168
|
+
addToHistory(enhancedNodes, edges);
|
|
3169
|
+
|
|
3170
|
+
setSuccessMessage(t('locale.ui.messages.success.validationRefreshed'));
|
|
3171
|
+
setTimeout(() => setSuccessMessage(null), 3000);
|
|
3172
|
+
}, [nodes, edges, addToHistory]);
|
|
3173
|
+
|
|
3174
|
+
return (
|
|
3175
|
+
<Box
|
|
3176
|
+
sx={{ display: 'flex', height: 'calc(100vh - 56px)', marginTop: '56px' }}
|
|
3177
|
+
>
|
|
3178
|
+
<Sidebar>
|
|
3179
|
+
<PropertiesPanel onAddNode={onAddNode} nodes={nodes} edges={edges} />
|
|
3180
|
+
</Sidebar>
|
|
3181
|
+
<Box
|
|
3182
|
+
sx={{
|
|
3183
|
+
flexGrow: 1,
|
|
3184
|
+
'& .react-flow__edge': {
|
|
3185
|
+
zIndex: '5000 !important',
|
|
3186
|
+
},
|
|
3187
|
+
'& .react-flow__edge-path': {
|
|
3188
|
+
pointerEvents: 'all !important',
|
|
3189
|
+
},
|
|
3190
|
+
'& .edge-path-selector': {
|
|
3191
|
+
strokeWidth: 20,
|
|
3192
|
+
fill: 'none',
|
|
3193
|
+
stroke: 'transparent',
|
|
3194
|
+
cursor: 'pointer',
|
|
3195
|
+
},
|
|
3196
|
+
'& .react-flow__edges': {
|
|
3197
|
+
zIndex: '5000 !important',
|
|
3198
|
+
},
|
|
3199
|
+
'& .react-flow__node': {
|
|
3200
|
+
pointerEvents: 'all !important',
|
|
3201
|
+
cursor: 'grab',
|
|
3202
|
+
'&:active': {
|
|
3203
|
+
cursor: 'grabbing',
|
|
3204
|
+
},
|
|
3205
|
+
'& .react-flow__edge': {
|
|
3206
|
+
zIndex: '5000 !important',
|
|
3207
|
+
},
|
|
3208
|
+
},
|
|
3209
|
+
'& .react-flow__node.dragging': {
|
|
3210
|
+
cursor: 'grabbing !important',
|
|
3211
|
+
},
|
|
3212
|
+
'& .react-flow.dragging': {
|
|
3213
|
+
cursor: 'grabbing',
|
|
3214
|
+
},
|
|
3215
|
+
}}
|
|
3216
|
+
>
|
|
3217
|
+
<Box
|
|
3218
|
+
sx={{
|
|
3219
|
+
width: '100%',
|
|
3220
|
+
height: '100%',
|
|
3221
|
+
padding: '10px',
|
|
3222
|
+
bgcolor: 'black',
|
|
3223
|
+
}}
|
|
3224
|
+
>
|
|
3225
|
+
<ReactFlow
|
|
3226
|
+
nodes={nodes}
|
|
3227
|
+
edges={edges}
|
|
3228
|
+
onNodesChange={onNodesChangeWithHistory}
|
|
3229
|
+
onEdgesChange={onEdgesChangeWithHistory}
|
|
3230
|
+
onConnect={onConnect}
|
|
3231
|
+
onNodeClick={onNodeClick}
|
|
3232
|
+
onNodeDoubleClick={onNodeDoubleClick}
|
|
3233
|
+
onNodeMouseEnter={onNodeMouseEnter}
|
|
3234
|
+
onNodeMouseLeave={onNodeMouseLeave}
|
|
3235
|
+
onEdgeClick={onEdgeClick}
|
|
3236
|
+
onInit={onInit}
|
|
3237
|
+
onDragOver={onDragOver}
|
|
3238
|
+
onDrop={onDrop}
|
|
3239
|
+
onDragEnter={onDragEnter}
|
|
3240
|
+
onDragLeave={onDragLeave}
|
|
3241
|
+
nodeTypes={nodeTypes}
|
|
3242
|
+
defaultEdgeOptions={defaultEdgeOptions}
|
|
3243
|
+
connectionLineType={ConnectionLineType.SmoothStep}
|
|
3244
|
+
fitView
|
|
3245
|
+
fitViewOptions={{
|
|
3246
|
+
padding: 0.2,
|
|
3247
|
+
minZoom: 0.5,
|
|
3248
|
+
maxZoom: 2,
|
|
3249
|
+
}}
|
|
3250
|
+
minZoom={0.1}
|
|
3251
|
+
maxZoom={2}
|
|
3252
|
+
defaultViewport={{ x: 0, y: 0, zoom: 1 }}
|
|
3253
|
+
style={{
|
|
3254
|
+
background: '#1a1a1a',
|
|
3255
|
+
}}
|
|
3256
|
+
proOptions={{ hideAttribution: true }}
|
|
3257
|
+
edgesUpdatable={true}
|
|
3258
|
+
elementsSelectable={true}
|
|
3259
|
+
selectNodesOnDrag={false}
|
|
3260
|
+
deleteKeyCode={null}
|
|
3261
|
+
multiSelectionKeyCode={['Meta', 'Ctrl']}
|
|
3262
|
+
className={(() => {
|
|
3263
|
+
const classes = [];
|
|
3264
|
+
|
|
3265
|
+
// Add base drag state classes
|
|
3266
|
+
if (currentDragData) {
|
|
3267
|
+
classes.push('drag-active');
|
|
3268
|
+
}
|
|
3269
|
+
|
|
3270
|
+
// Add real-time validation classes based on hovered node
|
|
3271
|
+
if (hoveredNodeDuringDrag && currentDragData) {
|
|
3272
|
+
const hoveredNode = nodes.find(
|
|
3273
|
+
(n) => n.id === hoveredNodeDuringDrag,
|
|
3274
|
+
);
|
|
3275
|
+
if (hoveredNode?.data?.dragHoverValid === false) {
|
|
3276
|
+
classes.push('drag-invalid', 'invalid-drop-target');
|
|
3277
|
+
} else if (hoveredNode?.data?.dragHoverValid === true) {
|
|
3278
|
+
classes.push('drag-valid', 'valid-drop-target');
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
|
|
3282
|
+
// Fallback to drag preview state if no specific node hover
|
|
3283
|
+
if (!hoveredNodeDuringDrag && dragPreview) {
|
|
3284
|
+
if (dragPreview.isValid) {
|
|
3285
|
+
classes.push('valid-drop-target');
|
|
3286
|
+
} else {
|
|
3287
|
+
classes.push('invalid-drop-target');
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
|
|
3291
|
+
return classes.join(' ');
|
|
3292
|
+
})()}
|
|
3293
|
+
>
|
|
3294
|
+
{/* Hide default controls */}
|
|
3295
|
+
|
|
3296
|
+
{/* Debug Panel for Drag State */}
|
|
3297
|
+
{currentDragData && (
|
|
3298
|
+
<Panel position="top-left" style={{ zIndex: 10 }}>
|
|
3299
|
+
<Box
|
|
3300
|
+
sx={{
|
|
3301
|
+
bgcolor: 'rgba(42, 42, 42, 0.95)',
|
|
3302
|
+
color: '#fff',
|
|
3303
|
+
padding: 2,
|
|
3304
|
+
borderRadius: 1,
|
|
3305
|
+
border: '1px solid #ff9800',
|
|
3306
|
+
minWidth: 250,
|
|
3307
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
|
3308
|
+
}}
|
|
3309
|
+
>
|
|
3310
|
+
<Typography
|
|
3311
|
+
variant="subtitle2"
|
|
3312
|
+
sx={{ mb: 1, color: '#ff9800' }}
|
|
3313
|
+
>
|
|
3314
|
+
{t('locale.ui.labels.dragState')}
|
|
3315
|
+
</Typography>
|
|
3316
|
+
<Typography
|
|
3317
|
+
variant="caption"
|
|
3318
|
+
sx={{ color: '#aaa', display: 'block' }}
|
|
3319
|
+
>
|
|
3320
|
+
{t('locale.ui.labels.service')}: {currentDragData.service}
|
|
3321
|
+
</Typography>
|
|
3322
|
+
<Typography
|
|
3323
|
+
variant="caption"
|
|
3324
|
+
sx={{ color: '#aaa', display: 'block' }}
|
|
3325
|
+
>
|
|
3326
|
+
{t('locale.ui.labels.cloud')}: {currentDragData.cloudProvider}
|
|
3327
|
+
</Typography>
|
|
3328
|
+
<Typography
|
|
3329
|
+
variant="caption"
|
|
3330
|
+
sx={{ color: '#aaa', display: 'block' }}
|
|
3331
|
+
>
|
|
3332
|
+
{t('locale.ui.labels.hoveredNode')}: {hoveredNodeDuringDrag || 'None'}
|
|
3333
|
+
</Typography>
|
|
3334
|
+
<Typography
|
|
3335
|
+
variant="caption"
|
|
3336
|
+
sx={{ color: '#aaa', display: 'block' }}
|
|
3337
|
+
>
|
|
3338
|
+
{t('locale.ui.labels.invalidOverlay')}: {invalidDropOverlay ? 'YES' : 'NO'}
|
|
3339
|
+
</Typography>
|
|
3340
|
+
{invalidDropOverlay && (
|
|
3341
|
+
<Typography
|
|
3342
|
+
variant="caption"
|
|
3343
|
+
sx={{ color: '#f44336', display: 'block' }}
|
|
3344
|
+
>
|
|
3345
|
+
{t('locale.ui.labels.message')}: {invalidDropOverlay.message}
|
|
3346
|
+
</Typography>
|
|
3347
|
+
)}
|
|
3348
|
+
</Box>
|
|
3349
|
+
</Panel>
|
|
3350
|
+
)}
|
|
3351
|
+
|
|
3352
|
+
{/* Selected Container Panel */}
|
|
3353
|
+
{selectedContainer && (
|
|
3354
|
+
<Panel position="top-right" style={{ zIndex: 10 }}>
|
|
3355
|
+
<Box
|
|
3356
|
+
sx={{
|
|
3357
|
+
bgcolor: 'rgba(42, 42, 42, 0.95)',
|
|
3358
|
+
color: '#fff',
|
|
3359
|
+
padding: 2,
|
|
3360
|
+
borderRadius: 1,
|
|
3361
|
+
border: '1px solid #4CAF50',
|
|
3362
|
+
minWidth: 200,
|
|
3363
|
+
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
|
|
3364
|
+
}}
|
|
3365
|
+
>
|
|
3366
|
+
<Typography
|
|
3367
|
+
variant="subtitle2"
|
|
3368
|
+
sx={{ mb: 1, color: '#4CAF50' }}
|
|
3369
|
+
>
|
|
3370
|
+
{t('locale.ui.labels.selectedContainer')}
|
|
3371
|
+
</Typography>
|
|
3372
|
+
<Typography variant="body2" sx={{ mb: 1 }}>
|
|
3373
|
+
{selectedContainer.data?.label ||
|
|
3374
|
+
selectedContainer.data?.service}
|
|
3375
|
+
</Typography>
|
|
3376
|
+
<Typography
|
|
3377
|
+
variant="caption"
|
|
3378
|
+
sx={{ color: '#aaa', mb: 2, display: 'block' }}
|
|
3379
|
+
>
|
|
3380
|
+
{t('locale.ui.labels.servicesWillBePlacedHere')}
|
|
3381
|
+
</Typography>
|
|
3382
|
+
<Button
|
|
3383
|
+
size="small"
|
|
3384
|
+
onClick={clearContainerSelection}
|
|
3385
|
+
sx={{
|
|
3386
|
+
color: '#ff5722',
|
|
3387
|
+
'&:hover': { bgcolor: 'rgba(255, 87, 34, 0.1)' },
|
|
3388
|
+
}}
|
|
3389
|
+
>
|
|
3390
|
+
{t('locale.ui.buttons.clearSelection')}
|
|
3391
|
+
</Button>
|
|
3392
|
+
</Box>
|
|
3393
|
+
</Panel>
|
|
3394
|
+
)}
|
|
3395
|
+
|
|
3396
|
+
{invalidDropOverlay && (
|
|
3397
|
+
<div
|
|
3398
|
+
style={{
|
|
3399
|
+
position: 'absolute',
|
|
3400
|
+
left: Math.max(10, invalidDropOverlay.position.x - 25),
|
|
3401
|
+
top: Math.max(10, invalidDropOverlay.position.y - 25),
|
|
3402
|
+
width: '50px',
|
|
3403
|
+
height: '50px',
|
|
3404
|
+
display: 'flex',
|
|
3405
|
+
alignItems: 'center',
|
|
3406
|
+
justifyContent: 'center',
|
|
3407
|
+
pointerEvents: 'none',
|
|
3408
|
+
zIndex: 15000,
|
|
3409
|
+
animation:
|
|
3410
|
+
`invalidDropOverlayAppear ${OVERLAY_TIMING.appearDuration}s ease-out, invalidDropPulse ${OVERLAY_TIMING.pulseDuration}s ease-in-out infinite ${OVERLAY_TIMING.appearDuration}s`,
|
|
3411
|
+
// Enhanced visibility styling
|
|
3412
|
+
border: '3px solid #ff0000',
|
|
3413
|
+
backgroundColor: 'rgba(255, 0, 0, 0.8)',
|
|
3414
|
+
borderRadius: '50%',
|
|
3415
|
+
boxShadow:
|
|
3416
|
+
'0 0 20px rgba(255, 0, 0, 0.8), 0 0 40px rgba(255, 0, 0, 0.4)',
|
|
3417
|
+
}}
|
|
3418
|
+
>
|
|
3419
|
+
{/* Stop Icon Background */}
|
|
3420
|
+
<div
|
|
3421
|
+
style={{
|
|
3422
|
+
width: '50px',
|
|
3423
|
+
height: '50px',
|
|
3424
|
+
borderRadius: '50%',
|
|
3425
|
+
backgroundColor: 'rgba(220, 53, 69, 0.95)',
|
|
3426
|
+
display: 'flex',
|
|
3427
|
+
alignItems: 'center',
|
|
3428
|
+
justifyContent: 'center',
|
|
3429
|
+
boxShadow:
|
|
3430
|
+
'0 4px 20px rgba(220, 53, 69, 0.6), 0 0 0 3px rgba(255, 255, 255, 0.9), 0 0 0 6px rgba(220, 53, 69, 0.4)',
|
|
3431
|
+
border: '2px solid rgba(220, 53, 69, 0.8)',
|
|
3432
|
+
animation:
|
|
3433
|
+
`stopIconRotate ${OVERLAY_TIMING.iconRotationDuration}s ease-in-out infinite, invalidDropPulse ${OVERLAY_TIMING.pulseDuration}s ease-in-out infinite`,
|
|
3434
|
+
}}
|
|
3435
|
+
>
|
|
3436
|
+
{/* Stop Sign Icon */}
|
|
3437
|
+
<div
|
|
3438
|
+
style={{
|
|
3439
|
+
width: '30px',
|
|
3440
|
+
height: '30px',
|
|
3441
|
+
borderRadius: '50%',
|
|
3442
|
+
border: '4px solid white',
|
|
3443
|
+
position: 'relative',
|
|
3444
|
+
display: 'flex',
|
|
3445
|
+
alignItems: 'center',
|
|
3446
|
+
justifyContent: 'center',
|
|
3447
|
+
}}
|
|
3448
|
+
>
|
|
3449
|
+
{/* Diagonal line for prohibition symbol */}
|
|
3450
|
+
<div
|
|
3451
|
+
style={{
|
|
3452
|
+
position: 'absolute',
|
|
3453
|
+
width: '30px',
|
|
3454
|
+
height: '4px',
|
|
3455
|
+
backgroundColor: 'white',
|
|
3456
|
+
transform: 'rotate(45deg)',
|
|
3457
|
+
}}
|
|
3458
|
+
/>
|
|
3459
|
+
</div>
|
|
3460
|
+
</div>
|
|
3461
|
+
|
|
3462
|
+
{/* Tooltip */}
|
|
3463
|
+
<div
|
|
3464
|
+
style={{
|
|
3465
|
+
position: 'absolute',
|
|
3466
|
+
top: '-60px',
|
|
3467
|
+
left: '50%',
|
|
3468
|
+
transform: 'translateX(-50%)',
|
|
3469
|
+
backgroundColor: 'rgba(220, 53, 69, 0.98)',
|
|
3470
|
+
color: 'white',
|
|
3471
|
+
padding: '8px 12px',
|
|
3472
|
+
borderRadius: '8px',
|
|
3473
|
+
fontSize: '12px',
|
|
3474
|
+
fontWeight: '600',
|
|
3475
|
+
whiteSpace: 'nowrap',
|
|
3476
|
+
maxWidth: '300px',
|
|
3477
|
+
textAlign: 'center',
|
|
3478
|
+
boxShadow:
|
|
3479
|
+
'0 4px 16px rgba(220, 53, 69, 0.4), 0 0 0 1px rgba(220, 53, 69, 0.6)',
|
|
3480
|
+
border: '2px solid rgba(220, 53, 69, 0.9)',
|
|
3481
|
+
backdropFilter: 'blur(5px)',
|
|
3482
|
+
zIndex: 10001,
|
|
3483
|
+
animation:
|
|
3484
|
+
`invalidDropTooltipAppear ${OVERLAY_TIMING.tooltipDuration}s ease-out ${OVERLAY_TIMING.tooltipDelay}s both`,
|
|
3485
|
+
}}
|
|
3486
|
+
>
|
|
3487
|
+
<div
|
|
3488
|
+
style={{
|
|
3489
|
+
fontSize: '10px',
|
|
3490
|
+
fontWeight: '800',
|
|
3491
|
+
marginBottom: '2px',
|
|
3492
|
+
color: '#fff',
|
|
3493
|
+
textTransform: 'uppercase',
|
|
3494
|
+
letterSpacing: '1px',
|
|
3495
|
+
}}
|
|
3496
|
+
>
|
|
3497
|
+
🚫 {t('locale.ui.messages.error.dropBlocked')}
|
|
3498
|
+
</div>
|
|
3499
|
+
{invalidDropOverlay.message}
|
|
3500
|
+
|
|
3501
|
+
{/* Tooltip Arrow */}
|
|
3502
|
+
<div
|
|
3503
|
+
style={{
|
|
3504
|
+
position: 'absolute',
|
|
3505
|
+
top: '100%',
|
|
3506
|
+
left: '50%',
|
|
3507
|
+
transform: 'translateX(-50%)',
|
|
3508
|
+
width: '0',
|
|
3509
|
+
height: '0',
|
|
3510
|
+
borderLeft: '6px solid transparent',
|
|
3511
|
+
borderRight: '6px solid transparent',
|
|
3512
|
+
borderTop: '6px solid rgba(220, 53, 69, 0.98)',
|
|
3513
|
+
}}
|
|
3514
|
+
/>
|
|
3515
|
+
</div>
|
|
3516
|
+
</div>
|
|
3517
|
+
)}
|
|
3518
|
+
|
|
3519
|
+
{/* Drag Preview */}
|
|
3520
|
+
{dragPreview && dragPreview.isVisible && (
|
|
3521
|
+
<div
|
|
3522
|
+
style={{
|
|
3523
|
+
position: 'absolute',
|
|
3524
|
+
left: dragPreview.position.x - 30,
|
|
3525
|
+
top: dragPreview.position.y - 30,
|
|
3526
|
+
width: '60px',
|
|
3527
|
+
height: '60px',
|
|
3528
|
+
backgroundColor: dragPreview.isValid
|
|
3529
|
+
? 'rgba(42, 42, 42, 0.9)'
|
|
3530
|
+
: 'rgba(220, 53, 69, 0.95)',
|
|
3531
|
+
border: dragPreview.isValid
|
|
3532
|
+
? '2px solid #61dafb'
|
|
3533
|
+
: '3px solid #dc3545',
|
|
3534
|
+
borderRadius: '8px',
|
|
3535
|
+
display: 'flex',
|
|
3536
|
+
alignItems: 'center',
|
|
3537
|
+
justifyContent: 'center',
|
|
3538
|
+
pointerEvents: 'none',
|
|
3539
|
+
zIndex: 9999,
|
|
3540
|
+
boxShadow: dragPreview.isValid
|
|
3541
|
+
? '0 4px 12px rgba(97, 218, 251, 0.3)'
|
|
3542
|
+
: '0 6px 20px rgba(220, 53, 69, 0.5), 0 0 0 1px rgba(220, 53, 69, 0.3), 0 0 40px rgba(220, 53, 69, 0.2)',
|
|
3543
|
+
backdropFilter: 'blur(10px)',
|
|
3544
|
+
transform: dragPreview.isValid ? 'scale(1)' : 'scale(1.1)',
|
|
3545
|
+
transition: 'all 0.2s ease',
|
|
3546
|
+
animation: dragPreview.isValid
|
|
3547
|
+
? 'none'
|
|
3548
|
+
: 'invalidDropShake 0.5s ease-in-out infinite',
|
|
3549
|
+
outline: dragPreview.isValid
|
|
3550
|
+
? 'none'
|
|
3551
|
+
: '1px solid rgba(220, 53, 69, 0.6)',
|
|
3552
|
+
outlineOffset: dragPreview.isValid ? '0' : '4px',
|
|
3553
|
+
}}
|
|
3554
|
+
>
|
|
3555
|
+
<div
|
|
3556
|
+
style={{
|
|
3557
|
+
width: '24px',
|
|
3558
|
+
height: '24px',
|
|
3559
|
+
display: 'flex',
|
|
3560
|
+
alignItems: 'center',
|
|
3561
|
+
justifyContent: 'center',
|
|
3562
|
+
}}
|
|
3563
|
+
>
|
|
3564
|
+
{/* Service icon */}
|
|
3565
|
+
{(() => {
|
|
3566
|
+
const serviceInfo = getServiceInfo(
|
|
3567
|
+
dragPreview.service,
|
|
3568
|
+
dragPreview.cloudProvider as 'aws' | 'azure' | 'gcp',
|
|
3569
|
+
);
|
|
3570
|
+
return serviceInfo ? (
|
|
3571
|
+
<img
|
|
3572
|
+
src={serviceInfo.icon}
|
|
3573
|
+
alt={serviceInfo.name}
|
|
3574
|
+
width={24}
|
|
3575
|
+
height={24}
|
|
3576
|
+
style={{
|
|
3577
|
+
objectFit: 'contain',
|
|
3578
|
+
filter: dragPreview.isValid
|
|
3579
|
+
? 'none'
|
|
3580
|
+
: 'grayscale(100%) brightness(0.6) contrast(1.2) sepia(100%) hue-rotate(345deg) saturate(150%)',
|
|
3581
|
+
transition: 'filter 0.2s ease',
|
|
3582
|
+
}}
|
|
3583
|
+
/>
|
|
3584
|
+
) : (
|
|
3585
|
+
<div
|
|
3586
|
+
style={{
|
|
3587
|
+
width: '24px',
|
|
3588
|
+
height: '24px',
|
|
3589
|
+
backgroundColor: dragPreview.isValid
|
|
3590
|
+
? '#61dafb'
|
|
3591
|
+
: '#dc3545',
|
|
3592
|
+
borderRadius: '4px',
|
|
3593
|
+
}}
|
|
3594
|
+
/>
|
|
3595
|
+
);
|
|
3596
|
+
})()}
|
|
3597
|
+
|
|
3598
|
+
{/* Invalid drop overlay indicator */}
|
|
3599
|
+
{!dragPreview.isValid && (
|
|
3600
|
+
<div
|
|
3601
|
+
style={{
|
|
3602
|
+
position: 'absolute',
|
|
3603
|
+
top: '50%',
|
|
3604
|
+
left: '50%',
|
|
3605
|
+
transform: 'translate(-50%, -50%)',
|
|
3606
|
+
width: '36px',
|
|
3607
|
+
height: '36px',
|
|
3608
|
+
backgroundColor: 'rgba(220, 53, 69, 0.95)',
|
|
3609
|
+
borderRadius: '50%',
|
|
3610
|
+
display: 'flex',
|
|
3611
|
+
alignItems: 'center',
|
|
3612
|
+
justifyContent: 'center',
|
|
3613
|
+
boxShadow:
|
|
3614
|
+
'0 4px 12px rgba(220, 53, 69, 0.6), 0 0 0 2px rgba(255, 255, 255, 0.9)',
|
|
3615
|
+
animation: 'invalidDropPulse 1s ease-in-out infinite',
|
|
3616
|
+
cursor: 'not-allowed',
|
|
3617
|
+
}}
|
|
3618
|
+
>
|
|
3619
|
+
{/* Stop/Prohibition symbol */}
|
|
3620
|
+
<div
|
|
3621
|
+
style={{
|
|
3622
|
+
width: '20px',
|
|
3623
|
+
height: '20px',
|
|
3624
|
+
borderRadius: '50%',
|
|
3625
|
+
border: '3px solid white',
|
|
3626
|
+
position: 'relative',
|
|
3627
|
+
display: 'flex',
|
|
3628
|
+
alignItems: 'center',
|
|
3629
|
+
justifyContent: 'center',
|
|
3630
|
+
}}
|
|
3631
|
+
>
|
|
3632
|
+
{/* Diagonal line for prohibition symbol */}
|
|
3633
|
+
<div
|
|
3634
|
+
style={{
|
|
3635
|
+
position: 'absolute',
|
|
3636
|
+
width: '20px',
|
|
3637
|
+
height: '3px',
|
|
3638
|
+
backgroundColor: 'white',
|
|
3639
|
+
transform: 'rotate(45deg)',
|
|
3640
|
+
}}
|
|
3641
|
+
/>
|
|
3642
|
+
</div>
|
|
3643
|
+
</div>
|
|
3644
|
+
)}
|
|
3645
|
+
|
|
3646
|
+
{/* Valid drop indicator */}
|
|
3647
|
+
{dragPreview.isValid && (
|
|
3648
|
+
<div
|
|
3649
|
+
style={{
|
|
3650
|
+
position: 'absolute',
|
|
3651
|
+
top: '2px',
|
|
3652
|
+
right: '2px',
|
|
3653
|
+
width: '16px',
|
|
3654
|
+
height: '16px',
|
|
3655
|
+
backgroundColor: 'rgba(76, 175, 80, 0.9)',
|
|
3656
|
+
borderRadius: '50%',
|
|
3657
|
+
display: 'flex',
|
|
3658
|
+
alignItems: 'center',
|
|
3659
|
+
justifyContent: 'center',
|
|
3660
|
+
boxShadow: '0 2px 4px rgba(76, 175, 80, 0.4)',
|
|
3661
|
+
border: '1px solid rgba(255, 255, 255, 0.8)',
|
|
3662
|
+
}}
|
|
3663
|
+
>
|
|
3664
|
+
{/* Checkmark */}
|
|
3665
|
+
<div
|
|
3666
|
+
style={{
|
|
3667
|
+
width: '8px',
|
|
3668
|
+
height: '4px',
|
|
3669
|
+
borderLeft: '2px solid white',
|
|
3670
|
+
borderBottom: '2px solid white',
|
|
3671
|
+
transform: 'rotate(-45deg)',
|
|
3672
|
+
marginTop: '-1px',
|
|
3673
|
+
}}
|
|
3674
|
+
/>
|
|
3675
|
+
</div>
|
|
3676
|
+
)}
|
|
3677
|
+
</div>
|
|
3678
|
+
|
|
3679
|
+
{/* Validation/Target message tooltip */}
|
|
3680
|
+
{dragPreview.validationMessage && (
|
|
3681
|
+
<div
|
|
3682
|
+
style={{
|
|
3683
|
+
position: 'absolute',
|
|
3684
|
+
top: dragPreview.isValid ? '-45px' : '70px',
|
|
3685
|
+
left: '50%',
|
|
3686
|
+
transform: 'translateX(-50%)',
|
|
3687
|
+
backgroundColor: dragPreview.isValid
|
|
3688
|
+
? 'rgba(42, 42, 42, 0.95)'
|
|
3689
|
+
: 'rgba(220, 53, 69, 0.98)',
|
|
3690
|
+
color: 'white',
|
|
3691
|
+
padding: dragPreview.isValid ? '6px 10px' : '8px 12px',
|
|
3692
|
+
borderRadius: dragPreview.isValid ? '6px' : '8px',
|
|
3693
|
+
fontSize: dragPreview.isValid ? '11px' : '12px',
|
|
3694
|
+
fontWeight: dragPreview.isValid ? 'normal' : '600',
|
|
3695
|
+
whiteSpace: 'nowrap',
|
|
3696
|
+
maxWidth: '300px',
|
|
3697
|
+
textAlign: 'center',
|
|
3698
|
+
boxShadow: dragPreview.isValid
|
|
3699
|
+
? '0 2px 8px rgba(0, 0, 0, 0.3)'
|
|
3700
|
+
: '0 4px 16px rgba(220, 53, 69, 0.4), 0 0 0 1px rgba(220, 53, 69, 0.6)',
|
|
3701
|
+
zIndex: 10000,
|
|
3702
|
+
border: dragPreview.isValid
|
|
3703
|
+
? '1px solid rgba(97, 218, 251, 0.5)'
|
|
3704
|
+
: '2px solid rgba(220, 53, 69, 0.9)',
|
|
3705
|
+
backdropFilter: 'blur(5px)',
|
|
3706
|
+
animation: dragPreview.isValid
|
|
3707
|
+
? 'none'
|
|
3708
|
+
: 'invalidDropPulse 1s ease-in-out infinite',
|
|
3709
|
+
transition: 'all 0.2s ease',
|
|
3710
|
+
cursor: dragPreview.isValid ? 'copy' : 'not-allowed',
|
|
3711
|
+
}}
|
|
3712
|
+
>
|
|
3713
|
+
{!dragPreview.isValid && (
|
|
3714
|
+
<span
|
|
3715
|
+
style={{
|
|
3716
|
+
display: 'block',
|
|
3717
|
+
fontSize: '10px',
|
|
3718
|
+
fontWeight: '800',
|
|
3719
|
+
marginBottom: '2px',
|
|
3720
|
+
color: '#fff',
|
|
3721
|
+
textTransform: 'uppercase',
|
|
3722
|
+
letterSpacing: '1px',
|
|
3723
|
+
}}
|
|
3724
|
+
>
|
|
3725
|
+
🚫 {t('locale.ui.messages.error.invalidDrop')}
|
|
3726
|
+
</span>
|
|
3727
|
+
)}
|
|
3728
|
+
{dragPreview.validationMessage}
|
|
3729
|
+
{dragPreview.isValid && dragPreview.targetContainer && (
|
|
3730
|
+
<div
|
|
3731
|
+
style={{
|
|
3732
|
+
fontSize: '10px',
|
|
3733
|
+
opacity: 0.8,
|
|
3734
|
+
marginTop: '2px',
|
|
3735
|
+
}}
|
|
3736
|
+
>
|
|
3737
|
+
{t('locale.ui.labels.target')}: {dragPreview.targetContainer.data?.service || 'container'}
|
|
3738
|
+
</div>
|
|
3739
|
+
)}
|
|
3740
|
+
</div>
|
|
3741
|
+
)}
|
|
3742
|
+
</div>
|
|
3743
|
+
)}
|
|
3744
|
+
|
|
3745
|
+
{/* MiniMap positioned at bottom-left */}
|
|
3746
|
+
<MiniMapCommon />
|
|
3747
|
+
|
|
3748
|
+
{/* Diagram Panel with all controls */}
|
|
3749
|
+
<DiagramPanel
|
|
3750
|
+
historyIndex={historyIndex}
|
|
3751
|
+
historyLength={history.length}
|
|
3752
|
+
onUndo={handleUndo}
|
|
3753
|
+
onRedo={handleRedo}
|
|
3754
|
+
onClear={() => {
|
|
3755
|
+
// Prepare the new empty state
|
|
3756
|
+
const clearedState = { nodes: [], edges: [] };
|
|
3757
|
+
// Trim any future history if not at the end
|
|
3758
|
+
const newHistory = history.slice(0, historyIndex + 1);
|
|
3759
|
+
// Add the cleared state to history
|
|
3760
|
+
newHistory.push(clearedState);
|
|
3761
|
+
setNodes([]);
|
|
3762
|
+
setEdges([]);
|
|
3763
|
+
setHistory(newHistory);
|
|
3764
|
+
setHistoryIndex(newHistory.length - 1);
|
|
3765
|
+
}}
|
|
3766
|
+
onImport={(data) => {
|
|
3767
|
+
setNodes(data.nodes);
|
|
3768
|
+
setEdges(data.edges);
|
|
3769
|
+
// Add to history
|
|
3770
|
+
const newHistory = history.slice(0, historyIndex + 1);
|
|
3771
|
+
newHistory.push(data);
|
|
3772
|
+
setHistory(newHistory);
|
|
3773
|
+
setHistoryIndex(newHistory.length - 1);
|
|
3774
|
+
}}
|
|
3775
|
+
isSettingsOpen={isSettingsSidebarOpen}
|
|
3776
|
+
onToggleSettings={() => setIsSettingsSidebarOpen(true)}
|
|
3777
|
+
showCustomControls={true}
|
|
3778
|
+
onGridLayout={() => {
|
|
3779
|
+
// TODO: Implement grid layout
|
|
3780
|
+
console.log('Grid layout clicked');
|
|
3781
|
+
}}
|
|
3782
|
+
onTreeLayout={() => {
|
|
3783
|
+
// TODO: Implement tree layout
|
|
3784
|
+
console.log('Tree layout clicked');
|
|
3785
|
+
}}
|
|
3786
|
+
onAutoLayout={autoLayout}
|
|
3787
|
+
onRefreshValidation={refreshValidationProperties}
|
|
3788
|
+
/>
|
|
3789
|
+
|
|
3790
|
+
{/* AI Button positioned at bottom-right */}
|
|
3791
|
+
<AiButton />
|
|
3792
|
+
</ReactFlow>
|
|
3793
|
+
</Box>
|
|
3794
|
+
</Box>
|
|
3795
|
+
|
|
3796
|
+
<SettingsSidebar
|
|
3797
|
+
isOpen={isSettingsSidebarOpen}
|
|
3798
|
+
onClose={() => {
|
|
3799
|
+
setIsSettingsSidebarOpen(false);
|
|
3800
|
+
setSelectedNode(null);
|
|
3801
|
+
setSelectedEdge(null);
|
|
3802
|
+
}}
|
|
3803
|
+
selectedNode={selectedNode}
|
|
3804
|
+
selectedEdge={selectedEdge}
|
|
3805
|
+
onNodeUpdate={handleLabelSave}
|
|
3806
|
+
onNodeResize={handleResize}
|
|
3807
|
+
onEdgeUpdate={handleEdgeSettings}
|
|
3808
|
+
/>
|
|
3809
|
+
|
|
3810
|
+
{/* Add validation error snackbar */}
|
|
3811
|
+
<Snackbar
|
|
3812
|
+
open={!!validationError}
|
|
3813
|
+
autoHideDuration={6000}
|
|
3814
|
+
onClose={() => setValidationError(null)}
|
|
3815
|
+
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
|
3816
|
+
>
|
|
3817
|
+
<Alert onClose={() => setValidationError(null)} severity="error">
|
|
3818
|
+
{validationError}
|
|
3819
|
+
</Alert>
|
|
3820
|
+
</Snackbar>
|
|
3821
|
+
|
|
3822
|
+
{/* Add success message snackbar */}
|
|
3823
|
+
<Snackbar
|
|
3824
|
+
open={!!successMessage}
|
|
3825
|
+
autoHideDuration={3000}
|
|
3826
|
+
onClose={() => setSuccessMessage(null)}
|
|
3827
|
+
anchorOrigin={{ vertical: 'bottom', horizontal: 'center' }}
|
|
3828
|
+
>
|
|
3829
|
+
<Alert onClose={() => setSuccessMessage(null)} severity="success">
|
|
3830
|
+
{successMessage}
|
|
3831
|
+
</Alert>
|
|
3832
|
+
</Snackbar>
|
|
3833
|
+
|
|
3834
|
+
{/* SubnetSelector is no longer needed - using click-to-select instead */}
|
|
3835
|
+
</Box>
|
|
3836
|
+
);
|
|
3837
|
+
};
|
|
3838
|
+
|
|
3839
|
+
// Main wrapper component with ReactFlowProvider
|
|
3840
|
+
const CloudArchitectureDiagram: React.FC<CloudArchitectureDiagramProps> = (props) => {
|
|
3841
|
+
return (
|
|
3842
|
+
<ReactFlowProvider>
|
|
3843
|
+
<CloudArchitectureDiagramContent {...props} />
|
|
3844
|
+
</ReactFlowProvider>
|
|
3845
|
+
);
|
|
3846
|
+
};
|
|
3847
|
+
|
|
3848
|
+
// Export both the component and the localization schema interface for consumers
|
|
3849
|
+
export { LocalizationSchema };
|
|
3850
|
+
export default CloudArchitectureDiagram;
|
|
3851
|
+
|
|
3852
|
+
// Usage example with custom localization:
|
|
3853
|
+
/*
|
|
3854
|
+
import CloudArchitectureDiagram, { LocalizationSchema } from './CloudArchitectureDiagram';
|
|
3855
|
+
|
|
3856
|
+
const customLocalization: Partial<LocalizationSchema> = {
|
|
3857
|
+
locale: {
|
|
3858
|
+
core: {
|
|
3859
|
+
coordinate: {
|
|
3860
|
+
x: "Coordenada X", // Spanish
|
|
3861
|
+
y: "Coordenada Y" // Spanish
|
|
3862
|
+
},
|
|
3863
|
+
size: {
|
|
3864
|
+
width: "Ancho", // Spanish
|
|
3865
|
+
height: "Alto" // Spanish
|
|
3866
|
+
},
|
|
3867
|
+
common: {
|
|
3868
|
+
title: "Título", // Spanish
|
|
3869
|
+
description: "Descripción", // Spanish
|
|
3870
|
+
cloudProvider: "Proveedor de Nube" // Spanish
|
|
3871
|
+
}
|
|
3872
|
+
},
|
|
3873
|
+
ui: {
|
|
3874
|
+
buttons: {
|
|
3875
|
+
undo: "Deshacer",
|
|
3876
|
+
redo: "Rehacer",
|
|
3877
|
+
clear: "Limpiar",
|
|
3878
|
+
autoLayout: "Diseño Automático",
|
|
3879
|
+
clearSelection: "Limpiar Selección"
|
|
3880
|
+
},
|
|
3881
|
+
messages: {
|
|
3882
|
+
success: {
|
|
3883
|
+
validationRefreshed: "¡Propiedades de validación actualizadas para todos los nodos!",
|
|
3884
|
+
nodePlaced: "Servicio colocado en: {container}",
|
|
3885
|
+
nodesArranged: "{count} nodos organizados con espaciado adecuado"
|
|
3886
|
+
},
|
|
3887
|
+
error: {
|
|
3888
|
+
dropBlocked: "COLOCACIÓN BLOQUEADA",
|
|
3889
|
+
invalidDrop: "COLOCACIÓN INVÁLIDA"
|
|
3890
|
+
},
|
|
3891
|
+
info: {
|
|
3892
|
+
dropToPlace: "Soltar para colocar servicio",
|
|
3893
|
+
selectContainer: "Soltar para seleccionar contenedor",
|
|
3894
|
+
willBePlacedIn: "Será colocado en: {container}",
|
|
3895
|
+
willBePlacedAtRoot: "Será colocado en el nivel raíz"
|
|
3896
|
+
}
|
|
3897
|
+
},
|
|
3898
|
+
labels: {
|
|
3899
|
+
selectedContainer: "Contenedor Seleccionado",
|
|
3900
|
+
servicesWillBePlacedHere: "Los servicios se colocarán aquí"
|
|
3901
|
+
}
|
|
3902
|
+
}
|
|
3903
|
+
}
|
|
3904
|
+
};
|
|
3905
|
+
|
|
3906
|
+
// Usage:
|
|
3907
|
+
<CloudArchitectureDiagram
|
|
3908
|
+
initialNodes={[]}
|
|
3909
|
+
initialEdges={[]}
|
|
3910
|
+
localization={customLocalization}
|
|
3911
|
+
/>
|
|
3912
|
+
*/
|