@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.
Files changed (452) hide show
  1. package/README.md +2 -0
  2. package/package.json +109 -0
  3. package/src/App.tsx +31 -0
  4. package/src/Router.tsx +115 -0
  5. package/src/__mocks__/defaultModuleMock.ts +1 -0
  6. package/src/__mocks__/fileMock.ts +1 -0
  7. package/src/__mocks__/styleMock.ts +1 -0
  8. package/src/assets/Clock-11.1s-18px.svg +16 -0
  9. package/src/assets/Clock-11.1s-28px.svg +16 -0
  10. package/src/assets/authentication.svg +1 -0
  11. package/src/assets/canvas-backdrop-0.png +0 -0
  12. package/src/assets/canvas-backdrop-1.png +0 -0
  13. package/src/assets/canvas-backdrop-2.png +0 -0
  14. package/src/assets/canvas-backdrop-3.png +0 -0
  15. package/src/assets/canvas-backdrop-4.png +0 -0
  16. package/src/assets/canvas-backdrop-5.png +0 -0
  17. package/src/assets/canvas-backdrop.png +0 -0
  18. package/src/assets/checkmark-animation.gif +0 -0
  19. package/src/assets/checkmark-animation.mp4 +0 -0
  20. package/src/assets/code-formatting/format-black.svg +6 -0
  21. package/src/assets/code-formatting/format-dark-grey.svg +6 -0
  22. package/src/assets/code-formatting/format-light-grey.svg +6 -0
  23. package/src/assets/code-formatting/format-white.svg +6 -0
  24. package/src/assets/code-formatting/inline-black.svg +5 -0
  25. package/src/assets/code-formatting/inline-dark-grey.svg +5 -0
  26. package/src/assets/code-formatting/inline-light-grey.svg +5 -0
  27. package/src/assets/code-formatting/inline-white.svg +5 -0
  28. package/src/assets/contained-logo-full-word.png +0 -0
  29. package/src/assets/cron-job-color.png +0 -0
  30. package/src/assets/cron-job.png +0 -0
  31. package/src/assets/database-table-color.png +0 -0
  32. package/src/assets/database-table.png +0 -0
  33. package/src/assets/datatype-icons/black/any.svg +1 -0
  34. package/src/assets/datatype-icons/black/binary.svg +1 -0
  35. package/src/assets/datatype-icons/black/boolean.svg +3 -0
  36. package/src/assets/datatype-icons/black/date-time.svg +3 -0
  37. package/src/assets/datatype-icons/black/definition-entity.svg +6 -0
  38. package/src/assets/datatype-icons/black/key-file.svg +1 -0
  39. package/src/assets/datatype-icons/black/list.svg +3 -0
  40. package/src/assets/datatype-icons/black/null.svg +3 -0
  41. package/src/assets/datatype-icons/black/number.svg +13 -0
  42. package/src/assets/datatype-icons/black/project.svg +12 -0
  43. package/src/assets/datatype-icons/black/sql-program.svg +2 -0
  44. package/src/assets/datatype-icons/black/text.svg +3 -0
  45. package/src/assets/datatype-icons/black/unknown.svg +3 -0
  46. package/src/assets/datatype-icons/black/uuid.svg +4 -0
  47. package/src/assets/datatype-icons/black/void.svg +1 -0
  48. package/src/assets/datatype-icons/dark-grey/any.svg +1 -0
  49. package/src/assets/datatype-icons/dark-grey/boolean.svg +3 -0
  50. package/src/assets/datatype-icons/dark-grey/date-time.svg +3 -0
  51. package/src/assets/datatype-icons/dark-grey/definition-entity.svg +6 -0
  52. package/src/assets/datatype-icons/dark-grey/list.svg +3 -0
  53. package/src/assets/datatype-icons/dark-grey/null.svg +3 -0
  54. package/src/assets/datatype-icons/dark-grey/number.svg +13 -0
  55. package/src/assets/datatype-icons/dark-grey/project.svg +12 -0
  56. package/src/assets/datatype-icons/dark-grey/sql-program.svg +2 -0
  57. package/src/assets/datatype-icons/dark-grey/text.svg +3 -0
  58. package/src/assets/datatype-icons/dark-grey/unknown.svg +3 -0
  59. package/src/assets/datatype-icons/dark-grey/uuid.svg +4 -0
  60. package/src/assets/datatype-icons/dark-grey/void.svg +1 -0
  61. package/src/assets/datatype-icons/light-grey/any.svg +1 -0
  62. package/src/assets/datatype-icons/light-grey/boolean.svg +3 -0
  63. package/src/assets/datatype-icons/light-grey/date-time.svg +3 -0
  64. package/src/assets/datatype-icons/light-grey/definition-entity.svg +6 -0
  65. package/src/assets/datatype-icons/light-grey/list.svg +3 -0
  66. package/src/assets/datatype-icons/light-grey/null.svg +3 -0
  67. package/src/assets/datatype-icons/light-grey/number.svg +13 -0
  68. package/src/assets/datatype-icons/light-grey/project.svg +12 -0
  69. package/src/assets/datatype-icons/light-grey/sql-program.svg +2 -0
  70. package/src/assets/datatype-icons/light-grey/text.svg +3 -0
  71. package/src/assets/datatype-icons/light-grey/unknown.svg +3 -0
  72. package/src/assets/datatype-icons/light-grey/uuid.svg +4 -0
  73. package/src/assets/datatype-icons/light-grey/void.svg +1 -0
  74. package/src/assets/edit.png +0 -0
  75. package/src/assets/execution.svg +13 -0
  76. package/src/assets/favicon.svg +14 -0
  77. package/src/assets/file-search.svg +1 -0
  78. package/src/assets/http-endpoint.png +0 -0
  79. package/src/assets/image-input-placeholder.png +0 -0
  80. package/src/assets/logo-full-word-white.png +0 -0
  81. package/src/assets/logo-full-word.png +0 -0
  82. package/src/assets/password.svg +85 -0
  83. package/src/assets/pencil.png +0 -0
  84. package/src/assets/publish-project-rich-icon-2.svg +1 -0
  85. package/src/assets/publish-project-rich-icon.svg +1 -0
  86. package/src/assets/relational-database.png +0 -0
  87. package/src/assets/resources.svg +3 -0
  88. package/src/assets/resume-icon-14px.png +0 -0
  89. package/src/assets/server.png +0 -0
  90. package/src/assets/small-status/checkmark.svg +4 -0
  91. package/src/assets/small-status/error.svg +4 -0
  92. package/src/assets/small-status/loading.svg +4 -0
  93. package/src/assets/small-status/skipped.svg +11 -0
  94. package/src/assets/sql-connection-config.svg +1 -0
  95. package/src/assets/sql-row-transformer.svg +1 -0
  96. package/src/assets/ssl-certificate-config.svg +1 -0
  97. package/src/assets/sync.svg +1 -0
  98. package/src/assets/testing-logic-icon.svg +1 -0
  99. package/src/assets/versions.svg +25 -0
  100. package/src/assets/visual-programming-icon.svg +1 -0
  101. package/src/assets/warning-sign-24px.png +0 -0
  102. package/src/auth/index.ts +318 -0
  103. package/src/components/DialogLoader.tsx +94 -0
  104. package/src/components/EntityDialogHeader.tsx +110 -0
  105. package/src/components/EntityDialogSectionHeader.tsx +214 -0
  106. package/src/components/GalleryAddExternalIntegrationInfoDialog.tsx +87 -0
  107. package/src/components/GenerateProjectStartingLogicPromptDialog.tsx +281 -0
  108. package/src/components/LegacyRouteRedirector.tsx +55 -0
  109. package/src/components/ProPlanChip.tsx +23 -0
  110. package/src/components/ReportBugDialog.tsx +412 -0
  111. package/src/components/RequestIntegrationAccessDialog.tsx +261 -0
  112. package/src/components/UseTemplateProjectDialog.tsx +193 -0
  113. package/src/components/WorkspaceLayout.tsx +152 -0
  114. package/src/components/animated-svg/AnimatedCheckmark.tsx +41 -0
  115. package/src/components/animated-svg/AnimatedCrossmark.tsx +51 -0
  116. package/src/components/animated-svg/AnimatedEmailSending.tsx +38 -0
  117. package/src/components/animated-svg/AnimatedLoading.tsx +72 -0
  118. package/src/components/animated-svg/animated-svg.css +239 -0
  119. package/src/components/canvas/Canvas.tsx +16 -0
  120. package/src/components/canvas/CreateEntityMenu.tsx +2020 -0
  121. package/src/components/canvas/canvas.css +10 -0
  122. package/src/components/canvas/create-entity-menu.css +579 -0
  123. package/src/components/canvas-search/CanvasSearch.tsx +501 -0
  124. package/src/components/canvas-search/canvas-search.css +126 -0
  125. package/src/components/canvas-settings-menu/CanvasSettingsMenuButton.tsx +515 -0
  126. package/src/components/canvas-settings-menu/canvas-settings-menu.css +96 -0
  127. package/src/components/circular-image-upload/CircularImageUpload.tsx +113 -0
  128. package/src/components/circular-image-upload/circular-image-upload.css +69 -0
  129. package/src/components/costs/CostsDialog.tsx +459 -0
  130. package/src/components/data-type/DataTypeBuilder.tsx +3127 -0
  131. package/src/components/data-type/data-type-builder.css +45 -0
  132. package/src/components/dialogs/BetaAcknowledgeDialog.tsx +43 -0
  133. package/src/components/dialogs/ComplexDataDialog.tsx +458 -0
  134. package/src/components/dialogs/CronBuilderDialog.tsx +2145 -0
  135. package/src/components/dialogs/ExternalIntegrationConnections.tsx +565 -0
  136. package/src/components/dialogs/JsonEditorDialog.tsx +1392 -0
  137. package/src/components/dialogs/StringEditorDialog.tsx +268 -0
  138. package/src/components/dialogs/argument-declaration/ArgumentDeclaration.tsx +1167 -0
  139. package/src/components/dialogs/argument-declaration/ArgumentDeclarationDialogContent.tsx +128 -0
  140. package/src/components/dialogs/beta-dialog.css +165 -0
  141. package/src/components/dialogs/condition/Condition.tsx +431 -0
  142. package/src/components/dialogs/condition/ConditionDialogContent.tsx +126 -0
  143. package/src/components/dialogs/definition-entity/DefinitionEntityDialogContent.tsx +973 -0
  144. package/src/components/dialogs/function-call/FunctionCall.tsx +442 -0
  145. package/src/components/dialogs/function-call/FunctionCallDialogContent.tsx +126 -0
  146. package/src/components/dialogs/function-declaration/FunctionDeclaration.tsx +926 -0
  147. package/src/components/dialogs/function-declaration/FunctionDeclarationDialogContent.tsx +124 -0
  148. package/src/components/dialogs/generating-project-starting-logic-overlay/GeneratingProjectStartingLogicOverlay.tsx +176 -0
  149. package/src/components/dialogs/generating-project-starting-logic-overlay/generating-project-starting-logic-overlay.css +13 -0
  150. package/src/components/dialogs/global-event/GlobalEvent.tsx +475 -0
  151. package/src/components/dialogs/global-event/GlobalEventDialogContent.tsx +126 -0
  152. package/src/components/dialogs/help/HelpDialog.tsx +217 -0
  153. package/src/components/dialogs/help/HelpDilalogHomeContent.tsx +178 -0
  154. package/src/components/dialogs/help/help-dialog.css +116 -0
  155. package/src/components/dialogs/help/help-icon/HelpIconButton.tsx +41 -0
  156. package/src/components/dialogs/help/help-icon/help-icon.css +9 -0
  157. package/src/components/dialogs/input-map/InputMap.tsx +635 -0
  158. package/src/components/dialogs/input-map/InputMapDialogContent.tsx +126 -0
  159. package/src/components/dialogs/json-editor-dialog.css +4 -0
  160. package/src/components/dialogs/loop/Loop.tsx +650 -0
  161. package/src/components/dialogs/loop/LoopDialogContent.tsx +122 -0
  162. package/src/components/dialogs/operation/Operation.tsx +440 -0
  163. package/src/components/dialogs/operation/OperationDialogContent.tsx +126 -0
  164. package/src/components/dialogs/output-map/OutputMap.tsx +536 -0
  165. package/src/components/dialogs/output-map/OutputMapDialogContent.tsx +126 -0
  166. package/src/components/dialogs/property/Property.tsx +1490 -0
  167. package/src/components/dialogs/property/PropertyDialogContent.tsx +106 -0
  168. package/src/components/dialogs/search-statement/ColumnSelector.tsx +334 -0
  169. package/src/components/dialogs/search-statement/ConditionBuilder.tsx +750 -0
  170. package/src/components/dialogs/search-statement/DataAggregationSection.tsx +621 -0
  171. package/src/components/dialogs/search-statement/DataSourceSelection.tsx +734 -0
  172. package/src/components/dialogs/search-statement/EntityMetadataSection.tsx +135 -0
  173. package/src/components/dialogs/search-statement/FilterConditionsSection.tsx +151 -0
  174. package/src/components/dialogs/search-statement/InlineInputMap.tsx +153 -0
  175. package/src/components/dialogs/search-statement/LiteralValue.tsx +616 -0
  176. package/src/components/dialogs/search-statement/MainSourceAndInputsSection.tsx +271 -0
  177. package/src/components/dialogs/search-statement/NestedSearchStatementBuilder.tsx +170 -0
  178. package/src/components/dialogs/search-statement/OutputFormatSection.tsx +1779 -0
  179. package/src/components/dialogs/search-statement/ResultsSection.tsx +344 -0
  180. package/src/components/dialogs/search-statement/SearchStatementBuilder.tsx +251 -0
  181. package/src/components/dialogs/search-statement/SearchStatementDialogContent.tsx +398 -0
  182. package/src/components/dialogs/search-statement/ValueSelector.tsx +766 -0
  183. package/src/components/dialogs/search-statement/search-statement-context.tsx +1630 -0
  184. package/src/components/dialogs/search-statement/search-statement-dialog.css +56 -0
  185. package/src/components/dialogs/search-statement/test.sql +111 -0
  186. package/src/components/dialogs/value-descriptor/ValueDescriptor.tsx +824 -0
  187. package/src/components/dialogs/value-descriptor/ValueDescriptorDialogContent.tsx +124 -0
  188. package/src/components/dialogs/variable-declaration/VariableDeclaration.tsx +836 -0
  189. package/src/components/dialogs/variable-declaration/VariableDeclarationDialogContent.tsx +106 -0
  190. package/src/components/dialogs/variable-instance/VariableInstance.tsx +443 -0
  191. package/src/components/dialogs/variable-instance/VariableInstanceDialogContent.tsx +124 -0
  192. package/src/components/draggable-entity-card/ArgumentDeclaration.tsx +736 -0
  193. package/src/components/draggable-entity-card/CollapseEntityButton.tsx +170 -0
  194. package/src/components/draggable-entity-card/ConditionCard.tsx +1062 -0
  195. package/src/components/draggable-entity-card/ConnectionDeleteButton.tsx +309 -0
  196. package/src/components/draggable-entity-card/DataTypeIcon.tsx +624 -0
  197. package/src/components/draggable-entity-card/DraggableEntityCard.tsx +617 -0
  198. package/src/components/draggable-entity-card/ErrorMapProperty.tsx +464 -0
  199. package/src/components/draggable-entity-card/EventCard.tsx +700 -0
  200. package/src/components/draggable-entity-card/ExecutionInProgressValue.tsx +327 -0
  201. package/src/components/draggable-entity-card/FunctionDeclarationCard.tsx +819 -0
  202. package/src/components/draggable-entity-card/InputMapProperty.tsx +1067 -0
  203. package/src/components/draggable-entity-card/InternalCall.tsx +978 -0
  204. package/src/components/draggable-entity-card/InternalCallExecutionNode.tsx +643 -0
  205. package/src/components/draggable-entity-card/LogicScopeCallerNode.tsx +262 -0
  206. package/src/components/draggable-entity-card/LoopCard.tsx +791 -0
  207. package/src/components/draggable-entity-card/MainValueInput.tsx +523 -0
  208. package/src/components/draggable-entity-card/MainValueOutput.tsx +458 -0
  209. package/src/components/draggable-entity-card/MethodDeclaration.tsx +1088 -0
  210. package/src/components/draggable-entity-card/NestedCondition.tsx +1025 -0
  211. package/src/components/draggable-entity-card/OutputMapProperty.tsx +843 -0
  212. package/src/components/draggable-entity-card/PassthroughEntityCard.tsx +1247 -0
  213. package/src/components/draggable-entity-card/ReturnedError.tsx +549 -0
  214. package/src/components/draggable-entity-card/SmallSuccessFailureNodes.tsx +523 -0
  215. package/src/components/draggable-entity-card/SuccessFailureNodes.tsx +509 -0
  216. package/src/components/draggable-entity-card/TestEntityButton.tsx +946 -0
  217. package/src/components/draggable-entity-card/TestMenu.tsx +523 -0
  218. package/src/components/draggable-entity-card/TestMenuValidationDropdown.tsx +84 -0
  219. package/src/components/draggable-entity-card/UnreachableMarker.tsx +114 -0
  220. package/src/components/draggable-entity-card/VariableCard.tsx +1577 -0
  221. package/src/components/draggable-entity-card/VariableScopeMarker.tsx +117 -0
  222. package/src/components/draggable-entity-card/collapse-entity-button.css +44 -0
  223. package/src/components/draggable-entity-card/definition-entity/DefinitionEntityCard.tsx +1181 -0
  224. package/src/components/draggable-entity-card/definition-entity/DefinitionEntityIcon.tsx +36 -0
  225. package/src/components/draggable-entity-card/definition-entity/DefinitionEntityProperty.tsx +478 -0
  226. package/src/components/draggable-entity-card/definition-entity/DynamicFooterActions.tsx +112 -0
  227. package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/ExportCredentialsFooterAction.tsx +461 -0
  228. package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/RestablishConnectionFooterAction.tsx +199 -0
  229. package/src/components/draggable-entity-card/definition-entity/actions/external-integration-connection/restablish-connection-footer-action.css +85 -0
  230. package/src/components/draggable-entity-card/definition-entity/actions/google-drive/GoogleDriveFilePickerAPIFooterAction.tsx +277 -0
  231. package/src/components/draggable-entity-card/definition-entity/actions/google-drive/google-drive-file-picker-api-footer-action.css +107 -0
  232. package/src/components/draggable-entity-card/definition-entity/actions/persisted-entity/DatabaseFooterAction.tsx +452 -0
  233. package/src/components/draggable-entity-card/definition-entity/actions/persisted-entity/database-footer-action.css +86 -0
  234. package/src/components/draggable-entity-card/definition-entity/definition-entity-card.css +17 -0
  235. package/src/components/draggable-entity-card/draggable-entity-card.css +1140 -0
  236. package/src/components/draggable-entity-card/entity-locked-icon/EntityLockedIcon.tsx +133 -0
  237. package/src/components/draggable-entity-card/entity-locked-icon/entity-locked.css +8 -0
  238. package/src/components/draggable-entity-card/expand-properties-icon-button/ExpandPropertiesIconButton.tsx +84 -0
  239. package/src/components/draggable-entity-card/expand-properties-icon-button/expand-properties-icon-button.css +21 -0
  240. package/src/components/draggable-entity-card/implement-entity-icon/ImplementEntityIcon.tsx +74 -0
  241. package/src/components/draggable-entity-card/implement-entity-icon/implement-entity-icon.css +13 -0
  242. package/src/components/draggable-entity-card/logic-error/LogicErrorIconMenu.tsx +424 -0
  243. package/src/components/draggable-entity-card/logic-error/logic-error.css +23 -0
  244. package/src/components/draggable-entity-card/new-card-input-button/NewCardInputButton.tsx +193 -0
  245. package/src/components/draggable-entity-card/new-card-input-button/NewDynamicInputButton.tsx +214 -0
  246. package/src/components/draggable-entity-card/new-card-input-button/new-card-input-button.css +71 -0
  247. package/src/components/draggable-entity-card/new-card-output-button/NewCardOutputButton.tsx +192 -0
  248. package/src/components/draggable-entity-card/new-card-output-button/new-card-output-button.css +71 -0
  249. package/src/components/draggable-entity-card/termination-statement/TerminationStatementCard.tsx +1543 -0
  250. package/src/components/draggable-entity-card/termination-statement/termination-statement-card.css +17 -0
  251. package/src/components/draggable-entity-card/test-entity-button.css +55 -0
  252. package/src/components/draggable-entity-card/test-menu.css +181 -0
  253. package/src/components/draggable-entity-card/unreachable-marker.css +43 -0
  254. package/src/components/draggable-entity-card/variable-scope-marker.css +22 -0
  255. package/src/components/dynamic-value/DynamicValue.tsx +2395 -0
  256. package/src/components/dynamic-value/DynamicValueEntry.tsx +1957 -0
  257. package/src/components/dynamic-value/dynamic-value.css +230 -0
  258. package/src/components/editor/ElyxMonacoEditor.tsx +38 -0
  259. package/src/components/entity-error/EntityErrorListItem.tsx +47 -0
  260. package/src/components/entity-error/entity-error.css +198 -0
  261. package/src/components/entity-icon/EntityIcon.tsx +292 -0
  262. package/src/components/entity-icon/entity-icon.css +39 -0
  263. package/src/components/gallery-card/CreateNewProject.tsx +222 -0
  264. package/src/components/gallery-card/GalleryCard.tsx +171 -0
  265. package/src/components/gallery-card/MarketplaceCard.tsx +87 -0
  266. package/src/components/gallery-card/ProjectDuplicationCard.tsx +575 -0
  267. package/src/components/gallery-card/gallery-card.css +25 -0
  268. package/src/components/notifications/NotificationsIconButton.tsx +124 -0
  269. package/src/components/notifications/NotificationsPanel.tsx +385 -0
  270. package/src/components/notifications/notifications.css +189 -0
  271. package/src/components/online-users/LocalOnlineUsers.tsx +175 -0
  272. package/src/components/online-users/PageOnlineUsers.tsx +297 -0
  273. package/src/components/online-users/online-users.css +72 -0
  274. package/src/components/page-backdrop/PageBackdrop.tsx +8 -0
  275. package/src/components/page-backdrop/page-backdrop.css +7 -0
  276. package/src/components/project-configuration/DeleteProjectConfirmationDialog.tsx +134 -0
  277. package/src/components/project-configuration/ProjectConfigurationDialog.tsx +972 -0
  278. package/src/components/project-configuration/ProjectDataForm.tsx +121 -0
  279. package/src/components/project-configuration/UnpublishProjectConfirmationDialog.tsx +162 -0
  280. package/src/components/project-configuration/project-configuration-content.css +209 -0
  281. package/src/components/project-name/ProjectName.tsx +2025 -0
  282. package/src/components/project-name/project-name.css +599 -0
  283. package/src/components/publishing/Publication.tsx +133 -0
  284. package/src/components/publishing/history/PublicationHistoryContent.tsx +414 -0
  285. package/src/components/publishing/history/PublicationHistoryDialog.tsx +234 -0
  286. package/src/components/publishing/preview/PublicationPreviewDialog.tsx +1158 -0
  287. package/src/components/publishing/preview/PublishingPriceForecast.tsx +160 -0
  288. package/src/components/publishing/preview/PublishingResourcesDetails.tsx +91 -0
  289. package/src/components/publishing/publication-sequence/PublishingSequenceContent.tsx +375 -0
  290. package/src/components/publishing/publication-sequence/PublishingSequenceDialog.tsx +344 -0
  291. package/src/components/publishing/publishing-dialog.css +142 -0
  292. package/src/components/publishing/utils.ts +227 -0
  293. package/src/components/resources/ResourcesDialog.tsx +591 -0
  294. package/src/components/resources/UpgradeBanner.tsx +102 -0
  295. package/src/components/resources/codebase/CodebaseDetails.tsx +156 -0
  296. package/src/components/resources/cron-job/CronJobsList.tsx +532 -0
  297. package/src/components/resources/functions/FunctionsList.tsx +454 -0
  298. package/src/components/resources/http-api/HttpAPI.tsx +566 -0
  299. package/src/components/resources/http-api/HttpAPIClientModule.tsx +37 -0
  300. package/src/components/resources/logs/LogsViewer.tsx +768 -0
  301. package/src/components/resources/query.ts +74 -0
  302. package/src/components/resources/relational-database/DatabaseTable.tsx +905 -0
  303. package/src/components/resources/relational-database/RelationalDatabase.tsx +83 -0
  304. package/src/components/resources/relational-database/RelationalDatabaseSecrets.tsx +361 -0
  305. package/src/components/resources/resources-dialog.css +74 -0
  306. package/src/components/test-relational-database/DatabaseTable.tsx +913 -0
  307. package/src/components/test-relational-database/TestDatabaseDialogContent.tsx +670 -0
  308. package/src/components/test-relational-database/query.ts +74 -0
  309. package/src/components/toolbar/ToolBar.tsx +236 -0
  310. package/src/components/toolbar/toolbar.css +78 -0
  311. package/src/components/transaction-history/TransactionHistoryDialog.tsx +268 -0
  312. package/src/components/user/CurrentUserAvatar.tsx +65 -0
  313. package/src/components/user/UserChip.tsx +62 -0
  314. package/src/components/user/user.css +39 -0
  315. package/src/components/user-profile/ChangePasswordForm.tsx +67 -0
  316. package/src/components/user-profile/OwnUserProfileContent.tsx +665 -0
  317. package/src/components/user-profile/PublicUserProfileContent.tsx +99 -0
  318. package/src/components/user-profile/UserDataForm.tsx +75 -0
  319. package/src/components/user-profile/UserProfileDialog.tsx +110 -0
  320. package/src/components/user-profile/user-profile-content.css +25 -0
  321. package/src/config.ts +130 -0
  322. package/src/globals.d.ts +13 -0
  323. package/src/index.html +27 -0
  324. package/src/index.tsx +23 -0
  325. package/src/lib/badge/Badge.tsx +35 -0
  326. package/src/lib/badge/badge.css +32 -0
  327. package/src/lib/button/Button.tsx +129 -0
  328. package/src/lib/button/button.css +145 -0
  329. package/src/lib/canvas/canvas-undo-redo.ts +263 -0
  330. package/src/lib/canvas/defs.ts +170 -0
  331. package/src/lib/canvas/index.test.ts +189 -0
  332. package/src/lib/canvas/index.ts +6999 -0
  333. package/src/lib/canvas/utils.ts +59 -0
  334. package/src/lib/card/Card.tsx +62 -0
  335. package/src/lib/card/LoadingCard.tsx +82 -0
  336. package/src/lib/card/card.css +259 -0
  337. package/src/lib/chip/Chip.tsx +79 -0
  338. package/src/lib/chip/chip.css +0 -0
  339. package/src/lib/dialog/Dialog.tsx +122 -0
  340. package/src/lib/dialog/SmallDialog.tsx +61 -0
  341. package/src/lib/dialog/dialog.css +40 -0
  342. package/src/lib/display-data-structure/index.tsx +21 -0
  343. package/src/lib/dropdown/CanvasDropdownMenuCard.tsx +68 -0
  344. package/src/lib/dropdown/CanvasDropdownMenuCardOption.tsx +136 -0
  345. package/src/lib/dropdown/DropdownButton.tsx +104 -0
  346. package/src/lib/dropdown/DropdownMenuCard.tsx +324 -0
  347. package/src/lib/dropdown/DropdownMenuPopup.tsx +27 -0
  348. package/src/lib/dropdown/dropdown-button.css +76 -0
  349. package/src/lib/dropdown/dropdown-menu.css +151 -0
  350. package/src/lib/json-editor/RawJsonEditor.tsx +137 -0
  351. package/src/lib/json-editor/json-editor.css +35 -0
  352. package/src/lib/loader/Loader.tsx +120 -0
  353. package/src/lib/loader/loader.css +38 -0
  354. package/src/lib/pagination/Pagination.tsx +64 -0
  355. package/src/lib/popup/CanvasPopupBaseComponent.tsx +103 -0
  356. package/src/lib/popup/Popup.tsx +243 -0
  357. package/src/lib/popup/popup.css +16 -0
  358. package/src/lib/table/RowForm.tsx +301 -0
  359. package/src/lib/table/Table.tsx +1069 -0
  360. package/src/lib/table/table.css +249 -0
  361. package/src/lib/table/types.ts +108 -0
  362. package/src/lib/text-area/TextArea.tsx +183 -0
  363. package/src/lib/text-area/text-area.css +156 -0
  364. package/src/lib/text-field/TextField.tsx +218 -0
  365. package/src/lib/text-field/index.ts +8 -0
  366. package/src/lib/text-field/text-field.css +201 -0
  367. package/src/lib/tooltip/Tooltip.tsx +24 -0
  368. package/src/lib/tooltip/tooltip.css +17 -0
  369. package/src/localization/index.ts +47 -0
  370. package/src/main.css +343 -0
  371. package/src/pages/Auth.tsx +848 -0
  372. package/src/pages/Editor.tsx +883 -0
  373. package/src/pages/ErrorPage.tsx +179 -0
  374. package/src/pages/Gallery.tsx +1693 -0
  375. package/src/pages/NewPaymentMethodCallback.tsx +53 -0
  376. package/src/pages/NotFoundPage.tsx +126 -0
  377. package/src/pages/PricingPlans.tsx +155 -0
  378. package/src/pages/auth.css +304 -0
  379. package/src/pages/gallery.css +421 -0
  380. package/src/payments/index.ts +187 -0
  381. package/src/popup-notification/index.ts +90 -0
  382. package/src/services/database/index.ts +1 -0
  383. package/src/services/database/utils.ts +1301 -0
  384. package/src/services/editor/CanvasElement.tsx +2934 -0
  385. package/src/services/editor/CanvasElementConnectionDeleteButton.ts +204 -0
  386. package/src/services/editor/CanvasPopup.tsx +749 -0
  387. package/src/services/editor/EditorService.ts +8157 -0
  388. package/src/services/editor/area.ts +1312 -0
  389. package/src/services/editor/connections.ts +1019 -0
  390. package/src/services/editor/create/condition.ts +25 -0
  391. package/src/services/editor/create/definition-entity.ts +29 -0
  392. package/src/services/editor/create/function-call.ts +25 -0
  393. package/src/services/editor/create/global-event.ts +33 -0
  394. package/src/services/editor/create/loop.ts +25 -0
  395. package/src/services/editor/create/operation.ts +30 -0
  396. package/src/services/editor/create/utils.ts +140 -0
  397. package/src/services/editor/create/variable-declaration.ts +135 -0
  398. package/src/services/editor/create/variable-instance.ts +100 -0
  399. package/src/services/editor/editor-ui-extensions-context.ts +43 -0
  400. package/src/services/editor/entities-metadata.json +9310 -0
  401. package/src/services/editor/icons.ts +1093 -0
  402. package/src/services/editor/index.ts +1 -0
  403. package/src/services/editor/layout.ts +102 -0
  404. package/src/services/editor/modules/built-in-function-implementations/base.ts +14 -0
  405. package/src/services/editor/modules/built-in-function-implementations/create-persisted-entity/index.ts +56 -0
  406. package/src/services/editor/modules/built-in-function-implementations/delete-persisted-entity/index.ts +55 -0
  407. package/src/services/editor/modules/built-in-function-implementations/index.ts +4 -0
  408. package/src/services/editor/modules/built-in-function-implementations/update-persisted-entity/index.ts +56 -0
  409. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/get-files.ts +183 -0
  410. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/list-drives.ts +124 -0
  411. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/list-root-folders.ts +125 -0
  412. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/smart-fetch-document.ts +702 -0
  413. package/src/services/editor/modules/operations-implementations/external-integrations/google-drive/upload-document.ts +535 -0
  414. package/src/services/editor/modules/operations-implementations/external-integrations/google-gemini/generate-content.ts +193 -0
  415. package/src/services/editor/modules/operations-implementations/external-integrations/google-mail/get-emails.ts +586 -0
  416. package/src/services/editor/modules/operations-implementations/external-integrations/google-mail/send-email.ts +386 -0
  417. package/src/services/editor/modules/operations-implementations/external-integrations/index.ts +12 -0
  418. package/src/services/editor/modules/operations-implementations/external-integrations/slack/channels.ts +240 -0
  419. package/src/services/editor/modules/operations-implementations/external-integrations/slack/messages.ts +210 -0
  420. package/src/services/editor/modules/operations-implementations/external-integrations/slack/replies.ts +200 -0
  421. package/src/services/editor/modules/operations-implementations/external-integrations/slack/send-message.ts +177 -0
  422. package/src/services/editor/modules/operations-implementations/index.ts +1 -0
  423. package/src/services/editor/modules/search-node-implementation/index.ts +42 -0
  424. package/src/services/editor/modules/sql-migrations-generation.tsx +1054 -0
  425. package/src/services/editor/publication/publication.ts +578 -0
  426. package/src/services/editor/ui.ts +1348 -0
  427. package/src/services/editor/utils.ts +5868 -0
  428. package/src/services/editor/value-store.ts +619 -0
  429. package/src/services/execution/built-in-function-implementations.ts +422 -0
  430. package/src/services/execution/index.ts +4747 -0
  431. package/src/services/execution/logic.ts +121 -0
  432. package/src/services/execution/test-instance.tsx +2296 -0
  433. package/src/services/execution/utils.ts +33 -0
  434. package/src/services/execution/value-resolution.test.ts +424 -0
  435. package/src/services/execution/value-resolution.ts +4087 -0
  436. package/src/services/integrations/ExternalIntegrationsService.ts +439 -0
  437. package/src/services/integrations/api.ts +175 -0
  438. package/src/services/local-relational-database/idb_helper.ts +66 -0
  439. package/src/services/local-relational-database/index.ts +3308 -0
  440. package/src/services/local-relational-database/utils.ts +403 -0
  441. package/src/services/notifications/index.ts +525 -0
  442. package/src/services/user/index.ts +144 -0
  443. package/src/setupTests.ts +1 -0
  444. package/src/socket/socket.ts +248 -0
  445. package/src/socket/utils.ts +10 -0
  446. package/src/store/workspace.ts +12 -0
  447. package/src/theme.ts +19 -0
  448. package/src/utils/DOM.ts +39 -0
  449. package/src/utils/date.ts +169 -0
  450. package/src/utils/index.ts +158 -0
  451. package/src/utils/react.tsx +679 -0
  452. 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
+ }