@elyx-code/editor-ui 0.0.2
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/README.md +2 -0
- package/package.json +109 -0
- package/src/App.tsx +31 -0
- package/src/Router.tsx +115 -0
- package/src/__mocks__/defaultModuleMock.ts +1 -0
- package/src/__mocks__/fileMock.ts +1 -0
- package/src/__mocks__/styleMock.ts +1 -0
- package/src/assets/Clock-11.1s-18px.svg +16 -0
- package/src/assets/Clock-11.1s-28px.svg +16 -0
- package/src/assets/authentication.svg +1 -0
- package/src/assets/canvas-backdrop-0.png +0 -0
- package/src/assets/canvas-backdrop-1.png +0 -0
- package/src/assets/canvas-backdrop-2.png +0 -0
- package/src/assets/canvas-backdrop-3.png +0 -0
- package/src/assets/canvas-backdrop-4.png +0 -0
- package/src/assets/canvas-backdrop-5.png +0 -0
- package/src/assets/canvas-backdrop.png +0 -0
- package/src/assets/checkmark-animation.gif +0 -0
- package/src/assets/checkmark-animation.mp4 +0 -0
- package/src/assets/code-formatting/format-black.svg +6 -0
- package/src/assets/code-formatting/format-dark-grey.svg +6 -0
- package/src/assets/code-formatting/format-light-grey.svg +6 -0
- package/src/assets/code-formatting/format-white.svg +6 -0
- package/src/assets/code-formatting/inline-black.svg +5 -0
- package/src/assets/code-formatting/inline-dark-grey.svg +5 -0
- package/src/assets/code-formatting/inline-light-grey.svg +5 -0
- package/src/assets/code-formatting/inline-white.svg +5 -0
- package/src/assets/contained-logo-full-word.png +0 -0
- package/src/assets/cron-job-color.png +0 -0
- package/src/assets/cron-job.png +0 -0
- package/src/assets/database-table-color.png +0 -0
- package/src/assets/database-table.png +0 -0
- package/src/assets/datatype-icons/black/any.svg +1 -0
- package/src/assets/datatype-icons/black/binary.svg +1 -0
- package/src/assets/datatype-icons/black/boolean.svg +3 -0
- package/src/assets/datatype-icons/black/date-time.svg +3 -0
- package/src/assets/datatype-icons/black/definition-entity.svg +6 -0
- package/src/assets/datatype-icons/black/key-file.svg +1 -0
- package/src/assets/datatype-icons/black/list.svg +3 -0
- package/src/assets/datatype-icons/black/null.svg +3 -0
- package/src/assets/datatype-icons/black/number.svg +13 -0
- package/src/assets/datatype-icons/black/project.svg +12 -0
- package/src/assets/datatype-icons/black/sql-program.svg +2 -0
- package/src/assets/datatype-icons/black/text.svg +3 -0
- package/src/assets/datatype-icons/black/unknown.svg +3 -0
- package/src/assets/datatype-icons/black/uuid.svg +4 -0
- package/src/assets/datatype-icons/black/void.svg +1 -0
- package/src/assets/datatype-icons/dark-grey/any.svg +1 -0
- package/src/assets/datatype-icons/dark-grey/boolean.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/date-time.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/definition-entity.svg +6 -0
- package/src/assets/datatype-icons/dark-grey/list.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/null.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/number.svg +13 -0
- package/src/assets/datatype-icons/dark-grey/project.svg +12 -0
- package/src/assets/datatype-icons/dark-grey/sql-program.svg +2 -0
- package/src/assets/datatype-icons/dark-grey/text.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/unknown.svg +3 -0
- package/src/assets/datatype-icons/dark-grey/uuid.svg +4 -0
- package/src/assets/datatype-icons/dark-grey/void.svg +1 -0
- package/src/assets/datatype-icons/light-grey/any.svg +1 -0
- package/src/assets/datatype-icons/light-grey/boolean.svg +3 -0
- package/src/assets/datatype-icons/light-grey/date-time.svg +3 -0
- package/src/assets/datatype-icons/light-grey/definition-entity.svg +6 -0
- package/src/assets/datatype-icons/light-grey/list.svg +3 -0
- package/src/assets/datatype-icons/light-grey/null.svg +3 -0
- package/src/assets/datatype-icons/light-grey/number.svg +13 -0
- package/src/assets/datatype-icons/light-grey/project.svg +12 -0
- package/src/assets/datatype-icons/light-grey/sql-program.svg +2 -0
- package/src/assets/datatype-icons/light-grey/text.svg +3 -0
- package/src/assets/datatype-icons/light-grey/unknown.svg +3 -0
- package/src/assets/datatype-icons/light-grey/uuid.svg +4 -0
- package/src/assets/datatype-icons/light-grey/void.svg +1 -0
- package/src/assets/edit.png +0 -0
- package/src/assets/execution.svg +13 -0
- package/src/assets/favicon.svg +14 -0
- package/src/assets/file-search.svg +1 -0
- package/src/assets/http-endpoint.png +0 -0
- package/src/assets/image-input-placeholder.png +0 -0
- package/src/assets/logo-full-word-white.png +0 -0
- package/src/assets/logo-full-word.png +0 -0
- package/src/assets/password.svg +85 -0
- package/src/assets/pencil.png +0 -0
- package/src/assets/publish-project-rich-icon-2.svg +1 -0
- package/src/assets/publish-project-rich-icon.svg +1 -0
- package/src/assets/relational-database.png +0 -0
- package/src/assets/resources.svg +3 -0
- package/src/assets/resume-icon-14px.png +0 -0
- package/src/assets/server.png +0 -0
- package/src/assets/small-status/checkmark.svg +4 -0
- package/src/assets/small-status/error.svg +4 -0
- package/src/assets/small-status/loading.svg +4 -0
- package/src/assets/small-status/skipped.svg +11 -0
- package/src/assets/sql-connection-config.svg +1 -0
- package/src/assets/sql-row-transformer.svg +1 -0
- package/src/assets/ssl-certificate-config.svg +1 -0
- package/src/assets/sync.svg +1 -0
- package/src/assets/testing-logic-icon.svg +1 -0
- package/src/assets/versions.svg +25 -0
- package/src/assets/visual-programming-icon.svg +1 -0
- package/src/assets/warning-sign-24px.png +0 -0
- package/src/auth/index.ts +318 -0
- package/src/components/DialogLoader.tsx +94 -0
- package/src/components/EntityDialogHeader.tsx +110 -0
- package/src/components/EntityDialogSectionHeader.tsx +214 -0
- package/src/components/GalleryAddExternalIntegrationInfoDialog.tsx +87 -0
- package/src/components/GenerateProjectStartingLogicPromptDialog.tsx +281 -0
- package/src/components/LegacyRouteRedirector.tsx +55 -0
- package/src/components/ProPlanChip.tsx +23 -0
- package/src/components/ReportBugDialog.tsx +412 -0
- package/src/components/RequestIntegrationAccessDialog.tsx +261 -0
- package/src/components/UseTemplateProjectDialog.tsx +193 -0
- package/src/components/WorkspaceLayout.tsx +152 -0
- package/src/components/animated-svg/AnimatedCheckmark.tsx +41 -0
- package/src/components/animated-svg/AnimatedCrossmark.tsx +51 -0
- package/src/components/animated-svg/AnimatedEmailSending.tsx +38 -0
- package/src/components/animated-svg/AnimatedLoading.tsx +72 -0
- package/src/components/animated-svg/animated-svg.css +239 -0
- package/src/components/canvas/Canvas.tsx +16 -0
- package/src/components/canvas/CreateEntityMenu.tsx +2020 -0
- package/src/components/canvas/canvas.css +10 -0
- package/src/components/canvas/create-entity-menu.css +579 -0
- package/src/components/canvas-search/CanvasSearch.tsx +501 -0
- package/src/components/canvas-search/canvas-search.css +126 -0
- package/src/components/canvas-settings-menu/CanvasSettingsMenuButton.tsx +515 -0
- package/src/components/canvas-settings-menu/canvas-settings-menu.css +96 -0
- package/src/components/circular-image-upload/CircularImageUpload.tsx +113 -0
- package/src/components/circular-image-upload/circular-image-upload.css +69 -0
- package/src/components/costs/CostsDialog.tsx +459 -0
- package/src/components/data-type/DataTypeBuilder.tsx +3127 -0
- package/src/components/data-type/data-type-builder.css +45 -0
- package/src/components/dialogs/BetaAcknowledgeDialog.tsx +43 -0
- package/src/components/dialogs/ComplexDataDialog.tsx +458 -0
- package/src/components/dialogs/CronBuilderDialog.tsx +2145 -0
- package/src/components/dialogs/ExternalIntegrationConnections.tsx +565 -0
- package/src/components/dialogs/JsonEditorDialog.tsx +1392 -0
- package/src/components/dialogs/StringEditorDialog.tsx +268 -0
- package/src/components/dialogs/argument-declaration/ArgumentDeclaration.tsx +1167 -0
- package/src/components/dialogs/argument-declaration/ArgumentDeclarationDialogContent.tsx +128 -0
- package/src/components/dialogs/beta-dialog.css +165 -0
- package/src/components/dialogs/condition/Condition.tsx +431 -0
- package/src/components/dialogs/condition/ConditionDialogContent.tsx +126 -0
- package/src/components/dialogs/definition-entity/DefinitionEntityDialogContent.tsx +973 -0
- package/src/components/dialogs/function-call/FunctionCall.tsx +442 -0
- package/src/components/dialogs/function-call/FunctionCallDialogContent.tsx +126 -0
- package/src/components/dialogs/function-declaration/FunctionDeclaration.tsx +926 -0
- package/src/components/dialogs/function-declaration/FunctionDeclarationDialogContent.tsx +124 -0
- package/src/components/dialogs/generating-project-starting-logic-overlay/GeneratingProjectStartingLogicOverlay.tsx +176 -0
- package/src/components/dialogs/generating-project-starting-logic-overlay/generating-project-starting-logic-overlay.css +13 -0
- package/src/components/dialogs/global-event/GlobalEvent.tsx +475 -0
- package/src/components/dialogs/global-event/GlobalEventDialogContent.tsx +126 -0
- package/src/components/dialogs/help/HelpDialog.tsx +217 -0
- package/src/components/dialogs/help/HelpDilalogHomeContent.tsx +178 -0
- package/src/components/dialogs/help/help-dialog.css +116 -0
- package/src/components/dialogs/help/help-icon/HelpIconButton.tsx +41 -0
- package/src/components/dialogs/help/help-icon/help-icon.css +9 -0
- package/src/components/dialogs/input-map/InputMap.tsx +635 -0
- package/src/components/dialogs/input-map/InputMapDialogContent.tsx +126 -0
- package/src/components/dialogs/json-editor-dialog.css +4 -0
- package/src/components/dialogs/loop/Loop.tsx +650 -0
- package/src/components/dialogs/loop/LoopDialogContent.tsx +122 -0
- package/src/components/dialogs/operation/Operation.tsx +440 -0
- package/src/components/dialogs/operation/OperationDialogContent.tsx +126 -0
- package/src/components/dialogs/output-map/OutputMap.tsx +536 -0
- package/src/components/dialogs/output-map/OutputMapDialogContent.tsx +126 -0
- package/src/components/dialogs/property/Property.tsx +1490 -0
- package/src/components/dialogs/property/PropertyDialogContent.tsx +106 -0
- package/src/components/dialogs/search-statement/ColumnSelector.tsx +334 -0
- package/src/components/dialogs/search-statement/ConditionBuilder.tsx +750 -0
- package/src/components/dialogs/search-statement/DataAggregationSection.tsx +621 -0
- package/src/components/dialogs/search-statement/DataSourceSelection.tsx +734 -0
- package/src/components/dialogs/search-statement/EntityMetadataSection.tsx +135 -0
- package/src/components/dialogs/search-statement/FilterConditionsSection.tsx +151 -0
- package/src/components/dialogs/search-statement/InlineInputMap.tsx +153 -0
- package/src/components/dialogs/search-statement/LiteralValue.tsx +616 -0
- package/src/components/dialogs/search-statement/MainSourceAndInputsSection.tsx +271 -0
- package/src/components/dialogs/search-statement/NestedSearchStatementBuilder.tsx +170 -0
- package/src/components/dialogs/search-statement/OutputFormatSection.tsx +1779 -0
- package/src/components/dialogs/search-statement/ResultsSection.tsx +344 -0
- package/src/components/dialogs/search-statement/SearchStatementBuilder.tsx +251 -0
- package/src/components/dialogs/search-statement/SearchStatementDialogContent.tsx +398 -0
- package/src/components/dialogs/search-statement/ValueSelector.tsx +766 -0
- package/src/components/dialogs/search-statement/search-statement-context.tsx +1630 -0
- package/src/components/dialogs/search-statement/search-statement-dialog.css +56 -0
- package/src/components/dialogs/search-statement/test.sql +111 -0
- package/src/components/dialogs/value-descriptor/ValueDescriptor.tsx +824 -0
- package/src/components/dialogs/value-descriptor/ValueDescriptorDialogContent.tsx +124 -0
- package/src/components/dialogs/variable-declaration/VariableDeclaration.tsx +836 -0
- package/src/components/dialogs/variable-declaration/VariableDeclarationDialogContent.tsx +106 -0
- package/src/components/dialogs/variable-instance/VariableInstance.tsx +443 -0
- package/src/components/dialogs/variable-instance/VariableInstanceDialogContent.tsx +124 -0
- package/src/components/draggable-entity-card/ArgumentDeclaration.tsx +736 -0
- package/src/components/draggable-entity-card/CollapseEntityButton.tsx +170 -0
- package/src/components/draggable-entity-card/ConditionCard.tsx +1062 -0
- package/src/components/draggable-entity-card/ConnectionDeleteButton.tsx +309 -0
- package/src/components/draggable-entity-card/DataTypeIcon.tsx +624 -0
- package/src/components/draggable-entity-card/DraggableEntityCard.tsx +617 -0
- package/src/components/draggable-entity-card/ErrorMapProperty.tsx +464 -0
- package/src/components/draggable-entity-card/EventCard.tsx +700 -0
- package/src/components/draggable-entity-card/ExecutionInProgressValue.tsx +327 -0
- package/src/components/draggable-entity-card/FunctionDeclarationCard.tsx +819 -0
- package/src/components/draggable-entity-card/InputMapProperty.tsx +1067 -0
- package/src/components/draggable-entity-card/InternalCall.tsx +978 -0
- package/src/components/draggable-entity-card/InternalCallExecutionNode.tsx +643 -0
- package/src/components/draggable-entity-card/LogicScopeCallerNode.tsx +262 -0
- package/src/components/draggable-entity-card/LoopCard.tsx +791 -0
- package/src/components/draggable-entity-card/MainValueInput.tsx +523 -0
- package/src/components/draggable-entity-card/MainValueOutput.tsx +458 -0
- package/src/components/draggable-entity-card/MethodDeclaration.tsx +1088 -0
- package/src/components/draggable-entity-card/NestedCondition.tsx +1025 -0
- package/src/components/draggable-entity-card/OutputMapProperty.tsx +843 -0
- package/src/components/draggable-entity-card/PassthroughEntityCard.tsx +1247 -0
- package/src/components/draggable-entity-card/ReturnedError.tsx +549 -0
- package/src/components/draggable-entity-card/SmallSuccessFailureNodes.tsx +523 -0
- package/src/components/draggable-entity-card/SuccessFailureNodes.tsx +509 -0
- package/src/components/draggable-entity-card/TestEntityButton.tsx +946 -0
- package/src/components/draggable-entity-card/TestMenu.tsx +523 -0
- package/src/components/draggable-entity-card/TestMenuValidationDropdown.tsx +84 -0
- package/src/components/draggable-entity-card/UnreachableMarker.tsx +114 -0
- package/src/components/draggable-entity-card/VariableCard.tsx +1577 -0
- package/src/components/draggable-entity-card/VariableScopeMarker.tsx +117 -0
- package/src/components/draggable-entity-card/collapse-entity-button.css +44 -0
- package/src/components/draggable-entity-card/definition-entity/DefinitionEntityCard.tsx +1181 -0
- package/src/components/draggable-entity-card/definition-entity/DefinitionEntityIcon.tsx +36 -0
- package/src/components/draggable-entity-card/definition-entity/DefinitionEntityProperty.tsx +478 -0
- package/src/components/draggable-entity-card/definition-entity/DynamicFooterActions.tsx +112 -0
- package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/ExportCredentialsFooterAction.tsx +461 -0
- package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/RestablishConnectionFooterAction.tsx +199 -0
- package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/restablish-connection-footer-action.css +85 -0
- package/src/components/draggable-entity-card/definition-entity/actions/google-drive/GoogleDriveFilePickerAPIFooterAction.tsx +277 -0
- package/src/components/draggable-entity-card/definition-entity/actions/google-drive/google-drive-file-picker-api-footer-action.css +107 -0
- package/src/components/draggable-entity-card/definition-entity/actions/persisted-entity/DatabaseFooterAction.tsx +452 -0
- package/src/components/draggable-entity-card/definition-entity/actions/persisted-entity/database-footer-action.css +86 -0
- package/src/components/draggable-entity-card/definition-entity/definition-entity-card.css +17 -0
- package/src/components/draggable-entity-card/draggable-entity-card.css +1140 -0
- package/src/components/draggable-entity-card/entity-locked-icon/EntityLockedIcon.tsx +133 -0
- package/src/components/draggable-entity-card/entity-locked-icon/entity-locked.css +8 -0
- package/src/components/draggable-entity-card/expand-properties-icon-button/ExpandPropertiesIconButton.tsx +84 -0
- package/src/components/draggable-entity-card/expand-properties-icon-button/expand-properties-icon-button.css +21 -0
- package/src/components/draggable-entity-card/implement-entity-icon/ImplementEntityIcon.tsx +74 -0
- package/src/components/draggable-entity-card/implement-entity-icon/implement-entity-icon.css +13 -0
- package/src/components/draggable-entity-card/logic-error/LogicErrorIconMenu.tsx +424 -0
- package/src/components/draggable-entity-card/logic-error/logic-error.css +23 -0
- package/src/components/draggable-entity-card/new-card-input-button/NewCardInputButton.tsx +193 -0
- package/src/components/draggable-entity-card/new-card-input-button/NewDynamicInputButton.tsx +214 -0
- package/src/components/draggable-entity-card/new-card-input-button/new-card-input-button.css +71 -0
- package/src/components/draggable-entity-card/new-card-output-button/NewCardOutputButton.tsx +192 -0
- package/src/components/draggable-entity-card/new-card-output-button/new-card-output-button.css +71 -0
- package/src/components/draggable-entity-card/termination-statement/TerminationStatementCard.tsx +1543 -0
- package/src/components/draggable-entity-card/termination-statement/termination-statement-card.css +17 -0
- package/src/components/draggable-entity-card/test-entity-button.css +55 -0
- package/src/components/draggable-entity-card/test-menu.css +181 -0
- package/src/components/draggable-entity-card/unreachable-marker.css +43 -0
- package/src/components/draggable-entity-card/variable-scope-marker.css +22 -0
- package/src/components/dynamic-value/DynamicValue.tsx +2395 -0
- package/src/components/dynamic-value/DynamicValueEntry.tsx +1957 -0
- package/src/components/dynamic-value/dynamic-value.css +230 -0
- package/src/components/editor/ElyxMonacoEditor.tsx +38 -0
- package/src/components/entity-error/EntityErrorListItem.tsx +47 -0
- package/src/components/entity-error/entity-error.css +198 -0
- package/src/components/entity-icon/EntityIcon.tsx +292 -0
- package/src/components/entity-icon/entity-icon.css +39 -0
- package/src/components/gallery-card/CreateNewProject.tsx +222 -0
- package/src/components/gallery-card/GalleryCard.tsx +171 -0
- package/src/components/gallery-card/MarketplaceCard.tsx +87 -0
- package/src/components/gallery-card/ProjectDuplicationCard.tsx +575 -0
- package/src/components/gallery-card/gallery-card.css +25 -0
- package/src/components/notifications/NotificationsIconButton.tsx +124 -0
- package/src/components/notifications/NotificationsPanel.tsx +385 -0
- package/src/components/notifications/notifications.css +189 -0
- package/src/components/online-users/LocalOnlineUsers.tsx +175 -0
- package/src/components/online-users/PageOnlineUsers.tsx +297 -0
- package/src/components/online-users/online-users.css +72 -0
- package/src/components/page-backdrop/PageBackdrop.tsx +8 -0
- package/src/components/page-backdrop/page-backdrop.css +7 -0
- package/src/components/project-configuration/DeleteProjectConfirmationDialog.tsx +134 -0
- package/src/components/project-configuration/ProjectConfigurationDialog.tsx +972 -0
- package/src/components/project-configuration/ProjectDataForm.tsx +121 -0
- package/src/components/project-configuration/UnpublishProjectConfirmationDialog.tsx +162 -0
- package/src/components/project-configuration/project-configuration-content.css +209 -0
- package/src/components/project-name/ProjectName.tsx +2025 -0
- package/src/components/project-name/project-name.css +599 -0
- package/src/components/publishing/Publication.tsx +133 -0
- package/src/components/publishing/history/PublicationHistoryContent.tsx +414 -0
- package/src/components/publishing/history/PublicationHistoryDialog.tsx +234 -0
- package/src/components/publishing/preview/PublicationPreviewDialog.tsx +1158 -0
- package/src/components/publishing/preview/PublishingPriceForecast.tsx +160 -0
- package/src/components/publishing/preview/PublishingResourcesDetails.tsx +91 -0
- package/src/components/publishing/publication-sequence/PublishingSequenceContent.tsx +375 -0
- package/src/components/publishing/publication-sequence/PublishingSequenceDialog.tsx +344 -0
- package/src/components/publishing/publishing-dialog.css +142 -0
- package/src/components/publishing/utils.ts +227 -0
- package/src/components/resources/ResourcesDialog.tsx +591 -0
- package/src/components/resources/UpgradeBanner.tsx +102 -0
- package/src/components/resources/codebase/CodebaseDetails.tsx +156 -0
- package/src/components/resources/cron-job/CronJobsList.tsx +532 -0
- package/src/components/resources/functions/FunctionsList.tsx +454 -0
- package/src/components/resources/http-api/HttpAPI.tsx +566 -0
- package/src/components/resources/http-api/HttpAPIClientModule.tsx +37 -0
- package/src/components/resources/logs/LogsViewer.tsx +768 -0
- package/src/components/resources/query.ts +74 -0
- package/src/components/resources/relational-database/DatabaseTable.tsx +905 -0
- package/src/components/resources/relational-database/RelationalDatabase.tsx +83 -0
- package/src/components/resources/relational-database/RelationalDatabaseSecrets.tsx +361 -0
- package/src/components/resources/resources-dialog.css +74 -0
- package/src/components/test-relational-database/DatabaseTable.tsx +913 -0
- package/src/components/test-relational-database/TestDatabaseDialogContent.tsx +670 -0
- package/src/components/test-relational-database/query.ts +74 -0
- package/src/components/toolbar/ToolBar.tsx +236 -0
- package/src/components/toolbar/toolbar.css +78 -0
- package/src/components/transaction-history/TransactionHistoryDialog.tsx +268 -0
- package/src/components/user/CurrentUserAvatar.tsx +65 -0
- package/src/components/user/UserChip.tsx +62 -0
- package/src/components/user/user.css +39 -0
- package/src/components/user-profile/ChangePasswordForm.tsx +67 -0
- package/src/components/user-profile/OwnUserProfileContent.tsx +665 -0
- package/src/components/user-profile/PublicUserProfileContent.tsx +99 -0
- package/src/components/user-profile/UserDataForm.tsx +75 -0
- package/src/components/user-profile/UserProfileDialog.tsx +110 -0
- package/src/components/user-profile/user-profile-content.css +25 -0
- package/src/config.ts +130 -0
- package/src/globals.d.ts +13 -0
- package/src/index.html +27 -0
- package/src/index.tsx +23 -0
- package/src/lib/badge/Badge.tsx +35 -0
- package/src/lib/badge/badge.css +32 -0
- package/src/lib/button/Button.tsx +129 -0
- package/src/lib/button/button.css +145 -0
- package/src/lib/canvas/canvas-undo-redo.ts +263 -0
- package/src/lib/canvas/defs.ts +170 -0
- package/src/lib/canvas/index.test.ts +189 -0
- package/src/lib/canvas/index.ts +6999 -0
- package/src/lib/canvas/utils.ts +59 -0
- package/src/lib/card/Card.tsx +62 -0
- package/src/lib/card/LoadingCard.tsx +82 -0
- package/src/lib/card/card.css +259 -0
- package/src/lib/chip/Chip.tsx +79 -0
- package/src/lib/chip/chip.css +0 -0
- package/src/lib/dialog/Dialog.tsx +122 -0
- package/src/lib/dialog/SmallDialog.tsx +61 -0
- package/src/lib/dialog/dialog.css +40 -0
- package/src/lib/display-data-structure/index.tsx +21 -0
- package/src/lib/dropdown/CanvasDropdownMenuCard.tsx +68 -0
- package/src/lib/dropdown/CanvasDropdownMenuCardOption.tsx +136 -0
- package/src/lib/dropdown/DropdownButton.tsx +104 -0
- package/src/lib/dropdown/DropdownMenuCard.tsx +324 -0
- package/src/lib/dropdown/DropdownMenuPopup.tsx +27 -0
- package/src/lib/dropdown/dropdown-button.css +76 -0
- package/src/lib/dropdown/dropdown-menu.css +151 -0
- package/src/lib/json-editor/RawJsonEditor.tsx +137 -0
- package/src/lib/json-editor/json-editor.css +35 -0
- package/src/lib/loader/Loader.tsx +120 -0
- package/src/lib/loader/loader.css +38 -0
- package/src/lib/pagination/Pagination.tsx +64 -0
- package/src/lib/popup/CanvasPopupBaseComponent.tsx +103 -0
- package/src/lib/popup/Popup.tsx +243 -0
- package/src/lib/popup/popup.css +16 -0
- package/src/lib/table/RowForm.tsx +301 -0
- package/src/lib/table/Table.tsx +1069 -0
- package/src/lib/table/table.css +249 -0
- package/src/lib/table/types.ts +108 -0
- package/src/lib/text-area/TextArea.tsx +183 -0
- package/src/lib/text-area/text-area.css +156 -0
- package/src/lib/text-field/TextField.tsx +218 -0
- package/src/lib/text-field/index.ts +8 -0
- package/src/lib/text-field/text-field.css +201 -0
- package/src/lib/tooltip/Tooltip.tsx +24 -0
- package/src/lib/tooltip/tooltip.css +17 -0
- package/src/localization/index.ts +47 -0
- package/src/main.css +343 -0
- package/src/pages/Auth.tsx +848 -0
- package/src/pages/Editor.tsx +883 -0
- package/src/pages/ErrorPage.tsx +179 -0
- package/src/pages/Gallery.tsx +1693 -0
- package/src/pages/NewPaymentMethodCallback.tsx +53 -0
- package/src/pages/NotFoundPage.tsx +126 -0
- package/src/pages/PricingPlans.tsx +155 -0
- package/src/pages/auth.css +304 -0
- package/src/pages/gallery.css +421 -0
- package/src/payments/index.ts +187 -0
- package/src/popup-notification/index.ts +90 -0
- package/src/services/database/index.ts +1 -0
- package/src/services/database/utils.ts +1301 -0
- package/src/services/editor/CanvasElement.tsx +2934 -0
- package/src/services/editor/CanvasElementConnectionDeleteButton.ts +204 -0
- package/src/services/editor/CanvasPopup.tsx +749 -0
- package/src/services/editor/EditorService.ts +8157 -0
- package/src/services/editor/area.ts +1312 -0
- package/src/services/editor/connections.ts +1019 -0
- package/src/services/editor/create/condition.ts +25 -0
- package/src/services/editor/create/definition-entity.ts +29 -0
- package/src/services/editor/create/function-call.ts +25 -0
- package/src/services/editor/create/global-event.ts +33 -0
- package/src/services/editor/create/loop.ts +25 -0
- package/src/services/editor/create/operation.ts +30 -0
- package/src/services/editor/create/utils.ts +140 -0
- package/src/services/editor/create/variable-declaration.ts +135 -0
- package/src/services/editor/create/variable-instance.ts +100 -0
- package/src/services/editor/editor-ui-extensions-context.ts +43 -0
- package/src/services/editor/entities-metadata.json +9310 -0
- package/src/services/editor/icons.ts +1093 -0
- package/src/services/editor/index.ts +1 -0
- package/src/services/editor/layout.ts +102 -0
- package/src/services/editor/modules/built-in-function-implementations/base.ts +14 -0
- package/src/services/editor/modules/built-in-function-implementations/create-persisted-entity/index.ts +56 -0
- package/src/services/editor/modules/built-in-function-implementations/delete-persisted-entity/index.ts +55 -0
- package/src/services/editor/modules/built-in-function-implementations/index.ts +4 -0
- package/src/services/editor/modules/built-in-function-implementations/update-persisted-entity/index.ts +56 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/get-files.ts +183 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/list-drives.ts +124 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/list-root-folders.ts +125 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/smart-fetch-document.ts +702 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/upload-document.ts +535 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-gemini/generate-content.ts +193 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-mail/get-emails.ts +586 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/google-mail/send-email.ts +386 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/index.ts +12 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/slack/channels.ts +240 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/slack/messages.ts +210 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/slack/replies.ts +200 -0
- package/src/services/editor/modules/operations-implementations/external-integrations/slack/send-message.ts +177 -0
- package/src/services/editor/modules/operations-implementations/index.ts +1 -0
- package/src/services/editor/modules/search-node-implementation/index.ts +42 -0
- package/src/services/editor/modules/sql-migrations-generation.tsx +1054 -0
- package/src/services/editor/publication/publication.ts +578 -0
- package/src/services/editor/ui.ts +1348 -0
- package/src/services/editor/utils.ts +5868 -0
- package/src/services/editor/value-store.ts +619 -0
- package/src/services/execution/built-in-function-implementations.ts +422 -0
- package/src/services/execution/index.ts +4747 -0
- package/src/services/execution/logic.ts +121 -0
- package/src/services/execution/test-instance.tsx +2296 -0
- package/src/services/execution/utils.ts +33 -0
- package/src/services/execution/value-resolution.test.ts +424 -0
- package/src/services/execution/value-resolution.ts +4087 -0
- package/src/services/integrations/ExternalIntegrationsService.ts +439 -0
- package/src/services/integrations/api.ts +175 -0
- package/src/services/local-relational-database/idb_helper.ts +66 -0
- package/src/services/local-relational-database/index.ts +3308 -0
- package/src/services/local-relational-database/utils.ts +403 -0
- package/src/services/notifications/index.ts +525 -0
- package/src/services/user/index.ts +144 -0
- package/src/setupTests.ts +1 -0
- package/src/socket/socket.ts +248 -0
- package/src/socket/utils.ts +10 -0
- package/src/store/workspace.ts +12 -0
- package/src/theme.ts +19 -0
- package/src/utils/DOM.ts +39 -0
- package/src/utils/date.ts +169 -0
- package/src/utils/index.ts +158 -0
- package/src/utils/react.tsx +679 -0
- package/src/utils/testing.ts +103 -0
|
@@ -0,0 +1,2296 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import {
|
|
3
|
+
FunctionCallState,
|
|
4
|
+
VariableDeclarationState,
|
|
5
|
+
DraggableExecutableEntityState,
|
|
6
|
+
DraggableCallerEntityState,
|
|
7
|
+
EntryPointEntityState,
|
|
8
|
+
DraggableCallableEntityState,
|
|
9
|
+
PassThroughCallableEntityState,
|
|
10
|
+
getParentCanvasEntity,
|
|
11
|
+
PropertyState,
|
|
12
|
+
EntityWithValueState,
|
|
13
|
+
OperationState,
|
|
14
|
+
ExecutionTerminationType,
|
|
15
|
+
TERMINATION_TYPES,
|
|
16
|
+
TerminationEntityState,
|
|
17
|
+
VARIABLE_TYPES,
|
|
18
|
+
CALLER_TYPES,
|
|
19
|
+
UserManagedEntityState,
|
|
20
|
+
FunctionDeclarationState,
|
|
21
|
+
EntityError,
|
|
22
|
+
ExecutableEntityState,
|
|
23
|
+
getRelatedCallableElementsOnTheRight,
|
|
24
|
+
flattenRelatedCallableElementsOnTheRight,
|
|
25
|
+
filterOutDuplicateEntities,
|
|
26
|
+
getParentEntryPoint,
|
|
27
|
+
CALLABLE_TYPES,
|
|
28
|
+
ENTRY_POINT_TYPES,
|
|
29
|
+
EXECUTABLE_TYPES,
|
|
30
|
+
EntityType,
|
|
31
|
+
PASS_THROUGH_CALLABLE_TYPES,
|
|
32
|
+
VariableState,
|
|
33
|
+
ITest,
|
|
34
|
+
flattenRelatedCallableElementsOnTheRightAsync,
|
|
35
|
+
filterOutDuplicateEntitiesAsync,
|
|
36
|
+
CallableEntityState,
|
|
37
|
+
getRelatedCallableElementsOnTheRightAsync,
|
|
38
|
+
CallerEntityState,
|
|
39
|
+
resolveEntityName
|
|
40
|
+
} from '@elyx-code/project-logic-tree';
|
|
41
|
+
import {
|
|
42
|
+
Execution,
|
|
43
|
+
ExecutionState,
|
|
44
|
+
IExecutionCallbackContext,
|
|
45
|
+
IExecutionClient,
|
|
46
|
+
IExecutionResult,
|
|
47
|
+
TestableEntityState
|
|
48
|
+
} from '.';
|
|
49
|
+
import { DraggableObject } from '../../lib/canvas';
|
|
50
|
+
import {
|
|
51
|
+
getCanvasEntityEntryCallerNodeDOMId,
|
|
52
|
+
getCanvasEntityExecutionNodeId,
|
|
53
|
+
getCanvasEntityErrorCallerNodeDOMId,
|
|
54
|
+
getCanvasEntitySuccessCallerNodeDOMId,
|
|
55
|
+
getEntityIdFromCanvasId,
|
|
56
|
+
highlightElement,
|
|
57
|
+
getCanvasEntityDraggableContainerDOMId,
|
|
58
|
+
getMethodDeclarationDOMId
|
|
59
|
+
} from '../editor/ui';
|
|
60
|
+
import { EditorService } from '../editor';
|
|
61
|
+
import { validateLogic, validateLogicAsync } from './logic';
|
|
62
|
+
import { IDynamicValue } from '../editor/value-store';
|
|
63
|
+
import TestMenu from '../../components/draggable-entity-card/TestMenu';
|
|
64
|
+
import CanvasPopup, { ICanvasPopupProps } from '../editor/CanvasPopup';
|
|
65
|
+
import CanvasPopupBaseComponent from '../../lib/popup/CanvasPopupBaseComponent';
|
|
66
|
+
import { CanvasEntityElement } from '../editor/CanvasElement';
|
|
67
|
+
import { createRoot, ReactRoot } from '../../utils/react';
|
|
68
|
+
import { hasAnimation } from '../../utils/DOM';
|
|
69
|
+
import WarningIcon from '../../assets/warning-sign-24px.png';
|
|
70
|
+
import { Logger, YieldOptions, YieldTracker } from '@elyx-code/common-ts-utils';
|
|
71
|
+
|
|
72
|
+
// Promise delay
|
|
73
|
+
function delay(ms: number) {
|
|
74
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Wait half the time given
|
|
78
|
+
function delayHalf(ms: number) {
|
|
79
|
+
return delay(ms / 2);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let menuNode: ReactRoot;
|
|
83
|
+
let requestedUnmountMenu: boolean = false;
|
|
84
|
+
let activeUINodes: CanvasPopup[] = [];
|
|
85
|
+
|
|
86
|
+
export interface IEntityComponentProps {
|
|
87
|
+
canvasElement: CanvasEntityElement;
|
|
88
|
+
entity: TestableEntityState;
|
|
89
|
+
project: EditorService;
|
|
90
|
+
disabled?: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// A test assembles a logic and executes it.
|
|
94
|
+
// This class is the interface between the execution and the UX
|
|
95
|
+
export class Test extends Execution implements ITest {
|
|
96
|
+
editor: EditorService;
|
|
97
|
+
// The entities that aren't part of the test,
|
|
98
|
+
// but have already been calculated to be related to the test
|
|
99
|
+
// and can be added
|
|
100
|
+
entitiesRelatedToExecution: TestableEntityState[] = [];
|
|
101
|
+
|
|
102
|
+
// The delay between execution steps
|
|
103
|
+
static DEFAULT_DELAY: number = 600;
|
|
104
|
+
DELAY: number = Test.DEFAULT_DELAY;
|
|
105
|
+
|
|
106
|
+
validation: EntityError[] = [];
|
|
107
|
+
|
|
108
|
+
persistExecutionValues: boolean = false;
|
|
109
|
+
|
|
110
|
+
constructor(project: EditorService, entryPoint: TestableEntityState) {
|
|
111
|
+
super(project.logic, [entryPoint], entryPoint, null, {
|
|
112
|
+
requestExtension: project.requestExtension,
|
|
113
|
+
getValueTypePreference: project.getValueTypePreference,
|
|
114
|
+
setValueTypePreference: project.setValueTypePreference
|
|
115
|
+
});
|
|
116
|
+
this.editor = project;
|
|
117
|
+
|
|
118
|
+
this.inheritValuesFromContext(this.editor);
|
|
119
|
+
|
|
120
|
+
this.onEntityExecuted = this.onEntityExecuted.bind(this);
|
|
121
|
+
this.onBeforeEntityExecution = this.onBeforeEntityExecution.bind(this);
|
|
122
|
+
this.onValueWritten = this.onValueWritten.bind(this);
|
|
123
|
+
this.onValueRemoved = this.onValueRemoved.bind(this);
|
|
124
|
+
|
|
125
|
+
// This is a callback that gets called when an entity is added to the test
|
|
126
|
+
this.onAddToSkippedList((entity) => {
|
|
127
|
+
this.notifyEntity(entity);
|
|
128
|
+
|
|
129
|
+
// Cancel the execution connection animation
|
|
130
|
+
if (CALLABLE_TYPES.includes(entity.type)) {
|
|
131
|
+
// Start animation of connection to the entities on the right
|
|
132
|
+
const canvasElement = this.editor.getElement(
|
|
133
|
+
getParentCanvasEntity(entity)?.id
|
|
134
|
+
);
|
|
135
|
+
const connectionsTargetId =
|
|
136
|
+
getCanvasEntityExecutionNodeId(entity);
|
|
137
|
+
|
|
138
|
+
canvasElement.draggableObject.connections.forEach(
|
|
139
|
+
(connection) => {
|
|
140
|
+
if (
|
|
141
|
+
connection.targetParentObject.id ===
|
|
142
|
+
connectionsTargetId
|
|
143
|
+
) {
|
|
144
|
+
connection.stopAnimation();
|
|
145
|
+
connection.element.classList.add('disabled');
|
|
146
|
+
|
|
147
|
+
if (connection.complementaryLineElement) {
|
|
148
|
+
connection.complementaryLineElement.classList.add(
|
|
149
|
+
'disabled'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
this.onRemoveFromSkippedList(async (entity, options) => {
|
|
158
|
+
if (!options?.skipNotify) {
|
|
159
|
+
await this.notifyEntityAsync(entity);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
this.onAbortExecution(async (entity, options) => {
|
|
163
|
+
Logger.log('Aborting execution for entity: ', entity.id);
|
|
164
|
+
if (!options?.skipNotify) {
|
|
165
|
+
await this.notifyEntityAsync(entity);
|
|
166
|
+
}
|
|
167
|
+
this.cancelSuccessAnimation(
|
|
168
|
+
entity as PassThroughCallableEntityState
|
|
169
|
+
);
|
|
170
|
+
this.cancelErrorAnimation(entity as PassThroughCallableEntityState);
|
|
171
|
+
});
|
|
172
|
+
this.onAddEntity(async (entity, options) => {
|
|
173
|
+
const tracker = YieldTracker.from(options || {});
|
|
174
|
+
|
|
175
|
+
if (!options?.skipValidate) {
|
|
176
|
+
await this.validateLogicAsync({ tracker });
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!options?.skipNotify) {
|
|
180
|
+
await this.notifyEntityAsync(entity);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!options?.skipRender) {
|
|
184
|
+
await this.render();
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
this.onRemoveEntity(async (entity, options) => {
|
|
188
|
+
const tracker = YieldTracker.from(options || {});
|
|
189
|
+
|
|
190
|
+
if (!options?.skipValidate) {
|
|
191
|
+
await this.validateLogicAsync({ tracker });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (!options?.skipNotify) {
|
|
195
|
+
await this.notifyEntityAsync(entity);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (!options?.skipRender) {
|
|
199
|
+
await this.render();
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
this.lastExecutionResults.onWrite((owner, value) => {
|
|
204
|
+
this.editor.emit('value-written', {
|
|
205
|
+
owner,
|
|
206
|
+
value
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
this.lastExecutionResults.onRemove((owner) => {
|
|
210
|
+
this.editor.emit('value-removed', {
|
|
211
|
+
owner
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static async handleEntityTestButtonClick(
|
|
217
|
+
e: MouseEvent,
|
|
218
|
+
props: IEntityComponentProps
|
|
219
|
+
) {
|
|
220
|
+
if (props.disabled) {
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (
|
|
225
|
+
!props.project.test &&
|
|
226
|
+
props.entity.type !== EntityType.ReturnStatement &&
|
|
227
|
+
props.entity.type !== EntityType.ContinueStatement &&
|
|
228
|
+
props.entity.type !== EntityType.BreakStatement
|
|
229
|
+
) {
|
|
230
|
+
const autoExecute = !!(e.ctrlKey || e.metaKey);
|
|
231
|
+
|
|
232
|
+
props.project.initTest(props.entity, { autoExecute });
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (!!props.project.test) {
|
|
237
|
+
const tracker = YieldTracker.from({});
|
|
238
|
+
|
|
239
|
+
const canBeAddedToCurrentTest =
|
|
240
|
+
props.project.test.getCanBeAddedToTest(props.entity);
|
|
241
|
+
|
|
242
|
+
const canBeRemovedFromCurrentTest =
|
|
243
|
+
props.project.test.getCanBeRemovedFromTest(props.entity);
|
|
244
|
+
|
|
245
|
+
// Remove entity from current test
|
|
246
|
+
if (canBeRemovedFromCurrentTest) {
|
|
247
|
+
props.project.test.removeEntity(props.entity);
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
props.entity.type === EntityType.Loop &&
|
|
251
|
+
!!props.entity.body
|
|
252
|
+
) {
|
|
253
|
+
props.project.test?.removeEntity(props.entity.body);
|
|
254
|
+
|
|
255
|
+
const loopBodyEntities =
|
|
256
|
+
(await flattenRelatedCallableElementsOnTheRightAsync(
|
|
257
|
+
props.entity.body,
|
|
258
|
+
false,
|
|
259
|
+
{ tracker }
|
|
260
|
+
)) as (
|
|
261
|
+
| DraggableExecutableEntityState
|
|
262
|
+
| DraggableCallerEntityState
|
|
263
|
+
| VariableDeclarationState
|
|
264
|
+
)[];
|
|
265
|
+
|
|
266
|
+
for (const entity of loopBodyEntities) {
|
|
267
|
+
props.project.test?.removeEntity(entity);
|
|
268
|
+
|
|
269
|
+
await props.project.emitAsync(entity.id, {});
|
|
270
|
+
|
|
271
|
+
await tracker.tick();
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Add to current test
|
|
276
|
+
} else if (canBeAddedToCurrentTest) {
|
|
277
|
+
props.project.test.addEntity(props.entity);
|
|
278
|
+
|
|
279
|
+
if (
|
|
280
|
+
props.entity.type === EntityType.Loop &&
|
|
281
|
+
!!props.entity.body
|
|
282
|
+
) {
|
|
283
|
+
props.project.test?.addEntity(props.entity.body);
|
|
284
|
+
|
|
285
|
+
const loopBodyEntities =
|
|
286
|
+
(await flattenRelatedCallableElementsOnTheRightAsync(
|
|
287
|
+
props.entity.body,
|
|
288
|
+
false,
|
|
289
|
+
{ tracker }
|
|
290
|
+
)) as (
|
|
291
|
+
| DraggableExecutableEntityState
|
|
292
|
+
| DraggableCallerEntityState
|
|
293
|
+
| VariableDeclarationState
|
|
294
|
+
)[];
|
|
295
|
+
|
|
296
|
+
for (const entity of loopBodyEntities) {
|
|
297
|
+
props.project.test?.addEntity(entity);
|
|
298
|
+
|
|
299
|
+
await props.project.emitAsync(entity.id, {});
|
|
300
|
+
|
|
301
|
+
await tracker.tick();
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
get DELAY_LOCAL_STORAGE_KEY() {
|
|
309
|
+
return `${this.editor.logic.id}--execution-delay`;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
get SHOULD_PERSIST_EXECUTION_VALUES_LOCAL_STORAGE_KEY() {
|
|
313
|
+
return `${this.editor.logic.id}--persist-execution-values`;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Add the value of the store from the editor to the execution stores
|
|
317
|
+
initExecutionValueStores() {
|
|
318
|
+
this.localTestValues = this.editor.localTestValues;
|
|
319
|
+
this.persistedExecutionResults = this.editor.persistedExecutionResults;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async pause() {
|
|
323
|
+
// Pause the ongoing execution
|
|
324
|
+
this.state = ExecutionState.Paused;
|
|
325
|
+
|
|
326
|
+
await this.editor.emitAsync('test-paused', {});
|
|
327
|
+
|
|
328
|
+
this.render();
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async stop(options: YieldOptions = {}) {
|
|
332
|
+
const tracker = YieldTracker.from(options);
|
|
333
|
+
|
|
334
|
+
// Stop the ongoing execution
|
|
335
|
+
this.state = ExecutionState.Stopped;
|
|
336
|
+
|
|
337
|
+
await Promise.all([
|
|
338
|
+
this.resetUIState({ tracker }),
|
|
339
|
+
|
|
340
|
+
// Remove this instance from memory, so all its state will also be erased
|
|
341
|
+
this.editor.removeTest({ tracker, skipValidate: true }),
|
|
342
|
+
|
|
343
|
+
this.editor.emitAsync('test-exited', {})
|
|
344
|
+
]);
|
|
345
|
+
|
|
346
|
+
// Copy all values in the 'lastExecutionResults' store, into the 'persistedExecutionResults' store
|
|
347
|
+
if (this.persistExecutionValues) {
|
|
348
|
+
Logger.log('Saving last execution results into persisted results');
|
|
349
|
+
const allValuesFromExecution = this.lastExecutionResults.values;
|
|
350
|
+
|
|
351
|
+
for (const key of Object.keys(allValuesFromExecution)) {
|
|
352
|
+
const value = allValuesFromExecution[key];
|
|
353
|
+
|
|
354
|
+
const valueOwner = this.editor.logic.get(key) as
|
|
355
|
+
| PropertyState
|
|
356
|
+
| EntityWithValueState;
|
|
357
|
+
|
|
358
|
+
if (!valueOwner) {
|
|
359
|
+
return;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (valueOwner.type === EntityType.ArgumentDeclaration) {
|
|
363
|
+
this.editor.persistedExecutionResults.writeValue(
|
|
364
|
+
valueOwner,
|
|
365
|
+
value
|
|
366
|
+
);
|
|
367
|
+
} else if (valueOwner.type === EntityType.OutputMap) {
|
|
368
|
+
if (
|
|
369
|
+
VARIABLE_TYPES.includes(valueOwner.parent.type) &&
|
|
370
|
+
valueOwner.declaration?.type === EntityType.Property &&
|
|
371
|
+
valueOwner.declaration.static
|
|
372
|
+
) {
|
|
373
|
+
this.editor.localTestValues.writeValue(
|
|
374
|
+
valueOwner,
|
|
375
|
+
value
|
|
376
|
+
);
|
|
377
|
+
} else {
|
|
378
|
+
this.editor.persistedExecutionResults.writeValue(
|
|
379
|
+
valueOwner,
|
|
380
|
+
value
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
} else {
|
|
384
|
+
this.editor.localTestValues.writeValue(valueOwner, value);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
await tracker.tick();
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
const allValuesFromExecution = {
|
|
391
|
+
...this.lastExecutionResults.values
|
|
392
|
+
};
|
|
393
|
+
this.lastExecutionResults.clear();
|
|
394
|
+
|
|
395
|
+
await Promise.all(
|
|
396
|
+
Object.keys(allValuesFromExecution).map(async (key) => {
|
|
397
|
+
await this.editor.emitAsync(key, {});
|
|
398
|
+
})
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
await this.editor.validateProjectState({ tracker });
|
|
403
|
+
|
|
404
|
+
for (const error of this.validation) {
|
|
405
|
+
await this.notifyEntityAsync(error.entity as TestableEntityState);
|
|
406
|
+
|
|
407
|
+
await tracker.tick();
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
this.editor.emitAsync('test-exited', {});
|
|
411
|
+
await this.clear();
|
|
412
|
+
|
|
413
|
+
this.editor.off('value-written', this.onValueWritten);
|
|
414
|
+
this.editor.off('value-removed', this.onValueRemoved);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
async resume() {
|
|
418
|
+
// Resume the ongoing execution
|
|
419
|
+
this.state = ExecutionState.Running;
|
|
420
|
+
|
|
421
|
+
await this.editor.emitAsync('test-resumed', {});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async close(options: YieldOptions = {}) {
|
|
425
|
+
const tracker = YieldTracker.from(options);
|
|
426
|
+
|
|
427
|
+
this.state = ExecutionState.Stopped;
|
|
428
|
+
|
|
429
|
+
// Stop the ongoing execution
|
|
430
|
+
const oldMenuRef = menuNode;
|
|
431
|
+
requestedUnmountMenu = true;
|
|
432
|
+
await menuNode.unmount();
|
|
433
|
+
menuNode = undefined;
|
|
434
|
+
|
|
435
|
+
await Promise.all([
|
|
436
|
+
(async () => {
|
|
437
|
+
for (const canvasObj of this.editor.canvas.draggableObjects) {
|
|
438
|
+
this.editor.canvas.enable(canvasObj);
|
|
439
|
+
|
|
440
|
+
await tracker.tick();
|
|
441
|
+
}
|
|
442
|
+
})(),
|
|
443
|
+
|
|
444
|
+
this.stop({ tracker })
|
|
445
|
+
]);
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
await oldMenuRef.unmount();
|
|
449
|
+
} catch (e) {
|
|
450
|
+
// Ignore errors here
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async restart(options: YieldOptions = {}) {
|
|
455
|
+
const tracker = YieldTracker.from(options);
|
|
456
|
+
|
|
457
|
+
// Restart the ongoing execution
|
|
458
|
+
this.state = ExecutionState.NotStarted;
|
|
459
|
+
|
|
460
|
+
await this.resetUIState({ tracker });
|
|
461
|
+
await this.editor.emitAsync('test-restart', {});
|
|
462
|
+
|
|
463
|
+
this.executing = [];
|
|
464
|
+
|
|
465
|
+
this.executed = [];
|
|
466
|
+
|
|
467
|
+
this.skipped = [];
|
|
468
|
+
|
|
469
|
+
await this.validateLogicAsync({ tracker });
|
|
470
|
+
|
|
471
|
+
// Run test instantly
|
|
472
|
+
if (!this.validation.length) {
|
|
473
|
+
await this.start({ tracker });
|
|
474
|
+
} else {
|
|
475
|
+
this.handleInvalidLogic();
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async render() {
|
|
480
|
+
if (requestedUnmountMenu) {
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
if (!menuNode) {
|
|
485
|
+
menuNode = createRoot('#test-menu-container');
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
await menuNode.render(TestMenu, {
|
|
489
|
+
project: this.editor
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
setDelay(delay: number) {
|
|
494
|
+
this.DELAY = delay;
|
|
495
|
+
localStorage.setItem(this.DELAY_LOCAL_STORAGE_KEY, String(delay));
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
setPersistExecutionValues(persist: boolean) {
|
|
499
|
+
this.persistExecutionValues = persist;
|
|
500
|
+
localStorage.setItem(
|
|
501
|
+
this.SHOULD_PERSIST_EXECUTION_VALUES_LOCAL_STORAGE_KEY,
|
|
502
|
+
String(persist)
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async unmountMenu() {
|
|
507
|
+
requestedUnmountMenu = true;
|
|
508
|
+
|
|
509
|
+
if (menuNode) {
|
|
510
|
+
await menuNode.unmount();
|
|
511
|
+
menuNode = undefined;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Checks the local storage for a persisted delay
|
|
516
|
+
// If it exists, it sets it as the current delay
|
|
517
|
+
// If it doesn't, it sets the default delay and persists it in the local storage
|
|
518
|
+
initPersistedDelayConfig() {
|
|
519
|
+
const persistedDelay = localStorage.getItem(
|
|
520
|
+
this.DELAY_LOCAL_STORAGE_KEY
|
|
521
|
+
);
|
|
522
|
+
|
|
523
|
+
if (persistedDelay) {
|
|
524
|
+
this.setDelay(Number(persistedDelay));
|
|
525
|
+
} else {
|
|
526
|
+
this.setDelay(Test.DEFAULT_DELAY);
|
|
527
|
+
localStorage.setItem(
|
|
528
|
+
this.DELAY_LOCAL_STORAGE_KEY,
|
|
529
|
+
String(Test.DEFAULT_DELAY)
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// Checks the local storage for a persisted boolean
|
|
535
|
+
// If it exists, it sets it as the current value
|
|
536
|
+
// If it doesn't, it sets the default value and persists it in the local storage
|
|
537
|
+
initPersistedExecutionValuesConfig() {
|
|
538
|
+
const persistedValue = localStorage.getItem(
|
|
539
|
+
this.SHOULD_PERSIST_EXECUTION_VALUES_LOCAL_STORAGE_KEY
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
if (persistedValue) {
|
|
543
|
+
this.setPersistExecutionValues(persistedValue === 'true');
|
|
544
|
+
} else {
|
|
545
|
+
this.setPersistExecutionValues(false);
|
|
546
|
+
localStorage.setItem(
|
|
547
|
+
this.SHOULD_PERSIST_EXECUTION_VALUES_LOCAL_STORAGE_KEY,
|
|
548
|
+
String(false)
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async onValueWritten(owner: EntityWithValueState, value: IDynamicValue) {
|
|
554
|
+
if (!this.editor.test) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
if (this.state === ExecutionState.Running) {
|
|
559
|
+
this.render();
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
await this.validateLogicAsync();
|
|
564
|
+
this.render();
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
async onValueRemoved(owner: EntityWithValueState) {
|
|
568
|
+
if (!this.editor.test) {
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
if (this.state === ExecutionState.Running) {
|
|
573
|
+
this.render();
|
|
574
|
+
return;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
await this.validateLogicAsync();
|
|
578
|
+
this.render();
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
async init(
|
|
582
|
+
options: YieldOptions & { autoExecute?: boolean } = {}
|
|
583
|
+
): Promise<void> {
|
|
584
|
+
const { autoExecute = false, ...yieldOptions } = options;
|
|
585
|
+
const tracker = YieldTracker.from(yieldOptions);
|
|
586
|
+
|
|
587
|
+
// Resolve stored local config values
|
|
588
|
+
this.initPersistedDelayConfig();
|
|
589
|
+
this.initPersistedExecutionValuesConfig();
|
|
590
|
+
|
|
591
|
+
// Render once the menu at the beginning to be responsive
|
|
592
|
+
requestedUnmountMenu = false;
|
|
593
|
+
await this.render();
|
|
594
|
+
|
|
595
|
+
if (ENTRY_POINT_TYPES.includes(this.entryPoint.type)) {
|
|
596
|
+
await this.addEntitiesAsync(
|
|
597
|
+
(await flattenRelatedCallableElementsOnTheRightAsync(
|
|
598
|
+
this.entryPoint,
|
|
599
|
+
false,
|
|
600
|
+
{ tracker }
|
|
601
|
+
)) as TestableEntityState[],
|
|
602
|
+
{
|
|
603
|
+
tracker,
|
|
604
|
+
skipRender: true,
|
|
605
|
+
skipValidate: true,
|
|
606
|
+
skipNotify: true
|
|
607
|
+
}
|
|
608
|
+
);
|
|
609
|
+
} else if (
|
|
610
|
+
this.entryPoint.type === EntityType.Loop &&
|
|
611
|
+
!!this.entryPoint.body
|
|
612
|
+
) {
|
|
613
|
+
await this.addEntitiesAsync(
|
|
614
|
+
(await flattenRelatedCallableElementsOnTheRightAsync(
|
|
615
|
+
this.entryPoint.body,
|
|
616
|
+
false,
|
|
617
|
+
{ tracker }
|
|
618
|
+
)) as TestableEntityState[],
|
|
619
|
+
{
|
|
620
|
+
tracker,
|
|
621
|
+
skipRender: true,
|
|
622
|
+
skipValidate: true,
|
|
623
|
+
skipNotify: true
|
|
624
|
+
}
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
this.lastExecutionResults?.clear();
|
|
629
|
+
|
|
630
|
+
this.editor.on('value-written', this.onValueWritten);
|
|
631
|
+
this.editor.on('value-removed', this.onValueRemoved);
|
|
632
|
+
|
|
633
|
+
// Loop over the entities already in the test execution state and emit an event so they can do their respective re-calculations
|
|
634
|
+
for (const entity of this.entities) {
|
|
635
|
+
await this.editor.emitAsync(entity.id, {});
|
|
636
|
+
|
|
637
|
+
await tracker.tick();
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
await this.recalculateEntitiesRelatedToTestAsync({ tracker });
|
|
641
|
+
await this.stopAllTestEntitiesExecutionConnectionAnimationsAsync({
|
|
642
|
+
tracker
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
await this.editor.emitAsync('test-setup-started', {});
|
|
646
|
+
|
|
647
|
+
console.log('[Test.init] about to validate logic');
|
|
648
|
+
|
|
649
|
+
this.validation = await validateLogicAsync(this.entities, this.editor, {
|
|
650
|
+
tracker
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
console.log('Validation: ', this.validation);
|
|
654
|
+
|
|
655
|
+
// Check if user is currently pressing the ctrl or ⌘ key
|
|
656
|
+
if (autoExecute) {
|
|
657
|
+
// Run test instantly
|
|
658
|
+
if (!this.validation.length) {
|
|
659
|
+
await this.start({ tracker });
|
|
660
|
+
} else {
|
|
661
|
+
this.handleInvalidLogic();
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Update the menu at the end of the setup with all the right data
|
|
666
|
+
await this.render();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// All cleanup logic should be done here
|
|
670
|
+
async clear() {
|
|
671
|
+
activeUINodes = [];
|
|
672
|
+
menuNode = undefined;
|
|
673
|
+
|
|
674
|
+
this.entities = [];
|
|
675
|
+
|
|
676
|
+
this.state = ExecutionState.NotStarted;
|
|
677
|
+
|
|
678
|
+
this.entitiesRelatedToExecution = [];
|
|
679
|
+
|
|
680
|
+
this.executing = [];
|
|
681
|
+
|
|
682
|
+
this.executed = [];
|
|
683
|
+
|
|
684
|
+
this.skipped = [];
|
|
685
|
+
|
|
686
|
+
this.validation = [];
|
|
687
|
+
|
|
688
|
+
this.lastExecutionResults.clear();
|
|
689
|
+
|
|
690
|
+
this.editor.test = null;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
resolveIsContinuationOfSelectedLogic(entity: TestableEntityState): boolean {
|
|
694
|
+
const seenChildren: Set<string> = new Set();
|
|
695
|
+
|
|
696
|
+
const firstLevelChildrenOfTestEntities = filterOutDuplicateEntities(
|
|
697
|
+
this.entities.flatMap((entity) => {
|
|
698
|
+
if (seenChildren.has(entity.id)) {
|
|
699
|
+
return [];
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
seenChildren.add(entity.id);
|
|
703
|
+
|
|
704
|
+
const childrenOfTestEntities =
|
|
705
|
+
getRelatedCallableElementsOnTheRight(entity);
|
|
706
|
+
|
|
707
|
+
// Remove those that are not called by the entity or are variable declaration,
|
|
708
|
+
// so we don't get callable entities that aren't directly called by the entity
|
|
709
|
+
return childrenOfTestEntities.filter((child) => {
|
|
710
|
+
if (
|
|
711
|
+
CALLABLE_TYPES.includes(child.type) &&
|
|
712
|
+
!(
|
|
713
|
+
child as DraggableCallableEntityState
|
|
714
|
+
).calledBy.includes(
|
|
715
|
+
entity as DraggableCallerEntityState
|
|
716
|
+
)
|
|
717
|
+
) {
|
|
718
|
+
return false;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
return true;
|
|
722
|
+
});
|
|
723
|
+
})
|
|
724
|
+
);
|
|
725
|
+
|
|
726
|
+
const isContinuationOfLogic =
|
|
727
|
+
firstLevelChildrenOfTestEntities.includes(entity);
|
|
728
|
+
|
|
729
|
+
return isContinuationOfLogic;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
async recalculateEntitiesRelatedToTestAsync(options: YieldOptions = {}) {
|
|
733
|
+
const tracker = YieldTracker.from(options);
|
|
734
|
+
|
|
735
|
+
const scopeOwnerOfEntryPointEntityOrSelf: TestableEntityState =
|
|
736
|
+
getParentEntryPoint(this.entryPoint) || this.entryPoint;
|
|
737
|
+
|
|
738
|
+
const allScopeChildren =
|
|
739
|
+
await flattenRelatedCallableElementsOnTheRightAsync(
|
|
740
|
+
scopeOwnerOfEntryPointEntityOrSelf,
|
|
741
|
+
false,
|
|
742
|
+
{ tracker }
|
|
743
|
+
);
|
|
744
|
+
|
|
745
|
+
const seenChildren: Set<string> = new Set();
|
|
746
|
+
|
|
747
|
+
const flat: (ExecutableEntityState | CallableEntityState)[] = [];
|
|
748
|
+
|
|
749
|
+
for (const entity of this.entities) {
|
|
750
|
+
if (seenChildren.has(entity.id)) {
|
|
751
|
+
continue;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
seenChildren.add(entity.id);
|
|
755
|
+
|
|
756
|
+
const childrenOfTestEntities =
|
|
757
|
+
await getRelatedCallableElementsOnTheRightAsync(entity, false, {
|
|
758
|
+
tracker
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
// Remove those that are not called by the entity or are variable declaration,
|
|
762
|
+
// so we don't get callable entities that aren't directly called by the entity
|
|
763
|
+
flat.push(
|
|
764
|
+
...childrenOfTestEntities.filter((child) => {
|
|
765
|
+
if (
|
|
766
|
+
CALLABLE_TYPES.includes(child.type) &&
|
|
767
|
+
!(
|
|
768
|
+
child as DraggableCallableEntityState
|
|
769
|
+
).calledBy.includes(
|
|
770
|
+
entity as DraggableCallerEntityState
|
|
771
|
+
)
|
|
772
|
+
) {
|
|
773
|
+
return false;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
return true;
|
|
777
|
+
})
|
|
778
|
+
);
|
|
779
|
+
|
|
780
|
+
await tracker.tick();
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const firstLevelChildrenOfTestEntities =
|
|
784
|
+
await filterOutDuplicateEntitiesAsync(flat, { tracker });
|
|
785
|
+
|
|
786
|
+
for (const entity of allScopeChildren) {
|
|
787
|
+
const isChildAlreadyPartOfTest = this.hasEntityWithId(entity.id);
|
|
788
|
+
const isContinuationOfLogic =
|
|
789
|
+
firstLevelChildrenOfTestEntities.includes(entity);
|
|
790
|
+
|
|
791
|
+
if (
|
|
792
|
+
// If it isn't part of the test already
|
|
793
|
+
!isChildAlreadyPartOfTest &&
|
|
794
|
+
// AND isn't related to the selected objects on the right (meaning continuation of the already selected logic)
|
|
795
|
+
!isContinuationOfLogic
|
|
796
|
+
) {
|
|
797
|
+
this.removeFromCanBeAddedToTest(
|
|
798
|
+
entity as
|
|
799
|
+
| DraggableExecutableEntityState
|
|
800
|
+
| DraggableCallerEntityState
|
|
801
|
+
| VariableDeclarationState
|
|
802
|
+
);
|
|
803
|
+
this.editor.emit(entity.id, {});
|
|
804
|
+
} else {
|
|
805
|
+
if (isContinuationOfLogic && !isChildAlreadyPartOfTest) {
|
|
806
|
+
this.addToCanBeAddedToTest(
|
|
807
|
+
entity as
|
|
808
|
+
| DraggableExecutableEntityState
|
|
809
|
+
| DraggableCallerEntityState
|
|
810
|
+
| VariableDeclarationState
|
|
811
|
+
);
|
|
812
|
+
this.editor.emit(entity.id, {});
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
await tracker.tick();
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
recalculateEntitiesRelatedToTest() {
|
|
821
|
+
const scopeOwnerOfEntryPointEntityOrSelf: TestableEntityState =
|
|
822
|
+
getParentEntryPoint(this.entryPoint) || this.entryPoint;
|
|
823
|
+
|
|
824
|
+
const allScopeChildren = flattenRelatedCallableElementsOnTheRight(
|
|
825
|
+
scopeOwnerOfEntryPointEntityOrSelf
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
const seenChildren: Set<string> = new Set();
|
|
829
|
+
|
|
830
|
+
const firstLevelChildrenOfTestEntities = filterOutDuplicateEntities(
|
|
831
|
+
this.entities.flatMap((entity) => {
|
|
832
|
+
if (seenChildren.has(entity.id)) {
|
|
833
|
+
return [];
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
seenChildren.add(entity.id);
|
|
837
|
+
|
|
838
|
+
const childrenOfTestEntities =
|
|
839
|
+
getRelatedCallableElementsOnTheRight(entity);
|
|
840
|
+
|
|
841
|
+
// Remove those that are not called by the entity or are variable declaration,
|
|
842
|
+
// so we don't get callable entities that aren't directly called by the entity
|
|
843
|
+
return childrenOfTestEntities.filter((child) => {
|
|
844
|
+
if (
|
|
845
|
+
CALLABLE_TYPES.includes(child.type) &&
|
|
846
|
+
!(
|
|
847
|
+
child as DraggableCallableEntityState
|
|
848
|
+
).calledBy.includes(
|
|
849
|
+
entity as DraggableCallerEntityState
|
|
850
|
+
)
|
|
851
|
+
) {
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
return true;
|
|
856
|
+
});
|
|
857
|
+
})
|
|
858
|
+
);
|
|
859
|
+
|
|
860
|
+
allScopeChildren.forEach((entity) => {
|
|
861
|
+
const isChildAlreadyPartOfTest = this.hasEntityWithId(entity.id);
|
|
862
|
+
const isContinuationOfLogic =
|
|
863
|
+
firstLevelChildrenOfTestEntities.includes(entity);
|
|
864
|
+
|
|
865
|
+
if (
|
|
866
|
+
// If it isn't part of the test already
|
|
867
|
+
!isChildAlreadyPartOfTest &&
|
|
868
|
+
// AND isn't related to the selected objects on the right (meaning continuation of the already selected logic)
|
|
869
|
+
!isContinuationOfLogic
|
|
870
|
+
) {
|
|
871
|
+
this.removeFromCanBeAddedToTest(
|
|
872
|
+
entity as
|
|
873
|
+
| DraggableExecutableEntityState
|
|
874
|
+
| DraggableCallerEntityState
|
|
875
|
+
| VariableDeclarationState
|
|
876
|
+
);
|
|
877
|
+
this.editor.emit(entity.id, {});
|
|
878
|
+
} else {
|
|
879
|
+
if (isContinuationOfLogic && !isChildAlreadyPartOfTest) {
|
|
880
|
+
this.addToCanBeAddedToTest(
|
|
881
|
+
entity as
|
|
882
|
+
| DraggableExecutableEntityState
|
|
883
|
+
| DraggableCallerEntityState
|
|
884
|
+
| VariableDeclarationState
|
|
885
|
+
);
|
|
886
|
+
this.editor.emit(entity.id, {});
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
});
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// If an entity is no longer connected to the rest of the test, remove it
|
|
893
|
+
// Return true if the entity was deleted
|
|
894
|
+
removeIfUnconnected(entity: TestableEntityState): boolean {
|
|
895
|
+
const isContinuationOfLogic =
|
|
896
|
+
this.resolveIsContinuationOfSelectedLogic(entity);
|
|
897
|
+
|
|
898
|
+
if (!isContinuationOfLogic) {
|
|
899
|
+
super.removeEntity(entity);
|
|
900
|
+
return true;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
return false;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
removeEntity(entity: TestableEntityState) {
|
|
907
|
+
Logger.log('Removing entity:', entity.id, this.entities);
|
|
908
|
+
|
|
909
|
+
super.removeEntity(entity); // This is done to avoid looping more than necessary
|
|
910
|
+
Logger.log('Entities after removal:', this.entities);
|
|
911
|
+
|
|
912
|
+
// We will recalculate and emit events only once, at the end
|
|
913
|
+
let deletedEntityIds: string[] = [entity.id];
|
|
914
|
+
|
|
915
|
+
// Remove all other entities that are connected to this one if they are in the test or in the related list
|
|
916
|
+
this.entities.forEach((entity) => {
|
|
917
|
+
const deleted = this.removeIfUnconnected(entity);
|
|
918
|
+
if (deleted) {
|
|
919
|
+
deletedEntityIds.push(entity.id);
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
|
|
923
|
+
this.recalculateEntitiesRelatedToTestAsync();
|
|
924
|
+
|
|
925
|
+
deletedEntityIds.forEach((id) => {
|
|
926
|
+
this.editor.emit(id, {});
|
|
927
|
+
});
|
|
928
|
+
this.editor.emit('test-entity-removed', {
|
|
929
|
+
entity
|
|
930
|
+
});
|
|
931
|
+
|
|
932
|
+
Logger.log('Recalculating validation');
|
|
933
|
+
this.validation = this.validateLogic();
|
|
934
|
+
|
|
935
|
+
Logger.log('Validation after removal: ', this.validation);
|
|
936
|
+
|
|
937
|
+
this.render();
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
addEntity(entity: TestableEntityState): void {
|
|
941
|
+
super.addEntity(entity);
|
|
942
|
+
|
|
943
|
+
const deps: TestableEntityState[] = [entity];
|
|
944
|
+
|
|
945
|
+
if (entity.type === EntityType.VariableInstance) {
|
|
946
|
+
[
|
|
947
|
+
entity.declaration,
|
|
948
|
+
...entity.declaration.variableInstances
|
|
949
|
+
].forEach((relatedEntity) => {
|
|
950
|
+
super.addEntity(relatedEntity);
|
|
951
|
+
if (relatedEntity.id !== entity.id) {
|
|
952
|
+
deps.push(relatedEntity);
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
// Remove entity from the list of entities that can be added to the test
|
|
958
|
+
const index = this.entitiesRelatedToExecution.indexOf(entity);
|
|
959
|
+
if (index > -1) {
|
|
960
|
+
this.entitiesRelatedToExecution.splice(index, 1);
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
this.validation = this.validateLogic();
|
|
964
|
+
|
|
965
|
+
this.recalculateEntitiesRelatedToTestAsync();
|
|
966
|
+
|
|
967
|
+
deps.forEach((ent) => {
|
|
968
|
+
this.editor.emit(ent.id, {});
|
|
969
|
+
this.editor.emit('test-entity-added', {
|
|
970
|
+
ent
|
|
971
|
+
});
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
async addEntityAsync(
|
|
976
|
+
entity: TestableEntityState,
|
|
977
|
+
options: YieldOptions & {
|
|
978
|
+
skipValidate?: boolean;
|
|
979
|
+
skipRender?: boolean;
|
|
980
|
+
skipNotify?: boolean;
|
|
981
|
+
} = {}
|
|
982
|
+
): Promise<void> {
|
|
983
|
+
super.addEntity(entity);
|
|
984
|
+
|
|
985
|
+
const deps: TestableEntityState[] = [entity];
|
|
986
|
+
|
|
987
|
+
const tracker = YieldTracker.from(options);
|
|
988
|
+
|
|
989
|
+
if (entity.type === EntityType.VariableInstance) {
|
|
990
|
+
for (const relatedEntity of [
|
|
991
|
+
entity.declaration,
|
|
992
|
+
...entity.declaration.variableInstances
|
|
993
|
+
]) {
|
|
994
|
+
super.addEntity(relatedEntity);
|
|
995
|
+
if (relatedEntity.id !== entity.id) {
|
|
996
|
+
deps.push(relatedEntity);
|
|
997
|
+
}
|
|
998
|
+
await tracker.tick();
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// Remove entity from the list of entities that can be added to the test
|
|
1003
|
+
const index = this.entitiesRelatedToExecution.indexOf(entity);
|
|
1004
|
+
if (index > -1) {
|
|
1005
|
+
this.entitiesRelatedToExecution.splice(index, 1);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
this.validation = this.validateLogic();
|
|
1009
|
+
|
|
1010
|
+
await this.recalculateEntitiesRelatedToTestAsync({
|
|
1011
|
+
tracker,
|
|
1012
|
+
...options
|
|
1013
|
+
});
|
|
1014
|
+
|
|
1015
|
+
for (const ent of deps) {
|
|
1016
|
+
await this.editor.emitAsync(ent.id, {});
|
|
1017
|
+
await this.editor.emitAsync('test-entity-added', {
|
|
1018
|
+
ent
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
await tracker.tick();
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
addEntities(entities: TestableEntityState[]) {
|
|
1026
|
+
entities.forEach((entity) => this.addEntity(entity));
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
async addEntitiesAsync(
|
|
1030
|
+
entities: TestableEntityState[],
|
|
1031
|
+
options: YieldOptions & {
|
|
1032
|
+
skipValidate?: boolean;
|
|
1033
|
+
skipRender?: boolean;
|
|
1034
|
+
skipNotify?: boolean;
|
|
1035
|
+
} = {}
|
|
1036
|
+
) {
|
|
1037
|
+
const tracker = YieldTracker.from(options);
|
|
1038
|
+
|
|
1039
|
+
const promises: Promise<void>[] = [];
|
|
1040
|
+
|
|
1041
|
+
for (const entity of entities) {
|
|
1042
|
+
promises.push(this.addEntityAsync(entity, options));
|
|
1043
|
+
|
|
1044
|
+
await tracker.tick();
|
|
1045
|
+
}
|
|
1046
|
+
|
|
1047
|
+
await Promise.all(promises);
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
disabledConnectionsNotInTest(
|
|
1051
|
+
draggableObject: DraggableObject,
|
|
1052
|
+
entity: UserManagedEntityState
|
|
1053
|
+
) {
|
|
1054
|
+
if (draggableObject.canvas.disabled) {
|
|
1055
|
+
draggableObject.connections.forEach((connection) => {
|
|
1056
|
+
connection.disable();
|
|
1057
|
+
connection.element.classList.add('disabled');
|
|
1058
|
+
|
|
1059
|
+
if (connection.complementaryLineElement) {
|
|
1060
|
+
connection.complementaryLineElement.classList.add(
|
|
1061
|
+
'disabled'
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
});
|
|
1065
|
+
|
|
1066
|
+
return;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
const canBeAddedToCurrentTest = this.getCanBeAddedToTest(entity);
|
|
1070
|
+
|
|
1071
|
+
const isPartOfTest = this.hasEntity(
|
|
1072
|
+
entity as
|
|
1073
|
+
| DraggableExecutableEntityState
|
|
1074
|
+
| DraggableCallerEntityState
|
|
1075
|
+
| VariableState
|
|
1076
|
+
);
|
|
1077
|
+
|
|
1078
|
+
// If the entity is not part of the test or related to it then
|
|
1079
|
+
// Make sure all connections are disabled
|
|
1080
|
+
// If the test is running, we disable also entities related to the test but not in it
|
|
1081
|
+
if (
|
|
1082
|
+
!isPartOfTest &&
|
|
1083
|
+
(this.state === ExecutionState.Running
|
|
1084
|
+
? true
|
|
1085
|
+
: !canBeAddedToCurrentTest)
|
|
1086
|
+
) {
|
|
1087
|
+
draggableObject.connections.forEach((connection) => {
|
|
1088
|
+
const sourceEntityId = getEntityIdFromCanvasId(
|
|
1089
|
+
connection.startId
|
|
1090
|
+
);
|
|
1091
|
+
const targetEntityId = getEntityIdFromCanvasId(
|
|
1092
|
+
connection.endId
|
|
1093
|
+
);
|
|
1094
|
+
|
|
1095
|
+
const sourceEntity = this.editor.logic.get(
|
|
1096
|
+
sourceEntityId
|
|
1097
|
+
) as UserManagedEntityState;
|
|
1098
|
+
const targetEntity = this.editor.logic.get(
|
|
1099
|
+
targetEntityId
|
|
1100
|
+
) as UserManagedEntityState;
|
|
1101
|
+
|
|
1102
|
+
const sourceIsInTest = this.hasEntity(
|
|
1103
|
+
sourceEntity as
|
|
1104
|
+
| DraggableExecutableEntityState
|
|
1105
|
+
| DraggableCallerEntityState
|
|
1106
|
+
| VariableDeclarationState
|
|
1107
|
+
);
|
|
1108
|
+
const targetIsInTest = this.hasEntity(
|
|
1109
|
+
targetEntity as
|
|
1110
|
+
| DraggableExecutableEntityState
|
|
1111
|
+
| DraggableCallerEntityState
|
|
1112
|
+
| VariableDeclarationState
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
if (!sourceIsInTest && !targetIsInTest) {
|
|
1116
|
+
connection.disable();
|
|
1117
|
+
connection.element.classList.add('disabled');
|
|
1118
|
+
|
|
1119
|
+
if (connection.complementaryLineElement) {
|
|
1120
|
+
connection.complementaryLineElement.classList.add(
|
|
1121
|
+
'disabled'
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
});
|
|
1126
|
+
|
|
1127
|
+
return;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
// Set connections that don't come from other entities in the test to disabled
|
|
1131
|
+
// By adding the 'disabled' class to them
|
|
1132
|
+
draggableObject.connections.forEach((connection) => {
|
|
1133
|
+
const sourceEntityId = getEntityIdFromCanvasId(connection.startId);
|
|
1134
|
+
const targetEntityId = getEntityIdFromCanvasId(connection.endId);
|
|
1135
|
+
|
|
1136
|
+
const sourceEntity = this.editor.logic.get(
|
|
1137
|
+
sourceEntityId
|
|
1138
|
+
) as UserManagedEntityState;
|
|
1139
|
+
const targetEntity = this.editor.logic.get(
|
|
1140
|
+
targetEntityId
|
|
1141
|
+
) as UserManagedEntityState;
|
|
1142
|
+
|
|
1143
|
+
const counterpartEntity =
|
|
1144
|
+
connection.sourceParentObject === draggableObject
|
|
1145
|
+
? targetEntity
|
|
1146
|
+
: sourceEntity;
|
|
1147
|
+
|
|
1148
|
+
if (counterpartEntity) {
|
|
1149
|
+
const counterpartIsInTest = this.hasEntity(
|
|
1150
|
+
counterpartEntity as
|
|
1151
|
+
| DraggableExecutableEntityState
|
|
1152
|
+
| DraggableCallerEntityState
|
|
1153
|
+
| VariableDeclarationState
|
|
1154
|
+
);
|
|
1155
|
+
|
|
1156
|
+
if (counterpartIsInTest) {
|
|
1157
|
+
// Remove the disabled class if it has it
|
|
1158
|
+
connection.enable();
|
|
1159
|
+
connection.element.classList.remove('disabled');
|
|
1160
|
+
return;
|
|
1161
|
+
}
|
|
1162
|
+
} else {
|
|
1163
|
+
connection.disable();
|
|
1164
|
+
connection.element.classList.add('disabled');
|
|
1165
|
+
|
|
1166
|
+
if (connection.complementaryLineElement) {
|
|
1167
|
+
connection.complementaryLineElement.classList.add(
|
|
1168
|
+
'disabled'
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
// An entity is available to be added to the test if it is related to the entities in the test logic but not yet in the test
|
|
1176
|
+
getCanBeAddedToTest(entity: UserManagedEntityState): boolean {
|
|
1177
|
+
const result =
|
|
1178
|
+
this.entitiesRelatedToExecution.includes(
|
|
1179
|
+
entity as TestableEntityState
|
|
1180
|
+
) && !this.hasEntity(entity);
|
|
1181
|
+
|
|
1182
|
+
return result;
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
removeFromCanBeAddedToTest(entity: TestableEntityState) {
|
|
1186
|
+
const index = this.entitiesRelatedToExecution.indexOf(entity);
|
|
1187
|
+
|
|
1188
|
+
if (index > -1) {
|
|
1189
|
+
this.entitiesRelatedToExecution.splice(index, 1);
|
|
1190
|
+
this.notifyEntity(entity);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// An entity is available to be added to the test if it is related to the entities in the test logic
|
|
1195
|
+
addToCanBeAddedToTest(entity: TestableEntityState) {
|
|
1196
|
+
if (!this.entitiesRelatedToExecution.includes(entity)) {
|
|
1197
|
+
this.entitiesRelatedToExecution.push(entity);
|
|
1198
|
+
this.notifyEntity(entity);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
// An entity is available to be removed from the test when it is in the test and it is not the entry point
|
|
1203
|
+
getCanBeRemovedFromTest(entity: TestableEntityState): boolean {
|
|
1204
|
+
if (entity === this.entryPoint) {
|
|
1205
|
+
return false;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
return this.entities.includes(
|
|
1209
|
+
entity as
|
|
1210
|
+
| DraggableExecutableEntityState
|
|
1211
|
+
| DraggableCallerEntityState
|
|
1212
|
+
| VariableState
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
// Test if the given entry point has all the arguments required to be executed
|
|
1217
|
+
validateLogic(): EntityError[] {
|
|
1218
|
+
const errors = validateLogic(this.entities, this.editor);
|
|
1219
|
+
|
|
1220
|
+
errors.forEach((error) => {
|
|
1221
|
+
this.notifyEntity(error.entity as TestableEntityState);
|
|
1222
|
+
});
|
|
1223
|
+
|
|
1224
|
+
return errors;
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
async validateLogicAsync(
|
|
1228
|
+
options: YieldOptions = {}
|
|
1229
|
+
): Promise<EntityError[]> {
|
|
1230
|
+
const tracker = YieldTracker.from(options);
|
|
1231
|
+
|
|
1232
|
+
const errors = await validateLogicAsync(
|
|
1233
|
+
this.entities,
|
|
1234
|
+
this.editor,
|
|
1235
|
+
options
|
|
1236
|
+
);
|
|
1237
|
+
|
|
1238
|
+
console.log(
|
|
1239
|
+
'[Test.validateLogicAsync] Errors: ',
|
|
1240
|
+
errors.map(
|
|
1241
|
+
(e) =>
|
|
1242
|
+
`${e.entity.type}: "${resolveEntityName(e.entity, this.project)}" -> ${e.code}`
|
|
1243
|
+
)
|
|
1244
|
+
);
|
|
1245
|
+
|
|
1246
|
+
for (const error of errors) {
|
|
1247
|
+
this.notifyEntity(error.entity as TestableEntityState);
|
|
1248
|
+
|
|
1249
|
+
await tracker.tick();
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
return errors;
|
|
1253
|
+
}
|
|
1254
|
+
|
|
1255
|
+
async validate() {
|
|
1256
|
+
this.validation = await this.validateLogicAsync();
|
|
1257
|
+
|
|
1258
|
+
await this.render();
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
async validateAsync(options: YieldOptions = {}) {
|
|
1262
|
+
const tracker = YieldTracker.from(options);
|
|
1263
|
+
|
|
1264
|
+
this.validation = await this.validateLogicAsync({ tracker });
|
|
1265
|
+
|
|
1266
|
+
await this.render();
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
cancelSuccessAnimation(entity: PassThroughCallableEntityState) {
|
|
1270
|
+
const successElementId = getCanvasEntitySuccessCallerNodeDOMId(entity);
|
|
1271
|
+
|
|
1272
|
+
const successEntity = document.getElementById(
|
|
1273
|
+
successElementId
|
|
1274
|
+
) as HTMLElement;
|
|
1275
|
+
|
|
1276
|
+
// Remove the 'bounce' class from the success entity in order to stop the animation
|
|
1277
|
+
successEntity?.classList.remove('bounce');
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
cancelErrorAnimation(entity: PassThroughCallableEntityState) {
|
|
1281
|
+
const errorElementId = getCanvasEntityErrorCallerNodeDOMId(entity);
|
|
1282
|
+
|
|
1283
|
+
const errorEntity = document.getElementById(
|
|
1284
|
+
errorElementId
|
|
1285
|
+
) as HTMLElement;
|
|
1286
|
+
|
|
1287
|
+
// Remove the 'bounce' class from the error entity in order to stop the animation
|
|
1288
|
+
errorEntity?.classList.remove('bounce');
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
cancelEntryPointCallerAnimation(entity: EntryPointEntityState) {
|
|
1292
|
+
const callerNodeElementId = getCanvasEntityEntryCallerNodeDOMId(entity);
|
|
1293
|
+
|
|
1294
|
+
const callerNodeEntity = document.getElementById(
|
|
1295
|
+
callerNodeElementId
|
|
1296
|
+
) as HTMLElement;
|
|
1297
|
+
|
|
1298
|
+
// Remove the 'bounce' class from the caller entity in order to stop the animation
|
|
1299
|
+
callerNodeEntity?.classList.remove('bounce');
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
async removeEntryPointCallerPopup(entity: EntryPointEntityState) {
|
|
1303
|
+
const popupId = 'entry-point-caller-tooltip--' + entity.id;
|
|
1304
|
+
|
|
1305
|
+
await this.removePopupById(popupId);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
async removeErrorCallPopup(entity: PassThroughCallableEntityState) {
|
|
1309
|
+
const popupId = 'error-tooltip--' + entity.id;
|
|
1310
|
+
|
|
1311
|
+
await this.removePopupById(popupId);
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
async removeSuccessCallPopup(entity: PassThroughCallableEntityState) {
|
|
1315
|
+
const popupId = 'success-tooltip--' + entity.id;
|
|
1316
|
+
|
|
1317
|
+
await this.removePopupById(popupId);
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
async animateEntryPointCallerNode(entity: EntryPointEntityState) {
|
|
1321
|
+
// Animate the branch node
|
|
1322
|
+
const callerNodeElementId = getCanvasEntityEntryCallerNodeDOMId(entity);
|
|
1323
|
+
|
|
1324
|
+
const callerNodeEntity = document.querySelector(
|
|
1325
|
+
`#${callerNodeElementId}`
|
|
1326
|
+
) as HTMLElement;
|
|
1327
|
+
|
|
1328
|
+
// Add the 'bounce' class to the caller entity in order to animate it
|
|
1329
|
+
callerNodeEntity.classList.add('bounce');
|
|
1330
|
+
|
|
1331
|
+
const callerNodeElementContainerId =
|
|
1332
|
+
getCanvasEntityEntryCallerNodeDOMId(entity) + '--container';
|
|
1333
|
+
|
|
1334
|
+
const parentCanvasElement = this.editor.getElement(
|
|
1335
|
+
getParentCanvasEntity(entity)?.id
|
|
1336
|
+
);
|
|
1337
|
+
|
|
1338
|
+
// Add tooltip to branch node
|
|
1339
|
+
this.popup(
|
|
1340
|
+
{
|
|
1341
|
+
anchorQuerySelector: `#${callerNodeElementContainerId}`,
|
|
1342
|
+
id: 'entry-point-caller-tooltip--' + entity.id,
|
|
1343
|
+
parentCanvasObject: parentCanvasElement.draggableObject,
|
|
1344
|
+
persisted: true,
|
|
1345
|
+
editor: this.editor
|
|
1346
|
+
},
|
|
1347
|
+
() => [
|
|
1348
|
+
CanvasPopupBaseComponent,
|
|
1349
|
+
{
|
|
1350
|
+
onClick: (e: any) => {
|
|
1351
|
+
e.stopPropagation();
|
|
1352
|
+
},
|
|
1353
|
+
containerClasses: ['tooltip']
|
|
1354
|
+
},
|
|
1355
|
+
'Start'
|
|
1356
|
+
],
|
|
1357
|
+
parentCanvasElement
|
|
1358
|
+
);
|
|
1359
|
+
|
|
1360
|
+
const callerNodeElementContainer = document.querySelector(
|
|
1361
|
+
`#${callerNodeElementContainerId}`
|
|
1362
|
+
) as HTMLElement;
|
|
1363
|
+
|
|
1364
|
+
callerNodeElementContainer.classList.add('bounce');
|
|
1365
|
+
|
|
1366
|
+
// The caller label should only be animated for 400ms then stop. While the node continues
|
|
1367
|
+
setTimeout(() => {
|
|
1368
|
+
callerNodeElementContainer.classList.remove('bounce');
|
|
1369
|
+
}, 400);
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
async animateErrorNode(
|
|
1373
|
+
entity: PassThroughCallableEntityState,
|
|
1374
|
+
type:
|
|
1375
|
+
| ExecutionTerminationType.CaughtError
|
|
1376
|
+
| ExecutionTerminationType.UnhandledError
|
|
1377
|
+
) {
|
|
1378
|
+
// Animate the failure branch node
|
|
1379
|
+
const errorElementId = getCanvasEntityErrorCallerNodeDOMId(entity);
|
|
1380
|
+
|
|
1381
|
+
const errorEntity = document.querySelector(
|
|
1382
|
+
`#${errorElementId}`
|
|
1383
|
+
) as HTMLElement;
|
|
1384
|
+
|
|
1385
|
+
// Add the 'bounce' class to the error entity in order to animate it
|
|
1386
|
+
errorEntity.classList.add('bounce');
|
|
1387
|
+
|
|
1388
|
+
const errorElementContainerId =
|
|
1389
|
+
getCanvasEntityErrorCallerNodeDOMId(entity) + '--container';
|
|
1390
|
+
|
|
1391
|
+
const parentCanvasElement = this.editor.getElement(
|
|
1392
|
+
getParentCanvasEntity(entity)?.id
|
|
1393
|
+
);
|
|
1394
|
+
|
|
1395
|
+
const popupId = 'error-tooltip--' + entity.id;
|
|
1396
|
+
|
|
1397
|
+
// Add tooltip to failure branch node
|
|
1398
|
+
this.popup(
|
|
1399
|
+
{
|
|
1400
|
+
anchorQuerySelector: `#${errorElementContainerId}`,
|
|
1401
|
+
id: popupId,
|
|
1402
|
+
parentCanvasObject: parentCanvasElement.draggableObject,
|
|
1403
|
+
arrowClasses: ['error-background'],
|
|
1404
|
+
persisted: true,
|
|
1405
|
+
editor: this.editor
|
|
1406
|
+
},
|
|
1407
|
+
() => [
|
|
1408
|
+
CanvasPopupBaseComponent,
|
|
1409
|
+
{
|
|
1410
|
+
onClick: (e: any) => {
|
|
1411
|
+
e.stopPropagation();
|
|
1412
|
+
},
|
|
1413
|
+
containerClasses: ['tooltip', 'error-background']
|
|
1414
|
+
},
|
|
1415
|
+
<>
|
|
1416
|
+
{entity.type === EntityType.Condition ? (
|
|
1417
|
+
'False'
|
|
1418
|
+
) : type === ExecutionTerminationType.CaughtError ? (
|
|
1419
|
+
'Error'
|
|
1420
|
+
) : (
|
|
1421
|
+
<span
|
|
1422
|
+
style={{
|
|
1423
|
+
display: 'flex',
|
|
1424
|
+
alignItems: 'center',
|
|
1425
|
+
gap: '4px'
|
|
1426
|
+
}}
|
|
1427
|
+
>
|
|
1428
|
+
<img
|
|
1429
|
+
src={WarningIcon}
|
|
1430
|
+
className="warning-icon logic-error-icon"
|
|
1431
|
+
/>
|
|
1432
|
+
Unhandled Error!
|
|
1433
|
+
</span>
|
|
1434
|
+
)}
|
|
1435
|
+
</>
|
|
1436
|
+
],
|
|
1437
|
+
parentCanvasElement
|
|
1438
|
+
);
|
|
1439
|
+
|
|
1440
|
+
const errorElementContainer = document.querySelector(
|
|
1441
|
+
`#${errorElementContainerId}`
|
|
1442
|
+
) as HTMLElement;
|
|
1443
|
+
|
|
1444
|
+
errorElementContainer.classList.add('bounce');
|
|
1445
|
+
|
|
1446
|
+
// The error label should only be animated for 400ms then stop. While the node continues
|
|
1447
|
+
setTimeout(() => {
|
|
1448
|
+
errorElementContainer.classList.remove('bounce');
|
|
1449
|
+
}, 400);
|
|
1450
|
+
}
|
|
1451
|
+
|
|
1452
|
+
async animateSuccessNode(entity: PassThroughCallableEntityState) {
|
|
1453
|
+
// Animate the success branch node
|
|
1454
|
+
const successElementId = getCanvasEntitySuccessCallerNodeDOMId(entity);
|
|
1455
|
+
|
|
1456
|
+
const successEntity = document.querySelector(
|
|
1457
|
+
`#${successElementId}`
|
|
1458
|
+
) as HTMLElement;
|
|
1459
|
+
|
|
1460
|
+
// Add the 'bounce' class to the success entity in order to animate it
|
|
1461
|
+
successEntity.classList.add('bounce');
|
|
1462
|
+
|
|
1463
|
+
const successElementContainerId =
|
|
1464
|
+
getCanvasEntitySuccessCallerNodeDOMId(entity) + '--container';
|
|
1465
|
+
|
|
1466
|
+
const parentCanvasElement = this.editor.getElement(
|
|
1467
|
+
getParentCanvasEntity(entity)?.id
|
|
1468
|
+
);
|
|
1469
|
+
|
|
1470
|
+
// Add tooltip to success branch node
|
|
1471
|
+
this.popup(
|
|
1472
|
+
{
|
|
1473
|
+
anchorQuerySelector: `#${successElementContainerId}`,
|
|
1474
|
+
parentCanvasObject: parentCanvasElement.draggableObject,
|
|
1475
|
+
id: 'success-tooltip--' + entity.id,
|
|
1476
|
+
arrowClasses: ['success-background'],
|
|
1477
|
+
persisted: true,
|
|
1478
|
+
editor: this.editor
|
|
1479
|
+
},
|
|
1480
|
+
() => [
|
|
1481
|
+
CanvasPopupBaseComponent,
|
|
1482
|
+
{
|
|
1483
|
+
onClick: (e: any) => {
|
|
1484
|
+
e.stopPropagation();
|
|
1485
|
+
},
|
|
1486
|
+
containerClasses: ['tooltip', 'success-background']
|
|
1487
|
+
},
|
|
1488
|
+
entity.type === EntityType.Condition ? 'True' : 'Success'
|
|
1489
|
+
],
|
|
1490
|
+
parentCanvasElement
|
|
1491
|
+
);
|
|
1492
|
+
|
|
1493
|
+
const successElementContainer = document.querySelector(
|
|
1494
|
+
`#${successElementContainerId}`
|
|
1495
|
+
) as HTMLElement;
|
|
1496
|
+
|
|
1497
|
+
successElementContainer.classList.add('bounce');
|
|
1498
|
+
|
|
1499
|
+
// The success label should only be animated for 400ms then stop. While the node continues
|
|
1500
|
+
setTimeout(() => {
|
|
1501
|
+
successElementContainer.classList.remove('bounce');
|
|
1502
|
+
}, 400);
|
|
1503
|
+
}
|
|
1504
|
+
|
|
1505
|
+
// A generic function that based on type, animates the output node that calles the next entity
|
|
1506
|
+
async animateCallerNode(
|
|
1507
|
+
entity: ExecutableEntityState,
|
|
1508
|
+
type:
|
|
1509
|
+
| ExecutionTerminationType.Success
|
|
1510
|
+
| ExecutionTerminationType.CaughtError
|
|
1511
|
+
| ExecutionTerminationType.UnhandledError
|
|
1512
|
+
) {
|
|
1513
|
+
if (PASS_THROUGH_CALLABLE_TYPES.includes(entity.type)) {
|
|
1514
|
+
if (type === ExecutionTerminationType.Success) {
|
|
1515
|
+
await this.animateSuccessNode(
|
|
1516
|
+
entity as PassThroughCallableEntityState
|
|
1517
|
+
);
|
|
1518
|
+
} else if (
|
|
1519
|
+
type === ExecutionTerminationType.CaughtError ||
|
|
1520
|
+
type === ExecutionTerminationType.UnhandledError
|
|
1521
|
+
) {
|
|
1522
|
+
await this.animateErrorNode(
|
|
1523
|
+
entity as PassThroughCallableEntityState,
|
|
1524
|
+
type
|
|
1525
|
+
);
|
|
1526
|
+
}
|
|
1527
|
+
} else if (ENTRY_POINT_TYPES.includes(entity.type)) {
|
|
1528
|
+
await this.animateEntryPointCallerNode(
|
|
1529
|
+
entity as EntryPointEntityState
|
|
1530
|
+
);
|
|
1531
|
+
}
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
// Sets up a new popup
|
|
1535
|
+
async popup(
|
|
1536
|
+
popupProps: ICanvasPopupProps,
|
|
1537
|
+
callback: (canvasObject: CanvasPopup) =>
|
|
1538
|
+
| [
|
|
1539
|
+
(
|
|
1540
|
+
| string
|
|
1541
|
+
| number
|
|
1542
|
+
| boolean
|
|
1543
|
+
| React.FC<any>
|
|
1544
|
+
| React.ComponentType<any>
|
|
1545
|
+
),
|
|
1546
|
+
{
|
|
1547
|
+
[key: string]: any;
|
|
1548
|
+
}
|
|
1549
|
+
]
|
|
1550
|
+
| [
|
|
1551
|
+
(
|
|
1552
|
+
| string
|
|
1553
|
+
| number
|
|
1554
|
+
| boolean
|
|
1555
|
+
| React.FC<any>
|
|
1556
|
+
| React.ComponentType<any>
|
|
1557
|
+
),
|
|
1558
|
+
{
|
|
1559
|
+
[key: string]: any;
|
|
1560
|
+
},
|
|
1561
|
+
React.ReactNode
|
|
1562
|
+
],
|
|
1563
|
+
parentCanvasElement: CanvasEntityElement
|
|
1564
|
+
): Promise<CanvasPopup> {
|
|
1565
|
+
const newPopup = CanvasPopup.init({
|
|
1566
|
+
...popupProps
|
|
1567
|
+
});
|
|
1568
|
+
|
|
1569
|
+
newPopup.setup();
|
|
1570
|
+
|
|
1571
|
+
await newPopup.bodyAsync(callback);
|
|
1572
|
+
|
|
1573
|
+
newPopup.show(false);
|
|
1574
|
+
|
|
1575
|
+
if (!activeUINodes.find((node) => node.id === newPopup.id)) {
|
|
1576
|
+
activeUINodes.push(newPopup);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
const refreshListener = async () => {
|
|
1580
|
+
await newPopup.refreshAsync(callback);
|
|
1581
|
+
newPopup.show(false);
|
|
1582
|
+
};
|
|
1583
|
+
|
|
1584
|
+
parentCanvasElement.node.on('update', refreshListener);
|
|
1585
|
+
|
|
1586
|
+
const unMountListener = async () => {
|
|
1587
|
+
parentCanvasElement.node.off('update', refreshListener);
|
|
1588
|
+
parentCanvasElement.node.off('unmount', unMountListener);
|
|
1589
|
+
|
|
1590
|
+
await this.removePopupById(newPopup.id);
|
|
1591
|
+
};
|
|
1592
|
+
|
|
1593
|
+
parentCanvasElement.node.on('unmount', unMountListener);
|
|
1594
|
+
|
|
1595
|
+
return newPopup;
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
async removePopupById(popupId: string) {
|
|
1599
|
+
const popup = activeUINodes.find((node) => node.id === popupId);
|
|
1600
|
+
|
|
1601
|
+
// remove from list of active popups
|
|
1602
|
+
activeUINodes = activeUINodes.filter((node) => node.id !== popupId);
|
|
1603
|
+
|
|
1604
|
+
if (popup) {
|
|
1605
|
+
await popup.destroy();
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
async removeAllPopups(options: YieldOptions = {}) {
|
|
1610
|
+
const tracker = YieldTracker.from(options);
|
|
1611
|
+
|
|
1612
|
+
const promises = [];
|
|
1613
|
+
|
|
1614
|
+
for (const popup of activeUINodes) {
|
|
1615
|
+
promises.push(
|
|
1616
|
+
(async () => {
|
|
1617
|
+
await popup.destroy();
|
|
1618
|
+
})()
|
|
1619
|
+
);
|
|
1620
|
+
|
|
1621
|
+
await tracker.tick();
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
await Promise.all(promises);
|
|
1625
|
+
|
|
1626
|
+
activeUINodes = [];
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1629
|
+
/*
|
|
1630
|
+
This function gets triggered every time an entity is about to be executed
|
|
1631
|
+
|
|
1632
|
+
It is used as a bridge between the logic execution and the interface
|
|
1633
|
+
It queues animations for the UI based on the result of the execution
|
|
1634
|
+
And handles any relevant state
|
|
1635
|
+
|
|
1636
|
+
Animations:
|
|
1637
|
+
- When an entity is about to be executed, animate the connectiono leading from its caller into itself
|
|
1638
|
+
- Add a delay before returning so the animation has time to play before the actual subsequent execution
|
|
1639
|
+
- Animate the input values to make it clear they have received the values from previous entities
|
|
1640
|
+
- Highlight the current entity that is being executed
|
|
1641
|
+
*/
|
|
1642
|
+
async onBeforeEntityExecution(
|
|
1643
|
+
entity: TestableEntityState,
|
|
1644
|
+
values: IDynamicValue[],
|
|
1645
|
+
context: IExecutionCallbackContext
|
|
1646
|
+
) {
|
|
1647
|
+
if (
|
|
1648
|
+
[
|
|
1649
|
+
ExecutionState.Stopped,
|
|
1650
|
+
ExecutionState.Paused,
|
|
1651
|
+
ExecutionState.NotStarted
|
|
1652
|
+
].includes(this.state)
|
|
1653
|
+
) {
|
|
1654
|
+
return;
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
Logger.log(
|
|
1658
|
+
'---------- onBeforeEntityExecution writing values ---------'
|
|
1659
|
+
);
|
|
1660
|
+
const targetExecution = context.execution || this;
|
|
1661
|
+
values.forEach((value) => {
|
|
1662
|
+
Logger.log('Value: ', value.valueOwner?.type, value.value?.value);
|
|
1663
|
+
targetExecution.lastExecutionResults.writeValue(value.valueOwner, value.value);
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
// Animate the incomming connection that execute the current entity
|
|
1667
|
+
const canvasElement = this.editor.getElement(
|
|
1668
|
+
getParentCanvasEntity(entity)?.id
|
|
1669
|
+
);
|
|
1670
|
+
|
|
1671
|
+
// If the entity is a variable declaration, any incomming connection is considered the caller
|
|
1672
|
+
// if (entity.type === EntityType.VariableDeclaration) {
|
|
1673
|
+
// // Check if it is a method (function declaration inside another canvas element)
|
|
1674
|
+
// } else if (
|
|
1675
|
+
if (
|
|
1676
|
+
entity.type === EntityType.FunctionDeclaration &&
|
|
1677
|
+
entity.parent.type !== EntityType.Project
|
|
1678
|
+
) {
|
|
1679
|
+
// Delete all previous popups related to it (in case this is a loop)
|
|
1680
|
+
// Because this method gets executed multiple times and we need to clear the previous popups everytime the iteration restarts
|
|
1681
|
+
if (entity.parent.type === EntityType.Loop) {
|
|
1682
|
+
const allEntitiesRelatedToLoopBody =
|
|
1683
|
+
flattenRelatedCallableElementsOnTheRight(
|
|
1684
|
+
entity
|
|
1685
|
+
) as DraggableCallableEntityState[];
|
|
1686
|
+
|
|
1687
|
+
if (!context.detached) {
|
|
1688
|
+
this.cancelEntryPointCallerAnimation(entity);
|
|
1689
|
+
|
|
1690
|
+
// Remove them from the state lists of 'executed', 'skipped' and 'executing'
|
|
1691
|
+
// so they can be re-added when the loop restarts
|
|
1692
|
+
allEntitiesRelatedToLoopBody.forEach((ent) => {
|
|
1693
|
+
this.removeFromExecutingList(ent);
|
|
1694
|
+
this.removeFromAlreadyExecutedList(ent);
|
|
1695
|
+
this.removeFromSkippedList(ent);
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
if (!context.detached) {
|
|
1700
|
+
await Promise.all([
|
|
1701
|
+
...allEntitiesRelatedToLoopBody.map(async (ent) => {
|
|
1702
|
+
let allEntitiesWithValuePopups: EntityWithValueState[] =
|
|
1703
|
+
[];
|
|
1704
|
+
let allChildrenPromises: Promise<void>[] = [];
|
|
1705
|
+
|
|
1706
|
+
if (
|
|
1707
|
+
PASS_THROUGH_CALLABLE_TYPES.includes(ent.type)
|
|
1708
|
+
) {
|
|
1709
|
+
allEntitiesWithValuePopups =
|
|
1710
|
+
(ent as OperationState).outputs || [];
|
|
1711
|
+
|
|
1712
|
+
// stop all animations
|
|
1713
|
+
this.cancelSuccessAnimation(
|
|
1714
|
+
ent as PassThroughCallableEntityState
|
|
1715
|
+
);
|
|
1716
|
+
this.cancelErrorAnimation(
|
|
1717
|
+
ent as PassThroughCallableEntityState
|
|
1718
|
+
);
|
|
1719
|
+
|
|
1720
|
+
// Remove all popups
|
|
1721
|
+
allChildrenPromises.push(
|
|
1722
|
+
this.removeSuccessCallPopup(
|
|
1723
|
+
ent as PassThroughCallableEntityState
|
|
1724
|
+
),
|
|
1725
|
+
this.removeErrorCallPopup(
|
|
1726
|
+
ent as PassThroughCallableEntityState
|
|
1727
|
+
)
|
|
1728
|
+
);
|
|
1729
|
+
} else if (TERMINATION_TYPES.includes(ent.type)) {
|
|
1730
|
+
allEntitiesWithValuePopups =
|
|
1731
|
+
(ent as TerminationEntityState).outputs ||
|
|
1732
|
+
[];
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
allChildrenPromises.push(
|
|
1736
|
+
...allEntitiesWithValuePopups.map(
|
|
1737
|
+
async (entityWithValue) => {
|
|
1738
|
+
const popupId = `execution-result--value-popup--${entityWithValue.id}`;
|
|
1739
|
+
|
|
1740
|
+
this.removePopupById(popupId);
|
|
1741
|
+
}
|
|
1742
|
+
)
|
|
1743
|
+
);
|
|
1744
|
+
|
|
1745
|
+
await Promise.all(allChildrenPromises);
|
|
1746
|
+
|
|
1747
|
+
const canvasElementOfEntity =
|
|
1748
|
+
this.editor.getElement(ent.id);
|
|
1749
|
+
|
|
1750
|
+
if (canvasElementOfEntity) {
|
|
1751
|
+
canvasElementOfEntity.draggableObject.connections.forEach(
|
|
1752
|
+
(connection) => {
|
|
1753
|
+
connection.stopAnimation();
|
|
1754
|
+
connection.resetOptions();
|
|
1755
|
+
}
|
|
1756
|
+
);
|
|
1757
|
+
}
|
|
1758
|
+
}),
|
|
1759
|
+
...entity.inputs.map(async (input) => {
|
|
1760
|
+
const popupId = `execution-result--value-popup--${input.id}`;
|
|
1761
|
+
|
|
1762
|
+
this.removePopupById(popupId);
|
|
1763
|
+
}),
|
|
1764
|
+
this.removeEntryPointCallerPopup(entity)
|
|
1765
|
+
]);
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
if (
|
|
1770
|
+
[
|
|
1771
|
+
ExecutionState.Stopped,
|
|
1772
|
+
ExecutionState.Paused,
|
|
1773
|
+
ExecutionState.NotStarted
|
|
1774
|
+
].includes(this.state)
|
|
1775
|
+
) {
|
|
1776
|
+
return;
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
// Wait
|
|
1780
|
+
if (!context.detached) {
|
|
1781
|
+
await delayHalf(this.DELAY);
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
if (
|
|
1785
|
+
[
|
|
1786
|
+
ExecutionState.Stopped,
|
|
1787
|
+
ExecutionState.Paused,
|
|
1788
|
+
ExecutionState.NotStarted
|
|
1789
|
+
].includes(this.state)
|
|
1790
|
+
) {
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1793
|
+
|
|
1794
|
+
// Update the UI so it starts its animations
|
|
1795
|
+
if (!context.detached) {
|
|
1796
|
+
this.notifyEntity(entity);
|
|
1797
|
+
|
|
1798
|
+
this.stopAllTestEntitiesExecutionConnectionAnimations();
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1801
|
+
if (
|
|
1802
|
+
[
|
|
1803
|
+
ExecutionState.Stopped,
|
|
1804
|
+
ExecutionState.Paused,
|
|
1805
|
+
ExecutionState.NotStarted
|
|
1806
|
+
].includes(this.state)
|
|
1807
|
+
) {
|
|
1808
|
+
return;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
// Re-Animate as usual
|
|
1812
|
+
// If there is any delay, no matter how much, wait for the animation
|
|
1813
|
+
if (this.DELAY && !context.detached) {
|
|
1814
|
+
// Highlight the current entity that is being executed
|
|
1815
|
+
const elementToHighlight = document.getElementById(
|
|
1816
|
+
getMethodDeclarationDOMId(entity)
|
|
1817
|
+
);
|
|
1818
|
+
|
|
1819
|
+
highlightElement(elementToHighlight);
|
|
1820
|
+
|
|
1821
|
+
// wait for animation to end
|
|
1822
|
+
await this.waitForAnimationEnd(elementToHighlight);
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
// For all other entities
|
|
1826
|
+
} else if (CALLABLE_TYPES.includes(entity.type)) {
|
|
1827
|
+
// Check if the entity is called by the success or failure branch of its caller
|
|
1828
|
+
// and change the connection animation color accordingly
|
|
1829
|
+
(entity as FunctionCallState).calledBy.forEach(
|
|
1830
|
+
(caller: CallerEntityState) => {
|
|
1831
|
+
if (
|
|
1832
|
+
[
|
|
1833
|
+
ExecutionState.Stopped,
|
|
1834
|
+
ExecutionState.Paused,
|
|
1835
|
+
ExecutionState.NotStarted
|
|
1836
|
+
].includes(this.state)
|
|
1837
|
+
) {
|
|
1838
|
+
return;
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1841
|
+
if (!caller || !this.entities.includes(caller)) {
|
|
1842
|
+
return;
|
|
1843
|
+
}
|
|
1844
|
+
|
|
1845
|
+
const isCalledBySuccessBranch = (
|
|
1846
|
+
(caller as FunctionCallState).successCalls || []
|
|
1847
|
+
).find((call) => call === entity);
|
|
1848
|
+
|
|
1849
|
+
const isCalledByErrorBranch = (
|
|
1850
|
+
(caller as FunctionCallState).errorCalls || []
|
|
1851
|
+
).find((call) => call === entity);
|
|
1852
|
+
|
|
1853
|
+
const isNeutralCall = (
|
|
1854
|
+
(caller as EntryPointEntityState).calls || []
|
|
1855
|
+
).find((call) => call === entity);
|
|
1856
|
+
|
|
1857
|
+
if (!context.detached) {
|
|
1858
|
+
canvasElement.draggableObject.connections.forEach(
|
|
1859
|
+
(connection) => {
|
|
1860
|
+
if (
|
|
1861
|
+
[
|
|
1862
|
+
ExecutionState.Stopped,
|
|
1863
|
+
ExecutionState.Paused,
|
|
1864
|
+
ExecutionState.NotStarted
|
|
1865
|
+
].includes(this.state)
|
|
1866
|
+
) {
|
|
1867
|
+
return;
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
// If this is the target entity
|
|
1871
|
+
if (
|
|
1872
|
+
connection.targetParentObject ===
|
|
1873
|
+
canvasElement.draggableObject
|
|
1874
|
+
) {
|
|
1875
|
+
// And the connection targets the execution node
|
|
1876
|
+
if (
|
|
1877
|
+
connection.endId ===
|
|
1878
|
+
getCanvasEntityExecutionNodeId(entity)
|
|
1879
|
+
) {
|
|
1880
|
+
connection.resetOptions();
|
|
1881
|
+
|
|
1882
|
+
if (isCalledBySuccessBranch) {
|
|
1883
|
+
connection.color =
|
|
1884
|
+
'var(--color-main-success)';
|
|
1885
|
+
|
|
1886
|
+
connection.startAnimation();
|
|
1887
|
+
connection.update();
|
|
1888
|
+
} else if (isCalledByErrorBranch) {
|
|
1889
|
+
connection.color =
|
|
1890
|
+
'var(--color-main-error)';
|
|
1891
|
+
|
|
1892
|
+
connection.startAnimation();
|
|
1893
|
+
connection.update();
|
|
1894
|
+
} else if (isNeutralCall) {
|
|
1895
|
+
connection.color =
|
|
1896
|
+
'var(--color-main-primary)';
|
|
1897
|
+
|
|
1898
|
+
connection.startAnimation();
|
|
1899
|
+
connection.update();
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
);
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
if (
|
|
1911
|
+
[
|
|
1912
|
+
ExecutionState.Stopped,
|
|
1913
|
+
ExecutionState.Paused,
|
|
1914
|
+
ExecutionState.NotStarted
|
|
1915
|
+
].includes(this.state)
|
|
1916
|
+
) {
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
// Wait
|
|
1921
|
+
if (!context.detached) {
|
|
1922
|
+
await delayHalf(this.DELAY);
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
if (
|
|
1926
|
+
[
|
|
1927
|
+
ExecutionState.Stopped,
|
|
1928
|
+
ExecutionState.Paused,
|
|
1929
|
+
ExecutionState.NotStarted
|
|
1930
|
+
].includes(this.state)
|
|
1931
|
+
) {
|
|
1932
|
+
return;
|
|
1933
|
+
}
|
|
1934
|
+
|
|
1935
|
+
// Update the UI so it starts its animations
|
|
1936
|
+
if (!context.detached) {
|
|
1937
|
+
this.notifyEntity(entity);
|
|
1938
|
+
|
|
1939
|
+
this.stopAllTestEntitiesExecutionConnectionAnimations();
|
|
1940
|
+
}
|
|
1941
|
+
|
|
1942
|
+
if (
|
|
1943
|
+
[
|
|
1944
|
+
ExecutionState.Stopped,
|
|
1945
|
+
ExecutionState.Paused,
|
|
1946
|
+
ExecutionState.NotStarted
|
|
1947
|
+
].includes(this.state)
|
|
1948
|
+
) {
|
|
1949
|
+
return;
|
|
1950
|
+
}
|
|
1951
|
+
|
|
1952
|
+
// If there is any delay, no matter how much, wait for the animation
|
|
1953
|
+
if (this.DELAY && !context.detached) {
|
|
1954
|
+
// Highlight the current entity that is being executed
|
|
1955
|
+
const elementToHighlight =
|
|
1956
|
+
canvasElement.cardBody ||
|
|
1957
|
+
canvasElement.ref ||
|
|
1958
|
+
canvasElement.draggableObject.element;
|
|
1959
|
+
|
|
1960
|
+
highlightElement(elementToHighlight);
|
|
1961
|
+
|
|
1962
|
+
// wait for animation to end
|
|
1963
|
+
await this.waitForAnimationEnd(elementToHighlight);
|
|
1964
|
+
}
|
|
1965
|
+
}
|
|
1966
|
+
|
|
1967
|
+
async waitForAnimationEnd(element: HTMLElement) {
|
|
1968
|
+
return new Promise((resolve) => {
|
|
1969
|
+
if (!hasAnimation(element)) {
|
|
1970
|
+
resolve(null);
|
|
1971
|
+
return;
|
|
1972
|
+
}
|
|
1973
|
+
|
|
1974
|
+
if (element.getAnimations().length === 0) {
|
|
1975
|
+
resolve(null);
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
|
|
1979
|
+
const handleAnimationEnd = () => {
|
|
1980
|
+
element.removeEventListener('animationend', handleAnimationEnd);
|
|
1981
|
+
element.removeEventListener(
|
|
1982
|
+
'animationcancel',
|
|
1983
|
+
handleAnimationEnd
|
|
1984
|
+
);
|
|
1985
|
+
element.removeEventListener(
|
|
1986
|
+
'transitionend',
|
|
1987
|
+
handleAnimationEnd
|
|
1988
|
+
);
|
|
1989
|
+
resolve(null);
|
|
1990
|
+
};
|
|
1991
|
+
|
|
1992
|
+
element.addEventListener('animationend', handleAnimationEnd);
|
|
1993
|
+
element.addEventListener('animationcancel', handleAnimationEnd);
|
|
1994
|
+
// Transition end
|
|
1995
|
+
element.addEventListener('transitionend', handleAnimationEnd);
|
|
1996
|
+
});
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
// Triggers an event that UI elememnts can subscribe to and update their state
|
|
2000
|
+
notifyEntity(entity: TestableEntityState) {
|
|
2001
|
+
this.editor.emit(entity.id, {});
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
async notifyEntityAsync(entity: TestableEntityState) {
|
|
2005
|
+
await this.editor.emitAsync(entity.id, {});
|
|
2006
|
+
}
|
|
2007
|
+
|
|
2008
|
+
/*
|
|
2009
|
+
This function gets triggered every time an entity is executed
|
|
2010
|
+
|
|
2011
|
+
It is used as a bridge between the logic execution and the interface
|
|
2012
|
+
It queues animations for the UI based on the result of the execution
|
|
2013
|
+
And handles any relevant state
|
|
2014
|
+
|
|
2015
|
+
Animations:
|
|
2016
|
+
- When an entity has completed executing, it will animate the output node that calls the next entity
|
|
2017
|
+
- We also animate any return values (outputs on the right of the entity card)
|
|
2018
|
+
*/
|
|
2019
|
+
async onEntityExecuted(
|
|
2020
|
+
entity: TestableEntityState,
|
|
2021
|
+
result: IExecutionResult,
|
|
2022
|
+
context: IExecutionCallbackContext
|
|
2023
|
+
) {
|
|
2024
|
+
if (context.detached) {
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
const isLoopBody =
|
|
2029
|
+
entity.type === EntityType.FunctionDeclaration &&
|
|
2030
|
+
entity.parent.type === EntityType.Loop;
|
|
2031
|
+
|
|
2032
|
+
// We don't add the body of the loop, because it needs to be executed multiple times
|
|
2033
|
+
if (!isLoopBody) {
|
|
2034
|
+
// Add entity to the list of entities that have already been executed
|
|
2035
|
+
this.addToAlreadyExecutedList(entity);
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
// Remove entity from the list of entities that are currently being executed
|
|
2039
|
+
this.removeFromExecutingList(entity);
|
|
2040
|
+
|
|
2041
|
+
this.notifyEntity(entity);
|
|
2042
|
+
this.stopAllTestEntitiesExecutionConnectionAnimations();
|
|
2043
|
+
|
|
2044
|
+
if (
|
|
2045
|
+
[
|
|
2046
|
+
ExecutionState.Stopped,
|
|
2047
|
+
ExecutionState.Paused,
|
|
2048
|
+
ExecutionState.NotStarted
|
|
2049
|
+
].includes(this.state)
|
|
2050
|
+
) {
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
|
|
2054
|
+
if (EXECUTABLE_TYPES.includes(entity.type)) {
|
|
2055
|
+
await this.animateCallerNode(
|
|
2056
|
+
entity as ExecutableEntityState,
|
|
2057
|
+
result.value as
|
|
2058
|
+
| ExecutionTerminationType.Success
|
|
2059
|
+
| ExecutionTerminationType.CaughtError
|
|
2060
|
+
);
|
|
2061
|
+
}
|
|
2062
|
+
|
|
2063
|
+
if (CALLER_TYPES.includes(entity.type)) {
|
|
2064
|
+
// Start animation of connection to the entities on the right
|
|
2065
|
+
const canvasElement = this.editor.getElement(
|
|
2066
|
+
getParentCanvasEntity(entity)?.id
|
|
2067
|
+
);
|
|
2068
|
+
|
|
2069
|
+
canvasElement.draggableObject.connections.forEach((connection) => {
|
|
2070
|
+
const allCallees = [];
|
|
2071
|
+
|
|
2072
|
+
if (result.value === ExecutionTerminationType.CaughtError) {
|
|
2073
|
+
allCallees.push(
|
|
2074
|
+
...((entity as FunctionCallState).errorCalls || [])
|
|
2075
|
+
);
|
|
2076
|
+
} else if (result.value === ExecutionTerminationType.Success) {
|
|
2077
|
+
allCallees.push(
|
|
2078
|
+
...((entity as FunctionCallState).successCalls || [])
|
|
2079
|
+
);
|
|
2080
|
+
allCallees.push(
|
|
2081
|
+
...((entity as FunctionDeclarationState).calls || [])
|
|
2082
|
+
);
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
const calleeEntity = allCallees.find(
|
|
2086
|
+
(callee) =>
|
|
2087
|
+
getCanvasEntityExecutionNodeId(callee) ===
|
|
2088
|
+
connection.endId
|
|
2089
|
+
);
|
|
2090
|
+
|
|
2091
|
+
if (!calleeEntity) {
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
|
|
2095
|
+
const targetIdIsPartOfTest = this.hasEntity(calleeEntity);
|
|
2096
|
+
|
|
2097
|
+
if (!targetIdIsPartOfTest) {
|
|
2098
|
+
return;
|
|
2099
|
+
}
|
|
2100
|
+
|
|
2101
|
+
const isSkipped = this.hasSkipped(calleeEntity);
|
|
2102
|
+
|
|
2103
|
+
if (isSkipped) {
|
|
2104
|
+
return;
|
|
2105
|
+
}
|
|
2106
|
+
|
|
2107
|
+
const isSuccessCall =
|
|
2108
|
+
getCanvasEntitySuccessCallerNodeDOMId(entity) ===
|
|
2109
|
+
connection.startId;
|
|
2110
|
+
const isErrorCall =
|
|
2111
|
+
getCanvasEntityErrorCallerNodeDOMId(entity) ===
|
|
2112
|
+
connection.startId;
|
|
2113
|
+
const isNeutralCall =
|
|
2114
|
+
getCanvasEntityEntryCallerNodeDOMId(entity) ===
|
|
2115
|
+
connection.startId;
|
|
2116
|
+
|
|
2117
|
+
if (isSuccessCall) {
|
|
2118
|
+
connection.color = 'var(--color-main-success)';
|
|
2119
|
+
} else if (isErrorCall) {
|
|
2120
|
+
connection.color = 'var(--color-main-error)';
|
|
2121
|
+
} else if (isNeutralCall) {
|
|
2122
|
+
connection.color = 'var(--color-main-primary)';
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
connection.startAnimation();
|
|
2126
|
+
connection.update();
|
|
2127
|
+
});
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
await delayHalf(this.DELAY);
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
async start(options: YieldOptions = {}) {
|
|
2134
|
+
const tracker = YieldTracker.from(options);
|
|
2135
|
+
|
|
2136
|
+
this.resetUIState();
|
|
2137
|
+
|
|
2138
|
+
this.state = ExecutionState.Running;
|
|
2139
|
+
|
|
2140
|
+
// Emit general 'execution-start' event
|
|
2141
|
+
await this.editor.emitAsync('execution-start', {});
|
|
2142
|
+
|
|
2143
|
+
// Some entities might update after the execution-start event, so we set the connections' state again
|
|
2144
|
+
await this.stopAllTestEntitiesExecutionConnectionAnimationsAsync({
|
|
2145
|
+
tracker
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
const scopeOwnerOfEntryPointEntityOrSelf: TestableEntityState =
|
|
2149
|
+
getParentEntryPoint(this.entryPoint) || this.entryPoint;
|
|
2150
|
+
|
|
2151
|
+
const allScopeChildren =
|
|
2152
|
+
await flattenRelatedCallableElementsOnTheRightAsync(
|
|
2153
|
+
scopeOwnerOfEntryPointEntityOrSelf,
|
|
2154
|
+
false,
|
|
2155
|
+
{ tracker }
|
|
2156
|
+
);
|
|
2157
|
+
|
|
2158
|
+
for (const entity of allScopeChildren) {
|
|
2159
|
+
// Disabled connections that are not part of the test
|
|
2160
|
+
const canvasParentEntity = getParentCanvasEntity(entity);
|
|
2161
|
+
const draggableObject = this.editor.canvas.getDraggableById(
|
|
2162
|
+
getCanvasEntityDraggableContainerDOMId(canvasParentEntity)
|
|
2163
|
+
);
|
|
2164
|
+
this.disabledConnectionsNotInTest(draggableObject, entity);
|
|
2165
|
+
|
|
2166
|
+
await tracker.tick();
|
|
2167
|
+
}
|
|
2168
|
+
|
|
2169
|
+
Logger.log('Starting execution');
|
|
2170
|
+
const clientInterface: IExecutionClient = {
|
|
2171
|
+
onBeforeEntityExecution: this.onBeforeEntityExecution.bind(this),
|
|
2172
|
+
onEntityExecuted: this.onEntityExecuted.bind(this)
|
|
2173
|
+
};
|
|
2174
|
+
try {
|
|
2175
|
+
await this.execute(clientInterface);
|
|
2176
|
+
} catch (e) {
|
|
2177
|
+
if (e && ((e as any).name === 'ExecutionStoppedError' || (e as any).message === 'Execution was stopped')) {
|
|
2178
|
+
Logger.log('Execution was stopped by user.');
|
|
2179
|
+
return;
|
|
2180
|
+
}
|
|
2181
|
+
throw e;
|
|
2182
|
+
}
|
|
2183
|
+
|
|
2184
|
+
// Emit general 'execution-end' event
|
|
2185
|
+
await this.editor.emitAsync('execution-end', {});
|
|
2186
|
+
}
|
|
2187
|
+
|
|
2188
|
+
handleInvalidLogic(): Promise<void> {
|
|
2189
|
+
Logger.log('Invalid logic');
|
|
2190
|
+
return Promise.resolve();
|
|
2191
|
+
}
|
|
2192
|
+
|
|
2193
|
+
async resetUIState(options: YieldOptions = {}) {
|
|
2194
|
+
const tracker = YieldTracker.from(options);
|
|
2195
|
+
|
|
2196
|
+
// Remove the active UI nodes
|
|
2197
|
+
await Promise.all([
|
|
2198
|
+
this.removeAllPopups({ tracker }),
|
|
2199
|
+
this.editor.canvas.unselectAllAsync({ tracker }),
|
|
2200
|
+
this.editor.canvas.unselectAllConnectionsAsync({ tracker })
|
|
2201
|
+
]);
|
|
2202
|
+
|
|
2203
|
+
// Reset the state on all the connections between entities in the test
|
|
2204
|
+
for (const entity of this.entities) {
|
|
2205
|
+
// Cleanup the UI state of the test
|
|
2206
|
+
// Remove animation from success node
|
|
2207
|
+
if (entity.type === EntityType.Loop) {
|
|
2208
|
+
this.cancelSuccessAnimation(
|
|
2209
|
+
entity as PassThroughCallableEntityState
|
|
2210
|
+
);
|
|
2211
|
+
this.cancelErrorAnimation(
|
|
2212
|
+
entity as PassThroughCallableEntityState
|
|
2213
|
+
);
|
|
2214
|
+
|
|
2215
|
+
this.cancelEntryPointCallerAnimation(
|
|
2216
|
+
entity.body as EntryPointEntityState
|
|
2217
|
+
);
|
|
2218
|
+
} else if (PASS_THROUGH_CALLABLE_TYPES.includes(entity.type)) {
|
|
2219
|
+
this.cancelSuccessAnimation(
|
|
2220
|
+
entity as PassThroughCallableEntityState
|
|
2221
|
+
);
|
|
2222
|
+
this.cancelErrorAnimation(
|
|
2223
|
+
entity as PassThroughCallableEntityState
|
|
2224
|
+
);
|
|
2225
|
+
} else if (ENTRY_POINT_TYPES.includes(entity.type)) {
|
|
2226
|
+
this.cancelEntryPointCallerAnimation(
|
|
2227
|
+
entity as EntryPointEntityState
|
|
2228
|
+
);
|
|
2229
|
+
}
|
|
2230
|
+
|
|
2231
|
+
const canvasParentEntity = getParentCanvasEntity(entity);
|
|
2232
|
+
const draggableObject = this.editor.canvas.getDraggableById(
|
|
2233
|
+
getCanvasEntityDraggableContainerDOMId(canvasParentEntity)
|
|
2234
|
+
);
|
|
2235
|
+
|
|
2236
|
+
draggableObject.connections.forEach((connection) => {
|
|
2237
|
+
connection.resetOptions();
|
|
2238
|
+
connection.stopAnimation();
|
|
2239
|
+
});
|
|
2240
|
+
|
|
2241
|
+
await tracker.tick();
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
notifyAllEntities() {
|
|
2246
|
+
this.entities.forEach((entity) => {
|
|
2247
|
+
this.notifyEntity(entity);
|
|
2248
|
+
});
|
|
2249
|
+
}
|
|
2250
|
+
|
|
2251
|
+
async notifyAllEntitiesAsync(options: YieldOptions = {}) {
|
|
2252
|
+
const tracker = YieldTracker.from(options);
|
|
2253
|
+
|
|
2254
|
+
for (const entity of this.entities) {
|
|
2255
|
+
this.notifyEntity(entity);
|
|
2256
|
+
|
|
2257
|
+
await tracker.tick();
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
async stopAllTestEntitiesExecutionConnectionAnimationsAsync(
|
|
2262
|
+
options: YieldOptions = {}
|
|
2263
|
+
) {
|
|
2264
|
+
const tracker = YieldTracker.from(options);
|
|
2265
|
+
|
|
2266
|
+
// Stop the animation on all the connection between entities in the test
|
|
2267
|
+
for (const entity of this.entities) {
|
|
2268
|
+
const canvasParentEntity = getParentCanvasEntity(entity);
|
|
2269
|
+
|
|
2270
|
+
const draggableObject = this.editor.canvas.getDraggableById(
|
|
2271
|
+
getCanvasEntityDraggableContainerDOMId(canvasParentEntity)
|
|
2272
|
+
);
|
|
2273
|
+
|
|
2274
|
+
draggableObject.connections.forEach((connection) => {
|
|
2275
|
+
connection.stopAnimation();
|
|
2276
|
+
});
|
|
2277
|
+
|
|
2278
|
+
await tracker.tick();
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
stopAllTestEntitiesExecutionConnectionAnimations() {
|
|
2283
|
+
// Stop the animation on all the connection between entities in the test
|
|
2284
|
+
this.entities.forEach((entity) => {
|
|
2285
|
+
const canvasParentEntity = getParentCanvasEntity(entity);
|
|
2286
|
+
|
|
2287
|
+
const draggableObject = this.editor.canvas.getDraggableById(
|
|
2288
|
+
getCanvasEntityDraggableContainerDOMId(canvasParentEntity)
|
|
2289
|
+
);
|
|
2290
|
+
|
|
2291
|
+
draggableObject.connections.forEach((connection) => {
|
|
2292
|
+
connection.stopAnimation();
|
|
2293
|
+
});
|
|
2294
|
+
});
|
|
2295
|
+
}
|
|
2296
|
+
}
|