@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,2020 @@
1
+ import React, {
2
+ useEffect,
3
+ useLayoutEffect,
4
+ useRef,
5
+ useState,
6
+ useDeferredValue,
7
+ useCallback
8
+ } from 'react';
9
+ import cx from 'classnames';
10
+ import './create-entity-menu.css';
11
+ import {
12
+ EntityType,
13
+ getFirstNonLoopScopeOwner,
14
+ getScopeOwner,
15
+ VALUE_WRITING_TYPES,
16
+ ValueDescriptorState,
17
+ mapActionDescriptorToTypeItRepresents,
18
+ checkScopeCompatibility,
19
+ ScopeCompatibilityType,
20
+ InternalCallState,
21
+ resolveEntityName,
22
+ getAllVariablesInScope as _getAllVariablesInScope,
23
+ ActionDescriptorState,
24
+ ArgumentDeclarationState,
25
+ BreakStatementState,
26
+ BuiltInBaseEntityState,
27
+ CallerEntityState,
28
+ ContinueStatementState,
29
+ DefinitionEntityState,
30
+ ExecutableEntityState,
31
+ FunctionDeclarationState,
32
+ GlobalEventState,
33
+ InputMapState,
34
+ OutputMapState,
35
+ ReturnStatementState,
36
+ SearchState,
37
+ VariableDeclarationState,
38
+ VariableState,
39
+ READABLE_ENTITY_TYPES
40
+ } from '@elyx-code/project-logic-tree';
41
+ import {
42
+ ENTITY_FA_ICON_NAMES,
43
+ getCreateEntityMenuFailureNodeId,
44
+ getCreateEntityMenuHeaderNodeId,
45
+ getCreateEntityMenuMainValueInputNodeId,
46
+ getCreateEntityMenuMainValueOutputNodeId,
47
+ getCreateEntityMenuSuccessNodeId
48
+ } from '../../services/editor/ui';
49
+ import TextField from '../../lib/text-field/TextField';
50
+ import {
51
+ ConnectionNodeType,
52
+ EditorService,
53
+ ENTITIES_METADATA
54
+ } from '../../services/editor';
55
+ import { Canvas, CanvasObject } from '../../lib/canvas';
56
+ import { resolveEntityIcon } from '../../services/editor/icons';
57
+ import EntityIcon from '../entity-icon/EntityIcon';
58
+ import Badge from '../../lib/badge/Badge';
59
+ import { useCanvasPopup } from '../../services/editor/CanvasPopup';
60
+ import CanvasPopupBaseComponent from '../../lib/popup/CanvasPopupBaseComponent';
61
+ import { ICanvasMenuElementComponentProps } from '../../services/editor/CanvasElement';
62
+ import { Logger, yieldToEventLoop } from '@elyx-code/common-ts-utils';
63
+ import { CircularProgress } from '@mui/material'; // <-- Import the loader
64
+
65
+ export interface ICreateEntityMenuProps extends ICanvasMenuElementComponentProps {
66
+ project: EditorService;
67
+ canvasObject: CanvasObject;
68
+ connectedNodeType: ConnectionNodeType;
69
+ // Only the source entity or the target entity can be defined at the same time
70
+ connectedSource?:
71
+ | CallerEntityState
72
+ | ArgumentDeclarationState
73
+ | OutputMapState
74
+ | ValueDescriptorState
75
+ | VariableState;
76
+ connectedTarget?: ExecutableEntityState | InputMapState | VariableState;
77
+ onCreateBaseEntity: (entityType: EntityType, event: MouseEvent) => void;
78
+ onCreateFromSpecificEntity: (
79
+ from: SearchableEntity,
80
+ event: MouseEvent
81
+ ) => void;
82
+ }
83
+
84
+ export interface ICreateBaseEntityResultOption {
85
+ type: EntityType;
86
+ icon: string;
87
+ label: {
88
+ singular: string;
89
+ };
90
+ disabledReasons: string[];
91
+ searchScore?: number;
92
+ matchedKeywords?: string[];
93
+ searchQuery?: string;
94
+ }
95
+
96
+ type SearchableEntity =
97
+ | VariableDeclarationState
98
+ | DefinitionEntityState
99
+ | SearchState
100
+ | ActionDescriptorState
101
+ | FunctionDeclarationState
102
+ | GlobalEventState
103
+ | ReturnStatementState
104
+ | BreakStatementState
105
+ | ContinueStatementState
106
+ | InternalCallState
107
+ | BuiltInBaseEntityState;
108
+
109
+ export interface ICreateSpecificEntityResultOption {
110
+ entity: SearchableEntity;
111
+ disabledReasons: string[];
112
+ searchScore?: number;
113
+ matchedKeywords?: string[];
114
+ searchQuery?: string;
115
+ }
116
+
117
+ // Fuzzy search and ranking utility
118
+ function computeSearchScore(
119
+ query: string,
120
+ mainName: string,
121
+ keywords: { word: string; score: number }[]
122
+ ): { score: number; matchedKeywords: string[] } {
123
+ if (!query) return { score: 100, matchedKeywords: [] };
124
+ const lowerQuery = query.toLowerCase().trim();
125
+ const lowerName = mainName.toLowerCase();
126
+
127
+ let matchedKeywords: string[] = [];
128
+
129
+ const scoreWord = (wordQuery: string) => {
130
+ let maxScore = 0;
131
+ if (lowerName === wordQuery) maxScore = Math.max(maxScore, 100);
132
+ else if (lowerName.startsWith(wordQuery))
133
+ maxScore = Math.max(maxScore, 80);
134
+ else if (lowerName.includes(wordQuery))
135
+ maxScore = Math.max(maxScore, 60);
136
+
137
+ for (const kw of keywords) {
138
+ const lowerKw = kw.word.toLowerCase();
139
+ let matchScore = 0;
140
+ if (lowerKw === wordQuery) matchScore = 90;
141
+ else if (lowerKw.startsWith(wordQuery)) matchScore = 70;
142
+ else if (lowerKw.includes(wordQuery)) matchScore = 50;
143
+
144
+ if (matchScore > 0) {
145
+ const finalMatchScore = matchScore * kw.score;
146
+ if (finalMatchScore > maxScore) {
147
+ maxScore = finalMatchScore;
148
+ if (!matchedKeywords.includes(kw.word)) {
149
+ matchedKeywords.push(kw.word);
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ if (maxScore === 0) {
156
+ let queryIdx = 0;
157
+ for (
158
+ let i = 0;
159
+ i < lowerName.length && queryIdx < wordQuery.length;
160
+ i++
161
+ ) {
162
+ if (lowerName[i] === wordQuery[queryIdx]) {
163
+ queryIdx++;
164
+ }
165
+ }
166
+ if (queryIdx === wordQuery.length) {
167
+ maxScore = 40;
168
+ } else if (queryIdx > 2) {
169
+ maxScore = 10 + queryIdx;
170
+ }
171
+
172
+ for (const kw of keywords) {
173
+ const lowerKw = kw.word.toLowerCase();
174
+ let kIdx = 0;
175
+ for (
176
+ let i = 0;
177
+ i < lowerKw.length && kIdx < wordQuery.length;
178
+ i++
179
+ ) {
180
+ if (lowerKw[i] === wordQuery[kIdx]) {
181
+ kIdx++;
182
+ }
183
+ }
184
+ if (kIdx === wordQuery.length) {
185
+ const fuzzyScore = 30 * kw.score;
186
+ if (fuzzyScore > maxScore) {
187
+ maxScore = fuzzyScore;
188
+ if (!matchedKeywords.includes(kw.word)) {
189
+ matchedKeywords.push(kw.word);
190
+ }
191
+ }
192
+ }
193
+ }
194
+ }
195
+ return maxScore;
196
+ };
197
+
198
+ const queryWords = lowerQuery.split(/[\s\-_/]+/).filter(Boolean);
199
+
200
+ let totalWordScore = 0;
201
+ for (const word of queryWords) {
202
+ const wordScore = scoreWord(word);
203
+ totalWordScore += wordScore;
204
+ }
205
+
206
+ const fullQueryScore = scoreWord(lowerQuery);
207
+
208
+ const finalScore = Math.max(
209
+ totalWordScore / Math.max(1, queryWords.length),
210
+ fullQueryScore
211
+ );
212
+
213
+ return { score: finalScore, matchedKeywords };
214
+ }
215
+
216
+ export const DEFAULT_CREATION_OPTIONS: ICreateBaseEntityResultOption[] = [
217
+ // ... (Your DEFAULT_CREATION_OPTIONS array remains unchanged)
218
+ {
219
+ type: EntityType.DefinitionEntity,
220
+ // icon: DefinitionEntityDataTypeBlackIcon,
221
+ icon: ENTITY_FA_ICON_NAMES[EntityType.DefinitionEntity],
222
+ label: READABLE_ENTITY_TYPES[EntityType.DefinitionEntity],
223
+ disabledReasons: []
224
+ },
225
+ {
226
+ type: EntityType.GlobalEvent,
227
+ icon: ENTITY_FA_ICON_NAMES[EntityType.GlobalEvent],
228
+ label: READABLE_ENTITY_TYPES[EntityType.GlobalEvent],
229
+ disabledReasons: []
230
+ },
231
+ {
232
+ type: EntityType.FunctionDeclaration,
233
+ icon: ENTITY_FA_ICON_NAMES[EntityType.FunctionDeclaration],
234
+ label: READABLE_ENTITY_TYPES[EntityType.FunctionDeclaration],
235
+ disabledReasons: []
236
+ },
237
+ {
238
+ type: EntityType.VariableDeclaration,
239
+ icon: ENTITY_FA_ICON_NAMES[EntityType.VariableDeclaration],
240
+ label: READABLE_ENTITY_TYPES[EntityType.VariableDeclaration],
241
+ disabledReasons: []
242
+ },
243
+ {
244
+ type: EntityType.Operation,
245
+ icon: ENTITY_FA_ICON_NAMES[EntityType.Operation],
246
+ label: READABLE_ENTITY_TYPES[EntityType.Operation],
247
+ disabledReasons: []
248
+ },
249
+ {
250
+ type: EntityType.FunctionCall,
251
+ icon: ENTITY_FA_ICON_NAMES[EntityType.FunctionCall],
252
+ label: READABLE_ENTITY_TYPES[EntityType.FunctionCall],
253
+ disabledReasons: []
254
+ },
255
+ {
256
+ type: EntityType.Condition,
257
+ icon: ENTITY_FA_ICON_NAMES[EntityType.Condition],
258
+ label: READABLE_ENTITY_TYPES[EntityType.Condition],
259
+ disabledReasons: []
260
+ },
261
+ {
262
+ type: EntityType.Loop,
263
+ icon: ENTITY_FA_ICON_NAMES[EntityType.Loop],
264
+ label: READABLE_ENTITY_TYPES[EntityType.Loop],
265
+ disabledReasons: []
266
+ },
267
+ {
268
+ type: EntityType.Search,
269
+ icon: ENTITY_FA_ICON_NAMES[EntityType.Search],
270
+ label: READABLE_ENTITY_TYPES[EntityType.Search],
271
+ disabledReasons: []
272
+ },
273
+ {
274
+ type: EntityType.ReturnStatement,
275
+ icon: ENTITY_FA_ICON_NAMES[EntityType.ReturnStatement],
276
+ label: READABLE_ENTITY_TYPES[EntityType.ReturnStatement],
277
+ disabledReasons: []
278
+ },
279
+ {
280
+ type: EntityType.ContinueStatement,
281
+ icon: ENTITY_FA_ICON_NAMES[EntityType.ContinueStatement],
282
+ label: READABLE_ENTITY_TYPES[EntityType.ContinueStatement],
283
+ disabledReasons: []
284
+ },
285
+ {
286
+ type: EntityType.BreakStatement,
287
+ icon: ENTITY_FA_ICON_NAMES[EntityType.BreakStatement],
288
+ label: READABLE_ENTITY_TYPES[EntityType.BreakStatement],
289
+ disabledReasons: []
290
+ }
291
+ ];
292
+
293
+ // const BANNED_SPECIFIC_ENTITIES: Set<EntityId> = new Set([
294
+ // BaseEntityNames.SQL_ROW_TRANSFORMER,
295
+ // ExternalIntegrationOperations.GoogleMailEmails
296
+ // ]);
297
+
298
+ // helper function to check if a string is a valid URL
299
+ function isValidURL(str: string) {
300
+ try {
301
+ new URL(str);
302
+ return true;
303
+ } catch (e) {
304
+ return false;
305
+ }
306
+ }
307
+
308
+ /*
309
+ This function generates the subtitle for the entity creation options
310
+ ... (rest of function is unchanged)
311
+ */
312
+ export function resolveActionCreationSubtitle(
313
+ entity: SearchableEntity
314
+ ): React.ReactNode {
315
+ // ... (Your resolveActionCreationSubtitle function remains unchanged)
316
+ // If the entity is a variable declaration, add to the subtitle the parent scope,
317
+ // Eg: global or part of X function
318
+ if (entity.type === EntityType.VariableDeclaration) {
319
+ if (entity.parent?.type !== EntityType.Project) {
320
+ return `Re-use variable from the ${resolveEntityName(
321
+ entity.parent,
322
+ entity.project
323
+ )} logic`;
324
+ }
325
+
326
+ return 'Re-use global variable';
327
+ }
328
+
329
+ if (
330
+ entity.type === EntityType.InternalCall &&
331
+ entity.parent?.type === EntityType.VariableDeclaration
332
+ ) {
333
+ return (
334
+ <>
335
+ Call the internal function from the{' '}
336
+ <b>{resolveEntityName(entity.parent, entity.project)}</b>{' '}
337
+ variable
338
+ </>
339
+ );
340
+ }
341
+
342
+ // If the entity is a function or operation declaration, specify that users will be adding a call to this function
343
+ if (EntityType.FunctionDeclaration === entity.type) {
344
+ return `Add an execution of this custom trigger`;
345
+ }
346
+
347
+ if (
348
+ entity.type === EntityType.ActionDescriptor &&
349
+ entity.project.isGlobalEventActionDescriptor(entity)
350
+ ) {
351
+ return `Add this global event trigger`;
352
+ }
353
+
354
+ if (
355
+ entity.type === EntityType.ActionDescriptor &&
356
+ entity.project.isOperationDeclaration(entity)
357
+ ) {
358
+ return `Add this operation`;
359
+ }
360
+
361
+ if (
362
+ entity.type === EntityType.ActionDescriptor &&
363
+ entity.project.isConditionDeclaration(entity)
364
+ ) {
365
+ return `Add this condition`;
366
+ }
367
+
368
+ if (
369
+ entity.type === EntityType.ActionDescriptor &&
370
+ entity.project.isLoopDeclaration(entity)
371
+ ) {
372
+ return `Add this loop`;
373
+ }
374
+
375
+ // If it is a base entity or definition entity, specify that users will extending or implementing this entity
376
+ if (
377
+ [EntityType.BuiltInBaseEntity, EntityType.DefinitionEntity].includes(
378
+ entity.type
379
+ )
380
+ ) {
381
+ if ((entity as DefinitionEntityState).abstract) {
382
+ return `Implement this ${READABLE_ENTITY_TYPES[entity.type].singular}`;
383
+ }
384
+
385
+ return `Extend this ${READABLE_ENTITY_TYPES[entity.type].singular}`;
386
+ }
387
+
388
+ // For the rest, just add a generic message
389
+ return `Add a ${READABLE_ENTITY_TYPES[entity.type].singular}`;
390
+ }
391
+
392
+ function renderHighlightedText(text: string, highlight?: string) {
393
+ if (!highlight) return <span>{text}</span>;
394
+ const words = highlight.split(/[\s\-_/]+/).filter(Boolean);
395
+ if (words.length === 0) return <span>{text}</span>;
396
+
397
+ const regex = new RegExp(`(${words.join('|')})`, 'gi');
398
+ const parts = text.split(regex);
399
+
400
+ return (
401
+ <span>
402
+ {parts.map((part, i) => {
403
+ const isMatch = words.some(
404
+ (w) => part.toLowerCase() === w.toLowerCase()
405
+ );
406
+ return isMatch ? (
407
+ <mark
408
+ key={i}
409
+ style={{
410
+ backgroundColor: 'rgba(255, 255, 0, 0.4)',
411
+ color: 'inherit',
412
+ borderRadius: '2px'
413
+ }}
414
+ >
415
+ {part}
416
+ </mark>
417
+ ) : (
418
+ <span key={i}>{part}</span>
419
+ );
420
+ })}
421
+ </span>
422
+ );
423
+ }
424
+
425
+ export function CreateBaseEntityOption(props: {
426
+ option: ICreateBaseEntityResultOption;
427
+ onCreateBaseEntity: (entityType: EntityType, event: MouseEvent) => void;
428
+ parentCanvasObject: CanvasObject;
429
+ project: EditorService;
430
+ isFocused?: boolean;
431
+ }) {
432
+ const rootElementRef = useRef<HTMLLIElement>(null);
433
+
434
+ useEffect(() => {
435
+ if (props.isFocused && rootElementRef.current) {
436
+ rootElementRef.current.scrollIntoView({
437
+ block: 'nearest',
438
+ behavior: 'smooth'
439
+ });
440
+ }
441
+ }, [props.isFocused]);
442
+ const memoizedCanvasPopupBody = useCallback(
443
+ () => (
444
+ <CanvasPopupBaseComponent
445
+ containerClasses={['tooltip', 'tooltip--large-font']}
446
+ id={`${props.option.type}--creation-menu-option--popup--body`}
447
+ >
448
+ {/* If it isn't the last item on the list, add a 'br' after itself */}
449
+ {!!props.option.disabledReasons.length ? (
450
+ <>
451
+ {props.option.disabledReasons.flatMap(
452
+ (reason, index) => [
453
+ <p
454
+ key={reason}
455
+ style={{
456
+ margin: 0,
457
+ maxWidth: 200,
458
+ wordWrap: 'break-word',
459
+ textAlign: 'center'
460
+ }}
461
+ >
462
+ {reason}
463
+ </p>,
464
+ index <
465
+ props.option.disabledReasons.length - 1 && (
466
+ <br />
467
+ )
468
+ ]
469
+ )}
470
+ </>
471
+ ) : (
472
+ `Create a new ${props.option.label.singular} at this position.`
473
+ )}
474
+ </CanvasPopupBaseComponent>
475
+ ),
476
+ [props.option, props.parentCanvasObject]
477
+ );
478
+
479
+ const canvasPopup = useCanvasPopup(
480
+ {
481
+ editor: props.project,
482
+ id: props.option.type + '--creation-menu-option--popup',
483
+ anchorQuerySelector:
484
+ '#' + props.option.type + '--creation-menu-option',
485
+ arrowClasses: ['tooltip-arrow'],
486
+ parentCanvasObject: props.parentCanvasObject,
487
+ placement: 'top',
488
+ arrowContainerSelector:
489
+ '#' + props.option.type + '--creation-menu-option--popup--body'
490
+ },
491
+ memoizedCanvasPopupBody
492
+ ).setupHover({
493
+ closeDelay: 80,
494
+ openDelay: 600
495
+ });
496
+
497
+ return (
498
+ <li ref={rootElementRef}>
499
+ <button
500
+ className={cx([
501
+ 'canvas__new-entity-menu__base-entity-button',
502
+ `entity-color__${props.option.type}`,
503
+ ...(!!props.option.disabledReasons.length
504
+ ? ['disabled']
505
+ : []),
506
+ ...(props.isFocused ? ['focused-option'] : [])
507
+ ])}
508
+ style={
509
+ props.isFocused
510
+ ? { backgroundColor: 'rgba(0, 0, 0, 0.05)' }
511
+ : {}
512
+ }
513
+ id={props.option.type + '--creation-menu-option'}
514
+ ref={canvasPopup.anchorRef}
515
+ disabled={!!props.option.disabledReasons.length}
516
+ onClick={(e) => {
517
+ props.onCreateBaseEntity(props.option.type, e as any);
518
+ }}
519
+ >
520
+ {!isValidURL(props.option.icon) ? (
521
+ <i
522
+ className={cx(['fa-solid', 'fa-' + props.option.icon])}
523
+ />
524
+ ) : (
525
+ <img
526
+ src={props.option.icon}
527
+ alt={props.option.label.singular}
528
+ />
529
+ )}
530
+ <div
531
+ style={{
532
+ display: 'flex',
533
+ flexDirection: 'column',
534
+ alignItems: 'flex-start'
535
+ }}
536
+ >
537
+ <span>
538
+ {renderHighlightedText(
539
+ props.option.label.singular,
540
+ props.option.searchQuery
541
+ )}
542
+ </span>
543
+ {props.option.matchedKeywords &&
544
+ props.option.matchedKeywords.length > 0 && (
545
+ <span
546
+ style={{
547
+ fontSize: '10px',
548
+ color: '#888',
549
+ marginTop: '2px'
550
+ }}
551
+ >
552
+ Matches:{' '}
553
+ {props.option.matchedKeywords
554
+ .map((kw) =>
555
+ renderHighlightedText(
556
+ kw,
557
+ props.option.searchQuery
558
+ )
559
+ )
560
+ .reduce((prev, curr) => (
561
+ <>
562
+ {prev}, {curr}
563
+ </>
564
+ ))}
565
+ </span>
566
+ )}
567
+ </div>
568
+ </button>
569
+ </li>
570
+ );
571
+ }
572
+
573
+ export function CreateSpecificEntityOption(props: {
574
+ option: ICreateSpecificEntityResultOption;
575
+ onCreateFromSpecificEntity: (
576
+ from: SearchableEntity,
577
+ event: MouseEvent
578
+ ) => void;
579
+ parentCanvasObject: CanvasObject;
580
+ project: EditorService;
581
+ isFocused?: boolean;
582
+ }) {
583
+ const rootElementRef = useRef<HTMLLIElement>(null);
584
+
585
+ useEffect(() => {
586
+ if (props.isFocused && rootElementRef.current) {
587
+ rootElementRef.current.scrollIntoView({
588
+ block: 'nearest',
589
+ behavior: 'smooth'
590
+ });
591
+ }
592
+ }, [props.isFocused]);
593
+
594
+ const memoizedCanvasPopupBody = useCallback(
595
+ () => (
596
+ <CanvasPopupBaseComponent
597
+ containerClasses={['tooltip', 'tooltip--large-font']}
598
+ id={
599
+ 'creation-menu-option--' +
600
+ props.option.entity.id +
601
+ '--popup--body'
602
+ }
603
+ >
604
+ {/* If it isn't the last item on the list, add a 'br' after itself */}
605
+ {!!props.option.disabledReasons.length
606
+ ? props.option.disabledReasons.flatMap((reason, index) => [
607
+ <p
608
+ key={reason}
609
+ style={{
610
+ margin: 0,
611
+ maxWidth: 200,
612
+ wordWrap: 'break-word',
613
+ textAlign: 'center'
614
+ }}
615
+ >
616
+ {reason}
617
+ </p>,
618
+ index < props.option.disabledReasons.length - 1 && (
619
+ <br />
620
+ )
621
+ ])
622
+ : [resolveActionCreationSubtitle(props.option.entity)]}
623
+ </CanvasPopupBaseComponent>
624
+ ),
625
+ [props.option, props.parentCanvasObject]
626
+ );
627
+
628
+ const canvasPopup = useCanvasPopup(
629
+ {
630
+ editor: props.project,
631
+ id: 'creation-menu-option--' + props.option.entity.id + '--popup',
632
+ anchorQuerySelector:
633
+ '#' + 'creation-menu-option--' + props.option.entity.id,
634
+ arrowClasses: ['tooltip-arrow'],
635
+ parentCanvasObject: props.parentCanvasObject,
636
+ placement: 'top',
637
+ arrowContainerSelector:
638
+ '#creation-menu-option--' +
639
+ props.option.entity.id +
640
+ '--popup--body'
641
+ },
642
+ memoizedCanvasPopupBody
643
+ ).setupHover({
644
+ closeDelay: 80,
645
+ openDelay: 600
646
+ });
647
+
648
+ const config = resolveEntityIcon(props.option.entity);
649
+
650
+ return (
651
+ <li ref={rootElementRef}>
652
+ <button
653
+ className={[
654
+ 'canvas__new-entity-menu__specific-entity-button',
655
+ `entity-color__${mapActionDescriptorToTypeItRepresents(
656
+ props.option.entity
657
+ )}`,
658
+ ...(!!props.option.disabledReasons.length
659
+ ? ['disabled']
660
+ : []),
661
+ ...(props.isFocused ? ['focused-option'] : [])
662
+ ].join(' ')}
663
+ style={
664
+ props.isFocused
665
+ ? { backgroundColor: 'rgba(0, 0, 0, 0.05)' }
666
+ : {}
667
+ }
668
+ id={`creation-menu-option--${props.option.entity.id}`}
669
+ ref={canvasPopup.anchorRef}
670
+ disabled={!!props.option.disabledReasons.length}
671
+ onClick={(e) => {
672
+ props.onCreateFromSpecificEntity(
673
+ props.option.entity,
674
+ e as any
675
+ );
676
+ }}
677
+ >
678
+ <EntityIcon
679
+ entity={props.option.entity}
680
+ size="medium"
681
+ color="black"
682
+ parentDisabledReasons={props.option.disabledReasons}
683
+ style={{
684
+ // Images are made bigger, so we compensate the left position
685
+ left: !!config.url ? -6 : undefined,
686
+ position: 'relative'
687
+ }}
688
+ project={props.project}
689
+ parentCanvasObject={props.parentCanvasObject}
690
+ />
691
+ <div
692
+ className="canvas__new-entity-menu__specific-entity-button__labels-container"
693
+ style={{
694
+ // Images are made bigger, so we compensate the left position
695
+ left: !!config.url ? -14 : undefined,
696
+ position: 'relative'
697
+ }}
698
+ >
699
+ <span className="canvas__new-entity-menu__entity-title">
700
+ {renderHighlightedText(
701
+ resolveEntityName(
702
+ props.option.entity,
703
+ props.option.entity.project
704
+ ),
705
+ props.option.searchQuery
706
+ )}
707
+ </span>
708
+ <span className="canvas__new-entity-menu__entity-subtitle">
709
+ {resolveActionCreationSubtitle(props.option.entity)}
710
+ </span>
711
+ {props.option.matchedKeywords &&
712
+ props.option.matchedKeywords.length > 0 && (
713
+ <span
714
+ style={{
715
+ fontSize: '10px',
716
+ color: '#888',
717
+ marginTop: '2px'
718
+ }}
719
+ >
720
+ Matches:{' '}
721
+ {props.option.matchedKeywords
722
+ .map((kw) =>
723
+ renderHighlightedText(
724
+ kw,
725
+ props.option.searchQuery
726
+ )
727
+ )
728
+ .reduce((prev, curr) => (
729
+ <>
730
+ {prev}, {curr}
731
+ </>
732
+ ))}
733
+ </span>
734
+ )}
735
+ </div>
736
+ </button>
737
+ </li>
738
+ );
739
+ }
740
+
741
+ const CreateEntityMenu = (props: ICreateEntityMenuProps) => {
742
+ const rootElementRef = useRef<HTMLDivElement | null>(null);
743
+
744
+ // --- Component State ---
745
+ const [searchValue, setSearchValue] = useState<string>('');
746
+ const deferredSearchValue = useDeferredValue(searchValue); // <-- ADDED
747
+ const [hasSearched, setHasSearched] = useState<boolean>(false);
748
+
749
+ const [specificEntityResults, setSpecificEntityResults] = useState<
750
+ ICreateSpecificEntityResultOption[]
751
+ >([]);
752
+ const [specificEntityDisabledResults, setSpecificEntityDisabledResults] =
753
+ useState<ICreateSpecificEntityResultOption[]>([]);
754
+ const [baseEntityResults, setBaseEntityResults] = useState<
755
+ ICreateBaseEntityResultOption[]
756
+ >([]);
757
+ const [baseEntityDisabledResults, setBaseEntityDisabledResults] = useState<
758
+ ICreateBaseEntityResultOption[]
759
+ >([]);
760
+
761
+ const [focusedIndex, setFocusedIndex] = useState<number>(0);
762
+
763
+ useEffect(() => {
764
+ setFocusedIndex(0);
765
+ }, [deferredSearchValue, baseEntityResults, specificEntityResults]);
766
+
767
+ // --- Loading State ---
768
+ const [isBaseLoading, setIsBaseLoading] = useState(true);
769
+ const [isSpecificLoading, setIsSpecificLoading] = useState(true);
770
+
771
+ // Null means that the user hasn't explicitly collapsed or expanded the section
772
+ // So it can be overriten by default behaviour
773
+ // A boolean means that the section is collapsed or expanded explicitly by the user and should not be overriten
774
+ const [baseEntitiesSectionCollapsed, setBaseEntitiesSectionCollapsed] =
775
+ useState<boolean | null>(null);
776
+ const [
777
+ specificEntitiesSectionCollapsed,
778
+ setSpecificEntitiesSectionCollapsed
779
+ ] = useState<boolean | null>(null);
780
+
781
+ const memoizedBaseEntitiesCountCanvasPopupBody = useCallback(
782
+ () => (
783
+ <CanvasPopupBaseComponent
784
+ containerClasses={['tooltip', 'tooltip--large-font']}
785
+ id={
786
+ 'create-new-entity-menu--base-entities--count-tooltip--popup--body'
787
+ }
788
+ >
789
+ {`There are ${baseEntityResults.length} base nodes available to create.`}
790
+ </CanvasPopupBaseComponent>
791
+ ),
792
+ [baseEntityResults]
793
+ );
794
+
795
+ // --- Tooltip Popups ---
796
+ const canvasPopupBaseEntitiesCountToolip = useCanvasPopup(
797
+ {
798
+ editor: props.project,
799
+ id: 'create-new-entity-menu--base-entities--count-tooltip',
800
+ anchorQuerySelector:
801
+ '#create-new-entity-menu--base-entities--counter-badge',
802
+ arrowClasses: ['tooltip-arrow'],
803
+ parentCanvasObject: props.canvasObject,
804
+ placement: 'top',
805
+ arrowContainerSelector:
806
+ '#create-new-entity-menu--base-entities--count-tooltip--popup--body'
807
+ },
808
+ memoizedBaseEntitiesCountCanvasPopupBody
809
+ // [baseEntityResults]
810
+ ).setupHover({
811
+ closeDelay: 400,
812
+ openDelay: 600
813
+ });
814
+
815
+ const memoizedSpecificEntitiesCountCanvasPopupBody = useCallback(
816
+ () => (
817
+ <CanvasPopupBaseComponent
818
+ containerClasses={['tooltip', 'tooltip--large-font']}
819
+ id={
820
+ 'create-new-entity-menu--specific-entities--count-tooltip--popup--body'
821
+ }
822
+ >
823
+ {`There are ${specificEntityResults.length} specific nodes available to create.`}
824
+ </CanvasPopupBaseComponent>
825
+ ),
826
+ [specificEntityResults]
827
+ );
828
+
829
+ const canvasPopupSpecificEntitiesCountToolip = useCanvasPopup(
830
+ {
831
+ editor: props.project,
832
+ id: 'create-new-entity-menu--specific-entities--count-tooltip',
833
+ anchorQuerySelector:
834
+ '#create-new-entity-menu--specific-entities--counter-badge',
835
+ arrowClasses: ['tooltip-arrow'],
836
+ parentCanvasObject: props.canvasObject,
837
+ placement: 'top',
838
+ arrowContainerSelector:
839
+ '#create-new-entity-menu--specific-entities--count-tooltip--popup--body'
840
+ },
841
+ memoizedSpecificEntitiesCountCanvasPopupBody
842
+ // [specificEntityResults]
843
+ ).setupHover({
844
+ closeDelay: 400,
845
+ openDelay: 600
846
+ });
847
+
848
+ // --- Data Fetching Logic ---
849
+
850
+ // Based on the connected entity, we should disable some of the base entity creation options
851
+ const getBasesEntityResults = (
852
+ searchQuery: string = ''
853
+ ): [ICreateBaseEntityResultOption[], ICreateBaseEntityResultOption[]] => {
854
+ try {
855
+ const isConnected =
856
+ !!props.connectedSource || !!props.connectedTarget;
857
+
858
+ const disabledOptions: ICreateBaseEntityResultOption[] = [];
859
+ const enabledOptions: ICreateBaseEntityResultOption[] = [];
860
+
861
+ // If there is a connected entity, source or target:
862
+ // Remove entry point entities and definition entity
863
+ for (const option of DEFAULT_CREATION_OPTIONS) {
864
+ if (ENTITIES_METADATA[option.type]?.hidden) continue;
865
+
866
+ const optionDisabledReasons: string[] = [];
867
+
868
+ if (isConnected) {
869
+ // Remove any entity that cannot be connected to anything
870
+ if ([EntityType.DefinitionEntity].includes(option.type)) {
871
+ optionDisabledReasons.push(
872
+ 'Definitions cannot be connected to any logic and must stand on their own.'
873
+ );
874
+
875
+ // Remove entry point entities when the connected entity is a source being called by the connection
876
+ // Which means that the current entity will call the connected entity
877
+ } else if (
878
+ ConnectionNodeType.CalledBy !==
879
+ props.connectedNodeType &&
880
+ [
881
+ EntityType.GlobalEvent,
882
+ EntityType.FunctionDeclaration
883
+ ].includes(option.type)
884
+ ) {
885
+ optionDisabledReasons.push(
886
+ 'Trigger nodes cannot be called by other nodes. They are the starting point of the logic.'
887
+ );
888
+
889
+ // If the connected entity is a property-out, it means it is setting the value of the new entity being created
890
+ // We should remove all that don't read values (all except variable declaration)
891
+ } else if (
892
+ ConnectionNodeType.PropertyOut ===
893
+ props.connectedNodeType &&
894
+ EntityType.VariableDeclaration !== option.type
895
+ ) {
896
+ optionDisabledReasons.push(
897
+ 'This node cannot receive values read from other nodes.'
898
+ );
899
+
900
+ // If the connected entity is a property-in, it means it is reading the value of the new entity being created
901
+ // We should remove all that don't set values
902
+ } else if (
903
+ ConnectionNodeType.PropertyIn ===
904
+ props.connectedNodeType &&
905
+ VALUE_WRITING_TYPES.includes(option.type)
906
+ ) {
907
+ optionDisabledReasons.push(
908
+ 'This node cannot set values to other nodes.'
909
+ );
910
+ }
911
+
912
+ if (option.type === EntityType.VariableDeclaration) {
913
+ const callerScopeOwner = getScopeOwner(
914
+ props.connectedSource,
915
+ props.project.logic
916
+ );
917
+
918
+ if (
919
+ ConnectionNodeType.PropertyOut ===
920
+ props.connectedNodeType &&
921
+ callerScopeOwner?.type === EntityType.Project
922
+ ) {
923
+ optionDisabledReasons.push(
924
+ 'Global variable declarations cannot have their value set directly. Create a variable instance instead.'
925
+ );
926
+ } else if (
927
+ [
928
+ ConnectionNodeType.EntryPointCaller,
929
+ ConnectionNodeType.ErrorCaller,
930
+ ConnectionNodeType.SuccessCaller
931
+ ].includes(props.connectedNodeType) &&
932
+ callerScopeOwner?.type === EntityType.Project
933
+ ) {
934
+ optionDisabledReasons.push(
935
+ 'Global variable declarations cannot be called by others. They will always be available globally when the project first loads.'
936
+ );
937
+ }
938
+ }
939
+ }
940
+
941
+ if (
942
+ props.connectedNodeType !==
943
+ ConnectionNodeType.SuccessCaller &&
944
+ props.connectedNodeType !==
945
+ ConnectionNodeType.ErrorCaller &&
946
+ props.connectedNodeType !==
947
+ ConnectionNodeType.EntryPointCaller
948
+ ) {
949
+ if (EntityType.ReturnStatement === option.type) {
950
+ optionDisabledReasons.push(
951
+ 'A termination must always be executed by another node.'
952
+ );
953
+ } else if (EntityType.ContinueStatement === option.type) {
954
+ optionDisabledReasons.push(
955
+ 'A continue node must always be executed by another node.'
956
+ );
957
+ } else if (EntityType.BreakStatement === option.type) {
958
+ optionDisabledReasons.push(
959
+ 'A break node must always be executed by another node.'
960
+ );
961
+ }
962
+ } else {
963
+ const callerScopeOwner = getScopeOwner(
964
+ props.connectedSource,
965
+ props.project.logic
966
+ );
967
+ const firstNonLoopScopeOwner = getFirstNonLoopScopeOwner(
968
+ props.connectedSource,
969
+ props.project.logic
970
+ );
971
+
972
+ if (EntityType.ContinueStatement === option.type) {
973
+ if (callerScopeOwner?.type !== EntityType.Loop) {
974
+ optionDisabledReasons.push(
975
+ 'Continue nodes can only be used inside the body of a loop.'
976
+ );
977
+ }
978
+ } else if (EntityType.BreakStatement === option.type) {
979
+ if (callerScopeOwner?.type !== EntityType.Loop) {
980
+ optionDisabledReasons.push(
981
+ 'Loop exit nodes can only be used inside the body of a loop.'
982
+ );
983
+ }
984
+ }
985
+
986
+ if (EntityType.ReturnStatement === option.type) {
987
+ Logger.log('callerScopeOwner: ', callerScopeOwner);
988
+ Logger.log(
989
+ 'firstNonLoopScopeOwner: ',
990
+ firstNonLoopScopeOwner
991
+ );
992
+ if (!firstNonLoopScopeOwner) {
993
+ let isBodyOfLoop =
994
+ callerScopeOwner?.type === EntityType.Loop;
995
+ optionDisabledReasons.push(
996
+ 'Termination nodes can only be used inside the body of a trigger.' +
997
+ (isBodyOfLoop
998
+ ? ' Without counting the body of a loop.'
999
+ : '')
1000
+ );
1001
+ } else if (
1002
+ callerScopeOwner.type === EntityType.Project
1003
+ ) {
1004
+ optionDisabledReasons.push(
1005
+ 'Termination nodes can only be used inside the body of a trigger.'
1006
+ );
1007
+ }
1008
+ }
1009
+ }
1010
+
1011
+ if (option.type === EntityType.GlobalEvent) {
1012
+ // Check if all global events have been implemented, then disable the option
1013
+ const stillHasAvailableOptions =
1014
+ !!props.project.logic.globalEventActionDescriptors.find(
1015
+ (actionDescriptor) =>
1016
+ !props.project.logic.events.find(
1017
+ (event) =>
1018
+ event.implements?.id ===
1019
+ actionDescriptor.id
1020
+ )
1021
+ );
1022
+
1023
+ if (!stillHasAvailableOptions) {
1024
+ optionDisabledReasons.push(
1025
+ 'All global events have been implemented. Add your logic to the existing ones.'
1026
+ );
1027
+ }
1028
+ }
1029
+
1030
+ if (optionDisabledReasons.length) {
1031
+ disabledOptions.push({
1032
+ ...option,
1033
+ disabledReasons: optionDisabledReasons
1034
+ });
1035
+ } else {
1036
+ enabledOptions.push(option);
1037
+ }
1038
+ }
1039
+
1040
+ const filteredEnabledOptions: ICreateBaseEntityResultOption[] = [];
1041
+ const filteredDisabledOptions: ICreateBaseEntityResultOption[] = [];
1042
+
1043
+ for (const option of enabledOptions) {
1044
+ const metadata = ENTITIES_METADATA[option.type] || {
1045
+ keywords: [] as { word: string; score: number }[]
1046
+ };
1047
+ const searchRes = computeSearchScore(
1048
+ searchQuery,
1049
+ option.label.singular,
1050
+ metadata.keywords
1051
+ );
1052
+ if (!searchQuery || searchRes.score > 0) {
1053
+ filteredEnabledOptions.push({
1054
+ ...option,
1055
+ searchScore: searchRes.score,
1056
+ matchedKeywords: searchRes.matchedKeywords,
1057
+ searchQuery
1058
+ });
1059
+ }
1060
+ }
1061
+ for (const option of disabledOptions) {
1062
+ const metadata = ENTITIES_METADATA[option.type] || {
1063
+ keywords: [] as { word: string; score: number }[]
1064
+ };
1065
+ const searchRes = computeSearchScore(
1066
+ searchQuery,
1067
+ option.label.singular,
1068
+ metadata.keywords
1069
+ );
1070
+ if (!searchQuery || searchRes.score > 0) {
1071
+ filteredDisabledOptions.push({
1072
+ ...option,
1073
+ searchScore: searchRes.score,
1074
+ matchedKeywords: searchRes.matchedKeywords,
1075
+ searchQuery
1076
+ });
1077
+ }
1078
+ }
1079
+
1080
+ filteredEnabledOptions.sort(
1081
+ (a, b) =>
1082
+ (b.searchScore || 0) - (a.searchScore || 0) ||
1083
+ a.label.singular.localeCompare(b.label.singular)
1084
+ );
1085
+ filteredDisabledOptions.sort(
1086
+ (a, b) =>
1087
+ (b.searchScore || 0) - (a.searchScore || 0) ||
1088
+ a.label.singular.localeCompare(b.label.singular)
1089
+ );
1090
+
1091
+ return [filteredEnabledOptions, filteredDisabledOptions];
1092
+ } catch (error) {
1093
+ Logger.error('Error in getBasesEntityResults: ', error);
1094
+ return [[], []];
1095
+ }
1096
+ };
1097
+
1098
+ const getAllVariablesInScope = (
1099
+ allVariablesInProject: VariableDeclarationState[]
1100
+ ): VariableDeclarationState[] => {
1101
+ return _getAllVariablesInScope(
1102
+ props.project.logic,
1103
+ allVariablesInProject,
1104
+ props.connectedSource || props.connectedTarget
1105
+ );
1106
+ };
1107
+
1108
+ const getInternalCallsFromVariables = (
1109
+ variables: VariableDeclarationState[]
1110
+ ): InternalCallState[] => {
1111
+ return variables.flatMap((variable) => variable.internalCalls);
1112
+ };
1113
+
1114
+ // Based on the search query, we should filter the specific entity creation options
1115
+ // Also based on the external connections, we should disable some of the options
1116
+ const getSpecificEntityResults = async (
1117
+ searchQuery: string = ''
1118
+ ): Promise<
1119
+ [
1120
+ ICreateSpecificEntityResultOption[],
1121
+ ICreateSpecificEntityResultOption[]
1122
+ ]
1123
+ > => {
1124
+ const yieldEvery = 200;
1125
+ let i = 0;
1126
+ const project = props.project.logic;
1127
+
1128
+ // --- 1. Data Gathering (Async) ---
1129
+ const allVariablesInProject = project.getVariableDeclarations();
1130
+ const internalCallsFromVariablesInScope = getInternalCallsFromVariables(
1131
+ getAllVariablesInScope(allVariablesInProject)
1132
+ );
1133
+
1134
+ if (
1135
+ allVariablesInProject.length +
1136
+ internalCallsFromVariablesInScope.length >
1137
+ yieldEvery
1138
+ ) {
1139
+ await yieldToEventLoop();
1140
+ }
1141
+
1142
+ const searchableEntitities: SearchableEntity[] = [
1143
+ ...project.builtInBaseEntities,
1144
+ ...project.operationDeclarations,
1145
+ ...project.conditionDeclarations,
1146
+ ...project.loopDeclarations,
1147
+ ...project.globalEventActionDescriptors,
1148
+ ...allVariablesInProject,
1149
+ ...internalCallsFromVariablesInScope,
1150
+ ...project.functions
1151
+ ];
1152
+
1153
+ const searchableEntititiesWithNames: {
1154
+ entity: SearchableEntity;
1155
+ name: string;
1156
+ }[] = [];
1157
+ for (const entity of searchableEntitities) {
1158
+ if (
1159
+ ENTITIES_METADATA[entity.id]?.hidden ||
1160
+ ENTITIES_METADATA[entity.type]?.hidden
1161
+ ) {
1162
+ continue;
1163
+ }
1164
+ searchableEntititiesWithNames.push({
1165
+ entity,
1166
+ name: resolveEntityName(entity, entity.project).toLowerCase()
1167
+ });
1168
+ if (++i % yieldEvery === 0) await yieldToEventLoop();
1169
+ }
1170
+
1171
+ // --- 2. Alphabetical Sort ---
1172
+ searchableEntititiesWithNames.sort((a, b) =>
1173
+ a.name.localeCompare(b.name)
1174
+ );
1175
+ await yieldToEventLoop(); // Yield after the sort
1176
+
1177
+ let specificOptions: ICreateSpecificEntityResultOption[];
1178
+
1179
+ if (!searchQuery) {
1180
+ specificOptions = searchableEntititiesWithNames.map((item) => ({
1181
+ entity: item.entity,
1182
+ disabledReasons: [] as string[]
1183
+ }));
1184
+ } else {
1185
+ // --- 3. Filtering (Async) ---
1186
+ const filteredItems: (ICreateSpecificEntityResultOption & {
1187
+ name: string;
1188
+ })[] = [];
1189
+ for (const item of searchableEntititiesWithNames) {
1190
+ const metadata = ENTITIES_METADATA[item.entity.id] ||
1191
+ ENTITIES_METADATA[item.entity.type] || {
1192
+ keywords: [] as { word: string; score: number }[]
1193
+ };
1194
+ const typeReadable =
1195
+ READABLE_ENTITY_TYPES[item.entity.type]?.singular || '';
1196
+ const additionalKeywords = typeReadable
1197
+ ? [{ word: typeReadable.toLowerCase(), score: 0.8 }]
1198
+ : [];
1199
+ const searchRes = computeSearchScore(searchQuery, item.name, [
1200
+ ...metadata.keywords,
1201
+ ...additionalKeywords
1202
+ ]);
1203
+ if (searchRes.score > 0) {
1204
+ filteredItems.push({
1205
+ entity: item.entity,
1206
+ name: item.name,
1207
+ disabledReasons: [],
1208
+ searchScore: searchRes.score,
1209
+ matchedKeywords: searchRes.matchedKeywords,
1210
+ searchQuery
1211
+ });
1212
+ }
1213
+ if (++i % yieldEvery === 0) await yieldToEventLoop();
1214
+ }
1215
+
1216
+ // --- 4. Re-sorting by relevance ---
1217
+ filteredItems.sort(
1218
+ (a, b) =>
1219
+ (b.searchScore || 0) - (a.searchScore || 0) ||
1220
+ a.name.localeCompare(b.name)
1221
+ );
1222
+ await yieldToEventLoop(); // Yield after the sort
1223
+
1224
+ specificOptions = filteredItems;
1225
+ }
1226
+
1227
+ // --- 5. Disabling Logic (Async) ---
1228
+ return disableSpecificEntityResults(specificOptions);
1229
+ };
1230
+
1231
+ const disableSpecificEntityResults = async (
1232
+ items: ICreateSpecificEntityResultOption[]
1233
+ ): Promise<
1234
+ [
1235
+ ICreateSpecificEntityResultOption[],
1236
+ ICreateSpecificEntityResultOption[]
1237
+ ]
1238
+ > => {
1239
+ const yieldEvery = 100;
1240
+ let i = 0;
1241
+ const connectedEntity = props.connectedSource || props.connectedTarget;
1242
+ const isConnected = !!connectedEntity;
1243
+
1244
+ const disabledOptions: ICreateSpecificEntityResultOption[] = [];
1245
+ const enabledOptions: ICreateSpecificEntityResultOption[] = [];
1246
+
1247
+ for (const item of items) {
1248
+ const entity = item.entity;
1249
+ const optionDisabledReasons: string[] = [];
1250
+
1251
+ // --- (All your disabling logic from the original function) ---
1252
+ if (
1253
+ isConnected &&
1254
+ [
1255
+ EntityType.DefinitionEntity,
1256
+ EntityType.BuiltInBaseEntity
1257
+ ].includes(entity.type)
1258
+ ) {
1259
+ optionDisabledReasons.push(
1260
+ 'Declarations cannot be connected to any logic and must stand on their own.'
1261
+ );
1262
+ } else if (
1263
+ ConnectionNodeType.CalledBy === props.connectedNodeType &&
1264
+ [
1265
+ EntityType.GlobalEvent,
1266
+ EntityType.FunctionDeclaration
1267
+ ].includes(entity.type)
1268
+ ) {
1269
+ optionDisabledReasons.push(
1270
+ 'Trigger nodes cannot be called by other nodes. They are the starting point of the logic.'
1271
+ );
1272
+ } else if (
1273
+ ConnectionNodeType.PropertyOut === props.connectedNodeType &&
1274
+ EntityType.VariableDeclaration !== entity.type
1275
+ ) {
1276
+ optionDisabledReasons.push(
1277
+ 'This node cannot receive values read from other nodes.'
1278
+ );
1279
+ } else if (
1280
+ ConnectionNodeType.PropertyIn === props.connectedNodeType &&
1281
+ VALUE_WRITING_TYPES.includes(entity.type)
1282
+ ) {
1283
+ optionDisabledReasons.push(
1284
+ 'This node cannot set values to other nodes.'
1285
+ );
1286
+ }
1287
+
1288
+ if (entity.type === EntityType.ActionDescriptor) {
1289
+ const hasAlreadyBeenImplemented =
1290
+ !!props.project.logic.events.find(
1291
+ (event) => event.implements?.id === entity?.id
1292
+ );
1293
+ if (hasAlreadyBeenImplemented) {
1294
+ optionDisabledReasons.push(
1295
+ 'This global event has already been implemented.'
1296
+ );
1297
+ }
1298
+ }
1299
+
1300
+ if (entity.type === EntityType.VariableDeclaration) {
1301
+ if (isConnected) {
1302
+ const isCompatibleScope = checkScopeCompatibility(
1303
+ connectedEntity,
1304
+ entity,
1305
+ props.project.logic
1306
+ );
1307
+ if (
1308
+ !isCompatibleScope.compatible &&
1309
+ isCompatibleScope.type !==
1310
+ ScopeCompatibilityType.BIsGlobalAIsNot
1311
+ ) {
1312
+ optionDisabledReasons.push(
1313
+ 'The variable declaration is not from a compatible scope.'
1314
+ );
1315
+ }
1316
+ } else {
1317
+ if (entity.parent?.type !== EntityType.Project) {
1318
+ optionDisabledReasons.push(
1319
+ 'Only global variable declarations can be used at the top level.'
1320
+ );
1321
+ }
1322
+ }
1323
+ }
1324
+ // --- (End of disabling logic) ---
1325
+
1326
+ if (optionDisabledReasons.length) {
1327
+ disabledOptions.push({
1328
+ ...item,
1329
+ disabledReasons: optionDisabledReasons
1330
+ });
1331
+ } else {
1332
+ enabledOptions.push({
1333
+ ...item,
1334
+ disabledReasons: []
1335
+ });
1336
+ }
1337
+
1338
+ if (++i % yieldEvery === 0) {
1339
+ await yieldToEventLoop();
1340
+ }
1341
+ }
1342
+
1343
+ Logger.log(
1344
+ 'enabledOptions: ',
1345
+ enabledOptions.map((e) =>
1346
+ resolveEntityName(e.entity, e.entity.project)
1347
+ )
1348
+ );
1349
+
1350
+ return [enabledOptions, disabledOptions];
1351
+ };
1352
+
1353
+ const onSearch = (newValue: string) => {
1354
+ if (!hasSearched) {
1355
+ setHasSearched(true);
1356
+ }
1357
+ setSearchValue(newValue);
1358
+ // The actual data fetching is now handled by the useEffect
1359
+ // watching deferredSearchValue
1360
+ };
1361
+
1362
+ // Main effect for loading all data
1363
+ useEffect(() => {
1364
+ // This function runs when the component mounts and when the search value changes
1365
+ const loadAllData = async () => {
1366
+ // Set both loading states
1367
+ setIsBaseLoading(true);
1368
+ setIsSpecificLoading(true);
1369
+
1370
+ // We can run these in parallel
1371
+ const basePromise = (async () => {
1372
+ const [newBaseEntityResults, newBaseEntityDisabledResults] =
1373
+ getBasesEntityResults(deferredSearchValue);
1374
+ setBaseEntityResults(newBaseEntityResults);
1375
+ setBaseEntityDisabledResults(newBaseEntityDisabledResults);
1376
+ setIsBaseLoading(false);
1377
+ })();
1378
+
1379
+ const specificPromise = (async () => {
1380
+ const [
1381
+ newSpecificEntityResults,
1382
+ newSpecificEntityDisabledResults
1383
+ ] = await getSpecificEntityResults(deferredSearchValue);
1384
+ setSpecificEntityResults(newSpecificEntityResults);
1385
+ setSpecificEntityDisabledResults(
1386
+ newSpecificEntityDisabledResults
1387
+ );
1388
+ setIsSpecificLoading(false);
1389
+ })();
1390
+
1391
+ await Promise.all([basePromise, specificPromise]);
1392
+ };
1393
+
1394
+ // Focus the input field when the component opens
1395
+ if (props.canvasObject.element) {
1396
+ const searchField =
1397
+ rootElementRef.current?.querySelector(
1398
+ '#entity-creation-search-field'
1399
+ ) ||
1400
+ props.canvasObject.element.querySelector(
1401
+ '#entity-creation-search-field'
1402
+ );
1403
+ if (searchField) {
1404
+ (searchField as HTMLInputElement).focus();
1405
+ }
1406
+ }
1407
+
1408
+ loadAllData();
1409
+ }, [props.project, deferredSearchValue]); // Re-run when project or search value changes
1410
+
1411
+ // Wheel event listener cleanup
1412
+ useEffect(() => {
1413
+ const el = rootElementRef.current;
1414
+ if (!el) return;
1415
+
1416
+ const onWheel = (e: WheelEvent) => {
1417
+ const isZoomGesture = e.ctrlKey || e.metaKey;
1418
+ const withinList = Boolean(
1419
+ (e.target as HTMLElement).closest(
1420
+ '.canvas__new-entity-menu__section-list'
1421
+ )
1422
+ );
1423
+
1424
+ if (withinList && !isZoomGesture) {
1425
+ e.stopPropagation();
1426
+ return;
1427
+ }
1428
+
1429
+ e.preventDefault();
1430
+ props.canvasObject.canvas.handleOnZoomMouseWheel(e);
1431
+ };
1432
+
1433
+ el.addEventListener('wheel', onWheel, { passive: false });
1434
+ el.addEventListener('mousewheel', onWheel as EventListener, {
1435
+ passive: false
1436
+ });
1437
+ el.addEventListener('DOMMouseScroll', onWheel as EventListener, {
1438
+ passive: false
1439
+ });
1440
+
1441
+ return () => {
1442
+ el.removeEventListener('wheel', onWheel);
1443
+ el.removeEventListener('mousewheel', onWheel as EventListener);
1444
+ el.removeEventListener('DOMMouseScroll', onWheel as EventListener);
1445
+ };
1446
+ }, [props.canvasObject.canvas]);
1447
+
1448
+ // --- Lifecycle props ---
1449
+ useEffect(() => {
1450
+ const element = rootElementRef.current;
1451
+ if (!!element && document.contains(element)) {
1452
+ props.onMount?.(element);
1453
+ }
1454
+ return () => {
1455
+ props.onUnmount?.();
1456
+ };
1457
+ }, [props.onMount, props.onUnmount]);
1458
+
1459
+ useEffect(() => {
1460
+ const element = rootElementRef.current;
1461
+ if (element) {
1462
+ props.onUpdate?.(element);
1463
+ }
1464
+ }, [props, props.onUpdate]);
1465
+
1466
+ useLayoutEffect(() => {
1467
+ props.onBeforeUpdate?.();
1468
+ }, [props, props.onBeforeUpdate]);
1469
+
1470
+ // --- Resolution logic for display ---
1471
+ const resolveBaseSectionCollapsed = () => {
1472
+ if (baseEntitiesSectionCollapsed === false) return false;
1473
+ if (baseEntitiesSectionCollapsed === true) return true;
1474
+ return false;
1475
+ };
1476
+
1477
+ const resolveSpecificSectionCollapsed = () => {
1478
+ return (
1479
+ specificEntitiesSectionCollapsed === true ||
1480
+ (specificEntitiesSectionCollapsed === null && !hasSearched)
1481
+ );
1482
+ };
1483
+
1484
+ const baseSectionCollapsed = resolveBaseSectionCollapsed();
1485
+ const specificSectionCollapsed = resolveSpecificSectionCollapsed();
1486
+
1487
+ const flatRenderedItems: Array<{
1488
+ type: 'base' | 'specific';
1489
+ disabled: boolean;
1490
+ option: any;
1491
+ }> = [];
1492
+ if (!baseSectionCollapsed) {
1493
+ baseEntityResults.forEach((o) =>
1494
+ flatRenderedItems.push({ type: 'base', disabled: false, option: o })
1495
+ );
1496
+ baseEntityDisabledResults.forEach((o) =>
1497
+ flatRenderedItems.push({ type: 'base', disabled: true, option: o })
1498
+ );
1499
+ }
1500
+ if (!specificSectionCollapsed) {
1501
+ specificEntityResults.forEach((o) =>
1502
+ flatRenderedItems.push({
1503
+ type: 'specific',
1504
+ disabled: false,
1505
+ option: o
1506
+ })
1507
+ );
1508
+ specificEntityDisabledResults.forEach((o) =>
1509
+ flatRenderedItems.push({
1510
+ type: 'specific',
1511
+ disabled: true,
1512
+ option: o
1513
+ })
1514
+ );
1515
+ }
1516
+
1517
+ const successId = getCreateEntityMenuSuccessNodeId();
1518
+ const failureId = getCreateEntityMenuFailureNodeId();
1519
+
1520
+ const showSuccessFailureNodes =
1521
+ !!props.connectedTarget &&
1522
+ [ConnectionNodeType.CalledBy].includes(props.connectedNodeType);
1523
+
1524
+ const showMainValueOutput =
1525
+ !!props.connectedTarget &&
1526
+ [ConnectionNodeType.PropertyIn].includes(props.connectedNodeType);
1527
+
1528
+ const showMainValueInput =
1529
+ !!props.connectedSource &&
1530
+ [ConnectionNodeType.PropertyOut].includes(props.connectedNodeType);
1531
+
1532
+ const showMainHeaderButton = !showMainValueInput;
1533
+
1534
+ // This is the spinner component to show while loading
1535
+ const loaderSpinner = (
1536
+ <CircularProgress
1537
+ size={16}
1538
+ sx={{ color: 'rgba(0, 0, 0, 0.54)', margin: '0 4px' }}
1539
+ />
1540
+ );
1541
+
1542
+ return (
1543
+ <div
1544
+ ref={rootElementRef}
1545
+ className="canvas__new-entity-menu"
1546
+ onClick={(e) => {
1547
+ e.stopPropagation();
1548
+ }}
1549
+ >
1550
+ {showMainHeaderButton && (
1551
+ <div
1552
+ className="test-entity-button large"
1553
+ id={getCreateEntityMenuHeaderNodeId()}
1554
+ {...{
1555
+ [Canvas.OBJ_ATTR_NAME]:
1556
+ getCreateEntityMenuHeaderNodeId()
1557
+ }}
1558
+ style={{ backgroundColor: 'white' }}
1559
+ >
1560
+ <span
1561
+ style={{
1562
+ display: 'flex',
1563
+ gap: '6px',
1564
+ alignItems: 'center',
1565
+ position: 'relative',
1566
+ left: '1px',
1567
+ top: '1px'
1568
+ }}
1569
+ >
1570
+ <i className="fa-solid fa-plus"></i>
1571
+ </span>
1572
+ </div>
1573
+ )}
1574
+ <div className="canvas__new-entity-menu__header">
1575
+ <h4 className="canvas__new-entity-menu__title">Add node</h4>
1576
+ {showSuccessFailureNodes && (
1577
+ <div
1578
+ style={{ top: '8px' }}
1579
+ className="canvas__node__float-right canvas__node__success-error-node-stack"
1580
+ >
1581
+ <span
1582
+ {...{
1583
+ [Canvas.OBJ_ATTR_NAME]: `${successId}--container`
1584
+ }}
1585
+ id={`${successId}--container`}
1586
+ >
1587
+ <div
1588
+ className="canvas__large-node canvas__node__success"
1589
+ style={{ cursor: 'unset' }}
1590
+ {...{
1591
+ [Canvas.OBJ_ATTR_NAME]: successId
1592
+ }}
1593
+ id={successId}
1594
+ >
1595
+ <div
1596
+ {...{
1597
+ [Canvas.OBJ_ATTR_NAME]: `${successId}--center-ref`
1598
+ }}
1599
+ id={`${successId}--center-ref`}
1600
+ className="canvas__large-node__center-ref"
1601
+ ></div>
1602
+ <div>
1603
+ <i className="fa-solid fa-check"></i>
1604
+ </div>
1605
+ </div>
1606
+ </span>
1607
+ <span
1608
+ {...{
1609
+ [Canvas.OBJ_ATTR_NAME]: `${failureId}--container`
1610
+ }}
1611
+ id={`${failureId}--container`}
1612
+ >
1613
+ <div
1614
+ className="canvas__large-node canvas__node__failure"
1615
+ style={{ cursor: 'unset' }}
1616
+ {...{
1617
+ [Canvas.OBJ_ATTR_NAME]: failureId
1618
+ }}
1619
+ id={failureId}
1620
+ >
1621
+ <div
1622
+ {...{
1623
+ [Canvas.OBJ_ATTR_NAME]: `${failureId}--center-ref`
1624
+ }}
1625
+ id={`${failureId}--center-ref`}
1626
+ className="canvas__large-node__center-ref"
1627
+ ></div>
1628
+ <div>
1629
+ <i className="fa-solid fa-xmark"></i>
1630
+ </div>
1631
+ </div>
1632
+ </span>
1633
+ </div>
1634
+ )}
1635
+ </div>
1636
+ <TextField
1637
+ inputProps={{
1638
+ id: 'entity-creation-search-field',
1639
+ onKeyDown: (e: React.KeyboardEvent) => {
1640
+ if (
1641
+ e.key === 'ArrowDown' ||
1642
+ (e.key === 'Tab' && !e.shiftKey)
1643
+ ) {
1644
+ e.stopPropagation();
1645
+ e.preventDefault();
1646
+
1647
+ const isLastBaseNode =
1648
+ flatRenderedItems.length > 0 &&
1649
+ flatRenderedItems[focusedIndex]?.type ===
1650
+ 'base' &&
1651
+ focusedIndex === flatRenderedItems.length - 1;
1652
+
1653
+ let maxIndex = flatRenderedItems.length - 1;
1654
+
1655
+ if (isLastBaseNode && specificSectionCollapsed) {
1656
+ setSpecificEntitiesSectionCollapsed(false);
1657
+ maxIndex +=
1658
+ specificEntityResults.length +
1659
+ specificEntityDisabledResults.length;
1660
+ }
1661
+
1662
+ setFocusedIndex((prev) =>
1663
+ Math.min(prev + 1, Math.max(0, maxIndex))
1664
+ );
1665
+ } else if (
1666
+ e.key === 'ArrowUp' ||
1667
+ (e.key === 'Tab' && e.shiftKey)
1668
+ ) {
1669
+ e.stopPropagation();
1670
+ e.preventDefault();
1671
+
1672
+ const isFirstSpecificNode =
1673
+ flatRenderedItems.length > 0 &&
1674
+ flatRenderedItems[focusedIndex]?.type ===
1675
+ 'specific' &&
1676
+ focusedIndex === 0;
1677
+
1678
+ if (isFirstSpecificNode && baseSectionCollapsed) {
1679
+ const baseCount =
1680
+ baseEntityResults.length +
1681
+ baseEntityDisabledResults.length;
1682
+ if (baseCount > 0) {
1683
+ setBaseEntitiesSectionCollapsed(false);
1684
+ setFocusedIndex(baseCount - 1);
1685
+ return;
1686
+ }
1687
+ }
1688
+
1689
+ setFocusedIndex((prev) => Math.max(prev - 1, 0));
1690
+ } else if (e.key === 'Enter') {
1691
+ e.stopPropagation();
1692
+ e.preventDefault();
1693
+ const item = flatRenderedItems[focusedIndex];
1694
+ if (item && !item.disabled) {
1695
+ if (item.type === 'base') {
1696
+ props.onCreateBaseEntity(
1697
+ item.option.type,
1698
+ e as any
1699
+ );
1700
+ } else {
1701
+ props.onCreateFromSpecificEntity(
1702
+ item.option.entity,
1703
+ e as any
1704
+ );
1705
+ }
1706
+ }
1707
+ }
1708
+ }
1709
+ }}
1710
+ variant="filled"
1711
+ label="Find node"
1712
+ iconEnd="fa-solid fa-magnifying-glass"
1713
+ value={searchValue}
1714
+ onChange={(value: any) => {
1715
+ onSearch(value);
1716
+ }}
1717
+ />
1718
+ {/* This element fakes the main value input of a normal card, so we can attach connections to it */}
1719
+ {showMainValueInput && (
1720
+ <div
1721
+ style={{
1722
+ position: 'absolute',
1723
+ height: '50px',
1724
+ width: '15px',
1725
+ backgroundColor: 'white',
1726
+ left: '-15px',
1727
+ top: '41px',
1728
+ borderRadius: '10px 0 0 10px'
1729
+ }}
1730
+ >
1731
+ <div
1732
+ id={getCreateEntityMenuMainValueInputNodeId()}
1733
+ {...{
1734
+ [Canvas.OBJ_ATTR_NAME]:
1735
+ getCreateEntityMenuMainValueInputNodeId()
1736
+ }}
1737
+ className={[
1738
+ 'canvas__small-node',
1739
+ 'canvas__small-node--position',
1740
+ 'canvas-property__node__connected',
1741
+ 'canvas__node__active',
1742
+ 'canvas__node__left'
1743
+ ].join(' ')}
1744
+ style={{
1745
+ top: '50%',
1746
+ transform: 'translateY(-50%)'
1747
+ }}
1748
+ />
1749
+ </div>
1750
+ )}
1751
+ {/* This element fakes the main value output of a normal card, so we can attach connections to it */}
1752
+ {showMainValueOutput && (
1753
+ <div
1754
+ style={{
1755
+ position: 'absolute',
1756
+ height: '50px',
1757
+ width: '15px',
1758
+ backgroundColor: 'white',
1759
+ right: '-15px',
1760
+ top: '41px',
1761
+ borderRadius: '0 10px 10px 0'
1762
+ }}
1763
+ >
1764
+ <div
1765
+ id={getCreateEntityMenuMainValueOutputNodeId()}
1766
+ {...{
1767
+ [Canvas.OBJ_ATTR_NAME]:
1768
+ getCreateEntityMenuMainValueOutputNodeId()
1769
+ }}
1770
+ className={[
1771
+ 'canvas__small-node',
1772
+ 'canvas__small-node--position',
1773
+ 'canvas-property__node__connected',
1774
+ 'canvas__node__active'
1775
+ ].join(' ')}
1776
+ style={{
1777
+ top: '50%',
1778
+ transform: 'translateY(-50%)'
1779
+ }}
1780
+ />
1781
+ </div>
1782
+ )}
1783
+ <button
1784
+ className="canvas__new-entity-menu__section-title-container"
1785
+ onClick={() => {
1786
+ const newCollapsedState = !resolveBaseSectionCollapsed();
1787
+ setBaseEntitiesSectionCollapsed(newCollapsedState);
1788
+ }}
1789
+ >
1790
+ <div
1791
+ style={{
1792
+ display: 'flex',
1793
+ alignItems: 'center',
1794
+ gap: '8px'
1795
+ }}
1796
+ >
1797
+ <p className="canvas__new-entity-menu__section-title">
1798
+ Node types
1799
+ </p>
1800
+ {isBaseLoading ? (
1801
+ loaderSpinner
1802
+ ) : (
1803
+ <Badge
1804
+ className="canvas-badge-overrides"
1805
+ id="create-new-entity-menu--base-entities--counter-badge"
1806
+ ref={canvasPopupBaseEntitiesCountToolip.anchorRef}
1807
+ >
1808
+ {baseEntityResults.length.toString()}
1809
+ </Badge>
1810
+ )}
1811
+ </div>
1812
+ <i
1813
+ className={`fa-solid ${
1814
+ baseSectionCollapsed
1815
+ ? 'fa-chevron-down'
1816
+ : 'fa-chevron-up'
1817
+ }`}
1818
+ ></i>
1819
+ </button>
1820
+ {!baseSectionCollapsed && (
1821
+ <ul className="canvas__new-entity-menu__section-list">
1822
+ {baseEntityResults.map((option) => {
1823
+ const isFocused =
1824
+ flatRenderedItems[focusedIndex]?.option === option;
1825
+ return (
1826
+ <CreateBaseEntityOption
1827
+ key={option.type}
1828
+ option={option}
1829
+ isFocused={isFocused}
1830
+ parentCanvasObject={props.canvasObject}
1831
+ onCreateBaseEntity={props.onCreateBaseEntity}
1832
+ project={props.project}
1833
+ />
1834
+ );
1835
+ })}
1836
+ {baseEntityDisabledResults.map((option) => {
1837
+ const isFocused =
1838
+ flatRenderedItems[focusedIndex]?.option === option;
1839
+ return (
1840
+ <CreateBaseEntityOption
1841
+ key={option.type}
1842
+ option={option}
1843
+ isFocused={isFocused}
1844
+ parentCanvasObject={props.canvasObject}
1845
+ onCreateBaseEntity={props.onCreateBaseEntity}
1846
+ project={props.project}
1847
+ />
1848
+ );
1849
+ })}
1850
+ </ul>
1851
+ )}
1852
+ <hr className="canvas__new-entity-menu__separator" />
1853
+ <button
1854
+ className="canvas__new-entity-menu__section-title-container"
1855
+ onClick={() => {
1856
+ setSpecificEntitiesSectionCollapsed(
1857
+ !resolveSpecificSectionCollapsed()
1858
+ );
1859
+ }}
1860
+ >
1861
+ <div
1862
+ style={{
1863
+ display: 'flex',
1864
+ alignItems: 'center',
1865
+ gap: '8px'
1866
+ }}
1867
+ >
1868
+ <p className="canvas__new-entity-menu__section-title">
1869
+ Specific nodes
1870
+ </p>
1871
+ {isSpecificLoading ? (
1872
+ loaderSpinner
1873
+ ) : (
1874
+ <Badge
1875
+ className="canvas-badge-overrides"
1876
+ id="create-new-entity-menu--specific-entities--counter-badge"
1877
+ ref={
1878
+ canvasPopupSpecificEntitiesCountToolip.anchorRef
1879
+ }
1880
+ >
1881
+ {specificEntityResults.length.toString()}
1882
+ </Badge>
1883
+ )}
1884
+ </div>
1885
+ <i
1886
+ className={`fa-solid ${
1887
+ specificSectionCollapsed
1888
+ ? 'fa-chevron-down'
1889
+ : 'fa-chevron-up'
1890
+ }`}
1891
+ ></i>
1892
+ </button>
1893
+ {!specificSectionCollapsed &&
1894
+ !isSpecificLoading && // <-- Don't show "no results" while loading
1895
+ !specificEntityResults.length &&
1896
+ !specificEntityDisabledResults.length && (
1897
+ <p className="canvas__new-entity-menu__section-empty-placeholder">
1898
+ No results found
1899
+ </p>
1900
+ )}
1901
+ {!specificSectionCollapsed &&
1902
+ (!!specificEntityResults.length ||
1903
+ !!specificEntityDisabledResults.length) && (
1904
+ <ul
1905
+ className="canvas__new-entity-menu__section-list"
1906
+ id="create-new-entity-menu--specific-entities--section-list"
1907
+ onScroll={(e) => {
1908
+ e.stopPropagation();
1909
+
1910
+ const list = document.getElementById(
1911
+ 'create-new-entity-menu--specific-entities--section-list'
1912
+ );
1913
+ if (list) {
1914
+ const currentScrollTop = list.scrollTop || 0;
1915
+ const height = list.scrollHeight || 0;
1916
+
1917
+ const scrollUpButton = document.querySelector(
1918
+ '.canvas__new-entity-menu__scroll-up-button'
1919
+ ) as HTMLDivElement;
1920
+ const scrollDownButton = document.querySelector(
1921
+ '.canvas__new-entity-menu__scroll-down-button'
1922
+ ) as HTMLDivElement;
1923
+
1924
+ if (scrollUpButton && scrollDownButton) {
1925
+ scrollUpButton.style.visibility =
1926
+ currentScrollTop === 0
1927
+ ? 'hidden'
1928
+ : 'visible';
1929
+ scrollDownButton.style.visibility =
1930
+ currentScrollTop + 400 >= height
1931
+ ? 'hidden'
1932
+ : 'visible';
1933
+ }
1934
+ }
1935
+ }}
1936
+ >
1937
+ {specificEntityResults.map((option) => {
1938
+ const isFocused =
1939
+ flatRenderedItems[focusedIndex]?.option ===
1940
+ option;
1941
+ return (
1942
+ <CreateSpecificEntityOption
1943
+ key={option.entity.id}
1944
+ option={option}
1945
+ isFocused={isFocused}
1946
+ parentCanvasObject={props.canvasObject}
1947
+ onCreateFromSpecificEntity={
1948
+ props.onCreateFromSpecificEntity
1949
+ }
1950
+ project={props.project}
1951
+ />
1952
+ );
1953
+ })}
1954
+ {specificEntityDisabledResults.map((option) => {
1955
+ const isFocused =
1956
+ flatRenderedItems[focusedIndex]?.option ===
1957
+ option;
1958
+ return (
1959
+ <CreateSpecificEntityOption
1960
+ key={option.entity.id}
1961
+ option={option}
1962
+ isFocused={isFocused}
1963
+ parentCanvasObject={props.canvasObject}
1964
+ onCreateFromSpecificEntity={
1965
+ props.onCreateFromSpecificEntity
1966
+ }
1967
+ project={props.project}
1968
+ />
1969
+ );
1970
+ })}
1971
+ <button
1972
+ style={{ visibility: 'hidden' }}
1973
+ className="canvas__new-entity-menu__scroll-button canvas__new-entity-menu__scroll-up-button"
1974
+ onClick={(e) => {
1975
+ const list = document.getElementById(
1976
+ 'create-new-entity-menu--specific-entities--section-list'
1977
+ );
1978
+ if (list) {
1979
+ list.scrollTo({
1980
+ top: 0,
1981
+ behavior: 'smooth'
1982
+ });
1983
+ e.currentTarget.style.visibility = 'hidden';
1984
+ }
1985
+ }}
1986
+ >
1987
+ <i className="fa-duotone fa-arrow-up-to-line"></i>
1988
+ </button>
1989
+ <button
1990
+ className="canvas__new-entity-menu__scroll-button canvas__new-entity-menu__scroll-down-button"
1991
+ onClick={(e) => {
1992
+ const list = document.getElementById(
1993
+ 'create-new-entity-menu--specific-entities--section-list'
1994
+ );
1995
+ if (list) {
1996
+ const currentScrollBottom =
1997
+ (list.scrollTop || 0) + 400;
1998
+ const height = list.scrollHeight || 0;
1999
+
2000
+ list.scrollTo({
2001
+ top: currentScrollBottom + 380,
2002
+ behavior: 'smooth'
2003
+ });
2004
+
2005
+ if (currentScrollBottom + 380 >= height) {
2006
+ e.currentTarget.style.visibility =
2007
+ 'hidden';
2008
+ }
2009
+ }
2010
+ }}
2011
+ >
2012
+ <i className="fa-solid fa-arrow-down"></i>
2013
+ </button>
2014
+ </ul>
2015
+ )}
2016
+ </div>
2017
+ );
2018
+ };
2019
+
2020
+ export default CreateEntityMenu;