@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,1779 @@
1
+ import React, { useCallback, useEffect, useState } from 'react';
2
+ import {
3
+ Autocomplete,
4
+ Box,
5
+ Collapse,
6
+ IconButton,
7
+ TextField,
8
+ Tooltip as MuiTooltip,
9
+ } from '@mui/material';
10
+ import { useSearchStatementContext } from './search-statement-context';
11
+ import {
12
+ EntityType,
13
+ InputMapState,
14
+ PrimitiveTypes,
15
+ ProjectState,
16
+ resolveEntityName,
17
+ searchStatementDefs,
18
+ searchStatementState as searchStatementStateLib,
19
+ searchStatementUtils,
20
+ } from '@elyx-code/project-logic-tree';
21
+ import DropdownMenuPopup from '../../../lib/dropdown/DropdownMenuPopup';
22
+ import ColumnSelector from './ColumnSelector';
23
+ import LiteralValueField, {
24
+ fromLiteralValueTypeToReadableLabel,
25
+ ILiteralValueOption,
26
+ LiteralValueTypeSelector,
27
+ } from './LiteralValue';
28
+ import { getPrimitiveTypeInputs } from '../../../services/editor/utils';
29
+ import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
30
+ import WarningIcon from '../../../assets/warning-sign-24px.png';
31
+ import EntityDialogSectionHeader from '../../EntityDialogSectionHeader';
32
+ import { Logger } from '@elyx-code/common-ts-utils';
33
+ import { debounce } from '../../../utils';
34
+ import { ConditionSelectorValue, ValueSelector } from './ValueSelector';
35
+
36
+ const {
37
+ AllColumnsSelector,
38
+ ColumnRef,
39
+ DataSource,
40
+ SearchStatementLiteralValue,
41
+ SortStatement,
42
+ ValueRef,
43
+ FunctionCall,
44
+ } = searchStatementStateLib;
45
+
46
+ const {
47
+ SearchStatementNodeType,
48
+ SQLFunctionCategory,
49
+ SearchLiteralValueType,
50
+ SortStatementDirection,
51
+ } = searchStatementDefs;
52
+
53
+ type DataSource = searchStatementStateLib.DataSource;
54
+ type ColumnRef = searchStatementStateLib.ColumnRef;
55
+ type SearchStatementNodeType = searchStatementDefs.SearchStatementNodeType;
56
+ type AllColumnsSelector = searchStatementStateLib.AllColumnsSelector;
57
+ type SearchLiteralValueType = searchStatementDefs.SearchLiteralValueType;
58
+ type SearchStatementLiteralValue =
59
+ searchStatementStateLib.SearchStatementLiteralValue;
60
+ type ValueRef = searchStatementStateLib.ValueRef;
61
+ type SortStatement = searchStatementStateLib.SortStatement;
62
+ type SortStatementDirection = searchStatementDefs.SortStatementDirection;
63
+ type FunctionCall = searchStatementStateLib.FunctionCall;
64
+
65
+ export type CalculatedSelectionsBySource = (
66
+ | {
67
+ dataSource: DataSource | null;
68
+ columns: ColumnRef[];
69
+ allColumnsSelected: null;
70
+ // Just so the UI doesn't jump
71
+ index: number;
72
+ }
73
+ | {
74
+ dataSource: DataSource;
75
+ columns: [];
76
+ allColumnsSelected: AllColumnsSelector;
77
+ // Just so the UI doesn't jump
78
+ index: number;
79
+ }
80
+ )[];
81
+
82
+ export const NumberSelector = ({
83
+ value,
84
+ onChange,
85
+ options,
86
+ id,
87
+ }: {
88
+ value: SearchStatementLiteralValue | ValueRef;
89
+ onChange: (value: SearchStatementLiteralValue | ValueRef) => void;
90
+ options: InputMapState[];
91
+ id?: string;
92
+ }) => {
93
+ const { project, searchStatementState, entity } = useSearchStatementContext();
94
+
95
+ const resolveLocalValueFromExternalValue = (
96
+ value: SearchStatementLiteralValue | ValueRef,
97
+ ): ValueRef | ILiteralValueOption => {
98
+ if (value?.type === SearchStatementNodeType.LiteralValue) {
99
+ return {
100
+ type: 'literal-value-option',
101
+ valueType: value.valueType,
102
+ };
103
+ }
104
+
105
+ return value;
106
+ };
107
+
108
+ const [localValue, _] = React.useState<ValueRef | ILiteralValueOption>(
109
+ resolveLocalValueFromExternalValue(value),
110
+ );
111
+
112
+ const sanitizedOptions: (ValueRef | ILiteralValueOption)[] = [
113
+ {
114
+ type: 'literal-value-option',
115
+ valueType: SearchLiteralValueType.Number,
116
+ },
117
+ ...(options.map((option) => {
118
+ if (option.type === EntityType.InputMap) {
119
+ const newValueRef = new ValueRef(searchStatementState);
120
+ newValueRef.inputMap = option;
121
+ return newValueRef;
122
+ }
123
+
124
+ return option;
125
+ }) as (ILiteralValueOption | ValueRef)[]),
126
+ ];
127
+
128
+ const showLiteralValueField: boolean =
129
+ !!localValue &&
130
+ localValue.type === 'literal-value-option' &&
131
+ localValue.valueType !== SearchLiteralValueType.Null;
132
+
133
+ const resolvedId = id || 'number-selector-value--' + ProjectState.UUID.uuid();
134
+
135
+ return (
136
+ <span
137
+ style={{
138
+ display: 'flex',
139
+ gap: 8,
140
+ alignItems: 'center',
141
+ }}
142
+ >
143
+ <Autocomplete
144
+ disablePortal
145
+ disabled={!entity.editable}
146
+ options={sanitizedOptions}
147
+ sx={{
148
+ width: 200,
149
+ }}
150
+ value={localValue}
151
+ getOptionLabel={(option) => {
152
+ if (option.type === 'literal-value-option') {
153
+ return `${fromLiteralValueTypeToReadableLabel(
154
+ option.valueType,
155
+ )} value`;
156
+ } else if (option.type === SearchStatementNodeType.ValueRef) {
157
+ return resolveEntityName(option.inputMap, project.logic);
158
+ }
159
+
160
+ return 'value';
161
+ }}
162
+ onChange={(e, newValue) => {
163
+ let resolvedNewValue:
164
+ | SearchStatementLiteralValue
165
+ | ColumnRef
166
+ | ValueRef;
167
+
168
+ if (newValue?.type === 'literal-value-option') {
169
+ resolvedNewValue = new SearchStatementLiteralValue(
170
+ searchStatementState,
171
+ );
172
+ resolvedNewValue.valueType = newValue.valueType;
173
+
174
+ // If null, then initialize the value to null
175
+ if (newValue.valueType === SearchLiteralValueType.Null) {
176
+ resolvedNewValue.value = null;
177
+ }
178
+ } else {
179
+ resolvedNewValue = newValue;
180
+ }
181
+
182
+ // Send a new literal value as well, but with the value property being empty,
183
+ // when the component is updated, the local value will be set
184
+ onChange(resolvedNewValue as ValueRef | SearchStatementLiteralValue);
185
+ }}
186
+ renderOption={(props, option) => {
187
+ const { key, ...optionProps } = props;
188
+ return (
189
+ <Box
190
+ key={key}
191
+ component='li'
192
+ sx={{ '& > img': { mr: 2, flexShrink: 0 } }}
193
+ {...optionProps}
194
+ >
195
+ <ConditionSelectorValue value={option} />
196
+ </Box>
197
+ );
198
+ }}
199
+ renderInput={(params) => (
200
+ <span
201
+ style={{ position: 'relative', display: 'block', height: '100%' }}
202
+ >
203
+ <span
204
+ id={`${resolvedId}--input-selected-value`}
205
+ style={{
206
+ position: 'absolute',
207
+ top: 8,
208
+ left: 0,
209
+ width: '100%',
210
+ height: '100%',
211
+ maxHeight: 30,
212
+ overflowY: 'hidden',
213
+ // To match start of text field text
214
+ paddingLeft: 14,
215
+ display: 'flex',
216
+ flexDirection: 'column',
217
+ }}
218
+ >
219
+ <ConditionSelectorValue value={value} />
220
+ </span>
221
+
222
+ <TextField
223
+ {...params}
224
+ size='small'
225
+ label={`Select source of value`}
226
+ // className={`condition-builder__${side}-input`}
227
+ slotProps={{
228
+ htmlInput: {
229
+ ...params.inputProps,
230
+ id: `${resolvedId}--input`,
231
+ style: {
232
+ opacity: 0,
233
+ },
234
+ },
235
+ }}
236
+ onFocus={(e) => {
237
+ e.target.style.opacity = '1';
238
+
239
+ const selectedValueElement = document.getElementById(
240
+ `${resolvedId}--input-selected-value`,
241
+ );
242
+
243
+ if (!!selectedValueElement) {
244
+ selectedValueElement.style.visibility = 'hidden';
245
+ }
246
+ }}
247
+ onBlur={(e) => {
248
+ e.target.style.opacity = '0';
249
+
250
+ const selectedValueElement = document.getElementById(
251
+ `${resolvedId}--input-selected-value`,
252
+ );
253
+
254
+ if (!!selectedValueElement) {
255
+ selectedValueElement.style.visibility = 'visible';
256
+ }
257
+ }}
258
+ />
259
+ </span>
260
+ )}
261
+ />
262
+
263
+ {showLiteralValueField && (
264
+ <LiteralValueField
265
+ disabled={!entity.editable}
266
+ value={value as SearchStatementLiteralValue}
267
+ onChange={(newValue) => {
268
+ onChange(newValue);
269
+ }}
270
+ />
271
+ )}
272
+ </span>
273
+ );
274
+ };
275
+
276
+ export const LiteralColumnOutputSelector = ({
277
+ columnRef,
278
+ index,
279
+ }: {
280
+ columnRef: ColumnRef | null;
281
+ index: number;
282
+ }) => {
283
+ const { editSelection, removeSelection, searchStatementState, entity } =
284
+ useSearchStatementContext();
285
+
286
+ const [localAliasValue, setLocalAliasValue] = useState<string>(
287
+ columnRef.as || '',
288
+ );
289
+ const [aliasValidationError, setAliasValidationError] = useState<
290
+ string | null
291
+ >(null);
292
+
293
+ // The alias value is only set to the state if its valid
294
+ // Since the alias is a free text field, the user could input a value which conflicts with the SQL syntax
295
+ // Therefore we that the value doesn't contain any forbidden keywords or combination of characters
296
+ const setAliasValue = (value: string) => {
297
+ Logger.log('setAliasValue: ', value);
298
+ let validationError: string | null = null;
299
+
300
+ // Step 1: Convert to uppercase for case-insensitive comparison
301
+ const upperValue = value.toUpperCase().trim();
302
+
303
+ // Step 2: Check for forbidden keywords (exact match only)
304
+ if (searchStatementUtils.FORBIDDEN_SQL_KEYWORDS.includes(upperValue)) {
305
+ validationError = `The alias "${value}" is a reserved SQL keyword and cannot be used.`;
306
+ }
307
+
308
+ // Step 3: Regex to allow only alphanumeric characters and underscores, and no spaces
309
+ else if (!/^[A-Za-z0-9_]+$/.test(value)) {
310
+ validationError =
311
+ 'Alias must contain only letters, numbers, and underscores, with no spaces.';
312
+ }
313
+
314
+ // Step 4: Check alias length (e.g., max 50 characters)
315
+ else if (value.length > 50) {
316
+ validationError = 'Alias must be 50 characters or fewer.';
317
+ }
318
+
319
+ // Only update the column alias if validation passes
320
+ if (!validationError) {
321
+ Logger.log('Here, no error');
322
+ columnRef.as = value;
323
+ setLocalAliasValue(value);
324
+ editSelection(columnRef);
325
+ setAliasValidationError(null);
326
+ } else {
327
+ setLocalAliasValue(value);
328
+ setAliasValidationError(validationError);
329
+ }
330
+ };
331
+
332
+ return (
333
+ <div
334
+ style={{
335
+ display: 'flex',
336
+ gap: 12,
337
+ }}
338
+ >
339
+ {/* Number, column selector dropdown, order type selector dropdown, then 3 dot icon buton */}
340
+ <div
341
+ style={{
342
+ height: 40,
343
+ display: 'flex',
344
+ alignItems: 'center',
345
+ }}
346
+ >
347
+ <p
348
+ style={{
349
+ margin: 0,
350
+ fontSize: 12,
351
+ fontWeight: 600,
352
+ color: 'rgba(0,0,0,0.54)',
353
+ }}
354
+ >
355
+ {(index + 1).toString()}
356
+ </p>
357
+ </div>
358
+
359
+ <LiteralValueTypeSelector
360
+ value={columnRef.source as SearchStatementLiteralValue}
361
+ onChange={(newValue) => {
362
+ columnRef.source = newValue;
363
+
364
+ editSelection(columnRef);
365
+ }}
366
+ />
367
+
368
+ <div
369
+ style={{
370
+ height: 40,
371
+ display: 'flex',
372
+ alignItems: 'center',
373
+ }}
374
+ >
375
+ <ArrowForwardIosIcon
376
+ component='svg'
377
+ sx={{
378
+ color: 'rgba(0,0,0,0.54)',
379
+ fontSize: 10,
380
+ }}
381
+ />
382
+ </div>
383
+
384
+ <TextField
385
+ disabled={!entity.editable}
386
+ id={`search-statement-dialog-property-order-${
387
+ columnRef.id || 'unknown'
388
+ }-select`}
389
+ sx={{ width: 200, marginTop: 0 }}
390
+ onChange={(e) => {
391
+ Logger.log('Value: ', e.target.value);
392
+ setAliasValue(e.target.value);
393
+ }}
394
+ size='small'
395
+ label='Output property name'
396
+ value={localAliasValue}
397
+ error={!!aliasValidationError}
398
+ helperText={aliasValidationError}
399
+ />
400
+
401
+ <div
402
+ style={{
403
+ height: 40,
404
+ display: 'flex',
405
+ alignItems: 'center',
406
+ }}
407
+ >
408
+ <IconButton
409
+ id={`search-statement-dialog-property-${
410
+ columnRef.id || 'unknown'
411
+ }-more-button`}
412
+ aria-label='more'
413
+ size='small'
414
+ style={{
415
+ width: 30,
416
+ height: 30,
417
+ fontSize: 18,
418
+ }}
419
+ >
420
+ <i className='fa-solid fa-ellipsis-v' />
421
+ </IconButton>
422
+ </div>
423
+
424
+ <DropdownMenuPopup
425
+ popupOptions={{
426
+ anchorQuerySelector: `#search-statement-dialog-property-${
427
+ columnRef.id || 'unknown'
428
+ }-more-button`,
429
+ placement: 'right',
430
+ }}
431
+ options={[
432
+ {
433
+ disabled: !entity.editable,
434
+ onClick: async () => {
435
+ removeSelection(columnRef);
436
+ },
437
+ label: 'Delete selection',
438
+ color: 'error',
439
+ iconEnd: 'fa-solid fa-trash-can',
440
+ tooltip: 'Click to delete this property selection',
441
+ },
442
+ ]}
443
+ />
444
+
445
+ {searchStatementState.duplicateColumnNames.includes(columnRef.as) && (
446
+ <MuiTooltip
447
+ placement='right'
448
+ enterDelay={300}
449
+ leaveDelay={200}
450
+ arrow
451
+ title={
452
+ <span>
453
+ <p
454
+ style={{
455
+ margin: 0,
456
+ fontSize: 10,
457
+ marginBottom: 4,
458
+ }}
459
+ >
460
+ Duplicate property name:
461
+ </p>
462
+ <p
463
+ style={{
464
+ margin: 0,
465
+ fontSize: 16,
466
+ fontWeight: 600,
467
+ }}
468
+ >
469
+ {columnRef.as}
470
+ </p>
471
+
472
+ <p
473
+ style={{
474
+ margin: 0,
475
+ fontSize: 8,
476
+ marginTop: 10,
477
+ color: 'rgba(255,255,255,0.7)',
478
+ }}
479
+ >
480
+ All duplicate columns will be ignored in the final result
481
+ </p>
482
+ </span>
483
+ }
484
+ >
485
+ <div
486
+ style={{
487
+ height: 40,
488
+ display: 'flex',
489
+ alignItems: 'center',
490
+ }}
491
+ >
492
+ <img className='search-statement__error-icon' src={WarningIcon} />
493
+ </div>
494
+ </MuiTooltip>
495
+ )}
496
+ </div>
497
+ );
498
+ };
499
+
500
+ export const FunctionColumnOutputSelector = ({
501
+ functionCall,
502
+ index,
503
+ }: {
504
+ functionCall: FunctionCall;
505
+ index: number;
506
+ }) => {
507
+ const { editSelection, removeSelection, searchStatementState, entity } =
508
+ useSearchStatementContext();
509
+
510
+ const [localAliasValue, setLocalAliasValue] = useState<string>(
511
+ functionCall.as || '',
512
+ );
513
+ const [aliasValidationError, setAliasValidationError] = useState<
514
+ string | null
515
+ >(null);
516
+
517
+ const setAliasValue = (value: string) => {
518
+ let validationError: string | null = null;
519
+ const upperValue = value.toUpperCase().trim();
520
+
521
+ if (searchStatementUtils.FORBIDDEN_SQL_KEYWORDS.includes(upperValue)) {
522
+ validationError = `The alias "${value}" is a reserved SQL keyword.`;
523
+ } else if (!/^[A-Za-z0-9_]+$/.test(value)) {
524
+ validationError =
525
+ 'Alias must contain only letters, numbers, and underscores.';
526
+ } else if (value.length > 50) {
527
+ validationError = 'Alias must be 50 characters or fewer.';
528
+ }
529
+
530
+ if (!validationError) {
531
+ functionCall.as = value;
532
+ setLocalAliasValue(value);
533
+ editSelection(functionCall);
534
+ setAliasValidationError(null);
535
+ } else {
536
+ setLocalAliasValue(value);
537
+ setAliasValidationError(validationError);
538
+ }
539
+ };
540
+
541
+ return (
542
+ <div style={{ display: 'flex', gap: 12, alignItems: 'flex-start' }}>
543
+ <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
544
+ <p
545
+ style={{
546
+ margin: 0,
547
+ fontSize: 12,
548
+ fontWeight: 600,
549
+ color: 'rgba(0,0,0,0.54)',
550
+ }}
551
+ >
552
+ {(index + 1).toString()}
553
+ </p>
554
+ </div>
555
+
556
+ {/* Intentionally NOT flexGrow:1 — letting the wrapper grow forces the
557
+ inner FunctionCallSelector (width:100%) to expand to the full row
558
+ width, which pushes the "Output property name" field all the way
559
+ right even when the function body is a single short argument.
560
+ Content-sizing keeps the sibling fields close while still allowing
561
+ the wrapper to grow when the function body itself is wide. */}
562
+ <span style={{ minWidth: 0 }}>
563
+ <ValueSelector
564
+ side='left'
565
+ value={functionCall}
566
+ disabled={!entity.editable}
567
+ // Prohibit table-valued functions in the SELECT clause!
568
+ disabledFunctionCategories={[SQLFunctionCategory.TableValued]}
569
+ options={searchStatementState.columns}
570
+ onChange={(newValue) => {
571
+ editSelection(newValue as FunctionCall);
572
+ }}
573
+ includeOnlyOptionTypes={[
574
+ searchStatementDefs.SearchStatementNodeType.FunctionCall,
575
+ ]}
576
+ horizontalSeparator={true}
577
+ />
578
+ </span>
579
+
580
+ <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
581
+ <ArrowForwardIosIcon
582
+ component='svg'
583
+ sx={{ color: 'rgba(0,0,0,0.54)', fontSize: 10 }}
584
+ />
585
+ </div>
586
+
587
+ <TextField
588
+ disabled={!entity.editable}
589
+ sx={{ width: 200, marginTop: 0 }}
590
+ onChange={(e) => setAliasValue(e.target.value)}
591
+ size='small'
592
+ label='Output property name'
593
+ value={localAliasValue}
594
+ error={!!aliasValidationError}
595
+ helperText={aliasValidationError}
596
+ />
597
+
598
+ <div style={{ height: 40, display: 'flex', alignItems: 'center' }}>
599
+ <IconButton
600
+ id={`search-statement-dialog-property-${functionCall.id}-more-button`}
601
+ aria-label='more'
602
+ size='small'
603
+ style={{ width: 30, height: 30, fontSize: 18 }}
604
+ >
605
+ <i className='fa-solid fa-ellipsis-v' />
606
+ </IconButton>
607
+ </div>
608
+
609
+ <DropdownMenuPopup
610
+ popupOptions={{
611
+ anchorQuerySelector: `#search-statement-dialog-property-${functionCall.id}-more-button`,
612
+ placement: 'right',
613
+ }}
614
+ options={[
615
+ {
616
+ disabled: !entity.editable,
617
+ onClick: async () => removeSelection(functionCall),
618
+ label: 'Delete selection',
619
+ color: 'error',
620
+ iconEnd: 'fa-solid fa-trash-can',
621
+ tooltip: 'Click to delete this function property',
622
+ },
623
+ ]}
624
+ />
625
+ </div>
626
+ );
627
+ };
628
+
629
+ export const SourcedColumnOutputSelector = ({
630
+ columnRef,
631
+ index,
632
+ }: {
633
+ columnRef: ColumnRef | null;
634
+ index: number;
635
+ }) => {
636
+ const { editSelection, removeSelection, searchStatementState, entity } =
637
+ useSearchStatementContext();
638
+
639
+ const [localAliasValue, setLocalAliasValue] = useState<string>(
640
+ columnRef.as || '',
641
+ );
642
+ const [aliasValidationError, setAliasValidationError] = useState<
643
+ string | null
644
+ >(null);
645
+
646
+ const debouncedEditSelection = useCallback(
647
+ debounce(
648
+ (value: string) => {
649
+ columnRef.as = value;
650
+ editSelection(columnRef);
651
+ },
652
+ 600,
653
+ false,
654
+ ),
655
+ [],
656
+ );
657
+
658
+ // The alias value is only set to the state if its valid
659
+ // Since the alias is a free text field, the user could input a value which conflicts with the SQL syntax
660
+ // Therefore we that the value doesn't contain any forbidden keywords or combination of characters
661
+ const setAliasValue = (value: string) => {
662
+ Logger.log('setAliasValue: ', value);
663
+ let validationError: string | null = null;
664
+
665
+ // Step 1: Convert to uppercase for case-insensitive comparison
666
+ const upperValue = value.toUpperCase().trim();
667
+
668
+ // Step 2: Check for forbidden keywords (exact match only)
669
+ if (searchStatementUtils.FORBIDDEN_SQL_KEYWORDS.includes(upperValue)) {
670
+ validationError = `The alias "${value}" is a reserved SQL keyword and cannot be used.`;
671
+ }
672
+
673
+ // Step 3: Regex to allow only alphanumeric characters and underscores, and no spaces
674
+ else if (!/^[A-Za-z0-9_]+$/.test(value)) {
675
+ validationError =
676
+ 'Alias must contain only letters, numbers, and underscores, with no spaces.';
677
+ }
678
+
679
+ // Step 4: Check alias length (e.g., max 50 characters)
680
+ else if (value.length > 50) {
681
+ validationError = 'Alias must be 50 characters or fewer.';
682
+ }
683
+
684
+ // Only update the column alias if validation passes
685
+ if (!validationError) {
686
+ columnRef.as = value;
687
+ setLocalAliasValue(value);
688
+ debouncedEditSelection(value);
689
+ setAliasValidationError(null);
690
+ } else {
691
+ setLocalAliasValue(value);
692
+ setAliasValidationError(validationError);
693
+ }
694
+ };
695
+
696
+ const resolvedSource: ColumnRef | FunctionCall =
697
+ columnRef.source?.type === EntityType.Property ||
698
+ columnRef.source?.type === SearchStatementNodeType.LiteralValue ||
699
+ columnRef.source?.type === SearchStatementNodeType.ValueRef
700
+ ? columnRef
701
+ : columnRef.source;
702
+
703
+ return (
704
+ <div
705
+ key={columnRef.id || 'unknown'}
706
+ style={{
707
+ display: 'flex',
708
+ gap: 12,
709
+ }}
710
+ >
711
+ {/* Number, column selector dropdown, order type selector dropdown, then 3 dot icon buton */}
712
+ <div
713
+ style={{
714
+ height: 40,
715
+ display: 'flex',
716
+ alignItems: 'center',
717
+ }}
718
+ >
719
+ <p
720
+ style={{
721
+ margin: 0,
722
+ fontSize: 12,
723
+ fontWeight: 600,
724
+ color: 'rgba(0,0,0,0.54)',
725
+ }}
726
+ >
727
+ {(index + 1).toString()}
728
+ </p>
729
+ </div>
730
+
731
+ <ColumnSelector
732
+ disabled={!entity.editable}
733
+ id={`search-statement-dialog-property-column-${
734
+ columnRef.id || 'unknown'
735
+ }-select`}
736
+ value={resolvedSource}
737
+ options={columnRef.parent.columns}
738
+ onChange={(newValue) => {
739
+ columnRef.source = newValue;
740
+ columnRef.as = newValue?.as || columnRef.as;
741
+
742
+ editSelection(columnRef);
743
+ }}
744
+ />
745
+
746
+ <div
747
+ style={{
748
+ height: 40,
749
+ display: 'flex',
750
+ alignItems: 'center',
751
+ }}
752
+ >
753
+ <ArrowForwardIosIcon
754
+ component='svg'
755
+ sx={{
756
+ color: 'rgba(0,0,0,0.54)',
757
+ fontSize: 10,
758
+ }}
759
+ />
760
+ </div>
761
+
762
+ <TextField
763
+ disabled={!entity.editable}
764
+ id={`search-statement-dialog-property-order-${
765
+ columnRef.id || 'unknown'
766
+ }-select`}
767
+ sx={{ width: 200, marginTop: 0 }}
768
+ onChange={(e) => {
769
+ Logger.log('Value: ', e.target.value);
770
+ setAliasValue(e.target.value);
771
+ }}
772
+ size='small'
773
+ label='Output property name'
774
+ value={localAliasValue}
775
+ error={!!aliasValidationError}
776
+ helperText={aliasValidationError}
777
+ />
778
+
779
+ <div
780
+ style={{
781
+ height: 40,
782
+ display: 'flex',
783
+ alignItems: 'center',
784
+ }}
785
+ >
786
+ <IconButton
787
+ id={`search-statement-dialog-property-${
788
+ columnRef.id || 'unknown'
789
+ }-more-button`}
790
+ disabled={!entity.editable}
791
+ aria-label='more'
792
+ size='small'
793
+ style={{
794
+ width: 30,
795
+ height: 30,
796
+ fontSize: 18,
797
+ }}
798
+ >
799
+ <i className='fa-solid fa-ellipsis-v' />
800
+ </IconButton>
801
+ </div>
802
+
803
+ <DropdownMenuPopup
804
+ popupOptions={{
805
+ anchorQuerySelector: `#search-statement-dialog-property-${
806
+ columnRef.id || 'unknown'
807
+ }-more-button`,
808
+ placement: 'right',
809
+ }}
810
+ options={[
811
+ {
812
+ disabled: !entity.editable,
813
+ onClick: async () => {
814
+ removeSelection(columnRef);
815
+ },
816
+ label: 'Delete selection',
817
+ color: 'error',
818
+ iconEnd: 'fa-solid fa-trash-can',
819
+ tooltip: 'Click to delete this property selection',
820
+ },
821
+ ]}
822
+ />
823
+
824
+ {searchStatementState.duplicateColumnNames.includes(columnRef.as) && (
825
+ <MuiTooltip
826
+ placement='right'
827
+ enterDelay={300}
828
+ leaveDelay={200}
829
+ arrow
830
+ title={
831
+ <span>
832
+ <p
833
+ style={{
834
+ margin: 0,
835
+ fontSize: 10,
836
+ marginBottom: 4,
837
+ }}
838
+ >
839
+ Duplicate property name:
840
+ </p>
841
+ <p
842
+ style={{
843
+ margin: 0,
844
+ fontSize: 16,
845
+ fontWeight: 600,
846
+ }}
847
+ >
848
+ {columnRef.as}
849
+ </p>
850
+
851
+ <p
852
+ style={{
853
+ margin: 0,
854
+ fontSize: 8,
855
+ marginTop: 10,
856
+ color: 'rgba(255,255,255,0.7)',
857
+ }}
858
+ >
859
+ All duplicate columns will be ignored in the final result
860
+ </p>
861
+ </span>
862
+ }
863
+ >
864
+ <div
865
+ style={{
866
+ height: 40,
867
+ display: 'flex',
868
+ alignItems: 'center',
869
+ }}
870
+ >
871
+ <img className='search-statement__error-icon' src={WarningIcon} />
872
+ </div>
873
+ </MuiTooltip>
874
+ )}
875
+ </div>
876
+ );
877
+ };
878
+
879
+ const OutputFormatSection = ({}: {}) => {
880
+ const {
881
+ searchStatementState,
882
+ offset,
883
+ limit,
884
+ asList,
885
+ setOffset,
886
+ setLimit,
887
+ setAsList,
888
+ sorting,
889
+ sortingUpdateKey,
890
+ addSorting,
891
+ removeSorting,
892
+ selections,
893
+ removeSelection,
894
+ addSelection,
895
+ entity,
896
+ offsetUpdateKey,
897
+ limitUpdateKey,
898
+ dataSourcesUpdateKey,
899
+ selectionsUpdateKey,
900
+ mainDataSource,
901
+ isNested,
902
+ aggregationsUpdateKey,
903
+ } = useSearchStatementContext();
904
+
905
+ const hasAggregation = selections.some(
906
+ (sel) =>
907
+ sel.type === SearchStatementNodeType.FunctionCall &&
908
+ (sel as FunctionCall).hasFunctionOfCategory(SQLFunctionCategory.Aggregate),
909
+ );
910
+
911
+ const [sectionOpen, setSectionOpen] = useState(
912
+ !!searchStatementState?.aggregations.length ||
913
+ !!searchStatementState?.sorting.length ||
914
+ !!searchStatementState?.selections.length,
915
+ );
916
+
917
+ const [calculatedSelections, setCalculatedSelections] =
918
+ useState<CalculatedSelectionsBySource>([]);
919
+
920
+ const recalculateSelectionsByTable = (
921
+ oldCalculation: CalculatedSelectionsBySource,
922
+ ): CalculatedSelectionsBySource => {
923
+ const allSources: CalculatedSelectionsBySource = [
924
+ {
925
+ dataSource: null,
926
+ columns: [],
927
+ allColumnsSelected: null,
928
+ index: 0,
929
+ },
930
+ ...searchStatementState.usedDataSources.map((dataSource, index) => ({
931
+ dataSource,
932
+ columns: [] as any,
933
+ allColumnsSelected: null as any,
934
+ // Just so the UI doesn't jump
935
+ index: index + 1,
936
+ })),
937
+ ];
938
+
939
+ const unsortedCalculation = selections.reduce<CalculatedSelectionsBySource>(
940
+ // @ts-ignore
941
+ (acc: CalculatedSelectionsBySource, selection) => {
942
+ if (selection.type === SearchStatementNodeType.AllColumnsSelector) {
943
+ const existingSeletionBySource = acc.find(
944
+ (item) => item.dataSource?.id === selection.parent?.id,
945
+ );
946
+
947
+ if (existingSeletionBySource) {
948
+ // existingSeletionBySource.columns = [];
949
+ existingSeletionBySource.allColumnsSelected = selection;
950
+ return acc;
951
+ } else {
952
+ const oldCalculationEquivalent = oldCalculation.findIndex(
953
+ (item) => item.dataSource?.id === selection.parent?.id,
954
+ );
955
+ const newIndex =
956
+ oldCalculationEquivalent === undefined ||
957
+ oldCalculationEquivalent === -1
958
+ ? acc.length
959
+ : oldCalculationEquivalent;
960
+
961
+ return [
962
+ ...acc,
963
+ {
964
+ dataSource: selection.parent,
965
+ columns: [] as const,
966
+ allColumnsSelected: selection,
967
+ index: newIndex,
968
+ } as {
969
+ dataSource: DataSource;
970
+ columns: ColumnRef[];
971
+ allColumnsSelected: AllColumnsSelector;
972
+ index: number;
973
+ },
974
+ ];
975
+ }
976
+ }
977
+
978
+ // These are literal column selections with explicit values, therefore have no parent data source
979
+ if (!selection.parent) {
980
+ (acc[0].columns as ColumnRef[]).push(selection as ColumnRef);
981
+
982
+ return acc;
983
+ }
984
+
985
+ const existingSeletionBySource = acc.find(
986
+ (item) => item.dataSource?.id === selection.parent?.id,
987
+ );
988
+
989
+ if (!existingSeletionBySource) {
990
+ const oldCalculationEquivalent = oldCalculation.findIndex(
991
+ (item) => item.dataSource?.id === selection.parent?.id,
992
+ );
993
+ const newIndex =
994
+ oldCalculationEquivalent === undefined ||
995
+ oldCalculationEquivalent === -1
996
+ ? acc.length
997
+ : oldCalculationEquivalent;
998
+
999
+ return [
1000
+ ...acc,
1001
+ {
1002
+ dataSource: selection.parent,
1003
+ columns: [selection],
1004
+ allColumnsSelected: null,
1005
+ index: newIndex,
1006
+ },
1007
+ ];
1008
+ } else if (
1009
+ !!existingSeletionBySource &&
1010
+ !(
1011
+ existingSeletionBySource as {
1012
+ dataSource: DataSource;
1013
+ columns: ColumnRef[];
1014
+ allColumnsSelected: null;
1015
+ index: number;
1016
+ }
1017
+ ).columns
1018
+ // @ts-ignore
1019
+ .includes(selection)
1020
+ ) {
1021
+ (
1022
+ existingSeletionBySource as {
1023
+ dataSource: DataSource;
1024
+ columns: ColumnRef[];
1025
+ allColumnsSelected: null;
1026
+ index: number;
1027
+ }
1028
+ ).columns.push(selection as ColumnRef);
1029
+ }
1030
+
1031
+ return acc;
1032
+ },
1033
+ allSources,
1034
+ );
1035
+
1036
+ // Sort the calculation by the index
1037
+ // @ts-ignore
1038
+ const sortedCalculation = unsortedCalculation.sort(
1039
+ // @ts-ignore
1040
+ (a, b) => a.index - b.index,
1041
+ );
1042
+
1043
+ return sortedCalculation as CalculatedSelectionsBySource;
1044
+ };
1045
+
1046
+ useEffect(() => {
1047
+ const selectionsBySource: CalculatedSelectionsBySource =
1048
+ recalculateSelectionsByTable(calculatedSelections);
1049
+
1050
+ setCalculatedSelections(selectionsBySource);
1051
+ }, [selectionsUpdateKey, aggregationsUpdateKey, dataSourcesUpdateKey]);
1052
+
1053
+ const allPrimitiveTypeInputs = getPrimitiveTypeInputs(entity, [
1054
+ PrimitiveTypes.Number,
1055
+ ]);
1056
+
1057
+ return (
1058
+ <section
1059
+ key={dataSourcesUpdateKey && selectionsUpdateKey && aggregationsUpdateKey}
1060
+ style={{
1061
+ display: 'flex',
1062
+ flexDirection: 'column',
1063
+ paddingBottom: sectionOpen ? 20 : 0,
1064
+ }}
1065
+ >
1066
+ <EntityDialogSectionHeader
1067
+ title='Output format'
1068
+ subtitle='Assemble the resulting data'
1069
+ hideShowSuffix='the output format section'
1070
+ open={sectionOpen}
1071
+ onToggle={() => setSectionOpen(!sectionOpen)}
1072
+ />
1073
+
1074
+ <Collapse in={sectionOpen}>
1075
+ <div
1076
+ style={{
1077
+ marginTop: 6,
1078
+ display: 'flex',
1079
+ flexDirection: 'column',
1080
+ gap: 4,
1081
+ marginLeft: 80,
1082
+ }}
1083
+ >
1084
+ <h3 style={{ margin: 0, fontSize: 12, fontWeight: 500 }}>
1085
+ Results order
1086
+ </h3>
1087
+ <span>
1088
+ <p
1089
+ style={{
1090
+ margin: 0,
1091
+ fontSize: 10,
1092
+ color: 'rgba(0,0,0,0.54)',
1093
+ }}
1094
+ >
1095
+ Define the ordering of the resulting list
1096
+ </p>
1097
+ <p
1098
+ style={{
1099
+ margin: 0,
1100
+ fontSize: 10,
1101
+ color: 'rgba(0,0,0,0.54)',
1102
+ }}
1103
+ >
1104
+ Move properties higher to give them more sorting priority
1105
+ </p>
1106
+ </span>
1107
+ <br className='quarter-space' />
1108
+ <div
1109
+ className='search-statement__numbered-rows-container'
1110
+ key={sortingUpdateKey}
1111
+ >
1112
+ {sorting
1113
+ .sort((a, b) => a.index - b.index)
1114
+ .map((sort, index) => (
1115
+ <div
1116
+ key={sort.column?.id || index}
1117
+ style={{
1118
+ display: 'flex',
1119
+ alignItems: 'center',
1120
+ gap: 12,
1121
+ }}
1122
+ >
1123
+ {/* Number, column selector dropdown, order type selector dropdown, then 3 dot icon buton */}
1124
+ <p
1125
+ style={{
1126
+ margin: 0,
1127
+ fontSize: 12,
1128
+ fontWeight: 600,
1129
+ color: 'rgba(0,0,0,0.54)',
1130
+ }}
1131
+ >
1132
+ {(index + 1).toString()}
1133
+ </p>
1134
+ <ColumnSelector
1135
+ disabled={!entity.editable}
1136
+ id={`search-statement-dialog-sort-column-${sort.index}-select`}
1137
+ value={sort.column}
1138
+ options={searchStatementState.columns}
1139
+ getOptionDisabled={(option) => {
1140
+ if (!!option?.literalValue) {
1141
+ return true;
1142
+ }
1143
+
1144
+ // If the column is already sorted, then disable it
1145
+ if (sort.column?.id === option.id) {
1146
+ return false;
1147
+ }
1148
+
1149
+ return !!sorting.find(
1150
+ (existingSort) => existingSort.column?.id === option.id,
1151
+ );
1152
+ }}
1153
+ onChange={(value) => {
1154
+ sort.column = value;
1155
+ addSorting(sort);
1156
+ }}
1157
+ />
1158
+ <Autocomplete
1159
+ id={`search-statement-dialog-sort-order-${sort.index}-select`}
1160
+ disabled={!entity.editable}
1161
+ disablePortal
1162
+ options={[
1163
+ {
1164
+ label: 'Ascending',
1165
+ value: SortStatementDirection.Ascending,
1166
+ },
1167
+ {
1168
+ label: 'Descending',
1169
+ value: SortStatementDirection.Descending,
1170
+ },
1171
+ ]}
1172
+ value={{
1173
+ label:
1174
+ sort.direction === SortStatementDirection.Ascending
1175
+ ? 'Ascending'
1176
+ : 'Descending',
1177
+ value: sort.direction,
1178
+ }}
1179
+ sx={{ width: 200 }}
1180
+ renderInput={(params) => (
1181
+ <TextField
1182
+ {...params}
1183
+ size='small'
1184
+ label='Select direction'
1185
+ />
1186
+ )}
1187
+ />
1188
+ <IconButton
1189
+ id={`search-statement-dialog-sort-${sort.index}-more-button`}
1190
+ aria-label='more'
1191
+ disabled={!entity.editable}
1192
+ size='small'
1193
+ style={{
1194
+ width: 30,
1195
+ height: 30,
1196
+ fontSize: 18,
1197
+ }}
1198
+ >
1199
+ <i className='fa-solid fa-ellipsis-v' />
1200
+ </IconButton>
1201
+
1202
+ <DropdownMenuPopup
1203
+ popupOptions={{
1204
+ anchorQuerySelector: `#search-statement-dialog-sort-${sort.index}-more-button`,
1205
+ placement: 'right',
1206
+ }}
1207
+ options={[
1208
+ {
1209
+ disabled: !entity.editable,
1210
+ onClick: async () => {
1211
+ removeSorting(sort);
1212
+ },
1213
+ label: 'Delete order',
1214
+ color: 'error',
1215
+ iconEnd: 'fa-solid fa-trash-can',
1216
+ tooltip: 'Click to delete this ordering',
1217
+ },
1218
+ ]}
1219
+ />
1220
+
1221
+ {(!sort.isValid ||
1222
+ !sort.column.isValid ||
1223
+ searchStatementState.duplicateColumnNames.includes(
1224
+ sort.column.as,
1225
+ )) && (
1226
+ <MuiTooltip
1227
+ placement='right'
1228
+ enterDelay={100}
1229
+ leaveDelay={200}
1230
+ arrow
1231
+ title={
1232
+ !sort.isValid ? (
1233
+ <>
1234
+ This ordering selection is incomplete.
1235
+ <br />
1236
+ Please select a column and sorting direction.
1237
+ <br />
1238
+ <br />
1239
+ Invalid sortings will be ignored in the final
1240
+ results.
1241
+ </>
1242
+ ) : (
1243
+ <>
1244
+ The property "{sort.column?.as}", referenced by this
1245
+ sorting is not valid
1246
+ <br />
1247
+ Fix the errors in that property to enable this
1248
+ sorting
1249
+ <br />
1250
+ <br />
1251
+ Invalid sortings will be ignored in the final
1252
+ results.
1253
+ </>
1254
+ )
1255
+ }
1256
+ >
1257
+ <img
1258
+ className='search-statement__error-icon'
1259
+ src={WarningIcon}
1260
+ />
1261
+ </MuiTooltip>
1262
+ )}
1263
+ </div>
1264
+ ))}
1265
+ </div>
1266
+
1267
+ {/* Small undelined link button to add new sort order */}
1268
+ <button
1269
+ id='search-statement-dialog-add-sort-order-button'
1270
+ onClick={() => {
1271
+ const newSort = new SortStatement(searchStatementState);
1272
+ newSort.index = sorting.length;
1273
+ addSorting(newSort);
1274
+ }}
1275
+ style={{
1276
+ fontSize: 12,
1277
+ color: 'rgba(0,0,0,0.54)',
1278
+ border: 0,
1279
+ background: 'none',
1280
+ cursor: 'pointer',
1281
+ textDecoration: 'underline',
1282
+ display: 'flex',
1283
+ padding: 0,
1284
+ }}
1285
+ >
1286
+ Add order
1287
+ </button>
1288
+
1289
+ <br className='quarter-space' />
1290
+
1291
+ <h3 style={{ margin: 0, fontSize: 12, fontWeight: 500 }}>
1292
+ Maximum amount of results
1293
+ </h3>
1294
+
1295
+ <br className='eighth-space' />
1296
+
1297
+ <span
1298
+ style={{
1299
+ display: 'flex',
1300
+ gap: 8,
1301
+ }}
1302
+ >
1303
+ <NumberSelector
1304
+ key={limitUpdateKey}
1305
+ value={limit}
1306
+ id={`search-statement-dialog-result-limit-select`}
1307
+ onChange={(value) => {
1308
+ setLimit(value);
1309
+ }}
1310
+ options={allPrimitiveTypeInputs}
1311
+ />
1312
+
1313
+ <div
1314
+ style={{
1315
+ display: 'flex',
1316
+ alignItems: 'center',
1317
+ gap: 8,
1318
+ }}
1319
+ >
1320
+ <input
1321
+ type='checkbox'
1322
+ id={`search-statement-dialog-result-as-list-checkbox`}
1323
+ disabled={isNested}
1324
+ checked={isNested ? true : asList}
1325
+ onChange={(e) => {
1326
+ if (!isNested) {
1327
+ setAsList(e.target.checked);
1328
+ }
1329
+ }}
1330
+ style={{
1331
+ margin: 0,
1332
+ }}
1333
+ />
1334
+ <label
1335
+ htmlFor={`search-statement-dialog-result-as-list-checkbox`}
1336
+ style={{
1337
+ fontSize: 10,
1338
+ color: 'rgba(0,0,0,0.54)',
1339
+ }}
1340
+ >
1341
+ As list
1342
+ <br />
1343
+ {asList
1344
+ ? '(Result is a list of entries)'
1345
+ : '(Result is the first item, as a single object)'}
1346
+ </label>
1347
+ </div>
1348
+ </span>
1349
+
1350
+ <br className='quarter-space' />
1351
+
1352
+ <h3 style={{ margin: 0, fontSize: 12, fontWeight: 500 }}>
1353
+ Skip first number of items
1354
+ </h3>
1355
+
1356
+ <br className='eighth-space' />
1357
+
1358
+ <NumberSelector
1359
+ key={offsetUpdateKey}
1360
+ value={offset}
1361
+ id={`search-statement-dialog-result-offset-select`}
1362
+ onChange={(value) => {
1363
+ setOffset(value);
1364
+ }}
1365
+ options={allPrimitiveTypeInputs}
1366
+ />
1367
+
1368
+ <br className='quarter-space' />
1369
+
1370
+ <h3 style={{ margin: 0, fontSize: 12, fontWeight: 500 }}>
1371
+ Resulting data structure
1372
+ </h3>
1373
+ <span>
1374
+ <p
1375
+ style={{
1376
+ margin: 0,
1377
+ fontSize: 10,
1378
+ color: 'rgba(0,0,0,0.54)',
1379
+ }}
1380
+ >
1381
+ Assemble the output entity from available properties
1382
+ </p>
1383
+ <p
1384
+ style={{
1385
+ margin: 0,
1386
+ fontSize: 10,
1387
+ color: 'rgba(0,0,0,0.54)',
1388
+ }}
1389
+ >
1390
+ from the entities found by the search
1391
+ </p>
1392
+ </span>
1393
+
1394
+ <br className='quarter-space' />
1395
+
1396
+ <div
1397
+ style={{
1398
+ display: 'flex',
1399
+ flexDirection: 'column',
1400
+ gap: 8,
1401
+ }}
1402
+ >
1403
+ {(calculatedSelections[0]?.columns || []).map(
1404
+ (selection: any, index) => {
1405
+ if (selection.type === SearchStatementNodeType.FunctionCall) {
1406
+ return (
1407
+ <FunctionColumnOutputSelector
1408
+ key={selection.id}
1409
+ functionCall={selection as FunctionCall}
1410
+ index={index}
1411
+ />
1412
+ );
1413
+ }
1414
+ return (
1415
+ <LiteralColumnOutputSelector
1416
+ key={selection.id}
1417
+ columnRef={selection as ColumnRef}
1418
+ index={index}
1419
+ />
1420
+ );
1421
+ },
1422
+ )}
1423
+
1424
+ <div style={{ display: 'flex', gap: '24px' }}>
1425
+ <button
1426
+ id='search-statement-dialog-add-literal-property-button'
1427
+ disabled={!entity.editable}
1428
+ onClick={() => {
1429
+ const newSelection = new ColumnRef(searchStatementState);
1430
+ const newLiteralString = new SearchStatementLiteralValue(
1431
+ searchStatementState,
1432
+ );
1433
+ newLiteralString.valueType = SearchLiteralValueType.String;
1434
+ // Leave `value` empty — the AST treats an empty literal as
1435
+ // invalid until the user supplies one. Auto-filling with
1436
+ // "Starting value" used to mask that incomplete-state.
1437
+ newSelection.source = newLiteralString;
1438
+ newSelection.as = 'literal_property';
1439
+ addSelection(newSelection);
1440
+ }}
1441
+ style={{
1442
+ fontSize: 12,
1443
+ color: 'rgba(0,0,0,0.54)',
1444
+ border: 0,
1445
+ background: 'none',
1446
+ cursor: 'pointer',
1447
+ textDecoration: 'underline',
1448
+ padding: 0,
1449
+ }}
1450
+ >
1451
+ Add literal property
1452
+ </button>
1453
+
1454
+ <button
1455
+ id='search-statement-dialog-add-function-property-button'
1456
+ disabled={!entity.editable}
1457
+ onClick={() => {
1458
+ const newSelection = new FunctionCall(searchStatementState);
1459
+ newSelection.as = 'function_property';
1460
+ addSelection(newSelection);
1461
+ }}
1462
+ style={{
1463
+ fontSize: 12,
1464
+ color: 'rgba(0,0,0,0.54)',
1465
+ border: 0,
1466
+ background: 'none',
1467
+ cursor: 'pointer',
1468
+ textDecoration: 'underline',
1469
+ padding: 0,
1470
+ }}
1471
+ >
1472
+ Add function property
1473
+ </button>
1474
+ </div>
1475
+
1476
+ <br className='quarter-space' />
1477
+
1478
+ {calculatedSelections.map((selection) => {
1479
+ if (!selection.dataSource) {
1480
+ return null;
1481
+ }
1482
+
1483
+ const showAddNewPropertyButton = !!(
1484
+ !selection.columns.length ||
1485
+ !selection.columns.find(
1486
+ (columnRef: ColumnRef) => !columnRef.source || !columnRef.as,
1487
+ )
1488
+ );
1489
+
1490
+ const showMainDataSourceRequiredForAggregationsError =
1491
+ !mainDataSource &&
1492
+ selection.dataSource?.source?.type ===
1493
+ SearchStatementNodeType.AggregationStatement;
1494
+
1495
+ const showInvalidAggregationSourceError =
1496
+ !showMainDataSourceRequiredForAggregationsError &&
1497
+ selection.dataSource?.source?.type ===
1498
+ SearchStatementNodeType.AggregationStatement &&
1499
+ !selection.dataSource.source.isValid;
1500
+
1501
+ return (
1502
+ <span
1503
+ key={selection.dataSource?.id}
1504
+ style={{
1505
+ display: 'flex',
1506
+ flexDirection: 'column',
1507
+ gap: 4,
1508
+ marginBottom: 16,
1509
+ }}
1510
+ >
1511
+ <h4
1512
+ key={dataSourcesUpdateKey}
1513
+ style={{
1514
+ margin: 0,
1515
+ fontSize: 12,
1516
+ fontWeight: 500,
1517
+ color: 'rgba(0, 0, 0, 0.54)',
1518
+ }}
1519
+ >
1520
+ From <b>{selection.dataSource?.as}</b>
1521
+ {' ' + selection.dataSource?.sourceType}
1522
+ </h4>
1523
+
1524
+ <br className='eighth-space' />
1525
+
1526
+ {showMainDataSourceRequiredForAggregationsError ||
1527
+ showInvalidAggregationSourceError ? (
1528
+ <span
1529
+ style={{
1530
+ display: 'flex',
1531
+ alignItems: 'center',
1532
+ gap: 12,
1533
+ }}
1534
+ >
1535
+ <img
1536
+ className='search-statement__error-icon'
1537
+ src={WarningIcon}
1538
+ />
1539
+
1540
+ {showMainDataSourceRequiredForAggregationsError && (
1541
+ <p
1542
+ style={{
1543
+ margin: 0,
1544
+ fontSize: 10,
1545
+ color: 'rgba(0,0,0,0.54)',
1546
+ }}
1547
+ >
1548
+ <b>Ignored aggregation</b>
1549
+ <br />
1550
+ Aggregations require a main data source to be merged.
1551
+ Add a main data source to the search statement to use
1552
+ this aggregation
1553
+ </p>
1554
+ )}
1555
+ {showInvalidAggregationSourceError && (
1556
+ <p
1557
+ style={{
1558
+ margin: 0,
1559
+ fontSize: 10,
1560
+ color: 'rgba(0,0,0,0.54)',
1561
+ }}
1562
+ >
1563
+ <b>Invalid aggregation source</b>
1564
+ <br />
1565
+ Resolve the errors in the aggregation selection and
1566
+ try again
1567
+ </p>
1568
+ )}
1569
+ </span>
1570
+ ) : (
1571
+ <>
1572
+ <div
1573
+ style={{
1574
+ display: 'flex',
1575
+ alignItems: 'center',
1576
+ gap: 8,
1577
+ }}
1578
+ >
1579
+ <div
1580
+ style={{
1581
+ display: 'flex',
1582
+ flexDirection: 'column',
1583
+ }}
1584
+ >
1585
+ <div
1586
+ style={{
1587
+ display: 'flex',
1588
+ alignItems: 'center',
1589
+ gap: 8,
1590
+ }}
1591
+ >
1592
+ <input
1593
+ type='checkbox'
1594
+ id={`search-statement-dialog-${selection.dataSource?.id}-checkbox`}
1595
+ checked={!!selection.allColumnsSelected}
1596
+ disabled={!entity.editable || hasAggregation}
1597
+ onChange={(e) => {
1598
+ if (!!selection.allColumnsSelected) {
1599
+ removeSelection(selection.allColumnsSelected);
1600
+ } else {
1601
+ const newAllColumnsSelection =
1602
+ new AllColumnsSelector(
1603
+ searchStatementState,
1604
+ );
1605
+
1606
+ newAllColumnsSelection.parent =
1607
+ selection.dataSource;
1608
+
1609
+ addSelection(newAllColumnsSelection);
1610
+ }
1611
+ }}
1612
+ style={{
1613
+ margin: 0,
1614
+ }}
1615
+ />
1616
+ <label
1617
+ htmlFor={`search-statement-dialog-${selection.dataSource?.id}-checkbox`}
1618
+ style={{
1619
+ fontSize: 10,
1620
+ color: 'rgba(0,0,0,0.54)',
1621
+ }}
1622
+ >
1623
+ {'Take all its properties (as inherited)'}
1624
+ </label>
1625
+ </div>
1626
+
1627
+ {!!selection.allColumnsSelected && (
1628
+ <p
1629
+ style={{
1630
+ margin: 0,
1631
+ fontSize: 10,
1632
+ color: 'rgba(0,0,0,0.54)',
1633
+ marginLeft: 22,
1634
+ }}
1635
+ >
1636
+ {selection.allColumnsSelected.selectedColumns.map(
1637
+ (column, index) =>
1638
+ column.as +
1639
+ (selection.allColumnsSelected.selectedColumns
1640
+ .length -
1641
+ 1 ===
1642
+ index
1643
+ ? ''
1644
+ : ', '),
1645
+ )}
1646
+ </p>
1647
+ )}
1648
+ </div>
1649
+
1650
+ {searchStatementStateLib.resolveAreThereConflictingColumnsInSelectAll(
1651
+ selection.allColumnsSelected,
1652
+ ) && (
1653
+ <MuiTooltip
1654
+ placement='right'
1655
+ enterDelay={300}
1656
+ leaveDelay={200}
1657
+ arrow
1658
+ title={
1659
+ <>
1660
+ <p
1661
+ style={{
1662
+ margin: 0,
1663
+ fontSize: 10,
1664
+ marginBottom: 4,
1665
+ }}
1666
+ >
1667
+ Duplicate property names:
1668
+ </p>
1669
+ {selection.allColumnsSelected?.selectedColumns
1670
+ .filter((column) =>
1671
+ searchStatementState.duplicateColumnNames.includes(
1672
+ column.as,
1673
+ ),
1674
+ )
1675
+ .map((column, index) => (
1676
+ <span
1677
+ key={column.id}
1678
+ style={{
1679
+ display: 'flex',
1680
+ alignItems: 'center',
1681
+ gap: 6,
1682
+ marginTop: index === 0 ? 0 : 4,
1683
+ }}
1684
+ >
1685
+ {/* Dot */}
1686
+ <div
1687
+ style={{
1688
+ width: 4,
1689
+ height: 4,
1690
+ borderRadius: '50%',
1691
+ background: 'rgba(255,255,255,0.7)',
1692
+ }}
1693
+ />
1694
+ <p
1695
+ key={column.id}
1696
+ style={{
1697
+ margin: 0,
1698
+ fontSize: 16,
1699
+ fontWeight: 600,
1700
+ }}
1701
+ >
1702
+ {column.as}
1703
+ </p>
1704
+ </span>
1705
+ ))}
1706
+ <p
1707
+ style={{
1708
+ margin: 0,
1709
+ fontSize: 8,
1710
+ marginTop: 10,
1711
+ color: 'rgba(255,255,255,0.7)',
1712
+ }}
1713
+ >
1714
+ All duplicate columns will be ignored in the
1715
+ final result
1716
+ </p>
1717
+ </>
1718
+ }
1719
+ >
1720
+ <img
1721
+ className='search-statement__error-icon'
1722
+ src={WarningIcon}
1723
+ />
1724
+ </MuiTooltip>
1725
+ )}
1726
+ </div>
1727
+
1728
+ <br className='quarter-space' />
1729
+ <div className='search-statement__numbered-rows-container'>
1730
+ {selection.columns.map((columnRef, index) => (
1731
+ <SourcedColumnOutputSelector
1732
+ key={columnRef.id}
1733
+ columnRef={columnRef}
1734
+ index={index}
1735
+ />
1736
+ ))}
1737
+ {/* Small undelined link button to add new sort order */}
1738
+ {/* Don't render if there is an incomplete selection already for this source */}
1739
+ {showAddNewPropertyButton && (
1740
+ <button
1741
+ id='search-statement-dialog-add-sort-order-button'
1742
+ disabled={!entity.editable}
1743
+ onClick={() => {
1744
+ const newSelection = new ColumnRef(
1745
+ searchStatementState,
1746
+ );
1747
+
1748
+ newSelection.parent = selection.dataSource;
1749
+
1750
+ addSelection(newSelection);
1751
+ }}
1752
+ style={{
1753
+ fontSize: 12,
1754
+ color: 'rgba(0,0,0,0.54)',
1755
+ border: 0,
1756
+ background: 'none',
1757
+ cursor: 'pointer',
1758
+ textDecoration: 'underline',
1759
+ display: 'flex',
1760
+ padding: 0,
1761
+ }}
1762
+ >
1763
+ Add property
1764
+ </button>
1765
+ )}
1766
+ </div>
1767
+ </>
1768
+ )}
1769
+ </span>
1770
+ );
1771
+ })}
1772
+ </div>
1773
+ </div>
1774
+ </Collapse>
1775
+ </section>
1776
+ );
1777
+ };
1778
+
1779
+ export default OutputFormatSection;