@flowuent-org/diagramming-core 1.3.10 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.editorconfig +13 -13
- package/.eslintignore +1 -1
- package/.eslintrc.json +35 -35
- package/.prettierignore +3 -3
- package/.prettierrc +3 -3
- package/.vscode/settings.json +6 -6
- package/README.md +95 -95
- package/apps/authentication/tsconfig.app.json +23 -23
- package/apps/authentication/tsconfig.json +21 -21
- package/apps/authentication/tsconfig.spec.json +28 -28
- package/apps/diagramming/.eslintrc.json +18 -18
- package/apps/diagramming/index.html +16 -16
- package/apps/diagramming/project.json +53 -53
- package/apps/diagramming/public/aws-icons/compute/App Runner.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Application Auto Scaling.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Batch.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Bottlerocket.svg +19 -19
- package/apps/diagramming/public/aws-icons/compute/Compute Optimizer.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/EC2 Auto Scaling.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/EC2 Image Builder.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/EC2.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Elastic Beanstalk.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Elastic Fabric Adapter.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Fargate.svg +19 -19
- package/apps/diagramming/public/aws-icons/compute/Genomics CLI.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Lambda.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Local Zones.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/NICE DCV.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/NICE EnginFrame.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Nitro Enclaves.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Outposts family.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Outposts rack.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Outposts servers.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/ParallelCluster.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Serverless Application Repository.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/SimSpace Weaver.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Deadline.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Frost.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Sequoia.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox Stoke.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Thinkbox XMesh.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/VMware Cloud on AWS.svg +17 -17
- package/apps/diagramming/public/aws-icons/compute/Wavelength.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/ECS Anywhere.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/EKS Anywhere.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/EKS Cloud.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/EKS Distro.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/Elastic Container Registry.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/Elastic Container Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/Elastic Kubernetes Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/container/Fargate.svg +19 -19
- package/apps/diagramming/public/aws-icons/container/Red Hat OpenShift Service on AWS.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Aurora.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Database Migration Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/DocumentDB.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/ElastiCache.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Keyspaces.svg +15 -15
- package/apps/diagramming/public/aws-icons/database/MemoryDB for Redis.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Neptune.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/RDS on VMware.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/RDS.svg +17 -17
- package/apps/diagramming/public/aws-icons/database/Timestream.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/App Mesh.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Client VPN.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Cloud Directory.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Cloud Map.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Cloud WAN.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/CloudFront.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Direct Connect.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Elastic Load Balancing.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Global Accelerator.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Private 5G.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/PrivateLink.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Route 53.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Site to Site VPN.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Transit Gateway.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/VPC Lattice.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Verified Access.svg +17 -17
- package/apps/diagramming/public/aws-icons/networking/Virtual Private Cloud.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Artifact.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Audit Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Certificate Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/CloudHSM.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Cognito.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Detective.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Directory Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Firewall Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/GuardDuty.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/IAM Identity Center.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Identity and Access Management.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Inspector.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Key Management Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Macie.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Network Firewall.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Resource Access Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Secrets Manager.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Security Hub.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Security Lake.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Shield.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Signer.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/Verified Permissions.svg +17 -17
- package/apps/diagramming/public/aws-icons/security/WAF.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Backup.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/EFS.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Elastic Block Store.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Elastic Disaster Recovery.svg +15 -15
- package/apps/diagramming/public/aws-icons/storage/FSx for Lustre.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/FSx for NetApp ONTAP.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/FSx for OpenZFS.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/FSx for WFS.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/FSx.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/File Cache.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/S3 on Outposts.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Simple Storage Service Glacier.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Simple Storage Service.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Snowball Edge.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Snowball.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Snowcone.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Snowmobile.svg +17 -17
- package/apps/diagramming/public/aws-icons/storage/Storage Gateway.svg +17 -17
- package/apps/diagramming/public/azure-icons/compute/02112-icon-service-Automanaged-VM.svg +1 -1
- package/apps/diagramming/public/azure-icons/databases/02390-icon-service-Azure-SQL.svg +1 -1
- package/apps/diagramming/public/azure-icons/databases/02392-icon-service-SSIS-Lift-And-Shift-IR.svg +1 -1
- package/apps/diagramming/public/azure-icons/networking/02145-icon-service-Resource-Management-Private-Link.svg +1 -1
- package/apps/diagramming/public/azure-icons/security/02247-icon-service-Microsoft-Defender-for-IoT.svg +1 -1
- package/apps/diagramming/public/sample-node-complete.json +63 -63
- package/apps/diagramming/public/sample-node-data.json +63 -63
- package/apps/diagramming/src/AutomationDiagramData.ts +147 -480
- package/apps/diagramming/src/CollaborationDiagramData.ts +127 -127
- package/apps/diagramming/src/CustomIconExamples.ts +233 -233
- package/apps/diagramming/src/DiagramTabs.tsx +205 -205
- package/apps/diagramming/src/Pages/DevPlayground.tsx +3 -3
- package/apps/diagramming/src/Pages/Home.tsx +111 -111
- package/apps/diagramming/src/SequenceDiagramData.ts +215 -215
- package/apps/diagramming/src/StateMachineDiagramData.ts +64 -64
- package/apps/diagramming/src/UseCaseDiagramData.ts +52 -52
- package/apps/diagramming/src/cloud-data.ts +371 -371
- package/apps/diagramming/src/components/AddNodeView.tsx +252 -252
- package/apps/diagramming/src/createSelecors.tsx +17 -17
- package/apps/diagramming/src/index.scss +12 -12
- package/apps/diagramming/src/main.tsx +463 -463
- package/apps/diagramming/src/node-data.ts +664 -664
- package/apps/diagramming/src/sample-workflow-content.ts +55 -55
- package/apps/diagramming/src/stencil-items.ts +31 -31
- package/apps/diagramming/src/style.css +13 -13
- package/apps/diagramming/src/tabsStore.tsx +152 -152
- package/apps/diagramming/src/vite-env.d.ts +1 -1
- package/apps/diagramming/tsconfig.app.json +23 -23
- package/apps/diagramming/tsconfig.json +21 -21
- package/apps/diagramming/tsconfig.spec.json +28 -28
- package/apps/diagramming/vite.config.ts +34 -34
- package/apps/diagramming/webpack.config.js +13 -13
- package/apps/workflow/.eslintrc.json +18 -18
- package/apps/workflow/group/group-form-hooks/index.ts +3 -3
- package/apps/workflow/group/group-form-hooks/useFormValidation.ts +20 -20
- package/apps/workflow/group/group-form-schema/SettingsTabFormSchema.ts +7 -7
- package/apps/workflow/group/group-form-schema/index.ts +3 -3
- package/apps/workflow/i18n.ts +19 -19
- package/apps/workflow/index.html +16 -16
- package/apps/workflow/locales/en/translation.json +713 -713
- package/apps/workflow/project.json +8 -8
- package/apps/workflow/src/app/app.module.css +1 -1
- package/apps/workflow/src/app/app.spec.tsx +15 -15
- package/apps/workflow/src/app/app.tsx +370 -370
- package/apps/workflow/src/main.tsx +13 -13
- package/apps/workflow/src/styles.css +1 -1
- package/apps/workflow/src/theme/index.ts +546 -546
- package/apps/workflow/tsconfig.app.json +23 -23
- package/apps/workflow/tsconfig.json +21 -21
- package/apps/workflow/tsconfig.spec.json +28 -28
- package/apps/workflow/vite.config.ts +51 -51
- package/nx.json +58 -58
- package/package.json +1 -1
- package/packages/api/.babelrc +12 -12
- package/packages/api/.eslintrc.json +18 -18
- package/packages/api/README.md +7 -7
- package/packages/api/project.json +8 -8
- package/packages/api/src/index.ts +3 -3
- package/packages/api/src/lib/jsonPlaceholderService/endpoints.ts +3 -3
- package/packages/api/src/lib/jsonPlaceholderService/service.ts +13 -13
- package/packages/api/tsconfig.json +17 -17
- package/packages/api/tsconfig.lib.json +24 -24
- package/packages/atoms/.babelrc +12 -12
- package/packages/atoms/.eslintrc.json +18 -18
- package/packages/atoms/README.md +7 -7
- package/packages/atoms/forms/AuthenticationTabForm/authenticationTabForm.json +224 -224
- package/packages/atoms/forms/AuthenticationTabForm/index.tsx +255 -255
- package/packages/atoms/forms/SettingsTabForm/index.tsx +85 -85
- package/packages/atoms/forms/SettingsTabForm/settingTabForm.json +27 -27
- package/packages/atoms/forms/index.ts +4 -4
- package/packages/atoms/project.json +8 -8
- package/packages/atoms/src/index.ts +33 -33
- package/packages/atoms/src/lib/Accordion/Accordion.module.css +7 -7
- package/packages/atoms/src/lib/Accordion/Accordion.spec.tsx +13 -13
- package/packages/atoms/src/lib/Accordion/Accordion.tsx +29 -29
- package/packages/atoms/src/lib/Alert/Alert.module.css +7 -7
- package/packages/atoms/src/lib/Alert/Alert.spec.tsx +10 -10
- package/packages/atoms/src/lib/Alert/Alert.tsx +19 -19
- package/packages/atoms/src/lib/Avatar/Avatar.module.css +7 -7
- package/packages/atoms/src/lib/Avatar/Avatar.spec.tsx +10 -10
- package/packages/atoms/src/lib/Avatar/Avatar.tsx +22 -22
- package/packages/atoms/src/lib/Box/Box.module.css +7 -7
- package/packages/atoms/src/lib/Box/Box.spec.tsx +10 -10
- package/packages/atoms/src/lib/Box/Box.tsx +19 -19
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.module.css +7 -7
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.spec.tsx +10 -10
- package/packages/atoms/src/lib/BulkEdit/BulkEdit.tsx +15 -15
- package/packages/atoms/src/lib/Button/Button.module.css +7 -7
- package/packages/atoms/src/lib/Button/Button.spec.tsx +10 -10
- package/packages/atoms/src/lib/Button/Button.tsx +78 -78
- package/packages/atoms/src/lib/Card/Card.module.css +7 -7
- package/packages/atoms/src/lib/Card/Card.spec.tsx +10 -10
- package/packages/atoms/src/lib/Card/Card.tsx +19 -19
- package/packages/atoms/src/lib/CardContent/CardContent.module.css +7 -7
- package/packages/atoms/src/lib/CardContent/CardContent.spec.tsx +10 -10
- package/packages/atoms/src/lib/CardContent/CardContent.tsx +22 -22
- package/packages/atoms/src/lib/FormTextField/FormTextField.module.css +7 -7
- package/packages/atoms/src/lib/FormTextField/FormTextField.spec.tsx +10 -10
- package/packages/atoms/src/lib/FormTextField/FormTextField.tsx +35 -35
- package/packages/atoms/src/lib/Grid/Grid.module.css +7 -7
- package/packages/atoms/src/lib/Grid/Grid.spec.tsx +10 -10
- package/packages/atoms/src/lib/Grid/Grid.tsx +19 -19
- package/packages/atoms/src/lib/Stack/Stack.module.css +7 -7
- package/packages/atoms/src/lib/Stack/Stack.spec.tsx +10 -10
- package/packages/atoms/src/lib/Stack/Stack.tsx +19 -19
- package/packages/atoms/src/lib/Switch/Switch.module.css +7 -7
- package/packages/atoms/src/lib/Switch/Switch.spec.tsx +10 -10
- package/packages/atoms/src/lib/Switch/Switch.tsx +46 -46
- package/packages/atoms/src/lib/TableView/TableView.module.css +7 -7
- package/packages/atoms/src/lib/TableView/TableView.spec.tsx +10 -10
- package/packages/atoms/src/lib/TableView/TableView.tsx +88 -88
- package/packages/atoms/src/lib/TextField/TextField.module.css +7 -7
- package/packages/atoms/src/lib/TextField/TextField.spec.tsx +10 -10
- package/packages/atoms/src/lib/TextField/TextField.tsx +17 -17
- package/packages/atoms/src/lib/Typography/Typography.module.css +7 -7
- package/packages/atoms/src/lib/Typography/Typography.spec.tsx +10 -10
- package/packages/atoms/src/lib/Typography/Typography.tsx +22 -22
- package/packages/atoms/tsconfig.json +17 -17
- package/packages/atoms/tsconfig.lib.json +30 -30
- package/packages/common/.babelrc +12 -12
- package/packages/common/.eslintrc.json +18 -18
- package/packages/common/README.md +7 -7
- package/packages/common/project.json +8 -8
- package/packages/common/src/index.ts +4 -4
- package/packages/common/src/lib/generateRandomProfilePic.ts +3 -3
- package/packages/common/src/lib/generateValidationSchema.ts +46 -46
- package/packages/common/tsconfig.json +17 -17
- package/packages/common/tsconfig.lib.json +24 -24
- package/packages/contexts/.babelrc +12 -12
- package/packages/contexts/.eslintrc.json +18 -18
- package/packages/contexts/README.md +7 -7
- package/packages/contexts/project.json +8 -8
- package/packages/contexts/src/index.ts +2 -2
- package/packages/contexts/tsconfig.json +17 -17
- package/packages/contexts/tsconfig.lib.json +24 -24
- package/packages/diagrams/.babelrc +12 -12
- package/packages/diagrams/.ctirc +208 -208
- package/packages/diagrams/.eslintrc.json +17 -17
- package/packages/diagrams/CUSTOM_ICONS.md +232 -232
- package/packages/diagrams/GOOGLE_SHEETS_IMPLEMENTATION.md +233 -233
- package/packages/diagrams/NODE_DATA_UPDATE_API.md +430 -430
- package/packages/diagrams/README.md +7 -7
- package/packages/diagrams/UNDO_REDO_API.md +306 -306
- package/packages/diagrams/locales/en/translation.json +713 -713
- package/packages/diagrams/package.json +27 -27
- package/packages/diagrams/pnpm-lock.yaml +2606 -2606
- package/packages/diagrams/project.json +42 -42
- package/packages/diagrams/rollup.config.js +26 -26
- package/packages/diagrams/src/DiagramFlow.tsx +7 -7
- package/packages/diagrams/src/declarations.d.ts +7 -7
- package/packages/diagrams/src/index.ts +8 -1
- package/packages/diagrams/src/index.ts.bak +99 -99
- package/packages/diagrams/src/lib/assets/markers/markers.param.tsx +101 -101
- package/packages/diagrams/src/lib/assets/markers/markers.type.ts +10 -10
- package/packages/diagrams/src/lib/atoms/ActorNode.tsx +123 -123
- package/packages/diagrams/src/lib/atoms/AddParallelColButton.tsx +26 -26
- package/packages/diagrams/src/lib/atoms/BendpointNode.tsx +71 -71
- package/packages/diagrams/src/lib/atoms/CardBlockTypeSelector.tsx +35 -35
- package/packages/diagrams/src/lib/atoms/CardEditableTitle.tsx +76 -76
- package/packages/diagrams/src/lib/atoms/CardMainContent.tsx +19 -19
- package/packages/diagrams/src/lib/atoms/ConnectionPoints.tsx +149 -149
- package/packages/diagrams/src/lib/atoms/ContentAssist.tsx +341 -341
- package/packages/diagrams/src/lib/atoms/DiagramControls.tsx +56 -56
- package/packages/diagrams/src/lib/atoms/ExpressionInput.tsx +437 -437
- package/packages/diagrams/src/lib/atoms/FloatingConnectionLine.tsx +73 -73
- package/packages/diagrams/src/lib/atoms/LazyIcon.tsx +31 -31
- package/packages/diagrams/src/lib/atoms/MarkerSelector.tsx +106 -106
- package/packages/diagrams/src/lib/atoms/MultiSelectInput.tsx +87 -87
- package/packages/diagrams/src/lib/atoms/ParameterInput.tsx +199 -199
- package/packages/diagrams/src/lib/atoms/PropertyInput.tsx +114 -114
- package/packages/diagrams/src/lib/atoms/StyledBox.tsx +69 -69
- package/packages/diagrams/src/lib/atoms/ValidationError.tsx +37 -37
- package/packages/diagrams/src/lib/atoms/ValidationMessage.tsx +63 -63
- package/packages/diagrams/src/lib/components/AiButton.tsx +52 -52
- package/packages/diagrams/src/lib/components/ControlEdgeList.tsx +303 -303
- package/packages/diagrams/src/lib/components/CreateFunctionModal.tsx +264 -264
- package/packages/diagrams/src/lib/components/CustomCodeModal.tsx +287 -287
- package/packages/diagrams/src/lib/components/DiagramPanel.tsx +331 -331
- package/packages/diagrams/src/lib/components/FunctionModalTooltip.tsx +30 -30
- package/packages/diagrams/src/lib/components/GraphQLCustomCodeModal.tsx +210 -210
- package/packages/diagrams/src/lib/components/Header.tsx +143 -143
- package/packages/diagrams/src/lib/components/MiniMapCommon.tsx +24 -24
- package/packages/diagrams/src/lib/components/ResizeIcon.tsx +81 -81
- package/packages/diagrams/src/lib/components/SideBar.tsx +40 -40
- package/packages/diagrams/src/lib/components/automation/AISuggestionsModal.tsx +269 -269
- package/packages/diagrams/src/lib/components/automation/AISuggestionsPanel.tsx +28 -175
- package/packages/diagrams/src/lib/components/automation/AutomationApiNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationDecisionNode.tsx +94 -0
- package/packages/diagrams/src/lib/components/automation/AutomationEndNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationExecutionPanel.tsx +439 -439
- package/packages/diagrams/src/lib/components/automation/AutomationFormattingNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationInteractionNode.tsx +693 -693
- package/packages/diagrams/src/lib/components/automation/AutomationMonitoringNode.tsx +399 -399
- package/packages/diagrams/src/lib/components/automation/AutomationNavigationNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationNodeCard.tsx +414 -0
- package/packages/diagrams/src/lib/components/automation/AutomationSlackNode.tsx +694 -694
- package/packages/diagrams/src/lib/components/automation/AutomationSmsNode.tsx +62 -0
- package/packages/diagrams/src/lib/components/automation/AutomationStartNode.tsx +1 -1
- package/packages/diagrams/src/lib/components/automation/AutomationTelegramNode.tsx +664 -664
- package/packages/diagrams/src/lib/components/automation/AutomationVoiceCallNode.tsx +65 -0
- package/packages/diagrams/src/lib/components/automation/AutomationWorkflowNode.tsx +85 -0
- package/packages/diagrams/src/lib/components/automation/NodeAIAssistantPopup.tsx +504 -504
- package/packages/diagrams/src/lib/components/automation/index.ts +29 -19
- package/packages/diagrams/src/lib/components/automation/statusColors.ts +124 -124
- package/packages/diagrams/src/lib/contexts/CardDataProvider.tsx +111 -111
- package/packages/diagrams/src/lib/contexts/DiagramProvider.tsx +392 -391
- package/packages/diagrams/src/lib/contexts/diagramStoreTypes.tsx +134 -134
- package/packages/diagrams/src/lib/contexts/onConnect.ts +39 -39
- package/packages/diagrams/src/lib/contexts/onDragStart.ts +45 -45
- package/packages/diagrams/src/lib/contexts/onNodeDragEnd.ts +179 -179
- package/packages/diagrams/src/lib/contexts/onNodesChange.ts +406 -406
- package/packages/diagrams/src/lib/contexts/onReconnect.ts +59 -59
- package/packages/diagrams/src/lib/contexts/onWorkflowNodeDelete.ts +65 -65
- package/packages/diagrams/src/lib/contexts/setContentHeight.ts +14 -14
- package/packages/diagrams/src/lib/contexts/setDiagramType.ts +10 -10
- package/packages/diagrams/src/lib/contexts/setEdgeShapeType.ts +7 -7
- package/packages/diagrams/src/lib/contexts/setEdges.ts +9 -9
- package/packages/diagrams/src/lib/contexts/setLayoutDirection.ts +9 -9
- package/packages/diagrams/src/lib/contexts/setNodeSetting.ts +17 -17
- package/packages/diagrams/src/lib/contexts/setNodes.ts +10 -10
- package/packages/diagrams/src/lib/contexts/setPannable.ts +7 -7
- package/packages/diagrams/src/lib/contexts/setSelectedEdge.ts +7 -7
- package/packages/diagrams/src/lib/contexts/setSelectedNode.ts +7 -7
- package/packages/diagrams/src/lib/contexts/undo.ts +163 -163
- package/packages/diagrams/src/lib/contexts/updateNodeSetting.ts +17 -17
- package/packages/diagrams/src/lib/examples/TwilioWhatsAppConfigExample.ts +128 -128
- package/packages/diagrams/src/lib/externals.ts +4 -4
- package/packages/diagrams/src/lib/hooks/customUseReactFlow.tsx +90 -90
- package/packages/diagrams/src/lib/hooks/updateNodes.ts +45 -45
- package/packages/diagrams/src/lib/hooks/useAddChildButton.tsx +61 -61
- package/packages/diagrams/src/lib/hooks/useAddSwitchCase.ts +75 -75
- package/packages/diagrams/src/lib/hooks/useAutoRegisterFunctions.ts +135 -135
- package/packages/diagrams/src/lib/hooks/useAutoRegisterVariables.ts +286 -286
- package/packages/diagrams/src/lib/hooks/useDeleteNodeByEvent.ts +64 -64
- package/packages/diagrams/src/lib/hooks/useDragCallbacks.tsx +122 -122
- package/packages/diagrams/src/lib/hooks/useFormValidation.ts +349 -349
- package/packages/diagrams/src/lib/hooks/useFunctionFormCallbacks.ts +136 -136
- package/packages/diagrams/src/lib/hooks/useIsSelectedNode.ts +11 -11
- package/packages/diagrams/src/lib/hooks/useModalControls.ts +13 -13
- package/packages/diagrams/src/lib/hooks/useNodeDragHandlers.ts +43 -43
- package/packages/diagrams/src/lib/hooks/useNodeSelection.ts +55 -55
- package/packages/diagrams/src/lib/hooks/useRenderDividers.tsx +19 -19
- package/packages/diagrams/src/lib/hooks/useRenderPins.tsx +74 -74
- package/packages/diagrams/src/lib/hooks/useResizeObserver.ts +21 -21
- package/packages/diagrams/src/lib/hooks/useTreeChildNode.ts +536 -536
- package/packages/diagrams/src/lib/i18n.ts +42 -42
- package/packages/diagrams/src/lib/icons/icons.tsx +324 -0
- package/packages/diagrams/src/lib/icons/index.ts +29 -0
- package/packages/diagrams/src/lib/icons/types.ts +7 -0
- package/packages/diagrams/src/lib/molecules/AddingBlock.tsx +147 -147
- package/packages/diagrams/src/lib/molecules/AvailableVariablesDisplay.tsx +57 -57
- package/packages/diagrams/src/lib/molecules/Block.tsx +143 -143
- package/packages/diagrams/src/lib/molecules/BlockProvider.tsx +28 -28
- package/packages/diagrams/src/lib/molecules/BlockWrapper.tsx +14 -14
- package/packages/diagrams/src/lib/molecules/ConditionRule.tsx +131 -131
- package/packages/diagrams/src/lib/molecules/DraggablePane.tsx +198 -198
- package/packages/diagrams/src/lib/molecules/EntityNodeBlocks.tsx +66 -66
- package/packages/diagrams/src/lib/molecules/SideHandles.tsx +215 -215
- package/packages/diagrams/src/lib/molecules/StencilItem.tsx +85 -85
- package/packages/diagrams/src/lib/molecules/WorkflowNodeActionButtons.tsx +62 -62
- package/packages/diagrams/src/lib/molecules/animated-add-button.tsx +83 -83
- package/packages/diagrams/src/lib/molecules/json-viewer.tsx +107 -107
- package/packages/diagrams/src/lib/organisms/AddNodeView.tsx +102 -102
- package/packages/diagrams/src/lib/organisms/Card/EntityNode.tsx +259 -259
- package/packages/diagrams/src/lib/organisms/Card/card.params.ts +29 -29
- package/packages/diagrams/src/lib/organisms/Card/card.types.ts +5 -5
- package/packages/diagrams/src/lib/organisms/CodeModal.tsx +105 -105
- package/packages/diagrams/src/lib/organisms/ConditionRuleGroup.tsx +138 -138
- package/packages/diagrams/src/lib/organisms/CustomEdge/EdgeMarkers.tsx +51 -51
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge-generator.tsx +135 -135
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge.params.ts +6 -6
- package/packages/diagrams/src/lib/organisms/CustomEdge/custom-edge.type.ts +15 -15
- package/packages/diagrams/src/lib/organisms/CustomEdge/useCreateBendPoint.tsx +121 -121
- package/packages/diagrams/src/lib/organisms/CustomEdge/useEdgeModal.ts +37 -37
- package/packages/diagrams/src/lib/organisms/CustomEdge/useEdgePath.tsx +99 -99
- package/packages/diagrams/src/lib/organisms/DownloadPanel.tsx +345 -345
- package/packages/diagrams/src/lib/organisms/EdgeModal.tsx +243 -243
- package/packages/diagrams/src/lib/organisms/HistoryPane.tsx +118 -118
- package/packages/diagrams/src/lib/organisms/NodeContextMenu.tsx +259 -259
- package/packages/diagrams/src/lib/organisms/PropertiesPane.tsx +276 -276
- package/packages/diagrams/src/lib/organisms/SmartDynamicForm.tsx +226 -226
- package/packages/diagrams/src/lib/organisms/Stencil.tsx +60 -60
- package/packages/diagrams/src/lib/organisms/UseDiagramStore.tsx +4 -4
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeActionButtons.tsx +45 -45
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/NodeTypeDisplay.tsx +114 -114
- package/packages/diagrams/src/lib/organisms/WorkFlowNode/WorkflowNodeIcons.tsx +21 -23
- package/packages/diagrams/src/lib/organisms/WorkflowNode.tsx +484 -484
- package/packages/diagrams/src/lib/services/GoogleSheetsService.ts +961 -961
- package/packages/diagrams/src/lib/services/SlackService.ts +134 -134
- package/packages/diagrams/src/lib/services/TwilioWhatsAppService.ts +273 -273
- package/packages/diagrams/src/lib/styles.css +55 -55
- package/packages/diagrams/src/lib/templates/PageLinks.tsx +13 -13
- package/packages/diagrams/src/lib/templates/arch/ArchDiagram.tsx +793 -793
- package/packages/diagrams/src/lib/templates/arch/components/EdgeDialog.tsx +82 -82
- package/packages/diagrams/src/lib/templates/arch/components/EdgeSettings.tsx +81 -81
- package/packages/diagrams/src/lib/templates/arch/components/GroupControls.tsx +59 -59
- package/packages/diagrams/src/lib/templates/arch/components/GroupSizeDialog.tsx +81 -81
- package/packages/diagrams/src/lib/templates/arch/components/NodeDialog.tsx +214 -214
- package/packages/diagrams/src/lib/templates/arch/components/nodeTypes.ts +91 -91
- package/packages/diagrams/src/lib/templates/arch/controls/ArchDiagramControls.tsx +349 -349
- package/packages/diagrams/src/lib/templates/arch/data/templatee-data.ts +212 -212
- package/packages/diagrams/src/lib/templates/cloud-arch/components/CloudArchitectureDiagram.tsx +3912 -3912
- package/packages/diagrams/src/lib/templates/cloud-arch/components/FormatConverter.tsx +809 -809
- package/packages/diagrams/src/lib/templates/cloud-arch/components/FragmentImporter.tsx +276 -276
- package/packages/diagrams/src/lib/templates/cloud-arch/components/PropertiesPanel.tsx +1063 -1063
- package/packages/diagrams/src/lib/templates/cloud-arch/components/ServiceIconPicker.tsx +138 -138
- package/packages/diagrams/src/lib/templates/cloud-arch/components/SettingsSidebar.tsx +656 -656
- package/packages/diagrams/src/lib/templates/cloud-arch/components/SubnetSelector.tsx +468 -468
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/ServiceNode.tsx +310 -310
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/SubnetNode.tsx +475 -475
- package/packages/diagrams/src/lib/templates/cloud-arch/components/nodes/UsersNode.tsx +277 -277
- package/packages/diagrams/src/lib/templates/cloud-arch/data/diagramManager.ts +364 -364
- package/packages/diagrams/src/lib/templates/cloud-arch/data/types.ts +183 -183
- package/packages/diagrams/src/lib/templates/cloud-arch/integration/diagramValidator.ts +379 -379
- package/packages/diagrams/src/lib/templates/cloud-arch/messages/errorMessages.json +95 -95
- package/packages/diagrams/src/lib/templates/cloud-arch/types/localization.ts +368 -368
- package/packages/diagrams/src/lib/templates/cloud-arch/types/validation.ts +72 -72
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/aws-icons.ts +557 -557
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/awsValidation.ts +446 -446
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/azure-icons.ts +802 -802
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/azureValidation.ts +495 -495
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/cloudValidation.ts +929 -929
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/errorMessages.ts +283 -283
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/gcp-icons.ts +186 -186
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/gcpValidation.ts +312 -312
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/genericValidation.ts +689 -689
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/iconMapping.ts +383 -383
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/idGenerator.ts +220 -220
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/localizationManager.ts +300 -300
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/restrictionEngine.ts +550 -550
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/restrictionValidator.ts +400 -400
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/serviceIcons.tsx +129 -129
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/serviceMapping.ts +330 -330
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/translations.ts +54 -54
- package/packages/diagrams/src/lib/templates/cloud-arch/utils/validationExtractor.ts +342 -342
- package/packages/diagrams/src/lib/templates/collaborationDiagram/CollaborationDiagram.tsx +15 -15
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/AddCollabEdgeDialog.tsx +181 -181
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollabLayout.ts +182 -182
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollabObjectNodeSettings.tsx +86 -86
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/CollaborationDiagramFlow.tsx +731 -731
- package/packages/diagrams/src/lib/templates/collaborationDiagram/components/DraggableEdgeLabel.tsx +123 -123
- package/packages/diagrams/src/lib/templates/collaborationDiagram/controls/CollaborationControls.tsx +131 -131
- package/packages/diagrams/src/lib/templates/collaborationDiagram/nodes/CollabEdge.tsx +170 -170
- package/packages/diagrams/src/lib/templates/collaborationDiagram/nodes/CollabObjectNode.tsx +178 -178
- package/packages/diagrams/src/lib/templates/home.style.css +16 -16
- package/packages/diagrams/src/lib/templates/node-forms/ApiForm.tsx +416 -416
- package/packages/diagrams/src/lib/templates/node-forms/CallForm.tsx +370 -370
- package/packages/diagrams/src/lib/templates/node-forms/ForLoopNode.tsx +313 -313
- package/packages/diagrams/src/lib/templates/node-forms/FunctionForm.tsx +211 -211
- package/packages/diagrams/src/lib/templates/node-forms/GraphQLForm.tsx +679 -679
- package/packages/diagrams/src/lib/templates/node-forms/IfNodeForm.tsx +97 -97
- package/packages/diagrams/src/lib/templates/node-forms/LetNodeForm.tsx +533 -533
- package/packages/diagrams/src/lib/templates/node-forms/LoopNode.tsx +149 -149
- package/packages/diagrams/src/lib/templates/node-forms/NodeForm.tsx +383 -383
- package/packages/diagrams/src/lib/templates/node-forms/ReturnNodeForm.tsx +96 -96
- package/packages/diagrams/src/lib/templates/node-forms/SetNodeForm.tsx +141 -141
- package/packages/diagrams/src/lib/templates/node-forms/SwitchNodeForm.tsx +249 -249
- package/packages/diagrams/src/lib/templates/node-forms/TryCatchForm.tsx +167 -167
- package/packages/diagrams/src/lib/templates/nodeFormSchema.json +84 -84
- package/packages/diagrams/src/lib/templates/sequence/SequenceDiagram.tsx +22 -22
- package/packages/diagrams/src/lib/templates/sequence/components/ActivationBarNode.tsx +33 -33
- package/packages/diagrams/src/lib/templates/sequence/components/AddMessageDialog.tsx +302 -302
- package/packages/diagrams/src/lib/templates/sequence/components/CombinedFragmentNode.tsx +65 -65
- package/packages/diagrams/src/lib/templates/sequence/components/EntitySettings.tsx +559 -559
- package/packages/diagrams/src/lib/templates/sequence/components/FragmentSettings.tsx +116 -116
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceComponents.tsx +14 -14
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceDiagramFlow.tsx +1361 -1361
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceEntityNode.tsx +394 -394
- package/packages/diagrams/src/lib/templates/sequence/components/SequenceMessageEdge.tsx +272 -272
- package/packages/diagrams/src/lib/templates/sequence/components/SimpleEntitySettings.tsx +89 -89
- package/packages/diagrams/src/lib/templates/sequence/controls/DiagramControls.tsx +707 -707
- package/packages/diagrams/src/lib/templates/sequence/data/template-data.ts +635 -635
- package/packages/diagrams/src/lib/templates/stateMachine/StateMachineDiagram.tsx +66 -66
- package/packages/diagrams/src/lib/templates/stateMachine/components/CompositeStateNodeSettings.tsx +638 -638
- package/packages/diagrams/src/lib/templates/stateMachine/components/CustomEdge.tsx +220 -220
- package/packages/diagrams/src/lib/templates/stateMachine/components/EdgeSettings.tsx +193 -193
- package/packages/diagrams/src/lib/templates/stateMachine/components/SelfConnectingEdge.tsx +41 -41
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineComponents.tsx +49 -49
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineDiagramFlow.tsx +1238 -1238
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateMachineLayout.ts +350 -350
- package/packages/diagrams/src/lib/templates/stateMachine/components/StateNodeSettings.tsx +188 -188
- package/packages/diagrams/src/lib/templates/stateMachine/components/useSelfTransition.ts +114 -114
- package/packages/diagrams/src/lib/templates/stateMachine/controls/StateDiagramControls.tsx +488 -488
- package/packages/diagrams/src/lib/templates/stateMachine/data/template-data.ts +4 -4
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ChoicePointNode.tsx +54 -54
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/CompositeStateNode.tsx +225 -225
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/EntryPointNode.tsx +31 -31
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ExitPointNode.tsx +44 -44
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/FinalStateNode.tsx +41 -41
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/ForkJoinNode.tsx +53 -53
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/HistoryStateNode.tsx +36 -36
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/InitialStateNode.tsx +29 -29
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/JunctionPointNode.tsx +33 -33
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/PinPointNode.tsx +111 -111
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/StateNode.tsx +124 -124
- package/packages/diagrams/src/lib/templates/stateMachine/nodes/bendPoint.tsx +71 -71
- package/packages/diagrams/src/lib/templates/systemFlow/SystemFlowDiagram.tsx +67 -67
- package/packages/diagrams/src/lib/templates/systemFlow/components/EdgeSettings.tsx +330 -330
- package/packages/diagrams/src/lib/templates/systemFlow/components/FloatingEdge.tsx +219 -219
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowComponents.tsx +28 -28
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowDiagramFlow.tsx +1179 -1179
- package/packages/diagrams/src/lib/templates/systemFlow/components/SystemFlowNodeSettings.tsx +151 -151
- package/packages/diagrams/src/lib/templates/systemFlow/controls/SystemFlowControls.tsx +435 -435
- package/packages/diagrams/src/lib/templates/systemFlow/data/template-data.ts +308 -308
- package/packages/diagrams/src/lib/templates/systemFlow/demo.tsx +91 -91
- package/packages/diagrams/src/lib/templates/systemFlow/index.ts +5 -5
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/BendpointNode.tsx +80 -80
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/SystemNode.tsx +92 -92
- package/packages/diagrams/src/lib/templates/systemFlow/nodes/TreeSystemNode.tsx +755 -755
- package/packages/diagrams/src/lib/templates/useCaseDiagram/UseCaseDiagram.tsx +16 -16
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/ActorNodeSettings.tsx +103 -103
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/AddRelationshipDialog.tsx +186 -186
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/PackageNodeSettings.tsx +205 -205
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/UseCaseDiagramFlow.tsx +637 -637
- package/packages/diagrams/src/lib/templates/useCaseDiagram/components/UseCaseNodeSettings.tsx +120 -120
- package/packages/diagrams/src/lib/templates/useCaseDiagram/controls/UseCaseControls.tsx +289 -289
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/PackageNode.tsx +83 -83
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/UseCaseEdge.tsx +115 -115
- package/packages/diagrams/src/lib/templates/useCaseDiagram/nodes/UseCaseNode.tsx +230 -230
- package/packages/diagrams/src/lib/templates/validationSchema.json +277 -277
- package/packages/diagrams/src/lib/theme.ts +439 -439
- package/packages/diagrams/src/lib/types/FunctionSignature.ts +11 -11
- package/packages/diagrams/src/lib/types/SmartDynamicFormField.ts +37 -37
- package/packages/diagrams/src/lib/types/automation-node-data-types.ts +45 -131
- package/packages/diagrams/src/lib/types/available-variables.ts +18 -18
- package/packages/diagrams/src/lib/types/card-node.ts +68 -68
- package/packages/diagrams/src/lib/types/collaboration-types.ts +114 -114
- package/packages/diagrams/src/lib/types/colors.ts +25 -25
- package/packages/diagrams/src/lib/types/condition-builder.ts +18 -18
- package/packages/diagrams/src/lib/types/diagram-types.ts +14 -14
- package/packages/diagrams/src/lib/types/edge-types.ts +39 -39
- package/packages/diagrams/src/lib/types/function-execution.ts +35 -35
- package/packages/diagrams/src/lib/types/hooks.types.ts +6 -6
- package/packages/diagrams/src/lib/types/ndoe-form-types.ts +139 -139
- package/packages/diagrams/src/lib/types/node-types.ts +13 -23
- package/packages/diagrams/src/lib/types/sequence-types.ts +178 -178
- package/packages/diagrams/src/lib/types/shared-node-types.ts +21 -21
- package/packages/diagrams/src/lib/types/state-machine-types.ts +225 -225
- package/packages/diagrams/src/lib/types/stencil-item.ts +7 -7
- package/packages/diagrams/src/lib/types/structures.ts +80 -80
- package/packages/diagrams/src/lib/types/system-flow-types.ts +129 -129
- package/packages/diagrams/src/lib/types/usecase-types.ts +84 -84
- package/packages/diagrams/src/lib/types/validation-types.ts +134 -134
- package/packages/diagrams/src/lib/types/workflow-content-dynamic-form-type.ts +23 -23
- package/packages/diagrams/src/lib/types/workflow-node-data-types.ts +172 -172
- package/packages/diagrams/src/lib/utils/AutomationExecutionEngine.ts +658 -1179
- package/packages/diagrams/src/lib/utils/add-new-block.ts +34 -34
- package/packages/diagrams/src/lib/utils/add-new-node.ts +52 -52
- package/packages/diagrams/src/lib/utils/addToHistory.ts +15 -15
- package/packages/diagrams/src/lib/utils/automation-flow-processor.ts +556 -619
- package/packages/diagrams/src/lib/utils/closestPoint.ts +45 -45
- package/packages/diagrams/src/lib/utils/color-options.tsx +26 -26
- package/packages/diagrams/src/lib/utils/compress-img.ts +59 -59
- package/packages/diagrams/src/lib/utils/configLoader.ts +329 -329
- package/packages/diagrams/src/lib/utils/constant-lengths.ts +8 -8
- package/packages/diagrams/src/lib/utils/create-updated.tsx +40 -40
- package/packages/diagrams/src/lib/utils/createHistoryChange.ts +3 -3
- package/packages/diagrams/src/lib/utils/dividerUtils.tsx +59 -59
- package/packages/diagrams/src/lib/utils/edge-hooks.ts +53 -53
- package/packages/diagrams/src/lib/utils/elkLayout.ts +297 -297
- package/packages/diagrams/src/lib/utils/event-hooks.ts +69 -69
- package/packages/diagrams/src/lib/utils/event-store.ts +58 -58
- package/packages/diagrams/src/lib/utils/flow-node-hooks.ts +28 -28
- package/packages/diagrams/src/lib/utils/flow-to-js.ts +1465 -1465
- package/packages/diagrams/src/lib/utils/functionGenerator.ts +38 -38
- package/packages/diagrams/src/lib/utils/generateEdgesFromNodeOrder.ts +113 -113
- package/packages/diagrams/src/lib/utils/helpers.types.ts +10 -10
- package/packages/diagrams/src/lib/utils/highlightText.tsx +93 -93
- package/packages/diagrams/src/lib/utils/iconMapper.tsx +76 -76
- package/packages/diagrams/src/lib/utils/logger.ts +5 -5
- package/packages/diagrams/src/lib/utils/model-hooks.ts +129 -129
- package/packages/diagrams/src/lib/utils/node-hooks.ts +78 -78
- package/packages/diagrams/src/lib/utils/nodeAIAssistantConfig.ts +54 -54
- package/packages/diagrams/src/lib/utils/nodeOrderByEdges.ts +437 -437
- package/packages/diagrams/src/lib/utils/nodeutils.ts +242 -242
- package/packages/diagrams/src/lib/utils/object.ts +13 -13
- package/packages/diagrams/src/lib/utils/useDebounce.ts +19 -19
- package/packages/diagrams/src/lib/utils/utilities.ts +105 -105
- package/packages/diagrams/src/lib/utils/validationEngine.ts +610 -610
- package/packages/diagrams/src/lib/utils/vhToPixels.ts +9 -9
- package/packages/diagrams/tsconfig.json +21 -21
- package/packages/diagrams/tsconfig.lib.json +26 -25
- package/packages/diagrams/webpack.config.js +17 -17
- package/packages/interfaces/.babelrc +12 -12
- package/packages/interfaces/.eslintrc.json +18 -18
- package/packages/interfaces/README.md +7 -7
- package/packages/interfaces/project.json +8 -8
- package/packages/interfaces/src/index.ts +21 -21
- package/packages/interfaces/src/lib/Button.tsx +1 -1
- package/packages/interfaces/src/lib/DataGrid.ts +14 -14
- package/packages/interfaces/src/lib/StyledBox.tsx +1 -1
- package/packages/interfaces/src/lib/ToolbarItem.ts +7 -7
- package/packages/interfaces/src/lib/UserCard.ts +8 -8
- package/packages/interfaces/src/lib/yup.ts +3 -3
- package/packages/interfaces/tsconfig.json +17 -17
- package/packages/interfaces/tsconfig.lib.json +24 -24
- package/packages/molecules/.babelrc +12 -12
- package/packages/molecules/.eslintrc.json +18 -18
- package/packages/molecules/README.md +7 -7
- package/packages/molecules/project.json +8 -8
- package/packages/molecules/src/index.ts +47 -41
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.module.css +7 -7
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.spec.tsx +10 -10
- package/packages/molecules/src/lib/ActionTextField/ActionTextField.tsx +33 -33
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.module.css +7 -7
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.spec.tsx +11 -11
- package/packages/molecules/src/lib/ClosableButton/CloseableButton.tsx +69 -69
- package/packages/molecules/src/lib/EditorTab/EditorTab.module.css +7 -7
- package/packages/molecules/src/lib/EditorTab/EditorTab.spec.tsx +10 -10
- package/packages/molecules/src/lib/EditorTab/EditorTab.tsx +42 -42
- package/packages/molecules/src/lib/EditorTab/TabPanel.tsx +25 -25
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.module.css +7 -7
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.spec.tsx +10 -10
- package/packages/molecules/src/lib/ParamsActionField/ParamsActionField.tsx +24 -24
- package/packages/molecules/src/lib/SvgIcons/icons.tsx +323 -291
- package/packages/molecules/src/lib/SvgIcons/index.ts +31 -25
- package/packages/molecules/src/lib/SvgIcons/types.ts +7 -7
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.module.css +7 -7
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.spec.tsx +10 -10
- package/packages/molecules/src/lib/TableWithInlineView/TableWithInlineView.tsx +47 -47
- package/packages/molecules/src/lib/UserCard/UserCard.module.css +7 -7
- package/packages/molecules/src/lib/UserCard/UserCard.spec.tsx +11 -11
- package/packages/molecules/src/lib/UserCard/UserCard.tsx +22 -22
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.module.css +7 -7
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.spec.tsx +10 -10
- package/packages/molecules/src/lib/VerbSelection/HTTPVerbSelection.tsx +72 -72
- package/packages/molecules/tsconfig.json +17 -17
- package/packages/molecules/tsconfig.lib.json +30 -30
- package/packages/organisms/.babelrc +12 -12
- package/packages/organisms/.eslintrc.json +18 -18
- package/packages/organisms/README.md +7 -7
- package/packages/organisms/project.json +8 -8
- package/packages/organisms/src/index.ts +4 -4
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.module.css +7 -7
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.spec.tsx +10 -10
- package/packages/organisms/src/lib/EditorToolbar/EditorToolbar.tsx +117 -117
- package/packages/organisms/src/lib/EditorToolbar/SortableItem.tsx +51 -51
- package/packages/organisms/src/lib/UserList/UserList.module.css +7 -7
- package/packages/organisms/src/lib/UserList/UserList.spec.tsx +11 -11
- package/packages/organisms/src/lib/UserList/UserList.tsx +48 -48
- package/packages/organisms/src/lib/UserList/store.ts +49 -49
- package/packages/organisms/tsconfig.json +17 -17
- package/packages/organisms/tsconfig.lib.json +24 -24
- package/packages/zustand/.babelrc +12 -12
- package/packages/zustand/.eslintrc.json +18 -18
- package/packages/zustand/README.md +7 -7
- package/packages/zustand/project.json +8 -8
- package/packages/zustand/src/index.ts +4 -4
- package/packages/zustand/tsconfig.json +17 -17
- package/packages/zustand/tsconfig.lib.json +24 -24
- package/tsconfig.base.json +29 -29
- package/packages/diagrams/src/lib/components/automation/AutomationAISuggestionNode.tsx +0 -304
- package/packages/diagrams/src/lib/components/automation/AutomationEmailNode.tsx +0 -773
- package/packages/diagrams/src/lib/components/automation/AutomationGoogleServicesNode.tsx +0 -779
- package/packages/diagrams/src/lib/components/automation/AutomationNoteNode.tsx +0 -422
- package/packages/diagrams/src/lib/components/automation/AutomationSheetsNode.tsx +0 -1135
- package/packages/diagrams/src/lib/examples/GoogleSheetsConfigurationExamples.ts +0 -306
|
@@ -1,961 +1,961 @@
|
|
|
1
|
-
// Browser-compatible Google Sheets service
|
|
2
|
-
// Note: This is a simplified version for browser environments
|
|
3
|
-
// For production use, consider using a backend API to handle Google Sheets operations
|
|
4
|
-
|
|
5
|
-
import * as XLSX from 'xlsx';
|
|
6
|
-
import { saveAs } from 'file-saver';
|
|
7
|
-
|
|
8
|
-
// GIS global injected by `https://accounts.google.com/gsi/client`
|
|
9
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
-
declare const google: any;
|
|
11
|
-
|
|
12
|
-
// Mock Google Sheets API for browser environment
|
|
13
|
-
const mockGoogleSheetsAPI = {
|
|
14
|
-
spreadsheets: {
|
|
15
|
-
values: {
|
|
16
|
-
update: async (params: any) => {
|
|
17
|
-
// Simulate API call delay
|
|
18
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
19
|
-
return {
|
|
20
|
-
data: {
|
|
21
|
-
updatedRows: params.requestBody.values.length,
|
|
22
|
-
updatedColumns: params.requestBody.values[0]?.length || 0,
|
|
23
|
-
updatedCells:
|
|
24
|
-
params.requestBody.values.length *
|
|
25
|
-
(params.requestBody.values[0]?.length || 0),
|
|
26
|
-
},
|
|
27
|
-
};
|
|
28
|
-
},
|
|
29
|
-
get: async (params: any) => {
|
|
30
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
31
|
-
return {
|
|
32
|
-
data: {
|
|
33
|
-
values: [],
|
|
34
|
-
},
|
|
35
|
-
};
|
|
36
|
-
},
|
|
37
|
-
clear: async (params: any) => {
|
|
38
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
39
|
-
return {
|
|
40
|
-
data: {
|
|
41
|
-
clearedRange: params.range,
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
},
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
export interface GoogleSheetsConfig {
|
|
50
|
-
spreadsheetId: string;
|
|
51
|
-
sheetName: string;
|
|
52
|
-
range: string;
|
|
53
|
-
credentials: {
|
|
54
|
-
type: 'service-account' | 'oauth' | 'api-key';
|
|
55
|
-
serviceAccountKey?: string;
|
|
56
|
-
oauthToken?: string;
|
|
57
|
-
apiKey?: string;
|
|
58
|
-
clientEmail?: string;
|
|
59
|
-
privateKey?: string;
|
|
60
|
-
// OAuth client flow (Google Identity Services)
|
|
61
|
-
clientId?: string;
|
|
62
|
-
scopes?: string[];
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface DataMapping {
|
|
67
|
-
headers: Array<{
|
|
68
|
-
column: string;
|
|
69
|
-
sourceField: string;
|
|
70
|
-
dataType: 'string' | 'number' | 'date' | 'boolean';
|
|
71
|
-
}>;
|
|
72
|
-
appendMode: boolean;
|
|
73
|
-
clearSheet: boolean;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export interface SheetsExportOptions {
|
|
77
|
-
emailRecipients?: string[];
|
|
78
|
-
fileName?: string;
|
|
79
|
-
exportFormat?: 'sheets' | 'excel';
|
|
80
|
-
includeHeaders?: boolean;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export interface SheetsResponse {
|
|
84
|
-
spreadsheetId: string;
|
|
85
|
-
sheetName: string;
|
|
86
|
-
range: string;
|
|
87
|
-
rowsAdded: number;
|
|
88
|
-
columnsMapped: number;
|
|
89
|
-
data: any[];
|
|
90
|
-
exportTime: string;
|
|
91
|
-
success: boolean;
|
|
92
|
-
spreadsheetUrl?: string;
|
|
93
|
-
downloadUrl?: string;
|
|
94
|
-
emailSent?: boolean;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export class GoogleSheetsService {
|
|
98
|
-
private sheets: any = null;
|
|
99
|
-
private auth: any = null;
|
|
100
|
-
private isBrowserEnvironment: boolean;
|
|
101
|
-
private accessToken: string | null = null;
|
|
102
|
-
private gisLoaded: boolean = false;
|
|
103
|
-
private hasRequestedConsent: boolean = false;
|
|
104
|
-
private requestedScopes: Set<string> = new Set();
|
|
105
|
-
|
|
106
|
-
constructor() {
|
|
107
|
-
// Detect if we're in a browser environment
|
|
108
|
-
this.isBrowserEnvironment = typeof window !== 'undefined';
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/**
|
|
112
|
-
* Initialize Google Sheets API with authentication
|
|
113
|
-
* Note: In browser environment, this uses a mock API for demonstration
|
|
114
|
-
*/
|
|
115
|
-
async initialize(config: GoogleSheetsConfig): Promise<void> {
|
|
116
|
-
try {
|
|
117
|
-
if (this.isBrowserEnvironment) {
|
|
118
|
-
// If OAuth credentials are provided, initialize Google Identity Services
|
|
119
|
-
if (
|
|
120
|
-
config.credentials?.type === 'oauth' &&
|
|
121
|
-
(config.credentials.clientEmail ||
|
|
122
|
-
config.credentials.oauthToken ||
|
|
123
|
-
config.credentials.clientId)
|
|
124
|
-
) {
|
|
125
|
-
// Prefer OAuth clientId flow (GIS)
|
|
126
|
-
if (config.credentials.clientId) {
|
|
127
|
-
await this.ensureGisLoaded();
|
|
128
|
-
await this.requestAccessToken(
|
|
129
|
-
config.credentials.clientId,
|
|
130
|
-
config.credentials.scopes || [
|
|
131
|
-
'https://www.googleapis.com/auth/spreadsheets',
|
|
132
|
-
],
|
|
133
|
-
);
|
|
134
|
-
this.sheets = 'rest';
|
|
135
|
-
this.auth = {
|
|
136
|
-
type: 'oauth',
|
|
137
|
-
clientId: config.credentials.clientId,
|
|
138
|
-
scopes: config.credentials.scopes || [
|
|
139
|
-
'https://www.googleapis.com/auth/spreadsheets',
|
|
140
|
-
],
|
|
141
|
-
};
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Fallback: if oauthToken is provided directly (e.g., injected), use it
|
|
146
|
-
if (config.credentials.oauthToken) {
|
|
147
|
-
this.accessToken = config.credentials.oauthToken;
|
|
148
|
-
this.sheets = 'rest';
|
|
149
|
-
this.auth = {
|
|
150
|
-
type: 'oauth',
|
|
151
|
-
clientId: config.credentials.clientId,
|
|
152
|
-
scopes: config.credentials.scopes || [
|
|
153
|
-
'https://www.googleapis.com/auth/spreadsheets',
|
|
154
|
-
],
|
|
155
|
-
};
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// Browser environment without OAuth clientId – use mock API
|
|
161
|
-
console.log(
|
|
162
|
-
'Browser environment detected - using mock Google Sheets API',
|
|
163
|
-
);
|
|
164
|
-
this.sheets = mockGoogleSheetsAPI;
|
|
165
|
-
this.auth = { type: config.credentials?.type || 'mock' };
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Node.js environment - would use real Google APIs
|
|
170
|
-
// This would require server-side implementation
|
|
171
|
-
throw new Error(
|
|
172
|
-
'Real Google Sheets API integration requires server-side implementation',
|
|
173
|
-
);
|
|
174
|
-
} catch (error) {
|
|
175
|
-
throw new Error(
|
|
176
|
-
`Failed to initialize Google Sheets API: ${error.message}`,
|
|
177
|
-
);
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Ensure Google Identity Services script is loaded
|
|
183
|
-
*/
|
|
184
|
-
private async ensureGisLoaded(): Promise<void> {
|
|
185
|
-
if (!this.isBrowserEnvironment) return;
|
|
186
|
-
if (this.gisLoaded) return;
|
|
187
|
-
|
|
188
|
-
await new Promise<void>((resolve, reject) => {
|
|
189
|
-
const existing = document.querySelector(
|
|
190
|
-
'script[src="https://accounts.google.com/gsi/client"]',
|
|
191
|
-
);
|
|
192
|
-
if (existing) {
|
|
193
|
-
this.gisLoaded = true;
|
|
194
|
-
resolve();
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
const script = document.createElement('script');
|
|
199
|
-
script.src = 'https://accounts.google.com/gsi/client';
|
|
200
|
-
script.async = true;
|
|
201
|
-
script.defer = true;
|
|
202
|
-
script.onload = () => {
|
|
203
|
-
this.gisLoaded = true;
|
|
204
|
-
resolve();
|
|
205
|
-
};
|
|
206
|
-
script.onerror = () => reject(new Error('Failed to load GIS script'));
|
|
207
|
-
document.head.appendChild(script);
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Request an OAuth access token using GIS token client
|
|
213
|
-
*/
|
|
214
|
-
private async requestAccessToken(
|
|
215
|
-
clientId: string,
|
|
216
|
-
scopes: string[],
|
|
217
|
-
forceConsent: boolean = false,
|
|
218
|
-
): Promise<string> {
|
|
219
|
-
if (!this.isBrowserEnvironment) {
|
|
220
|
-
throw new Error('OAuth is only available in browser environment');
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
await this.ensureGisLoaded();
|
|
224
|
-
|
|
225
|
-
const token = await new Promise<string>((resolve, reject) => {
|
|
226
|
-
try {
|
|
227
|
-
// @ts-ignore - google global injected by GIS
|
|
228
|
-
const tokenClient = google.accounts.oauth2.initTokenClient({
|
|
229
|
-
client_id: clientId,
|
|
230
|
-
scope: scopes.join(' '),
|
|
231
|
-
callback: (response: any) => {
|
|
232
|
-
// Handle both success and error shapes
|
|
233
|
-
if (response && response.access_token) {
|
|
234
|
-
this.hasRequestedConsent = true;
|
|
235
|
-
// Track that we've requested these scopes
|
|
236
|
-
scopes.forEach((s) => this.requestedScopes.add(s));
|
|
237
|
-
resolve(response.access_token);
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
const err = response?.error || 'unknown_error';
|
|
241
|
-
const desc = response?.error_description || 'No description';
|
|
242
|
-
reject(new Error(`OAuth token error: ${err} - ${desc}`));
|
|
243
|
-
},
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
// First-time call must show consent; subsequent calls can be silent
|
|
247
|
-
tokenClient.requestAccessToken({
|
|
248
|
-
prompt: forceConsent
|
|
249
|
-
? 'consent'
|
|
250
|
-
: this.hasRequestedConsent
|
|
251
|
-
? ''
|
|
252
|
-
: 'consent',
|
|
253
|
-
});
|
|
254
|
-
} catch (e) {
|
|
255
|
-
reject(e);
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
this.accessToken = token;
|
|
260
|
-
// Persist clientId and union of scopes in auth cache for later scope upgrades
|
|
261
|
-
if (this.auth && typeof this.auth === 'object') {
|
|
262
|
-
(this.auth as any).clientId = clientId;
|
|
263
|
-
const union = Array.from(
|
|
264
|
-
new Set([...(this.auth.scopes || []), ...scopes]),
|
|
265
|
-
);
|
|
266
|
-
(this.auth as any).scopes = union;
|
|
267
|
-
}
|
|
268
|
-
return token;
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
/**
|
|
272
|
-
* Ensure the active token includes the given scopes. If not, request a new token with the union.
|
|
273
|
-
*/
|
|
274
|
-
private async ensureScopes(requiredScopes: string[]): Promise<void> {
|
|
275
|
-
if (!this.isBrowserEnvironment) return;
|
|
276
|
-
if (!requiredScopes || requiredScopes.length === 0) return;
|
|
277
|
-
// If we don't have OAuth client flow, nothing to do
|
|
278
|
-
const clientId = (this as any).pendingClientId || undefined;
|
|
279
|
-
// We infer clientId from last initialize call by reading from DOM script params is not reliable.
|
|
280
|
-
// Instead, try to reuse the last known clientId from token creation by caching it in auth.
|
|
281
|
-
const authClientId: string | undefined =
|
|
282
|
-
(this.auth && this.auth.clientId) || undefined;
|
|
283
|
-
const effectiveClientId = authClientId || clientId;
|
|
284
|
-
if (!effectiveClientId) return;
|
|
285
|
-
|
|
286
|
-
const missing = requiredScopes.filter((s) => !this.requestedScopes.has(s));
|
|
287
|
-
if (missing.length === 0) return;
|
|
288
|
-
|
|
289
|
-
const union = Array.from(
|
|
290
|
-
new Set([...Array.from(this.requestedScopes), ...requiredScopes]),
|
|
291
|
-
);
|
|
292
|
-
await this.requestAccessToken(effectiveClientId, union, true);
|
|
293
|
-
if (this.auth && typeof this.auth === 'object') {
|
|
294
|
-
(this.auth as any).scopes = union;
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/**
|
|
299
|
-
* Build a minimal RFC 2822 MIME message (plain text) and return base64url string.
|
|
300
|
-
*/
|
|
301
|
-
private buildMimeMessage(params: {
|
|
302
|
-
from?: string;
|
|
303
|
-
to: string[];
|
|
304
|
-
subject?: string;
|
|
305
|
-
body?: string;
|
|
306
|
-
}): string {
|
|
307
|
-
const headers: string[] = [];
|
|
308
|
-
if (params.from) headers.push(`From: ${params.from}`);
|
|
309
|
-
headers.push(`To: ${params.to.join(', ')}`);
|
|
310
|
-
if (params.subject) headers.push(`Subject: ${params.subject}`);
|
|
311
|
-
headers.push('MIME-Version: 1.0');
|
|
312
|
-
headers.push('Content-Type: text/plain; charset=UTF-8');
|
|
313
|
-
|
|
314
|
-
const lines = [...headers, '', params.body || ''];
|
|
315
|
-
const raw = lines.join('\r\n');
|
|
316
|
-
return this.encodeBase64Url(raw);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/**
|
|
320
|
-
* Encode string to base64url (RFC 4648 §5), as required by Gmail API.
|
|
321
|
-
*/
|
|
322
|
-
private encodeBase64Url(input: string): string {
|
|
323
|
-
// btoa works with Latin1; for UTF-8 we need to encode first
|
|
324
|
-
const utf8 = unescape(encodeURIComponent(input));
|
|
325
|
-
const b64 = btoa(utf8);
|
|
326
|
-
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Attempt to send an email via Gmail API using the current OAuth token.
|
|
331
|
-
*/
|
|
332
|
-
private async sendViaGmailApi(params: {
|
|
333
|
-
from?: string;
|
|
334
|
-
to: string[];
|
|
335
|
-
subject?: string;
|
|
336
|
-
message?: string;
|
|
337
|
-
spreadsheetUrl?: string;
|
|
338
|
-
// attachments are not implemented in this client-side example
|
|
339
|
-
}): Promise<boolean> {
|
|
340
|
-
const requiredScope = 'https://www.googleapis.com/auth/gmail.send';
|
|
341
|
-
await this.ensureScopes([requiredScope]);
|
|
342
|
-
|
|
343
|
-
if (!this.accessToken) {
|
|
344
|
-
throw new Error('Not authenticated to Google APIs');
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
const bodyParts: string[] = [];
|
|
348
|
-
if (params.message) bodyParts.push(params.message);
|
|
349
|
-
if (params.spreadsheetUrl)
|
|
350
|
-
bodyParts.push(`Spreadsheet: ${params.spreadsheetUrl}`);
|
|
351
|
-
const body = bodyParts.join('\n\n');
|
|
352
|
-
|
|
353
|
-
const raw = this.buildMimeMessage({
|
|
354
|
-
from: params.from,
|
|
355
|
-
to: params.to,
|
|
356
|
-
subject: params.subject || 'Automation Output',
|
|
357
|
-
body,
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
const res = await fetch(
|
|
361
|
-
'https://gmail.googleapis.com/gmail/v1/users/me/messages/send',
|
|
362
|
-
{
|
|
363
|
-
method: 'POST',
|
|
364
|
-
headers: {
|
|
365
|
-
'Content-Type': 'application/json',
|
|
366
|
-
Authorization: `Bearer ${this.accessToken}`,
|
|
367
|
-
},
|
|
368
|
-
body: JSON.stringify({ raw }),
|
|
369
|
-
},
|
|
370
|
-
);
|
|
371
|
-
|
|
372
|
-
if (!res.ok) {
|
|
373
|
-
const text = await res.text();
|
|
374
|
-
throw new Error(
|
|
375
|
-
`Gmail send error ${res.status}: ${res.statusText} - ${text}`,
|
|
376
|
-
);
|
|
377
|
-
}
|
|
378
|
-
return true;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Helper: call Google Sheets REST API using fetch
|
|
383
|
-
*/
|
|
384
|
-
private async apiFetch(
|
|
385
|
-
path: string,
|
|
386
|
-
options: {
|
|
387
|
-
method?: string;
|
|
388
|
-
body?: any;
|
|
389
|
-
params?: Record<string, string>;
|
|
390
|
-
} = {},
|
|
391
|
-
): Promise<any> {
|
|
392
|
-
if (!this.accessToken) {
|
|
393
|
-
throw new Error('Not authenticated to Google APIs');
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const base = 'https://sheets.googleapis.com/v4';
|
|
397
|
-
const url = new URL(base + path);
|
|
398
|
-
if (options.params) {
|
|
399
|
-
Object.entries(options.params).forEach(([k, v]) =>
|
|
400
|
-
url.searchParams.set(k, v),
|
|
401
|
-
);
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const res = await fetch(url.toString(), {
|
|
405
|
-
method: options.method || 'GET',
|
|
406
|
-
headers: {
|
|
407
|
-
'Content-Type': 'application/json',
|
|
408
|
-
Authorization: `Bearer ${this.accessToken}`,
|
|
409
|
-
},
|
|
410
|
-
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
if (!res.ok) {
|
|
414
|
-
const text = await res.text();
|
|
415
|
-
throw new Error(
|
|
416
|
-
`Google API error ${res.status}: ${res.statusText} - ${text}`,
|
|
417
|
-
);
|
|
418
|
-
}
|
|
419
|
-
return res.json();
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
/**
|
|
423
|
-
* Send email via Gmail API if authorized; fallback to mailto for simple cases
|
|
424
|
-
*/
|
|
425
|
-
public async sendEmail(params: {
|
|
426
|
-
from?: string;
|
|
427
|
-
to: string[];
|
|
428
|
-
subject?: string;
|
|
429
|
-
message?: string;
|
|
430
|
-
spreadsheetUrl?: string;
|
|
431
|
-
attachExcelBlob?: Blob;
|
|
432
|
-
attachExcelFileName?: string;
|
|
433
|
-
disableUiFallback?: boolean;
|
|
434
|
-
}): Promise<boolean> {
|
|
435
|
-
// If we have OAuth token, try Gmail API (request scope on-demand)
|
|
436
|
-
const hasOauth = this.auth?.type === 'oauth' && this.accessToken;
|
|
437
|
-
if (hasOauth) {
|
|
438
|
-
try {
|
|
439
|
-
const sent = await this.sendViaGmailApi({
|
|
440
|
-
from: params.from,
|
|
441
|
-
to: params.to,
|
|
442
|
-
subject: params.subject,
|
|
443
|
-
message: params.message,
|
|
444
|
-
spreadsheetUrl: params.spreadsheetUrl,
|
|
445
|
-
});
|
|
446
|
-
if (sent) return true;
|
|
447
|
-
} catch (e) {
|
|
448
|
-
// Fall through to UI-based flow if API send fails (e.g., insufficient permissions)
|
|
449
|
-
console.warn(
|
|
450
|
-
'Gmail API send failed, falling back to UI:',
|
|
451
|
-
(e as any).message || e,
|
|
452
|
-
);
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
// Prefer: open Gmail compose in a new tab (more reliable than OS mailto handler)
|
|
457
|
-
if (this.isBrowserEnvironment && !params.disableUiFallback) {
|
|
458
|
-
const to = encodeURIComponent(params.to.join(','));
|
|
459
|
-
const subject = encodeURIComponent(params.subject || 'Automation Output');
|
|
460
|
-
const bodyParts: string[] = [];
|
|
461
|
-
if (params.message) bodyParts.push(params.message);
|
|
462
|
-
if (params.spreadsheetUrl)
|
|
463
|
-
bodyParts.push(`Spreadsheet: ${params.spreadsheetUrl}`);
|
|
464
|
-
const body = encodeURIComponent(bodyParts.join('\n\n'));
|
|
465
|
-
|
|
466
|
-
// Try Gmail compose
|
|
467
|
-
const gmailUrl = `https://mail.google.com/mail/?view=cm&fs=1&to=${to}&su=${subject}&body=${body}`;
|
|
468
|
-
const win = window.open(gmailUrl, '_blank', 'noopener,noreferrer');
|
|
469
|
-
if (win) {
|
|
470
|
-
return true;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
// Fallback: OS handler via mailto
|
|
474
|
-
// Note: attachments are not supported via mailto; advise using Gmail scope for API send
|
|
475
|
-
const a = document.createElement('a');
|
|
476
|
-
a.href = `mailto:${to}?subject=${subject}&body=${body}`;
|
|
477
|
-
a.style.display = 'none';
|
|
478
|
-
document.body.appendChild(a);
|
|
479
|
-
a.click();
|
|
480
|
-
document.body.removeChild(a);
|
|
481
|
-
|
|
482
|
-
return true;
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
return false;
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
/**
|
|
489
|
-
* Export data to Google Sheets
|
|
490
|
-
* Note: In browser environment, this simulates the export process
|
|
491
|
-
*/
|
|
492
|
-
async exportToSheets(
|
|
493
|
-
data: any[],
|
|
494
|
-
config: GoogleSheetsConfig,
|
|
495
|
-
dataMapping: DataMapping,
|
|
496
|
-
options: SheetsExportOptions = {},
|
|
497
|
-
): Promise<SheetsResponse> {
|
|
498
|
-
if (!this.sheets) {
|
|
499
|
-
throw new Error('Google Sheets API not initialized');
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
try {
|
|
503
|
-
// Prepare data for export
|
|
504
|
-
const mappedData = this.mapDataToColumns(data, dataMapping);
|
|
505
|
-
const includeHeaders = options.includeHeaders !== false;
|
|
506
|
-
|
|
507
|
-
// If using real OAuth flow, call REST API; otherwise, use mock behavior
|
|
508
|
-
const usingRest =
|
|
509
|
-
this.auth?.type === 'oauth' &&
|
|
510
|
-
this.accessToken &&
|
|
511
|
-
this.sheets === 'rest';
|
|
512
|
-
|
|
513
|
-
// Ensure spreadsheet exists when using REST
|
|
514
|
-
let spreadsheetId = config.spreadsheetId;
|
|
515
|
-
if (usingRest && !spreadsheetId) {
|
|
516
|
-
const created = await this.apiFetch('/spreadsheets', {
|
|
517
|
-
method: 'POST',
|
|
518
|
-
body: {
|
|
519
|
-
properties: { title: options.fileName || 'Automation Export' },
|
|
520
|
-
sheets: [
|
|
521
|
-
{
|
|
522
|
-
properties: { title: config.sheetName || 'Sheet1' },
|
|
523
|
-
},
|
|
524
|
-
],
|
|
525
|
-
},
|
|
526
|
-
});
|
|
527
|
-
spreadsheetId = created.spreadsheetId;
|
|
528
|
-
config.spreadsheetId = spreadsheetId;
|
|
529
|
-
}
|
|
530
|
-
|
|
531
|
-
// Ensure sheet exists
|
|
532
|
-
if (usingRest && spreadsheetId && config.sheetName) {
|
|
533
|
-
const meta = await this.apiFetch(`/spreadsheets/${spreadsheetId}`, {
|
|
534
|
-
params: { fields: 'sheets.properties.title' },
|
|
535
|
-
});
|
|
536
|
-
const hasSheet = (meta.sheets || []).some(
|
|
537
|
-
(s: any) => s.properties && s.properties.title === config.sheetName,
|
|
538
|
-
);
|
|
539
|
-
if (!hasSheet) {
|
|
540
|
-
await this.apiFetch(`/spreadsheets/${spreadsheetId}:batchUpdate`, {
|
|
541
|
-
method: 'POST',
|
|
542
|
-
body: {
|
|
543
|
-
requests: [
|
|
544
|
-
{
|
|
545
|
-
addSheet: { properties: { title: config.sheetName } },
|
|
546
|
-
},
|
|
547
|
-
],
|
|
548
|
-
},
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
// Clear sheet if requested
|
|
554
|
-
if (dataMapping.clearSheet) {
|
|
555
|
-
await this.clearSheet(config);
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
// Prepare range for writing (account for header row if included)
|
|
559
|
-
const totalRows = mappedData.length + (includeHeaders ? 1 : 0);
|
|
560
|
-
const range = this.prepareRange(config, dataMapping, totalRows);
|
|
561
|
-
|
|
562
|
-
// Prepare values array
|
|
563
|
-
const values = this.prepareValues(
|
|
564
|
-
mappedData,
|
|
565
|
-
dataMapping,
|
|
566
|
-
includeHeaders,
|
|
567
|
-
);
|
|
568
|
-
|
|
569
|
-
if (usingRest) {
|
|
570
|
-
await this.apiFetch(
|
|
571
|
-
`/spreadsheets/${config.spreadsheetId}/values/${encodeURIComponent(
|
|
572
|
-
range,
|
|
573
|
-
)}`,
|
|
574
|
-
{
|
|
575
|
-
method: 'PUT',
|
|
576
|
-
params: { valueInputOption: 'RAW' },
|
|
577
|
-
body: { values },
|
|
578
|
-
},
|
|
579
|
-
);
|
|
580
|
-
} else {
|
|
581
|
-
// Mock behavior
|
|
582
|
-
await this.sheets.spreadsheets.values.update({
|
|
583
|
-
spreadsheetId: config.spreadsheetId,
|
|
584
|
-
range: range,
|
|
585
|
-
valueInputOption: 'RAW',
|
|
586
|
-
requestBody: { values },
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
|
|
590
|
-
// Get spreadsheet URL
|
|
591
|
-
const spreadsheetUrl = `https://docs.google.com/spreadsheets/d/${config.spreadsheetId}`;
|
|
592
|
-
|
|
593
|
-
// Send email if recipients provided
|
|
594
|
-
let emailSent = false;
|
|
595
|
-
if (options.emailRecipients && options.emailRecipients.length > 0) {
|
|
596
|
-
emailSent = await this.sendEmailNotification(
|
|
597
|
-
options.emailRecipients,
|
|
598
|
-
spreadsheetUrl,
|
|
599
|
-
options.fileName || 'Google Sheets Export',
|
|
600
|
-
);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
return {
|
|
604
|
-
spreadsheetId: config.spreadsheetId,
|
|
605
|
-
sheetName: config.sheetName,
|
|
606
|
-
range: range,
|
|
607
|
-
rowsAdded: mappedData.length,
|
|
608
|
-
columnsMapped: dataMapping.headers.length,
|
|
609
|
-
data: mappedData,
|
|
610
|
-
exportTime: new Date().toISOString(),
|
|
611
|
-
success: true,
|
|
612
|
-
spreadsheetUrl,
|
|
613
|
-
emailSent,
|
|
614
|
-
};
|
|
615
|
-
} catch (error) {
|
|
616
|
-
throw new Error(`Failed to export to Google Sheets: ${error.message}`);
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
/**
|
|
621
|
-
* Export data to Excel file and download
|
|
622
|
-
*/
|
|
623
|
-
async exportToExcel(
|
|
624
|
-
data: any[],
|
|
625
|
-
dataMapping: DataMapping,
|
|
626
|
-
options: SheetsExportOptions = {},
|
|
627
|
-
): Promise<SheetsResponse> {
|
|
628
|
-
try {
|
|
629
|
-
// Prepare data for export
|
|
630
|
-
const mappedData = this.mapDataToColumns(data, dataMapping);
|
|
631
|
-
|
|
632
|
-
// Create workbook
|
|
633
|
-
const wb = XLSX.utils.book_new();
|
|
634
|
-
|
|
635
|
-
// Prepare worksheet data
|
|
636
|
-
const wsData = this.prepareExcelData(
|
|
637
|
-
mappedData,
|
|
638
|
-
dataMapping,
|
|
639
|
-
options.includeHeaders !== false,
|
|
640
|
-
);
|
|
641
|
-
|
|
642
|
-
// Create worksheet
|
|
643
|
-
const ws = XLSX.utils.aoa_to_sheet(wsData);
|
|
644
|
-
|
|
645
|
-
// Add worksheet to workbook
|
|
646
|
-
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
|
|
647
|
-
|
|
648
|
-
// Generate file name
|
|
649
|
-
const fileName =
|
|
650
|
-
options.fileName ||
|
|
651
|
-
`export_${new Date().toISOString().split('T')[0]}.xlsx`;
|
|
652
|
-
|
|
653
|
-
// Save file
|
|
654
|
-
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
|
|
655
|
-
const blob = new Blob([excelBuffer], {
|
|
656
|
-
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
657
|
-
});
|
|
658
|
-
saveAs(blob, fileName);
|
|
659
|
-
|
|
660
|
-
// Send email if recipients provided
|
|
661
|
-
let emailSent = false;
|
|
662
|
-
if (options.emailRecipients && options.emailRecipients.length > 0) {
|
|
663
|
-
emailSent = await this.sendEmailWithAttachment(
|
|
664
|
-
options.emailRecipients,
|
|
665
|
-
blob,
|
|
666
|
-
fileName,
|
|
667
|
-
);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
return {
|
|
671
|
-
spreadsheetId: '',
|
|
672
|
-
sheetName: 'Sheet1',
|
|
673
|
-
range: '',
|
|
674
|
-
rowsAdded: mappedData.length,
|
|
675
|
-
columnsMapped: dataMapping.headers.length,
|
|
676
|
-
data: mappedData,
|
|
677
|
-
exportTime: new Date().toISOString(),
|
|
678
|
-
success: true,
|
|
679
|
-
downloadUrl: URL.createObjectURL(blob),
|
|
680
|
-
emailSent,
|
|
681
|
-
};
|
|
682
|
-
} catch (error) {
|
|
683
|
-
throw new Error(`Failed to export to Excel: ${error.message}`);
|
|
684
|
-
}
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
/**
|
|
688
|
-
* Map data according to column configuration
|
|
689
|
-
*/
|
|
690
|
-
private mapDataToColumns(data: any[], dataMapping: DataMapping): any[] {
|
|
691
|
-
return data.map((item: any) => {
|
|
692
|
-
const row: any = {};
|
|
693
|
-
dataMapping.headers.forEach((header) => {
|
|
694
|
-
const value = this.getNestedValue(item, header.sourceField);
|
|
695
|
-
row[header.column] = this.formatValueForSheets(value, header.dataType);
|
|
696
|
-
});
|
|
697
|
-
return row;
|
|
698
|
-
});
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
/**
|
|
702
|
-
* Get nested value from object using dot notation
|
|
703
|
-
*/
|
|
704
|
-
private getNestedValue(obj: any, path: string): any {
|
|
705
|
-
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Format value according to data type
|
|
710
|
-
*/
|
|
711
|
-
private formatValueForSheets(value: any, dataType: string): any {
|
|
712
|
-
if (value === null || value === undefined) {
|
|
713
|
-
return '';
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
switch (dataType) {
|
|
717
|
-
case 'number':
|
|
718
|
-
return typeof value === 'number' ? value : parseFloat(value) || 0;
|
|
719
|
-
case 'date':
|
|
720
|
-
return value instanceof Date
|
|
721
|
-
? value.toISOString().split('T')[0]
|
|
722
|
-
: value;
|
|
723
|
-
case 'boolean':
|
|
724
|
-
return Boolean(value);
|
|
725
|
-
case 'string':
|
|
726
|
-
default:
|
|
727
|
-
return String(value);
|
|
728
|
-
}
|
|
729
|
-
}
|
|
730
|
-
|
|
731
|
-
/**
|
|
732
|
-
* Prepare range for writing data
|
|
733
|
-
*/
|
|
734
|
-
private prepareRange(
|
|
735
|
-
config: GoogleSheetsConfig,
|
|
736
|
-
dataMapping: DataMapping,
|
|
737
|
-
rowCount: number,
|
|
738
|
-
): string {
|
|
739
|
-
const startColumn = dataMapping.headers[0]?.column || 'A';
|
|
740
|
-
const endColumn =
|
|
741
|
-
dataMapping.headers[dataMapping.headers.length - 1]?.column || 'Z';
|
|
742
|
-
const startRow = dataMapping.clearSheet ? 1 : 1; // Simplified for browser
|
|
743
|
-
const endRow = startRow + rowCount - 1;
|
|
744
|
-
|
|
745
|
-
return `${config.sheetName}!${startColumn}${startRow}:${endColumn}${endRow}`;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/**
|
|
749
|
-
* Clear sheet content
|
|
750
|
-
*/
|
|
751
|
-
private async clearSheet(config: GoogleSheetsConfig): Promise<void> {
|
|
752
|
-
if (!this.sheets) return;
|
|
753
|
-
|
|
754
|
-
try {
|
|
755
|
-
const usingRest =
|
|
756
|
-
this.auth?.type === 'oauth' &&
|
|
757
|
-
this.accessToken &&
|
|
758
|
-
this.sheets === 'rest';
|
|
759
|
-
const range = `${config.sheetName}!A:Z`;
|
|
760
|
-
if (usingRest) {
|
|
761
|
-
await this.apiFetch(
|
|
762
|
-
`/spreadsheets/${config.spreadsheetId}/values/${encodeURIComponent(
|
|
763
|
-
range,
|
|
764
|
-
)}:clear`,
|
|
765
|
-
{ method: 'POST' },
|
|
766
|
-
);
|
|
767
|
-
} else {
|
|
768
|
-
await this.sheets.spreadsheets.values.clear({
|
|
769
|
-
spreadsheetId: config.spreadsheetId,
|
|
770
|
-
range,
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
} catch (error) {
|
|
774
|
-
console.warn('Failed to clear sheet:', (error as any).message || error);
|
|
775
|
-
}
|
|
776
|
-
}
|
|
777
|
-
|
|
778
|
-
/**
|
|
779
|
-
* Prepare values array for Google Sheets API
|
|
780
|
-
*/
|
|
781
|
-
private prepareValues(
|
|
782
|
-
mappedData: any[],
|
|
783
|
-
dataMapping: DataMapping,
|
|
784
|
-
includeHeaders: boolean,
|
|
785
|
-
): any[][] {
|
|
786
|
-
const values: any[][] = [];
|
|
787
|
-
|
|
788
|
-
// Add headers if requested
|
|
789
|
-
if (includeHeaders && dataMapping.headers.length > 0) {
|
|
790
|
-
const headers = dataMapping.headers.map((h) => h.column);
|
|
791
|
-
values.push(headers);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
// Add data rows
|
|
795
|
-
mappedData.forEach((row) => {
|
|
796
|
-
const rowValues = dataMapping.headers.map(
|
|
797
|
-
(header) => row[header.column] || '',
|
|
798
|
-
);
|
|
799
|
-
values.push(rowValues);
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
return values;
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
/**
|
|
806
|
-
* Prepare data for Excel export
|
|
807
|
-
*/
|
|
808
|
-
private prepareExcelData(
|
|
809
|
-
mappedData: any[],
|
|
810
|
-
dataMapping: DataMapping,
|
|
811
|
-
includeHeaders: boolean,
|
|
812
|
-
): any[][] {
|
|
813
|
-
const data: any[][] = [];
|
|
814
|
-
|
|
815
|
-
// Add headers if requested
|
|
816
|
-
if (includeHeaders && dataMapping.headers.length > 0) {
|
|
817
|
-
const headers = dataMapping.headers.map((h) => h.column);
|
|
818
|
-
data.push(headers);
|
|
819
|
-
}
|
|
820
|
-
|
|
821
|
-
// Add data rows
|
|
822
|
-
mappedData.forEach((row) => {
|
|
823
|
-
const rowValues = dataMapping.headers.map(
|
|
824
|
-
(header) => row[header.column] || '',
|
|
825
|
-
);
|
|
826
|
-
data.push(rowValues);
|
|
827
|
-
});
|
|
828
|
-
|
|
829
|
-
return data;
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
/**
|
|
833
|
-
* Send email notification with spreadsheet link
|
|
834
|
-
*/
|
|
835
|
-
private async sendEmailNotification(
|
|
836
|
-
recipients: string[],
|
|
837
|
-
spreadsheetUrl: string,
|
|
838
|
-
fileName: string,
|
|
839
|
-
): Promise<boolean> {
|
|
840
|
-
try {
|
|
841
|
-
// In browser environment, we'll simulate email sending
|
|
842
|
-
console.log(
|
|
843
|
-
`📧 Email notification would be sent to: ${recipients.join(', ')}`,
|
|
844
|
-
);
|
|
845
|
-
console.log(`📊 Spreadsheet URL: ${spreadsheetUrl}`);
|
|
846
|
-
console.log(`📁 File: ${fileName}`);
|
|
847
|
-
|
|
848
|
-
// Show a browser notification or alert
|
|
849
|
-
if (this.isBrowserEnvironment && 'Notification' in window) {
|
|
850
|
-
if (Notification.permission === 'granted') {
|
|
851
|
-
new Notification('Google Sheets Export', {
|
|
852
|
-
body: `Data exported successfully! Sent to ${recipients.length} recipient(s).`,
|
|
853
|
-
icon: '/favicon.ico',
|
|
854
|
-
});
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
return true;
|
|
859
|
-
} catch (error) {
|
|
860
|
-
console.error('Failed to send email notification:', error);
|
|
861
|
-
return false;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
|
|
865
|
-
/**
|
|
866
|
-
* Send email with Excel attachment
|
|
867
|
-
*/
|
|
868
|
-
private async sendEmailWithAttachment(
|
|
869
|
-
recipients: string[],
|
|
870
|
-
fileBlob: Blob,
|
|
871
|
-
fileName: string,
|
|
872
|
-
): Promise<boolean> {
|
|
873
|
-
try {
|
|
874
|
-
// In browser environment, we'll simulate email sending
|
|
875
|
-
console.log(
|
|
876
|
-
`📧 Email with attachment would be sent to: ${recipients.join(', ')}`,
|
|
877
|
-
);
|
|
878
|
-
console.log(`📎 Attachment: ${fileName}`);
|
|
879
|
-
console.log(`📊 File size: ${(fileBlob.size / 1024).toFixed(2)} KB`);
|
|
880
|
-
|
|
881
|
-
// Show a browser notification
|
|
882
|
-
if (this.isBrowserEnvironment && 'Notification' in window) {
|
|
883
|
-
if (Notification.permission === 'granted') {
|
|
884
|
-
new Notification('Excel Export', {
|
|
885
|
-
body: `Excel file "${fileName}" exported successfully! Sent to ${recipients.length} recipient(s).`,
|
|
886
|
-
icon: '/favicon.ico',
|
|
887
|
-
});
|
|
888
|
-
}
|
|
889
|
-
}
|
|
890
|
-
|
|
891
|
-
return true;
|
|
892
|
-
} catch (error) {
|
|
893
|
-
console.error('Failed to send email with attachment:', error);
|
|
894
|
-
return false;
|
|
895
|
-
}
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
/**
|
|
899
|
-
* Validate configuration
|
|
900
|
-
*/
|
|
901
|
-
validateConfig(config: GoogleSheetsConfig): {
|
|
902
|
-
valid: boolean;
|
|
903
|
-
errors: string[];
|
|
904
|
-
} {
|
|
905
|
-
const errors: string[] = [];
|
|
906
|
-
|
|
907
|
-
if (!config.spreadsheetId) {
|
|
908
|
-
// Allow creating a new spreadsheet when using OAuth clientId
|
|
909
|
-
if (
|
|
910
|
-
!(config.credentials?.type === 'oauth' && config.credentials.clientId)
|
|
911
|
-
) {
|
|
912
|
-
errors.push(
|
|
913
|
-
'Spreadsheet ID is required (or provide OAuth clientId to create a new one)',
|
|
914
|
-
);
|
|
915
|
-
}
|
|
916
|
-
}
|
|
917
|
-
|
|
918
|
-
if (!config.sheetName) {
|
|
919
|
-
errors.push('Sheet name is required');
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
if (!config.credentials) {
|
|
923
|
-
errors.push('Credentials are required');
|
|
924
|
-
} else {
|
|
925
|
-
if (config.credentials.type === 'service-account') {
|
|
926
|
-
if (
|
|
927
|
-
!config.credentials.serviceAccountKey &&
|
|
928
|
-
!config.credentials.clientEmail
|
|
929
|
-
) {
|
|
930
|
-
errors.push('Service account key or client email is required');
|
|
931
|
-
}
|
|
932
|
-
if (
|
|
933
|
-
!config.credentials.serviceAccountKey &&
|
|
934
|
-
!config.credentials.privateKey
|
|
935
|
-
) {
|
|
936
|
-
errors.push('Service account key or private key is required');
|
|
937
|
-
}
|
|
938
|
-
} else if (config.credentials.type === 'api-key') {
|
|
939
|
-
if (!config.credentials.apiKey) {
|
|
940
|
-
errors.push('API key is required');
|
|
941
|
-
}
|
|
942
|
-
} else if (config.credentials.type === 'oauth') {
|
|
943
|
-
// Accept either a provided token or clientId for GIS
|
|
944
|
-
if (!config.credentials.oauthToken && !config.credentials.clientId) {
|
|
945
|
-
errors.push('OAuth clientId or oauthToken is required');
|
|
946
|
-
}
|
|
947
|
-
if (config.credentials.clientId && !config.credentials.scopes) {
|
|
948
|
-
// Provide default scope if not present
|
|
949
|
-
config.credentials.scopes = [
|
|
950
|
-
'https://www.googleapis.com/auth/spreadsheets',
|
|
951
|
-
];
|
|
952
|
-
}
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
return {
|
|
957
|
-
valid: errors.length === 0,
|
|
958
|
-
errors,
|
|
959
|
-
};
|
|
960
|
-
}
|
|
961
|
-
}
|
|
1
|
+
// Browser-compatible Google Sheets service
|
|
2
|
+
// Note: This is a simplified version for browser environments
|
|
3
|
+
// For production use, consider using a backend API to handle Google Sheets operations
|
|
4
|
+
|
|
5
|
+
import * as XLSX from 'xlsx';
|
|
6
|
+
import { saveAs } from 'file-saver';
|
|
7
|
+
|
|
8
|
+
// GIS global injected by `https://accounts.google.com/gsi/client`
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
declare const google: any;
|
|
11
|
+
|
|
12
|
+
// Mock Google Sheets API for browser environment
|
|
13
|
+
const mockGoogleSheetsAPI = {
|
|
14
|
+
spreadsheets: {
|
|
15
|
+
values: {
|
|
16
|
+
update: async (params: any) => {
|
|
17
|
+
// Simulate API call delay
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
19
|
+
return {
|
|
20
|
+
data: {
|
|
21
|
+
updatedRows: params.requestBody.values.length,
|
|
22
|
+
updatedColumns: params.requestBody.values[0]?.length || 0,
|
|
23
|
+
updatedCells:
|
|
24
|
+
params.requestBody.values.length *
|
|
25
|
+
(params.requestBody.values[0]?.length || 0),
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
},
|
|
29
|
+
get: async (params: any) => {
|
|
30
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
31
|
+
return {
|
|
32
|
+
data: {
|
|
33
|
+
values: [],
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
clear: async (params: any) => {
|
|
38
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
39
|
+
return {
|
|
40
|
+
data: {
|
|
41
|
+
clearedRange: params.range,
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export interface GoogleSheetsConfig {
|
|
50
|
+
spreadsheetId: string;
|
|
51
|
+
sheetName: string;
|
|
52
|
+
range: string;
|
|
53
|
+
credentials: {
|
|
54
|
+
type: 'service-account' | 'oauth' | 'api-key';
|
|
55
|
+
serviceAccountKey?: string;
|
|
56
|
+
oauthToken?: string;
|
|
57
|
+
apiKey?: string;
|
|
58
|
+
clientEmail?: string;
|
|
59
|
+
privateKey?: string;
|
|
60
|
+
// OAuth client flow (Google Identity Services)
|
|
61
|
+
clientId?: string;
|
|
62
|
+
scopes?: string[];
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface DataMapping {
|
|
67
|
+
headers: Array<{
|
|
68
|
+
column: string;
|
|
69
|
+
sourceField: string;
|
|
70
|
+
dataType: 'string' | 'number' | 'date' | 'boolean';
|
|
71
|
+
}>;
|
|
72
|
+
appendMode: boolean;
|
|
73
|
+
clearSheet: boolean;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export interface SheetsExportOptions {
|
|
77
|
+
emailRecipients?: string[];
|
|
78
|
+
fileName?: string;
|
|
79
|
+
exportFormat?: 'sheets' | 'excel';
|
|
80
|
+
includeHeaders?: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface SheetsResponse {
|
|
84
|
+
spreadsheetId: string;
|
|
85
|
+
sheetName: string;
|
|
86
|
+
range: string;
|
|
87
|
+
rowsAdded: number;
|
|
88
|
+
columnsMapped: number;
|
|
89
|
+
data: any[];
|
|
90
|
+
exportTime: string;
|
|
91
|
+
success: boolean;
|
|
92
|
+
spreadsheetUrl?: string;
|
|
93
|
+
downloadUrl?: string;
|
|
94
|
+
emailSent?: boolean;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class GoogleSheetsService {
|
|
98
|
+
private sheets: any = null;
|
|
99
|
+
private auth: any = null;
|
|
100
|
+
private isBrowserEnvironment: boolean;
|
|
101
|
+
private accessToken: string | null = null;
|
|
102
|
+
private gisLoaded: boolean = false;
|
|
103
|
+
private hasRequestedConsent: boolean = false;
|
|
104
|
+
private requestedScopes: Set<string> = new Set();
|
|
105
|
+
|
|
106
|
+
constructor() {
|
|
107
|
+
// Detect if we're in a browser environment
|
|
108
|
+
this.isBrowserEnvironment = typeof window !== 'undefined';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Initialize Google Sheets API with authentication
|
|
113
|
+
* Note: In browser environment, this uses a mock API for demonstration
|
|
114
|
+
*/
|
|
115
|
+
async initialize(config: GoogleSheetsConfig): Promise<void> {
|
|
116
|
+
try {
|
|
117
|
+
if (this.isBrowserEnvironment) {
|
|
118
|
+
// If OAuth credentials are provided, initialize Google Identity Services
|
|
119
|
+
if (
|
|
120
|
+
config.credentials?.type === 'oauth' &&
|
|
121
|
+
(config.credentials.clientEmail ||
|
|
122
|
+
config.credentials.oauthToken ||
|
|
123
|
+
config.credentials.clientId)
|
|
124
|
+
) {
|
|
125
|
+
// Prefer OAuth clientId flow (GIS)
|
|
126
|
+
if (config.credentials.clientId) {
|
|
127
|
+
await this.ensureGisLoaded();
|
|
128
|
+
await this.requestAccessToken(
|
|
129
|
+
config.credentials.clientId,
|
|
130
|
+
config.credentials.scopes || [
|
|
131
|
+
'https://www.googleapis.com/auth/spreadsheets',
|
|
132
|
+
],
|
|
133
|
+
);
|
|
134
|
+
this.sheets = 'rest';
|
|
135
|
+
this.auth = {
|
|
136
|
+
type: 'oauth',
|
|
137
|
+
clientId: config.credentials.clientId,
|
|
138
|
+
scopes: config.credentials.scopes || [
|
|
139
|
+
'https://www.googleapis.com/auth/spreadsheets',
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Fallback: if oauthToken is provided directly (e.g., injected), use it
|
|
146
|
+
if (config.credentials.oauthToken) {
|
|
147
|
+
this.accessToken = config.credentials.oauthToken;
|
|
148
|
+
this.sheets = 'rest';
|
|
149
|
+
this.auth = {
|
|
150
|
+
type: 'oauth',
|
|
151
|
+
clientId: config.credentials.clientId,
|
|
152
|
+
scopes: config.credentials.scopes || [
|
|
153
|
+
'https://www.googleapis.com/auth/spreadsheets',
|
|
154
|
+
],
|
|
155
|
+
};
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Browser environment without OAuth clientId – use mock API
|
|
161
|
+
console.log(
|
|
162
|
+
'Browser environment detected - using mock Google Sheets API',
|
|
163
|
+
);
|
|
164
|
+
this.sheets = mockGoogleSheetsAPI;
|
|
165
|
+
this.auth = { type: config.credentials?.type || 'mock' };
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Node.js environment - would use real Google APIs
|
|
170
|
+
// This would require server-side implementation
|
|
171
|
+
throw new Error(
|
|
172
|
+
'Real Google Sheets API integration requires server-side implementation',
|
|
173
|
+
);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
throw new Error(
|
|
176
|
+
`Failed to initialize Google Sheets API: ${error.message}`,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Ensure Google Identity Services script is loaded
|
|
183
|
+
*/
|
|
184
|
+
private async ensureGisLoaded(): Promise<void> {
|
|
185
|
+
if (!this.isBrowserEnvironment) return;
|
|
186
|
+
if (this.gisLoaded) return;
|
|
187
|
+
|
|
188
|
+
await new Promise<void>((resolve, reject) => {
|
|
189
|
+
const existing = document.querySelector(
|
|
190
|
+
'script[src="https://accounts.google.com/gsi/client"]',
|
|
191
|
+
);
|
|
192
|
+
if (existing) {
|
|
193
|
+
this.gisLoaded = true;
|
|
194
|
+
resolve();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const script = document.createElement('script');
|
|
199
|
+
script.src = 'https://accounts.google.com/gsi/client';
|
|
200
|
+
script.async = true;
|
|
201
|
+
script.defer = true;
|
|
202
|
+
script.onload = () => {
|
|
203
|
+
this.gisLoaded = true;
|
|
204
|
+
resolve();
|
|
205
|
+
};
|
|
206
|
+
script.onerror = () => reject(new Error('Failed to load GIS script'));
|
|
207
|
+
document.head.appendChild(script);
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Request an OAuth access token using GIS token client
|
|
213
|
+
*/
|
|
214
|
+
private async requestAccessToken(
|
|
215
|
+
clientId: string,
|
|
216
|
+
scopes: string[],
|
|
217
|
+
forceConsent: boolean = false,
|
|
218
|
+
): Promise<string> {
|
|
219
|
+
if (!this.isBrowserEnvironment) {
|
|
220
|
+
throw new Error('OAuth is only available in browser environment');
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
await this.ensureGisLoaded();
|
|
224
|
+
|
|
225
|
+
const token = await new Promise<string>((resolve, reject) => {
|
|
226
|
+
try {
|
|
227
|
+
// @ts-ignore - google global injected by GIS
|
|
228
|
+
const tokenClient = google.accounts.oauth2.initTokenClient({
|
|
229
|
+
client_id: clientId,
|
|
230
|
+
scope: scopes.join(' '),
|
|
231
|
+
callback: (response: any) => {
|
|
232
|
+
// Handle both success and error shapes
|
|
233
|
+
if (response && response.access_token) {
|
|
234
|
+
this.hasRequestedConsent = true;
|
|
235
|
+
// Track that we've requested these scopes
|
|
236
|
+
scopes.forEach((s) => this.requestedScopes.add(s));
|
|
237
|
+
resolve(response.access_token);
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
const err = response?.error || 'unknown_error';
|
|
241
|
+
const desc = response?.error_description || 'No description';
|
|
242
|
+
reject(new Error(`OAuth token error: ${err} - ${desc}`));
|
|
243
|
+
},
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// First-time call must show consent; subsequent calls can be silent
|
|
247
|
+
tokenClient.requestAccessToken({
|
|
248
|
+
prompt: forceConsent
|
|
249
|
+
? 'consent'
|
|
250
|
+
: this.hasRequestedConsent
|
|
251
|
+
? ''
|
|
252
|
+
: 'consent',
|
|
253
|
+
});
|
|
254
|
+
} catch (e) {
|
|
255
|
+
reject(e);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
this.accessToken = token;
|
|
260
|
+
// Persist clientId and union of scopes in auth cache for later scope upgrades
|
|
261
|
+
if (this.auth && typeof this.auth === 'object') {
|
|
262
|
+
(this.auth as any).clientId = clientId;
|
|
263
|
+
const union = Array.from(
|
|
264
|
+
new Set([...(this.auth.scopes || []), ...scopes]),
|
|
265
|
+
);
|
|
266
|
+
(this.auth as any).scopes = union;
|
|
267
|
+
}
|
|
268
|
+
return token;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Ensure the active token includes the given scopes. If not, request a new token with the union.
|
|
273
|
+
*/
|
|
274
|
+
private async ensureScopes(requiredScopes: string[]): Promise<void> {
|
|
275
|
+
if (!this.isBrowserEnvironment) return;
|
|
276
|
+
if (!requiredScopes || requiredScopes.length === 0) return;
|
|
277
|
+
// If we don't have OAuth client flow, nothing to do
|
|
278
|
+
const clientId = (this as any).pendingClientId || undefined;
|
|
279
|
+
// We infer clientId from last initialize call by reading from DOM script params is not reliable.
|
|
280
|
+
// Instead, try to reuse the last known clientId from token creation by caching it in auth.
|
|
281
|
+
const authClientId: string | undefined =
|
|
282
|
+
(this.auth && this.auth.clientId) || undefined;
|
|
283
|
+
const effectiveClientId = authClientId || clientId;
|
|
284
|
+
if (!effectiveClientId) return;
|
|
285
|
+
|
|
286
|
+
const missing = requiredScopes.filter((s) => !this.requestedScopes.has(s));
|
|
287
|
+
if (missing.length === 0) return;
|
|
288
|
+
|
|
289
|
+
const union = Array.from(
|
|
290
|
+
new Set([...Array.from(this.requestedScopes), ...requiredScopes]),
|
|
291
|
+
);
|
|
292
|
+
await this.requestAccessToken(effectiveClientId, union, true);
|
|
293
|
+
if (this.auth && typeof this.auth === 'object') {
|
|
294
|
+
(this.auth as any).scopes = union;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Build a minimal RFC 2822 MIME message (plain text) and return base64url string.
|
|
300
|
+
*/
|
|
301
|
+
private buildMimeMessage(params: {
|
|
302
|
+
from?: string;
|
|
303
|
+
to: string[];
|
|
304
|
+
subject?: string;
|
|
305
|
+
body?: string;
|
|
306
|
+
}): string {
|
|
307
|
+
const headers: string[] = [];
|
|
308
|
+
if (params.from) headers.push(`From: ${params.from}`);
|
|
309
|
+
headers.push(`To: ${params.to.join(', ')}`);
|
|
310
|
+
if (params.subject) headers.push(`Subject: ${params.subject}`);
|
|
311
|
+
headers.push('MIME-Version: 1.0');
|
|
312
|
+
headers.push('Content-Type: text/plain; charset=UTF-8');
|
|
313
|
+
|
|
314
|
+
const lines = [...headers, '', params.body || ''];
|
|
315
|
+
const raw = lines.join('\r\n');
|
|
316
|
+
return this.encodeBase64Url(raw);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Encode string to base64url (RFC 4648 §5), as required by Gmail API.
|
|
321
|
+
*/
|
|
322
|
+
private encodeBase64Url(input: string): string {
|
|
323
|
+
// btoa works with Latin1; for UTF-8 we need to encode first
|
|
324
|
+
const utf8 = unescape(encodeURIComponent(input));
|
|
325
|
+
const b64 = btoa(utf8);
|
|
326
|
+
return b64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/g, '');
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* Attempt to send an email via Gmail API using the current OAuth token.
|
|
331
|
+
*/
|
|
332
|
+
private async sendViaGmailApi(params: {
|
|
333
|
+
from?: string;
|
|
334
|
+
to: string[];
|
|
335
|
+
subject?: string;
|
|
336
|
+
message?: string;
|
|
337
|
+
spreadsheetUrl?: string;
|
|
338
|
+
// attachments are not implemented in this client-side example
|
|
339
|
+
}): Promise<boolean> {
|
|
340
|
+
const requiredScope = 'https://www.googleapis.com/auth/gmail.send';
|
|
341
|
+
await this.ensureScopes([requiredScope]);
|
|
342
|
+
|
|
343
|
+
if (!this.accessToken) {
|
|
344
|
+
throw new Error('Not authenticated to Google APIs');
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const bodyParts: string[] = [];
|
|
348
|
+
if (params.message) bodyParts.push(params.message);
|
|
349
|
+
if (params.spreadsheetUrl)
|
|
350
|
+
bodyParts.push(`Spreadsheet: ${params.spreadsheetUrl}`);
|
|
351
|
+
const body = bodyParts.join('\n\n');
|
|
352
|
+
|
|
353
|
+
const raw = this.buildMimeMessage({
|
|
354
|
+
from: params.from,
|
|
355
|
+
to: params.to,
|
|
356
|
+
subject: params.subject || 'Automation Output',
|
|
357
|
+
body,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
const res = await fetch(
|
|
361
|
+
'https://gmail.googleapis.com/gmail/v1/users/me/messages/send',
|
|
362
|
+
{
|
|
363
|
+
method: 'POST',
|
|
364
|
+
headers: {
|
|
365
|
+
'Content-Type': 'application/json',
|
|
366
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
367
|
+
},
|
|
368
|
+
body: JSON.stringify({ raw }),
|
|
369
|
+
},
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
if (!res.ok) {
|
|
373
|
+
const text = await res.text();
|
|
374
|
+
throw new Error(
|
|
375
|
+
`Gmail send error ${res.status}: ${res.statusText} - ${text}`,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
return true;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Helper: call Google Sheets REST API using fetch
|
|
383
|
+
*/
|
|
384
|
+
private async apiFetch(
|
|
385
|
+
path: string,
|
|
386
|
+
options: {
|
|
387
|
+
method?: string;
|
|
388
|
+
body?: any;
|
|
389
|
+
params?: Record<string, string>;
|
|
390
|
+
} = {},
|
|
391
|
+
): Promise<any> {
|
|
392
|
+
if (!this.accessToken) {
|
|
393
|
+
throw new Error('Not authenticated to Google APIs');
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const base = 'https://sheets.googleapis.com/v4';
|
|
397
|
+
const url = new URL(base + path);
|
|
398
|
+
if (options.params) {
|
|
399
|
+
Object.entries(options.params).forEach(([k, v]) =>
|
|
400
|
+
url.searchParams.set(k, v),
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const res = await fetch(url.toString(), {
|
|
405
|
+
method: options.method || 'GET',
|
|
406
|
+
headers: {
|
|
407
|
+
'Content-Type': 'application/json',
|
|
408
|
+
Authorization: `Bearer ${this.accessToken}`,
|
|
409
|
+
},
|
|
410
|
+
body: options.body ? JSON.stringify(options.body) : undefined,
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
if (!res.ok) {
|
|
414
|
+
const text = await res.text();
|
|
415
|
+
throw new Error(
|
|
416
|
+
`Google API error ${res.status}: ${res.statusText} - ${text}`,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
return res.json();
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Send email via Gmail API if authorized; fallback to mailto for simple cases
|
|
424
|
+
*/
|
|
425
|
+
public async sendEmail(params: {
|
|
426
|
+
from?: string;
|
|
427
|
+
to: string[];
|
|
428
|
+
subject?: string;
|
|
429
|
+
message?: string;
|
|
430
|
+
spreadsheetUrl?: string;
|
|
431
|
+
attachExcelBlob?: Blob;
|
|
432
|
+
attachExcelFileName?: string;
|
|
433
|
+
disableUiFallback?: boolean;
|
|
434
|
+
}): Promise<boolean> {
|
|
435
|
+
// If we have OAuth token, try Gmail API (request scope on-demand)
|
|
436
|
+
const hasOauth = this.auth?.type === 'oauth' && this.accessToken;
|
|
437
|
+
if (hasOauth) {
|
|
438
|
+
try {
|
|
439
|
+
const sent = await this.sendViaGmailApi({
|
|
440
|
+
from: params.from,
|
|
441
|
+
to: params.to,
|
|
442
|
+
subject: params.subject,
|
|
443
|
+
message: params.message,
|
|
444
|
+
spreadsheetUrl: params.spreadsheetUrl,
|
|
445
|
+
});
|
|
446
|
+
if (sent) return true;
|
|
447
|
+
} catch (e) {
|
|
448
|
+
// Fall through to UI-based flow if API send fails (e.g., insufficient permissions)
|
|
449
|
+
console.warn(
|
|
450
|
+
'Gmail API send failed, falling back to UI:',
|
|
451
|
+
(e as any).message || e,
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// Prefer: open Gmail compose in a new tab (more reliable than OS mailto handler)
|
|
457
|
+
if (this.isBrowserEnvironment && !params.disableUiFallback) {
|
|
458
|
+
const to = encodeURIComponent(params.to.join(','));
|
|
459
|
+
const subject = encodeURIComponent(params.subject || 'Automation Output');
|
|
460
|
+
const bodyParts: string[] = [];
|
|
461
|
+
if (params.message) bodyParts.push(params.message);
|
|
462
|
+
if (params.spreadsheetUrl)
|
|
463
|
+
bodyParts.push(`Spreadsheet: ${params.spreadsheetUrl}`);
|
|
464
|
+
const body = encodeURIComponent(bodyParts.join('\n\n'));
|
|
465
|
+
|
|
466
|
+
// Try Gmail compose
|
|
467
|
+
const gmailUrl = `https://mail.google.com/mail/?view=cm&fs=1&to=${to}&su=${subject}&body=${body}`;
|
|
468
|
+
const win = window.open(gmailUrl, '_blank', 'noopener,noreferrer');
|
|
469
|
+
if (win) {
|
|
470
|
+
return true;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// Fallback: OS handler via mailto
|
|
474
|
+
// Note: attachments are not supported via mailto; advise using Gmail scope for API send
|
|
475
|
+
const a = document.createElement('a');
|
|
476
|
+
a.href = `mailto:${to}?subject=${subject}&body=${body}`;
|
|
477
|
+
a.style.display = 'none';
|
|
478
|
+
document.body.appendChild(a);
|
|
479
|
+
a.click();
|
|
480
|
+
document.body.removeChild(a);
|
|
481
|
+
|
|
482
|
+
return true;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Export data to Google Sheets
|
|
490
|
+
* Note: In browser environment, this simulates the export process
|
|
491
|
+
*/
|
|
492
|
+
async exportToSheets(
|
|
493
|
+
data: any[],
|
|
494
|
+
config: GoogleSheetsConfig,
|
|
495
|
+
dataMapping: DataMapping,
|
|
496
|
+
options: SheetsExportOptions = {},
|
|
497
|
+
): Promise<SheetsResponse> {
|
|
498
|
+
if (!this.sheets) {
|
|
499
|
+
throw new Error('Google Sheets API not initialized');
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
try {
|
|
503
|
+
// Prepare data for export
|
|
504
|
+
const mappedData = this.mapDataToColumns(data, dataMapping);
|
|
505
|
+
const includeHeaders = options.includeHeaders !== false;
|
|
506
|
+
|
|
507
|
+
// If using real OAuth flow, call REST API; otherwise, use mock behavior
|
|
508
|
+
const usingRest =
|
|
509
|
+
this.auth?.type === 'oauth' &&
|
|
510
|
+
this.accessToken &&
|
|
511
|
+
this.sheets === 'rest';
|
|
512
|
+
|
|
513
|
+
// Ensure spreadsheet exists when using REST
|
|
514
|
+
let spreadsheetId = config.spreadsheetId;
|
|
515
|
+
if (usingRest && !spreadsheetId) {
|
|
516
|
+
const created = await this.apiFetch('/spreadsheets', {
|
|
517
|
+
method: 'POST',
|
|
518
|
+
body: {
|
|
519
|
+
properties: { title: options.fileName || 'Automation Export' },
|
|
520
|
+
sheets: [
|
|
521
|
+
{
|
|
522
|
+
properties: { title: config.sheetName || 'Sheet1' },
|
|
523
|
+
},
|
|
524
|
+
],
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
spreadsheetId = created.spreadsheetId;
|
|
528
|
+
config.spreadsheetId = spreadsheetId;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Ensure sheet exists
|
|
532
|
+
if (usingRest && spreadsheetId && config.sheetName) {
|
|
533
|
+
const meta = await this.apiFetch(`/spreadsheets/${spreadsheetId}`, {
|
|
534
|
+
params: { fields: 'sheets.properties.title' },
|
|
535
|
+
});
|
|
536
|
+
const hasSheet = (meta.sheets || []).some(
|
|
537
|
+
(s: any) => s.properties && s.properties.title === config.sheetName,
|
|
538
|
+
);
|
|
539
|
+
if (!hasSheet) {
|
|
540
|
+
await this.apiFetch(`/spreadsheets/${spreadsheetId}:batchUpdate`, {
|
|
541
|
+
method: 'POST',
|
|
542
|
+
body: {
|
|
543
|
+
requests: [
|
|
544
|
+
{
|
|
545
|
+
addSheet: { properties: { title: config.sheetName } },
|
|
546
|
+
},
|
|
547
|
+
],
|
|
548
|
+
},
|
|
549
|
+
});
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Clear sheet if requested
|
|
554
|
+
if (dataMapping.clearSheet) {
|
|
555
|
+
await this.clearSheet(config);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// Prepare range for writing (account for header row if included)
|
|
559
|
+
const totalRows = mappedData.length + (includeHeaders ? 1 : 0);
|
|
560
|
+
const range = this.prepareRange(config, dataMapping, totalRows);
|
|
561
|
+
|
|
562
|
+
// Prepare values array
|
|
563
|
+
const values = this.prepareValues(
|
|
564
|
+
mappedData,
|
|
565
|
+
dataMapping,
|
|
566
|
+
includeHeaders,
|
|
567
|
+
);
|
|
568
|
+
|
|
569
|
+
if (usingRest) {
|
|
570
|
+
await this.apiFetch(
|
|
571
|
+
`/spreadsheets/${config.spreadsheetId}/values/${encodeURIComponent(
|
|
572
|
+
range,
|
|
573
|
+
)}`,
|
|
574
|
+
{
|
|
575
|
+
method: 'PUT',
|
|
576
|
+
params: { valueInputOption: 'RAW' },
|
|
577
|
+
body: { values },
|
|
578
|
+
},
|
|
579
|
+
);
|
|
580
|
+
} else {
|
|
581
|
+
// Mock behavior
|
|
582
|
+
await this.sheets.spreadsheets.values.update({
|
|
583
|
+
spreadsheetId: config.spreadsheetId,
|
|
584
|
+
range: range,
|
|
585
|
+
valueInputOption: 'RAW',
|
|
586
|
+
requestBody: { values },
|
|
587
|
+
});
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
// Get spreadsheet URL
|
|
591
|
+
const spreadsheetUrl = `https://docs.google.com/spreadsheets/d/${config.spreadsheetId}`;
|
|
592
|
+
|
|
593
|
+
// Send email if recipients provided
|
|
594
|
+
let emailSent = false;
|
|
595
|
+
if (options.emailRecipients && options.emailRecipients.length > 0) {
|
|
596
|
+
emailSent = await this.sendEmailNotification(
|
|
597
|
+
options.emailRecipients,
|
|
598
|
+
spreadsheetUrl,
|
|
599
|
+
options.fileName || 'Google Sheets Export',
|
|
600
|
+
);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
return {
|
|
604
|
+
spreadsheetId: config.spreadsheetId,
|
|
605
|
+
sheetName: config.sheetName,
|
|
606
|
+
range: range,
|
|
607
|
+
rowsAdded: mappedData.length,
|
|
608
|
+
columnsMapped: dataMapping.headers.length,
|
|
609
|
+
data: mappedData,
|
|
610
|
+
exportTime: new Date().toISOString(),
|
|
611
|
+
success: true,
|
|
612
|
+
spreadsheetUrl,
|
|
613
|
+
emailSent,
|
|
614
|
+
};
|
|
615
|
+
} catch (error) {
|
|
616
|
+
throw new Error(`Failed to export to Google Sheets: ${error.message}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
/**
|
|
621
|
+
* Export data to Excel file and download
|
|
622
|
+
*/
|
|
623
|
+
async exportToExcel(
|
|
624
|
+
data: any[],
|
|
625
|
+
dataMapping: DataMapping,
|
|
626
|
+
options: SheetsExportOptions = {},
|
|
627
|
+
): Promise<SheetsResponse> {
|
|
628
|
+
try {
|
|
629
|
+
// Prepare data for export
|
|
630
|
+
const mappedData = this.mapDataToColumns(data, dataMapping);
|
|
631
|
+
|
|
632
|
+
// Create workbook
|
|
633
|
+
const wb = XLSX.utils.book_new();
|
|
634
|
+
|
|
635
|
+
// Prepare worksheet data
|
|
636
|
+
const wsData = this.prepareExcelData(
|
|
637
|
+
mappedData,
|
|
638
|
+
dataMapping,
|
|
639
|
+
options.includeHeaders !== false,
|
|
640
|
+
);
|
|
641
|
+
|
|
642
|
+
// Create worksheet
|
|
643
|
+
const ws = XLSX.utils.aoa_to_sheet(wsData);
|
|
644
|
+
|
|
645
|
+
// Add worksheet to workbook
|
|
646
|
+
XLSX.utils.book_append_sheet(wb, ws, 'Sheet1');
|
|
647
|
+
|
|
648
|
+
// Generate file name
|
|
649
|
+
const fileName =
|
|
650
|
+
options.fileName ||
|
|
651
|
+
`export_${new Date().toISOString().split('T')[0]}.xlsx`;
|
|
652
|
+
|
|
653
|
+
// Save file
|
|
654
|
+
const excelBuffer = XLSX.write(wb, { bookType: 'xlsx', type: 'array' });
|
|
655
|
+
const blob = new Blob([excelBuffer], {
|
|
656
|
+
type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
|
657
|
+
});
|
|
658
|
+
saveAs(blob, fileName);
|
|
659
|
+
|
|
660
|
+
// Send email if recipients provided
|
|
661
|
+
let emailSent = false;
|
|
662
|
+
if (options.emailRecipients && options.emailRecipients.length > 0) {
|
|
663
|
+
emailSent = await this.sendEmailWithAttachment(
|
|
664
|
+
options.emailRecipients,
|
|
665
|
+
blob,
|
|
666
|
+
fileName,
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return {
|
|
671
|
+
spreadsheetId: '',
|
|
672
|
+
sheetName: 'Sheet1',
|
|
673
|
+
range: '',
|
|
674
|
+
rowsAdded: mappedData.length,
|
|
675
|
+
columnsMapped: dataMapping.headers.length,
|
|
676
|
+
data: mappedData,
|
|
677
|
+
exportTime: new Date().toISOString(),
|
|
678
|
+
success: true,
|
|
679
|
+
downloadUrl: URL.createObjectURL(blob),
|
|
680
|
+
emailSent,
|
|
681
|
+
};
|
|
682
|
+
} catch (error) {
|
|
683
|
+
throw new Error(`Failed to export to Excel: ${error.message}`);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
/**
|
|
688
|
+
* Map data according to column configuration
|
|
689
|
+
*/
|
|
690
|
+
private mapDataToColumns(data: any[], dataMapping: DataMapping): any[] {
|
|
691
|
+
return data.map((item: any) => {
|
|
692
|
+
const row: any = {};
|
|
693
|
+
dataMapping.headers.forEach((header) => {
|
|
694
|
+
const value = this.getNestedValue(item, header.sourceField);
|
|
695
|
+
row[header.column] = this.formatValueForSheets(value, header.dataType);
|
|
696
|
+
});
|
|
697
|
+
return row;
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Get nested value from object using dot notation
|
|
703
|
+
*/
|
|
704
|
+
private getNestedValue(obj: any, path: string): any {
|
|
705
|
+
return path.split('.').reduce((current, key) => current?.[key], obj);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Format value according to data type
|
|
710
|
+
*/
|
|
711
|
+
private formatValueForSheets(value: any, dataType: string): any {
|
|
712
|
+
if (value === null || value === undefined) {
|
|
713
|
+
return '';
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
switch (dataType) {
|
|
717
|
+
case 'number':
|
|
718
|
+
return typeof value === 'number' ? value : parseFloat(value) || 0;
|
|
719
|
+
case 'date':
|
|
720
|
+
return value instanceof Date
|
|
721
|
+
? value.toISOString().split('T')[0]
|
|
722
|
+
: value;
|
|
723
|
+
case 'boolean':
|
|
724
|
+
return Boolean(value);
|
|
725
|
+
case 'string':
|
|
726
|
+
default:
|
|
727
|
+
return String(value);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
/**
|
|
732
|
+
* Prepare range for writing data
|
|
733
|
+
*/
|
|
734
|
+
private prepareRange(
|
|
735
|
+
config: GoogleSheetsConfig,
|
|
736
|
+
dataMapping: DataMapping,
|
|
737
|
+
rowCount: number,
|
|
738
|
+
): string {
|
|
739
|
+
const startColumn = dataMapping.headers[0]?.column || 'A';
|
|
740
|
+
const endColumn =
|
|
741
|
+
dataMapping.headers[dataMapping.headers.length - 1]?.column || 'Z';
|
|
742
|
+
const startRow = dataMapping.clearSheet ? 1 : 1; // Simplified for browser
|
|
743
|
+
const endRow = startRow + rowCount - 1;
|
|
744
|
+
|
|
745
|
+
return `${config.sheetName}!${startColumn}${startRow}:${endColumn}${endRow}`;
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
/**
|
|
749
|
+
* Clear sheet content
|
|
750
|
+
*/
|
|
751
|
+
private async clearSheet(config: GoogleSheetsConfig): Promise<void> {
|
|
752
|
+
if (!this.sheets) return;
|
|
753
|
+
|
|
754
|
+
try {
|
|
755
|
+
const usingRest =
|
|
756
|
+
this.auth?.type === 'oauth' &&
|
|
757
|
+
this.accessToken &&
|
|
758
|
+
this.sheets === 'rest';
|
|
759
|
+
const range = `${config.sheetName}!A:Z`;
|
|
760
|
+
if (usingRest) {
|
|
761
|
+
await this.apiFetch(
|
|
762
|
+
`/spreadsheets/${config.spreadsheetId}/values/${encodeURIComponent(
|
|
763
|
+
range,
|
|
764
|
+
)}:clear`,
|
|
765
|
+
{ method: 'POST' },
|
|
766
|
+
);
|
|
767
|
+
} else {
|
|
768
|
+
await this.sheets.spreadsheets.values.clear({
|
|
769
|
+
spreadsheetId: config.spreadsheetId,
|
|
770
|
+
range,
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
} catch (error) {
|
|
774
|
+
console.warn('Failed to clear sheet:', (error as any).message || error);
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
/**
|
|
779
|
+
* Prepare values array for Google Sheets API
|
|
780
|
+
*/
|
|
781
|
+
private prepareValues(
|
|
782
|
+
mappedData: any[],
|
|
783
|
+
dataMapping: DataMapping,
|
|
784
|
+
includeHeaders: boolean,
|
|
785
|
+
): any[][] {
|
|
786
|
+
const values: any[][] = [];
|
|
787
|
+
|
|
788
|
+
// Add headers if requested
|
|
789
|
+
if (includeHeaders && dataMapping.headers.length > 0) {
|
|
790
|
+
const headers = dataMapping.headers.map((h) => h.column);
|
|
791
|
+
values.push(headers);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// Add data rows
|
|
795
|
+
mappedData.forEach((row) => {
|
|
796
|
+
const rowValues = dataMapping.headers.map(
|
|
797
|
+
(header) => row[header.column] || '',
|
|
798
|
+
);
|
|
799
|
+
values.push(rowValues);
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
return values;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Prepare data for Excel export
|
|
807
|
+
*/
|
|
808
|
+
private prepareExcelData(
|
|
809
|
+
mappedData: any[],
|
|
810
|
+
dataMapping: DataMapping,
|
|
811
|
+
includeHeaders: boolean,
|
|
812
|
+
): any[][] {
|
|
813
|
+
const data: any[][] = [];
|
|
814
|
+
|
|
815
|
+
// Add headers if requested
|
|
816
|
+
if (includeHeaders && dataMapping.headers.length > 0) {
|
|
817
|
+
const headers = dataMapping.headers.map((h) => h.column);
|
|
818
|
+
data.push(headers);
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// Add data rows
|
|
822
|
+
mappedData.forEach((row) => {
|
|
823
|
+
const rowValues = dataMapping.headers.map(
|
|
824
|
+
(header) => row[header.column] || '',
|
|
825
|
+
);
|
|
826
|
+
data.push(rowValues);
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
return data;
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Send email notification with spreadsheet link
|
|
834
|
+
*/
|
|
835
|
+
private async sendEmailNotification(
|
|
836
|
+
recipients: string[],
|
|
837
|
+
spreadsheetUrl: string,
|
|
838
|
+
fileName: string,
|
|
839
|
+
): Promise<boolean> {
|
|
840
|
+
try {
|
|
841
|
+
// In browser environment, we'll simulate email sending
|
|
842
|
+
console.log(
|
|
843
|
+
`📧 Email notification would be sent to: ${recipients.join(', ')}`,
|
|
844
|
+
);
|
|
845
|
+
console.log(`📊 Spreadsheet URL: ${spreadsheetUrl}`);
|
|
846
|
+
console.log(`📁 File: ${fileName}`);
|
|
847
|
+
|
|
848
|
+
// Show a browser notification or alert
|
|
849
|
+
if (this.isBrowserEnvironment && 'Notification' in window) {
|
|
850
|
+
if (Notification.permission === 'granted') {
|
|
851
|
+
new Notification('Google Sheets Export', {
|
|
852
|
+
body: `Data exported successfully! Sent to ${recipients.length} recipient(s).`,
|
|
853
|
+
icon: '/favicon.ico',
|
|
854
|
+
});
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
return true;
|
|
859
|
+
} catch (error) {
|
|
860
|
+
console.error('Failed to send email notification:', error);
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
/**
|
|
866
|
+
* Send email with Excel attachment
|
|
867
|
+
*/
|
|
868
|
+
private async sendEmailWithAttachment(
|
|
869
|
+
recipients: string[],
|
|
870
|
+
fileBlob: Blob,
|
|
871
|
+
fileName: string,
|
|
872
|
+
): Promise<boolean> {
|
|
873
|
+
try {
|
|
874
|
+
// In browser environment, we'll simulate email sending
|
|
875
|
+
console.log(
|
|
876
|
+
`📧 Email with attachment would be sent to: ${recipients.join(', ')}`,
|
|
877
|
+
);
|
|
878
|
+
console.log(`📎 Attachment: ${fileName}`);
|
|
879
|
+
console.log(`📊 File size: ${(fileBlob.size / 1024).toFixed(2)} KB`);
|
|
880
|
+
|
|
881
|
+
// Show a browser notification
|
|
882
|
+
if (this.isBrowserEnvironment && 'Notification' in window) {
|
|
883
|
+
if (Notification.permission === 'granted') {
|
|
884
|
+
new Notification('Excel Export', {
|
|
885
|
+
body: `Excel file "${fileName}" exported successfully! Sent to ${recipients.length} recipient(s).`,
|
|
886
|
+
icon: '/favicon.ico',
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
return true;
|
|
892
|
+
} catch (error) {
|
|
893
|
+
console.error('Failed to send email with attachment:', error);
|
|
894
|
+
return false;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
/**
|
|
899
|
+
* Validate configuration
|
|
900
|
+
*/
|
|
901
|
+
validateConfig(config: GoogleSheetsConfig): {
|
|
902
|
+
valid: boolean;
|
|
903
|
+
errors: string[];
|
|
904
|
+
} {
|
|
905
|
+
const errors: string[] = [];
|
|
906
|
+
|
|
907
|
+
if (!config.spreadsheetId) {
|
|
908
|
+
// Allow creating a new spreadsheet when using OAuth clientId
|
|
909
|
+
if (
|
|
910
|
+
!(config.credentials?.type === 'oauth' && config.credentials.clientId)
|
|
911
|
+
) {
|
|
912
|
+
errors.push(
|
|
913
|
+
'Spreadsheet ID is required (or provide OAuth clientId to create a new one)',
|
|
914
|
+
);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (!config.sheetName) {
|
|
919
|
+
errors.push('Sheet name is required');
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
if (!config.credentials) {
|
|
923
|
+
errors.push('Credentials are required');
|
|
924
|
+
} else {
|
|
925
|
+
if (config.credentials.type === 'service-account') {
|
|
926
|
+
if (
|
|
927
|
+
!config.credentials.serviceAccountKey &&
|
|
928
|
+
!config.credentials.clientEmail
|
|
929
|
+
) {
|
|
930
|
+
errors.push('Service account key or client email is required');
|
|
931
|
+
}
|
|
932
|
+
if (
|
|
933
|
+
!config.credentials.serviceAccountKey &&
|
|
934
|
+
!config.credentials.privateKey
|
|
935
|
+
) {
|
|
936
|
+
errors.push('Service account key or private key is required');
|
|
937
|
+
}
|
|
938
|
+
} else if (config.credentials.type === 'api-key') {
|
|
939
|
+
if (!config.credentials.apiKey) {
|
|
940
|
+
errors.push('API key is required');
|
|
941
|
+
}
|
|
942
|
+
} else if (config.credentials.type === 'oauth') {
|
|
943
|
+
// Accept either a provided token or clientId for GIS
|
|
944
|
+
if (!config.credentials.oauthToken && !config.credentials.clientId) {
|
|
945
|
+
errors.push('OAuth clientId or oauthToken is required');
|
|
946
|
+
}
|
|
947
|
+
if (config.credentials.clientId && !config.credentials.scopes) {
|
|
948
|
+
// Provide default scope if not present
|
|
949
|
+
config.credentials.scopes = [
|
|
950
|
+
'https://www.googleapis.com/auth/spreadsheets',
|
|
951
|
+
];
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
return {
|
|
957
|
+
valid: errors.length === 0,
|
|
958
|
+
errors,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
}
|