@rebasepro/studio 0.0.1-canary.09e5ec5

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 (361) hide show
  1. package/LICENSE +114 -0
  2. package/README.md +159 -0
  3. package/dist/ApiExplorer-gMJt5JrS.js +1053 -0
  4. package/dist/ApiExplorer-gMJt5JrS.js.map +1 -0
  5. package/dist/AuthSimulationSelector-BF4rkRGp.js +118 -0
  6. package/dist/AuthSimulationSelector-BF4rkRGp.js.map +1 -0
  7. package/dist/BranchesView-DcHZtvXo.js +292 -0
  8. package/dist/BranchesView-DcHZtvXo.js.map +1 -0
  9. package/dist/CronJobsView-CijCToeK.js +456 -0
  10. package/dist/CronJobsView-CijCToeK.js.map +1 -0
  11. package/dist/JSEditor-D8nVp3Lp.js +1308 -0
  12. package/dist/JSEditor-D8nVp3Lp.js.map +1 -0
  13. package/dist/MonacoEditor-CMYEjiRf.js +161 -0
  14. package/dist/MonacoEditor-CMYEjiRf.js.map +1 -0
  15. package/dist/RLSEditor-DBH09u9v.js +1831 -0
  16. package/dist/RLSEditor-DBH09u9v.js.map +1 -0
  17. package/dist/SQLEditor-CkVx9vgr.js +1792 -0
  18. package/dist/SQLEditor-CkVx9vgr.js.map +1 -0
  19. package/dist/SchemaVisualizer-BgD5Zb77.js +1069 -0
  20. package/dist/SchemaVisualizer-BgD5Zb77.js.map +1 -0
  21. package/dist/StorageView-CTqGFhY9.js +907 -0
  22. package/dist/StorageView-CTqGFhY9.js.map +1 -0
  23. package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
  24. package/dist/common/src/collections/index.d.ts +1 -0
  25. package/dist/common/src/data/buildRebaseData.d.ts +14 -0
  26. package/dist/common/src/index.d.ts +3 -0
  27. package/dist/common/src/util/builders.d.ts +57 -0
  28. package/dist/common/src/util/callbacks.d.ts +6 -0
  29. package/dist/common/src/util/collections.d.ts +11 -0
  30. package/dist/common/src/util/common.d.ts +2 -0
  31. package/dist/common/src/util/conditions.d.ts +26 -0
  32. package/dist/common/src/util/entities.d.ts +58 -0
  33. package/dist/common/src/util/enums.d.ts +3 -0
  34. package/dist/common/src/util/index.d.ts +16 -0
  35. package/dist/common/src/util/navigation_from_path.d.ts +34 -0
  36. package/dist/common/src/util/navigation_utils.d.ts +20 -0
  37. package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
  38. package/dist/common/src/util/paths.d.ts +14 -0
  39. package/dist/common/src/util/permissions.d.ts +5 -0
  40. package/dist/common/src/util/references.d.ts +2 -0
  41. package/dist/common/src/util/relations.d.ts +22 -0
  42. package/dist/common/src/util/resolutions.d.ts +72 -0
  43. package/dist/common/src/util/storage.d.ts +24 -0
  44. package/dist/core/src/components/AIIcon.d.ts +16 -0
  45. package/dist/core/src/components/ConfirmationDialog.d.ts +9 -0
  46. package/dist/core/src/components/Debug/UIReferenceView.d.ts +1 -0
  47. package/dist/core/src/components/Debug/UIStyleGuide.d.ts +1 -0
  48. package/dist/core/src/components/ErrorTooltip.d.ts +2 -0
  49. package/dist/core/src/components/ErrorView.d.ts +21 -0
  50. package/dist/core/src/components/LanguageToggle.d.ts +1 -0
  51. package/dist/core/src/components/LoginView/LoginView.d.ts +68 -0
  52. package/dist/core/src/components/LoginView/index.d.ts +2 -0
  53. package/dist/core/src/components/NotFoundPage.d.ts +1 -0
  54. package/dist/core/src/components/RebaseAuth.d.ts +10 -0
  55. package/dist/core/src/components/RebaseLogo.d.ts +7 -0
  56. package/dist/core/src/components/UnsavedChangesDialog.d.ts +9 -0
  57. package/dist/core/src/components/UserDisplay.d.ts +7 -0
  58. package/dist/core/src/components/UserSelectPopover.d.ts +62 -0
  59. package/dist/core/src/components/UserSettingsView.d.ts +1 -0
  60. package/dist/core/src/components/common/index.d.ts +6 -0
  61. package/dist/core/src/components/common/table_height.d.ts +5 -0
  62. package/dist/core/src/components/common/types.d.ts +63 -0
  63. package/dist/core/src/components/common/useColumnsIds.d.ts +9 -0
  64. package/dist/core/src/components/common/useDataTableController.d.ts +45 -0
  65. package/dist/core/src/components/common/useDebouncedData.d.ts +9 -0
  66. package/dist/core/src/components/common/useScrollRestoration.d.ts +14 -0
  67. package/dist/core/src/components/index.d.ts +16 -0
  68. package/dist/core/src/contexts/AdminModeController.d.ts +4 -0
  69. package/dist/core/src/contexts/AnalyticsContext.d.ts +3 -0
  70. package/dist/core/src/contexts/AuthControllerContext.d.ts +3 -0
  71. package/dist/core/src/contexts/CustomizationControllerContext.d.ts +3 -0
  72. package/dist/core/src/contexts/DataDriverContext.d.ts +3 -0
  73. package/dist/core/src/contexts/DatabaseAdminContext.d.ts +3 -0
  74. package/dist/core/src/contexts/DialogsProvider.d.ts +4 -0
  75. package/dist/core/src/contexts/EffectiveRoleController.d.ts +4 -0
  76. package/dist/core/src/contexts/InternalUserManagementContext.d.ts +3 -0
  77. package/dist/core/src/contexts/ModeController.d.ts +4 -0
  78. package/dist/core/src/contexts/RebaseClientInstanceContext.d.ts +6 -0
  79. package/dist/core/src/contexts/RebaseDataContext.d.ts +3 -0
  80. package/dist/core/src/contexts/SnackbarProvider.d.ts +2 -0
  81. package/dist/core/src/contexts/StorageSourceContext.d.ts +3 -0
  82. package/dist/core/src/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
  83. package/dist/core/src/contexts/index.d.ts +13 -0
  84. package/dist/core/src/core/PluginLifecycleManager.d.ts +17 -0
  85. package/dist/core/src/core/PluginProviderStack.d.ts +21 -0
  86. package/dist/core/src/core/Rebase.d.ts +14 -0
  87. package/dist/core/src/core/RebaseProps.d.ts +136 -0
  88. package/dist/core/src/core/RebaseRouter.d.ts +4 -0
  89. package/dist/core/src/core/RebaseRoutes.d.ts +17 -0
  90. package/dist/core/src/core/index.d.ts +4 -0
  91. package/dist/core/src/hooks/ApiConfigContext.d.ts +24 -0
  92. package/dist/core/src/hooks/data/delete.d.ts +31 -0
  93. package/dist/core/src/hooks/data/save.d.ts +34 -0
  94. package/dist/core/src/hooks/data/useCollectionFetch.d.ts +51 -0
  95. package/dist/core/src/hooks/data/useData.d.ts +13 -0
  96. package/dist/core/src/hooks/data/useDataOrder.d.ts +12 -0
  97. package/dist/core/src/hooks/data/useEntityFetch.d.ts +38 -0
  98. package/dist/core/src/hooks/data/useRelationSelector.d.ts +52 -0
  99. package/dist/core/src/hooks/data/useUserSelector.d.ts +31 -0
  100. package/dist/core/src/hooks/index.d.ts +37 -0
  101. package/dist/core/src/hooks/useAdminModeController.d.ts +19 -0
  102. package/dist/core/src/hooks/useAnalyticsController.d.ts +5 -0
  103. package/dist/core/src/hooks/useAuthController.d.ts +11 -0
  104. package/dist/core/src/hooks/useAuthSubscription.d.ts +2 -0
  105. package/dist/core/src/hooks/useBackendStorageSource.d.ts +30 -0
  106. package/dist/core/src/hooks/useBridgeRegistration.d.ts +18 -0
  107. package/dist/core/src/hooks/useBrowserTitleAndIcon.d.ts +6 -0
  108. package/dist/core/src/hooks/useBuildAdminModeController.d.ts +6 -0
  109. package/dist/core/src/hooks/useBuildEffectiveRoleController.d.ts +8 -0
  110. package/dist/core/src/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
  111. package/dist/core/src/hooks/useBuildModeController.d.ts +6 -0
  112. package/dist/core/src/hooks/useClipboard.d.ts +57 -0
  113. package/dist/core/src/hooks/useCollapsedGroups.d.ts +12 -0
  114. package/dist/core/src/hooks/useCustomizationController.d.ts +11 -0
  115. package/dist/core/src/hooks/useDialogsController.d.ts +11 -0
  116. package/dist/core/src/hooks/useEffectiveRoleController.d.ts +7 -0
  117. package/dist/core/src/hooks/useInternalUserManagementController.d.ts +12 -0
  118. package/dist/core/src/hooks/useLargeLayout.d.ts +1 -0
  119. package/dist/core/src/hooks/useModeController.d.ts +19 -0
  120. package/dist/core/src/hooks/usePermissions.d.ts +12 -0
  121. package/dist/core/src/hooks/useRebaseClient.d.ts +5 -0
  122. package/dist/core/src/hooks/useRebaseContext.d.ts +11 -0
  123. package/dist/core/src/hooks/useRebaseRegistry.d.ts +34 -0
  124. package/dist/core/src/hooks/useSlot.d.ts +18 -0
  125. package/dist/core/src/hooks/useSnackbarController.d.ts +20 -0
  126. package/dist/core/src/hooks/useStorageSource.d.ts +7 -0
  127. package/dist/core/src/hooks/useStudioBridge.d.ts +91 -0
  128. package/dist/core/src/hooks/useTranslation.d.ts +17 -0
  129. package/dist/core/src/hooks/useUnsavedChangesDialog.d.ts +12 -0
  130. package/dist/core/src/hooks/useUserConfigurationPersistence.d.ts +8 -0
  131. package/dist/core/src/hooks/useValidateAuthenticator.d.ts +21 -0
  132. package/dist/core/src/i18n/RebaseI18nProvider.d.ts +33 -0
  133. package/dist/core/src/index.d.ts +15 -0
  134. package/dist/core/src/internal/common.d.ts +3 -0
  135. package/dist/core/src/internal/useRestoreScroll.d.ts +6 -0
  136. package/dist/core/src/locales/de.d.ts +2 -0
  137. package/dist/core/src/locales/en.d.ts +10 -0
  138. package/dist/core/src/locales/es.d.ts +10 -0
  139. package/dist/core/src/locales/fr.d.ts +2 -0
  140. package/dist/core/src/locales/hi.d.ts +2 -0
  141. package/dist/core/src/locales/it.d.ts +2 -0
  142. package/dist/core/src/locales/pt.d.ts +7 -0
  143. package/dist/core/src/util/constants.d.ts +1 -0
  144. package/dist/core/src/util/createFormexStub.d.ts +2 -0
  145. package/dist/core/src/util/entity_cache.d.ts +27 -0
  146. package/dist/core/src/util/enums.d.ts +5 -0
  147. package/dist/core/src/util/icon_list.d.ts +5 -0
  148. package/dist/core/src/util/icon_synonyms.d.ts +1 -0
  149. package/dist/core/src/util/icons.d.ts +20 -0
  150. package/dist/core/src/util/index.d.ts +10 -0
  151. package/dist/core/src/util/previews.d.ts +4 -0
  152. package/dist/core/src/util/useStorageUploadController.d.ts +38 -0
  153. package/dist/core/src/util/useTraceUpdate.d.ts +2 -0
  154. package/dist/formex/src/Field.d.ts +52 -0
  155. package/dist/formex/src/Formex.d.ts +7 -0
  156. package/dist/formex/src/index.d.ts +5 -0
  157. package/dist/formex/src/types.d.ts +40 -0
  158. package/dist/formex/src/useCreateFormex.d.ts +14 -0
  159. package/dist/formex/src/utils.d.ts +16 -0
  160. package/dist/index.es.js +726 -0
  161. package/dist/index.es.js.map +1 -0
  162. package/dist/index.umd.js +9647 -0
  163. package/dist/index.umd.js.map +1 -0
  164. package/dist/studio/src/components/ApiExplorer/ApiExplorer.d.ts +9 -0
  165. package/dist/studio/src/components/ApiExplorer/EndpointDetail.d.ts +9 -0
  166. package/dist/studio/src/components/ApiExplorer/TryItPanel.d.ts +15 -0
  167. package/dist/studio/src/components/ApiExplorer/parseSpec.d.ts +16 -0
  168. package/dist/studio/src/components/ApiExplorer/types.d.ts +90 -0
  169. package/dist/studio/src/components/AuthSimulationSelector.d.ts +11 -0
  170. package/dist/studio/src/components/Branches/BranchesView.d.ts +1 -0
  171. package/dist/studio/src/components/CronJobs/CronJobsView.d.ts +1 -0
  172. package/dist/studio/src/components/JSEditor/JSEditor.d.ts +1 -0
  173. package/dist/studio/src/components/JSEditor/JSEditorSidebar.d.ts +21 -0
  174. package/dist/studio/src/components/JSEditor/JSMonacoEditor.d.ts +18 -0
  175. package/dist/studio/src/components/RLSEditor/PolicyEditor.d.ts +9 -0
  176. package/dist/studio/src/components/RLSEditor/RLSEditor.d.ts +19 -0
  177. package/dist/studio/src/components/RLSEditor/index.d.ts +1 -0
  178. package/dist/studio/src/components/RebaseStudio.d.ts +2 -0
  179. package/dist/studio/src/components/SQLEditor/ExplainVisualizer.d.ts +24 -0
  180. package/dist/studio/src/components/SQLEditor/MonacoEditor.d.ts +17 -0
  181. package/dist/studio/src/components/SQLEditor/SQLEditor.d.ts +11 -0
  182. package/dist/studio/src/components/SQLEditor/SQLEditorSidebar.d.ts +21 -0
  183. package/dist/studio/src/components/SQLEditor/SchemaBrowser.d.ts +8 -0
  184. package/dist/studio/src/components/SchemaVisualizer/RelationEdge.d.ts +3 -0
  185. package/dist/studio/src/components/SchemaVisualizer/SchemaVisualizer.d.ts +2 -0
  186. package/dist/studio/src/components/SchemaVisualizer/TableNode.d.ts +3 -0
  187. package/dist/studio/src/components/SchemaVisualizer/index.d.ts +5 -0
  188. package/dist/studio/src/components/SchemaVisualizer/schema-visualizer.utils.d.ts +42 -0
  189. package/dist/studio/src/components/SchemaVisualizer/useSchemaGraph.d.ts +37 -0
  190. package/dist/studio/src/components/StorageView/StorageView.d.ts +1 -0
  191. package/dist/studio/src/components/StudioHomePage.d.ts +9 -0
  192. package/dist/studio/src/index.d.ts +4 -0
  193. package/dist/studio/src/utils/entities.d.ts +0 -0
  194. package/dist/studio/src/utils/pgColumnToProperty.d.ts +6 -0
  195. package/dist/studio/src/utils/sql_utils.d.ts +52 -0
  196. package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
  197. package/dist/types/src/controllers/auth.d.ts +119 -0
  198. package/dist/types/src/controllers/client.d.ts +170 -0
  199. package/dist/types/src/controllers/collection_registry.d.ts +45 -0
  200. package/dist/types/src/controllers/customization_controller.d.ts +60 -0
  201. package/dist/types/src/controllers/data.d.ts +168 -0
  202. package/dist/types/src/controllers/data_driver.d.ts +160 -0
  203. package/dist/types/src/controllers/database_admin.d.ts +11 -0
  204. package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
  205. package/dist/types/src/controllers/effective_role.d.ts +4 -0
  206. package/dist/types/src/controllers/email.d.ts +34 -0
  207. package/dist/types/src/controllers/index.d.ts +18 -0
  208. package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
  209. package/dist/types/src/controllers/navigation.d.ts +213 -0
  210. package/dist/types/src/controllers/registry.d.ts +54 -0
  211. package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
  212. package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
  213. package/dist/types/src/controllers/snackbar.d.ts +24 -0
  214. package/dist/types/src/controllers/storage.d.ts +171 -0
  215. package/dist/types/src/index.d.ts +4 -0
  216. package/dist/types/src/rebase_context.d.ts +105 -0
  217. package/dist/types/src/types/backend.d.ts +536 -0
  218. package/dist/types/src/types/builders.d.ts +15 -0
  219. package/dist/types/src/types/chips.d.ts +5 -0
  220. package/dist/types/src/types/collections.d.ts +856 -0
  221. package/dist/types/src/types/cron.d.ts +102 -0
  222. package/dist/types/src/types/data_source.d.ts +64 -0
  223. package/dist/types/src/types/entities.d.ts +145 -0
  224. package/dist/types/src/types/entity_actions.d.ts +98 -0
  225. package/dist/types/src/types/entity_callbacks.d.ts +173 -0
  226. package/dist/types/src/types/entity_link_builder.d.ts +7 -0
  227. package/dist/types/src/types/entity_overrides.d.ts +10 -0
  228. package/dist/types/src/types/entity_views.d.ts +61 -0
  229. package/dist/types/src/types/export_import.d.ts +21 -0
  230. package/dist/types/src/types/index.d.ts +23 -0
  231. package/dist/types/src/types/locales.d.ts +4 -0
  232. package/dist/types/src/types/modify_collections.d.ts +5 -0
  233. package/dist/types/src/types/plugins.d.ts +279 -0
  234. package/dist/types/src/types/properties.d.ts +1176 -0
  235. package/dist/types/src/types/property_config.d.ts +70 -0
  236. package/dist/types/src/types/relations.d.ts +336 -0
  237. package/dist/types/src/types/slots.d.ts +252 -0
  238. package/dist/types/src/types/translations.d.ts +870 -0
  239. package/dist/types/src/types/user_management_delegate.d.ts +121 -0
  240. package/dist/types/src/types/websockets.d.ts +78 -0
  241. package/dist/types/src/users/index.d.ts +2 -0
  242. package/dist/types/src/users/roles.d.ts +22 -0
  243. package/dist/types/src/users/user.d.ts +46 -0
  244. package/dist/ui/src/components/Alert.d.ts +12 -0
  245. package/dist/ui/src/components/Autocomplete.d.ts +21 -0
  246. package/dist/ui/src/components/Avatar.d.ts +11 -0
  247. package/dist/ui/src/components/Badge.d.ts +8 -0
  248. package/dist/ui/src/components/BooleanSwitch.d.ts +14 -0
  249. package/dist/ui/src/components/BooleanSwitchWithLabel.d.ts +17 -0
  250. package/dist/ui/src/components/Button.d.ts +14 -0
  251. package/dist/ui/src/components/Card.d.ts +9 -0
  252. package/dist/ui/src/components/CenteredView.d.ts +9 -0
  253. package/dist/ui/src/components/Checkbox.d.ts +13 -0
  254. package/dist/ui/src/components/Chip.d.ts +26 -0
  255. package/dist/ui/src/components/CircularProgress.d.ts +5 -0
  256. package/dist/ui/src/components/CircularProgressCenter.d.ts +11 -0
  257. package/dist/ui/src/components/Collapse.d.ts +9 -0
  258. package/dist/ui/src/components/ColorPicker.d.ts +30 -0
  259. package/dist/ui/src/components/Container.d.ts +8 -0
  260. package/dist/ui/src/components/DateTimeField.d.ts +24 -0
  261. package/dist/ui/src/components/DebouncedTextField.d.ts +2 -0
  262. package/dist/ui/src/components/Dialog.d.ts +39 -0
  263. package/dist/ui/src/components/DialogActions.d.ts +7 -0
  264. package/dist/ui/src/components/DialogContent.d.ts +7 -0
  265. package/dist/ui/src/components/DialogTitle.d.ts +10 -0
  266. package/dist/ui/src/components/ErrorBoundary.d.ts +11 -0
  267. package/dist/ui/src/components/ExpandablePanel.d.ts +12 -0
  268. package/dist/ui/src/components/FileUpload.d.ts +23 -0
  269. package/dist/ui/src/components/IconButton.d.ts +12 -0
  270. package/dist/ui/src/components/InfoLabel.d.ts +5 -0
  271. package/dist/ui/src/components/InputLabel.d.ts +11 -0
  272. package/dist/ui/src/components/Label.d.ts +7 -0
  273. package/dist/ui/src/components/LoadingButton.d.ts +7 -0
  274. package/dist/ui/src/components/Markdown.d.ts +10 -0
  275. package/dist/ui/src/components/Menu.d.ts +23 -0
  276. package/dist/ui/src/components/Menubar.d.ts +80 -0
  277. package/dist/ui/src/components/MultiSelect.d.ts +48 -0
  278. package/dist/ui/src/components/Paper.d.ts +6 -0
  279. package/dist/ui/src/components/Popover.d.ts +24 -0
  280. package/dist/ui/src/components/RadioGroup.d.ts +28 -0
  281. package/dist/ui/src/components/ResizablePanels.d.ts +18 -0
  282. package/dist/ui/src/components/SearchBar.d.ts +22 -0
  283. package/dist/ui/src/components/Select.d.ts +43 -0
  284. package/dist/ui/src/components/Separator.d.ts +5 -0
  285. package/dist/ui/src/components/Sheet.d.ts +22 -0
  286. package/dist/ui/src/components/Skeleton.d.ts +6 -0
  287. package/dist/ui/src/components/Slider.d.ts +21 -0
  288. package/dist/ui/src/components/Table.d.ts +34 -0
  289. package/dist/ui/src/components/Tabs.d.ts +19 -0
  290. package/dist/ui/src/components/TextField.d.ts +58 -0
  291. package/dist/ui/src/components/TextareaAutosize.d.ts +43 -0
  292. package/dist/ui/src/components/ToggleButtonGroup.d.ts +30 -0
  293. package/dist/ui/src/components/Tooltip.d.ts +19 -0
  294. package/dist/ui/src/components/Typography.d.ts +36 -0
  295. package/dist/ui/src/components/VirtualTable/VirtualTable.d.ts +11 -0
  296. package/dist/ui/src/components/VirtualTable/VirtualTableCell.d.ts +21 -0
  297. package/dist/ui/src/components/VirtualTable/VirtualTableHeader.d.ts +29 -0
  298. package/dist/ui/src/components/VirtualTable/VirtualTableHeaderRow.d.ts +2 -0
  299. package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +243 -0
  300. package/dist/ui/src/components/VirtualTable/VirtualTableRow.d.ts +3 -0
  301. package/dist/ui/src/components/VirtualTable/index.d.ts +3 -0
  302. package/dist/ui/src/components/VirtualTable/types.d.ts +38 -0
  303. package/dist/ui/src/components/common/SelectInputLabel.d.ts +5 -0
  304. package/dist/ui/src/components/index.d.ts +53 -0
  305. package/dist/ui/src/hooks/PortalContainerContext.d.ts +31 -0
  306. package/dist/ui/src/hooks/index.d.ts +6 -0
  307. package/dist/ui/src/hooks/useDebounceCallback.d.ts +1 -0
  308. package/dist/ui/src/hooks/useDebounceValue.d.ts +1 -0
  309. package/dist/ui/src/hooks/useDebouncedCallback.d.ts +1 -0
  310. package/dist/ui/src/hooks/useInjectStyles.d.ts +7 -0
  311. package/dist/ui/src/hooks/useOutsideAlerter.d.ts +5 -0
  312. package/dist/ui/src/icons/GitHubIcon.d.ts +2 -0
  313. package/dist/ui/src/icons/HandleIcon.d.ts +1 -0
  314. package/dist/ui/src/icons/Icon.d.ts +20 -0
  315. package/dist/ui/src/icons/cool_icon_keys.d.ts +1 -0
  316. package/dist/ui/src/icons/icon_keys.d.ts +1 -0
  317. package/dist/ui/src/icons/index.d.ts +6 -0
  318. package/dist/ui/src/index.d.ts +5 -0
  319. package/dist/ui/src/styles.d.ts +12 -0
  320. package/dist/ui/src/util/chip_colors.d.ts +4 -0
  321. package/dist/ui/src/util/cls.d.ts +2 -0
  322. package/dist/ui/src/util/debounce.d.ts +10 -0
  323. package/dist/ui/src/util/hash.d.ts +1 -0
  324. package/dist/ui/src/util/index.d.ts +4 -0
  325. package/dist/ui/src/util/key_to_icon_component.d.ts +1 -0
  326. package/package.json +84 -0
  327. package/src/components/ApiExplorer/ApiExplorer.tsx +290 -0
  328. package/src/components/ApiExplorer/EndpointDetail.tsx +271 -0
  329. package/src/components/ApiExplorer/TryItPanel.tsx +510 -0
  330. package/src/components/ApiExplorer/parseSpec.ts +104 -0
  331. package/src/components/ApiExplorer/types.ts +84 -0
  332. package/src/components/AuthSimulationSelector.tsx +77 -0
  333. package/src/components/Branches/BranchesView.tsx +370 -0
  334. package/src/components/CronJobs/CronJobsView.tsx +346 -0
  335. package/src/components/JSEditor/JSEditor.tsx +1033 -0
  336. package/src/components/JSEditor/JSEditorSidebar.tsx +340 -0
  337. package/src/components/JSEditor/JSMonacoEditor.tsx +390 -0
  338. package/src/components/RLSEditor/PolicyEditor.tsx +444 -0
  339. package/src/components/RLSEditor/RLSEditor.tsx +692 -0
  340. package/src/components/RLSEditor/index.ts +1 -0
  341. package/src/components/RebaseStudio.tsx +121 -0
  342. package/src/components/SQLEditor/ExplainVisualizer.tsx +128 -0
  343. package/src/components/SQLEditor/MonacoEditor.tsx +203 -0
  344. package/src/components/SQLEditor/SQLEditor.tsx +1419 -0
  345. package/src/components/SQLEditor/SQLEditorSidebar.tsx +174 -0
  346. package/src/components/SQLEditor/SchemaBrowser.tsx +158 -0
  347. package/src/components/SchemaVisualizer/RelationEdge.tsx +102 -0
  348. package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +665 -0
  349. package/src/components/SchemaVisualizer/TableNode.tsx +257 -0
  350. package/src/components/SchemaVisualizer/index.ts +5 -0
  351. package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +140 -0
  352. package/src/components/SchemaVisualizer/useSchemaGraph.ts +397 -0
  353. package/src/components/StorageView/StorageView.tsx +1035 -0
  354. package/src/components/StudioHomePage.tsx +357 -0
  355. package/src/index.ts +31 -0
  356. package/src/utils/entities.ts +2 -0
  357. package/src/utils/pgColumnToProperty.test.ts +401 -0
  358. package/src/utils/pgColumnToProperty.ts +275 -0
  359. package/src/utils/sql_utils.test.ts +265 -0
  360. package/src/utils/sql_utils.ts +291 -0
  361. package/src/vite-env.d.ts +1 -0
@@ -0,0 +1,1035 @@
1
+
2
+ import React, { useState, useEffect, useCallback, useMemo } from "react";
3
+ import { Typography, cls, defaultBorderMixin, Button, IconButton, Tooltip, CircularProgress, ResizablePanels, Chip, Dialog, DialogContent, DialogActions, FileUpload , iconSize } from "@rebasepro/ui";
4
+ import { VideoIcon, Music2Icon, RefreshCwIcon, Trash2Icon, XIcon, PlusIcon, DownloadIcon, UploadCloudIcon, FolderIcon, FileTextIcon, ImageIcon, ArrowLeftIcon, FileIcon, HomeIcon, LayoutGridIcon, ListIcon } from "lucide-react";
5
+ import { useStorageSource, useSnackbarController, ErrorView } from "@rebasepro/core";
6
+ import type { StorageListResult } from "@rebasepro/types";
7
+
8
+ // ──────────────────────────────────────────────
9
+ // Types
10
+ // ──────────────────────────────────────────────
11
+
12
+ interface StorageFile {
13
+ name: string;
14
+ fullPath: string;
15
+ isFolder: boolean;
16
+ /** Only populated when metadata is fetched */
17
+ size?: number;
18
+ contentType?: string;
19
+ downloadUrl?: string;
20
+ }
21
+
22
+ // ──────────────────────────────────────────────
23
+ // Helpers
24
+ // ──────────────────────────────────────────────
25
+
26
+ function formatFileSize(bytes: number): string {
27
+ if (bytes < 1024) return `${bytes} B`;
28
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
29
+ if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
30
+ return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
31
+ }
32
+
33
+ function getFileIcon(contentType?: string) {
34
+ if (!contentType) return FileTextIcon;
35
+ if (contentType.startsWith("image/")) return ImageIcon;
36
+ if (contentType.startsWith("video/")) return VideoIcon;
37
+ if (contentType.startsWith("audio/")) return Music2Icon;
38
+ return FileTextIcon;
39
+ }
40
+
41
+ function getExtension(name: string): string {
42
+ const parts = name.split(".");
43
+ return parts.length > 1 ? parts[parts.length - 1].toUpperCase() : "";
44
+ }
45
+
46
+ function breadcrumbSegments(path: string): { label: string; path: string }[] {
47
+ if (!path || path === "/") return [{ label: "Root",
48
+ path: "" }];
49
+ const parts = path.split("/").filter(Boolean);
50
+ const segments = [{ label: "Root",
51
+ path: "" }];
52
+ let accumulated = "";
53
+ for (const part of parts) {
54
+ accumulated = accumulated ? `${accumulated}/${part}` : part;
55
+ segments.push({ label: part,
56
+ path: accumulated });
57
+ }
58
+ return segments;
59
+ }
60
+
61
+ // ──────────────────────────────────────────────
62
+ // Upload Dialog
63
+ // ──────────────────────────────────────────────
64
+
65
+ function UploadDialog({
66
+ open,
67
+ currentPath,
68
+ onClose,
69
+ onUpload
70
+ }: {
71
+ open: boolean;
72
+ currentPath: string;
73
+ onClose: () => void;
74
+ onUpload: (files: File[]) => Promise<void>;
75
+ }) {
76
+ const [uploading, setUploading] = useState(false);
77
+ const [selectedFiles, setSelectedFiles] = useState<File[]>([]);
78
+ const [error, setError] = useState<string | null>(null);
79
+
80
+ const handleFilesAdded = useCallback((files: File[]) => {
81
+ setSelectedFiles(prev => [...prev, ...files]);
82
+ }, []);
83
+
84
+ const handleRemoveFile = useCallback((index: number) => {
85
+ setSelectedFiles(prev => prev.filter((_, i) => i !== index));
86
+ }, []);
87
+
88
+ const handleUpload = useCallback(async () => {
89
+ if (selectedFiles.length === 0) return;
90
+ setUploading(true);
91
+ setError(null);
92
+ try {
93
+ await onUpload(selectedFiles);
94
+ setSelectedFiles([]);
95
+ onClose();
96
+ } catch (err) {
97
+ setError(err instanceof Error ? err.message : "Upload failed");
98
+ } finally {
99
+ setUploading(false);
100
+ }
101
+ }, [selectedFiles, onUpload, onClose]);
102
+
103
+ const handleClose = useCallback(() => {
104
+ if (!uploading) {
105
+ setSelectedFiles([]);
106
+ setError(null);
107
+ onClose();
108
+ }
109
+ }, [uploading, onClose]);
110
+
111
+ return (
112
+ <Dialog open={open} onOpenChange={(o) => !o && handleClose()} maxWidth="md">
113
+ <DialogContent className="p-0">
114
+ <div className="p-4 border-b border-surface-accent-200 dark:border-surface-accent-700">
115
+ <Typography variant="h6">Upload Files</Typography>
116
+ <Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark mt-1 block">
117
+ to <span className="font-mono text-primary">/{currentPath || "root"}</span>
118
+ </Typography>
119
+ </div>
120
+
121
+ <div className="p-4">
122
+ {/* Drop Zone */}
123
+ <FileUpload
124
+ accept={{} as Record<string, string[]>}
125
+ onFilesAdded={handleFilesAdded}
126
+ size="large"
127
+ uploadDescription={
128
+ <div className="flex flex-col items-center justify-center pointer-events-none mt-2">
129
+ <UploadCloudIcon className="text-surface-accent-400 mb-2 w-8 h-8"/>
130
+ <Typography variant="h6" className="font-bold">
131
+ Drop files here or click to browse
132
+ </Typography>
133
+ <Typography variant="caption" className="text-surface-accent-500 font-medium">
134
+ Any file type supported
135
+ </Typography>
136
+ </div>
137
+ }
138
+ />
139
+
140
+ {error && (
141
+ <Typography variant="caption" className="text-red-500 mt-2 block whitespace-pre-line">
142
+ {error}
143
+ </Typography>
144
+ )}
145
+
146
+ {selectedFiles.length > 0 && (
147
+ <div className="mt-4 space-y-2">
148
+ <Typography variant="caption" className="text-surface-accent-500">
149
+ Selected files ({selectedFiles.length})
150
+ </Typography>
151
+ <div className="max-h-40 overflow-auto space-y-1">
152
+ {selectedFiles.map((file, index) => (
153
+ <div
154
+ key={`${file.name}-${index}`}
155
+ className={cls(
156
+ "flex items-center justify-between p-2 rounded",
157
+ "bg-surface-accent-50 dark:bg-surface-accent-800"
158
+ )}
159
+ >
160
+ <div className="flex-1 min-w-0 mr-2">
161
+ <Typography variant="body2" className="truncate">
162
+ {file.name}
163
+ </Typography>
164
+ <Typography variant="caption" className="text-surface-accent-500">
165
+ {formatFileSize(file.size)}
166
+ </Typography>
167
+ </div>
168
+ <Button
169
+ variant="text"
170
+ size="small"
171
+ onClick={(e) => {
172
+ e.stopPropagation();
173
+ handleRemoveFile(index);
174
+ }}
175
+ disabled={uploading}
176
+ >
177
+ Remove
178
+ </Button>
179
+ </div>
180
+ ))}
181
+ </div>
182
+ </div>
183
+ )}
184
+ </div>
185
+ </DialogContent>
186
+
187
+ <DialogActions>
188
+ <Button variant="text" onClick={handleClose} disabled={uploading}>
189
+ Cancel
190
+ </Button>
191
+ <Button
192
+ variant="filled"
193
+ onClick={handleUpload}
194
+ disabled={selectedFiles.length === 0 || uploading}
195
+ >
196
+ {uploading ? (
197
+ <>
198
+ <CircularProgress size="smallest"/>
199
+ Uploading...
200
+ </>
201
+ ) : (
202
+ `Upload ${selectedFiles.length > 0 ? `(${selectedFiles.length})` : ""}`
203
+ )}
204
+ </Button>
205
+ </DialogActions>
206
+ </Dialog>
207
+ );
208
+ }
209
+
210
+ // ──────────────────────────────────────────────
211
+ // FileIcon preview panel
212
+ // ──────────────────────────────────────────────
213
+
214
+ function FilePreviewPanel({
215
+ file,
216
+ onClose,
217
+ onDelete,
218
+ downloadUrl
219
+ }: {
220
+ file: StorageFile;
221
+ onClose: () => void;
222
+ onDelete: () => void;
223
+ downloadUrl: string | null;
224
+ }) {
225
+ const isImage = file.contentType?.startsWith("image/");
226
+ const isVideo = file.contentType?.startsWith("video/");
227
+ const isAudio = file.contentType?.startsWith("audio/");
228
+ const FileIconComponent = getFileIcon(file.contentType);
229
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
230
+
231
+ return (
232
+ <>
233
+ <div className={cls(
234
+ "flex flex-col h-full border-l",
235
+ defaultBorderMixin,
236
+ "bg-white dark:bg-surface-950"
237
+ )}>
238
+ {/* Header */}
239
+ <div className={cls("flex items-center justify-between p-3 border-b shrink-0", defaultBorderMixin)}>
240
+ <Typography variant="body2" className="font-medium truncate flex-1 mr-2">
241
+ {file.name}
242
+ </Typography>
243
+ <div className="flex items-center gap-0.5">
244
+ {downloadUrl && (
245
+ <Tooltip title="Download">
246
+ <IconButton
247
+ size="small"
248
+ onClick={() => window.open(downloadUrl, "_blank")}
249
+ >
250
+ <DownloadIcon size={iconSize.smallest}/>
251
+ </IconButton>
252
+ </Tooltip>
253
+ )}
254
+ <Tooltip title="Delete">
255
+ <IconButton
256
+ size="small"
257
+ onClick={() => setDeleteDialogOpen(true)}
258
+ className="text-red-500 hover:bg-red-50 dark:hover:bg-red-900/20"
259
+ >
260
+ <Trash2Icon size={iconSize.smallest}/>
261
+ </IconButton>
262
+ </Tooltip>
263
+ <IconButton size="small" onClick={onClose}>
264
+ <XIcon size={iconSize.smallest}/>
265
+ </IconButton>
266
+ </div>
267
+ </div>
268
+
269
+ {/* Preview */}
270
+ <div className="flex-1 overflow-auto">
271
+ <div className="flex flex-col items-center justify-center min-h-[200px] p-4 bg-surface-50 dark:bg-surface-900 border-b border-surface-accent-200 dark:border-surface-accent-700">
272
+ {(() => {
273
+ const ext = getExtension(file.name)?.toLowerCase() || "";
274
+ const isImage = file.contentType?.startsWith("image/") || ["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(ext);
275
+ const isVideo = file.contentType?.startsWith("video/") || ["mp4", "webm", "ogg", "mov"].includes(ext);
276
+ const isAudio = file.contentType?.startsWith("audio/") || ["mp3", "wav", "ogg", "m4a"].includes(ext);
277
+ const downloadUrl = file.downloadUrl;
278
+
279
+ if (isImage && downloadUrl) {
280
+ return (
281
+ <img
282
+ src={downloadUrl}
283
+ alt={file.name}
284
+ className="max-w-full max-h-[400px] object-contain rounded-md shadow-sm"
285
+ />
286
+ );
287
+ } else if (isVideo && downloadUrl) {
288
+ return (
289
+ <video
290
+ src={downloadUrl}
291
+ className="max-w-full max-h-[400px] rounded-md"
292
+ controls
293
+ />
294
+ );
295
+ } else if (isAudio && downloadUrl) {
296
+ return (
297
+ <div className="flex flex-col items-center gap-4">
298
+ <Music2Icon className="text-surface-accent-400 w-10 h-10"/>
299
+ <audio src={downloadUrl} controls className="w-full max-w-xs"/>
300
+ </div>
301
+ );
302
+ } else {
303
+ return (
304
+ <div className="flex flex-col items-center gap-3 text-surface-accent-400">
305
+ <FileIconComponent className="w-10 h-10"/>
306
+ <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark">
307
+ No preview available
308
+ </Typography>
309
+ </div>
310
+ );
311
+ }
312
+ })()}
313
+ </div>
314
+ </div>
315
+
316
+ {/* Metadata */}
317
+ <div className="p-4 space-y-3">
318
+ <div>
319
+ <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark text-[10px] uppercase tracking-wider font-bold mb-1 block">
320
+ File Info
321
+ </Typography>
322
+ </div>
323
+ <div className="grid grid-cols-2 gap-3">
324
+ <div>
325
+ <Typography variant="caption" className="text-surface-accent-500 text-[11px]">
326
+ Name
327
+ </Typography>
328
+ <Typography variant="body2" className="text-[13px] break-all">
329
+ {file.name}
330
+ </Typography>
331
+ </div>
332
+ <div>
333
+ <Typography variant="caption" className="text-surface-accent-500 text-[11px]">
334
+ Type
335
+ </Typography>
336
+ <Typography variant="body2" className="text-[13px]">
337
+ {file.contentType || "Unknown"}
338
+ </Typography>
339
+ </div>
340
+ {file.size !== undefined && (
341
+ <div>
342
+ <Typography variant="caption" className="text-surface-accent-500 text-[11px]">
343
+ Size
344
+ </Typography>
345
+ <Typography variant="body2" className="text-[13px]">
346
+ {formatFileSize(file.size)}
347
+ </Typography>
348
+ </div>
349
+ )}
350
+ <div>
351
+ <Typography variant="caption" className="text-surface-accent-500 text-[11px]">
352
+ Extension
353
+ </Typography>
354
+ <Typography variant="body2" className="text-[13px] font-mono">
355
+ {getExtension(file.name) || "—"}
356
+ </Typography>
357
+ </div>
358
+ <div className="col-span-2">
359
+ <Typography variant="caption" className="text-surface-accent-500 text-[11px]">
360
+ Path
361
+ </Typography>
362
+ <Typography variant="body2" className="text-[13px] font-mono break-all">
363
+ {file.fullPath}
364
+ </Typography>
365
+ </div>
366
+ </div>
367
+
368
+ {downloadUrl && (
369
+ <div className="pt-2">
370
+ <Typography variant="caption" className="text-surface-accent-500 text-[11px] block mb-1">
371
+ URL
372
+ </Typography>
373
+ <div
374
+ className="p-2 rounded bg-surface-100 dark:bg-surface-950 cursor-pointer hover:bg-surface-200 dark:hover:bg-surface-700 transition-colors"
375
+ onClick={() => {
376
+ navigator.clipboard.writeText(downloadUrl);
377
+ }}
378
+ >
379
+ <Typography variant="caption" className="font-mono text-[11px] break-all text-primary">
380
+ {downloadUrl}
381
+ </Typography>
382
+ <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark text-[10px] block mt-1">
383
+ Click to copy
384
+ </Typography>
385
+ </div>
386
+ </div>
387
+ )}
388
+ </div>
389
+ </div>
390
+
391
+ {/* Delete Confirmation */}
392
+ <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
393
+ <DialogContent>
394
+ <Typography variant="subtitle1" className="mb-2">
395
+ Delete File?
396
+ </Typography>
397
+ <Typography className="text-surface-accent-600 dark:text-surface-accent-400">
398
+ Are you sure you want to delete &quot;{file.name}&quot;?
399
+ This action cannot be undone.
400
+ </Typography>
401
+ </DialogContent>
402
+ <DialogActions>
403
+ <Button variant="text" onClick={() => setDeleteDialogOpen(false)}>
404
+ Cancel
405
+ </Button>
406
+ <Button
407
+ variant="filled"
408
+ color="error"
409
+ onClick={() => {
410
+ setDeleteDialogOpen(false);
411
+ onDelete();
412
+ }}
413
+ >
414
+ Delete
415
+ </Button>
416
+ </DialogActions>
417
+ </Dialog>
418
+ </>
419
+ );
420
+ }
421
+
422
+ // ──────────────────────────────────────────────
423
+ // Sidebar (folder tree)
424
+ // ──────────────────────────────────────────────
425
+
426
+ function StorageSidebar({
427
+ folders,
428
+ currentPath,
429
+ onNavigate,
430
+ loading
431
+ }: {
432
+ folders: StorageFile[];
433
+ currentPath: string;
434
+ onNavigate: (path: string) => void;
435
+ loading: boolean;
436
+ }) {
437
+ const segments = breadcrumbSegments(currentPath);
438
+
439
+ return (
440
+ <div className={cls("flex flex-col h-full w-full bg-white dark:bg-surface-950 border-r", defaultBorderMixin)}>
441
+ <div className={cls("p-3 border-b flex justify-between items-center bg-surface-50 dark:bg-surface-900 shrink-0", defaultBorderMixin)}>
442
+ <Typography variant="caption" className="font-bold uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark">
443
+ Folders
444
+ </Typography>
445
+ </div>
446
+
447
+ <div className="flex-grow overflow-y-auto no-scrollbar p-1">
448
+ {/* Folder tree */}
449
+ <div
450
+ className={cls(
451
+ "flex items-center p-1.5 cursor-pointer rounded transition-colors",
452
+ currentPath === "" || !currentPath
453
+ ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light"
454
+ : "hover:bg-surface-100 dark:hover:bg-surface-950 text-text-secondary dark:text-text-secondary-dark"
455
+ )}
456
+ onClick={() => onNavigate("")}
457
+ >
458
+ <HomeIcon size={iconSize.smallest} className="mr-1.5 shrink-0"/>
459
+ <Typography variant="body2" className="text-xs truncate">Root</Typography>
460
+ </div>
461
+
462
+ {loading && folders.length === 0 ? (
463
+ <div className="flex justify-center p-4">
464
+ <CircularProgress size="small"/>
465
+ </div>
466
+ ) : (
467
+ <div className="mt-1 space-y-0.5">
468
+ {folders.map(folder => {
469
+ const isSelected = currentPath === folder.fullPath;
470
+ return (
471
+ <div
472
+ key={folder.fullPath}
473
+ className={cls(
474
+ "flex items-center p-1.5 cursor-pointer rounded transition-colors group",
475
+ isSelected
476
+ ? "bg-primary/10 text-primary dark:bg-primary/20 dark:text-primary-light"
477
+ : "hover:bg-surface-100 dark:hover:bg-surface-950 text-text-secondary dark:text-text-secondary-dark"
478
+ )}
479
+ onClick={() => onNavigate(folder.fullPath)}
480
+ >
481
+ <FolderIcon size={iconSize.smallest} className="mr-1.5 shrink-0 text-amber-500 dark:text-amber-400"/>
482
+ <Typography variant="body2" className="text-xs truncate flex-1 min-w-0">
483
+ {folder.name}
484
+ </Typography>
485
+ </div>
486
+ );
487
+ })}
488
+ </div>
489
+ )}
490
+ </div>
491
+ </div>
492
+ );
493
+ }
494
+
495
+ // ──────────────────────────────────────────────
496
+ // Main StorageView Export
497
+ // ──────────────────────────────────────────────
498
+
499
+ export const StorageView = () => {
500
+ const storageSource = useStorageSource();
501
+ const snackbarController = useSnackbarController();
502
+
503
+ // Navigation
504
+ const [currentPath, setCurrentPath] = useState("");
505
+ const [loading, setLoading] = useState(true);
506
+ const [error, setError] = useState<string | null>(null);
507
+
508
+ // Contents
509
+ const [folders, setFolders] = useState<StorageFile[]>([]);
510
+ const [files, setFiles] = useState<StorageFile[]>([]);
511
+
512
+ // Selection and preview
513
+ const [selectedFile, setSelectedFile] = useState<StorageFile | null>(null);
514
+ const [selectedDownloadUrl, setSelectedDownloadUrl] = useState<string | null>(null);
515
+
516
+ // Upload
517
+ const [uploadDialogOpen, setUploadDialogOpen] = useState(false);
518
+
519
+ // View mode
520
+ const [viewMode, setViewMode] = useState<"grid" | "list">("grid");
521
+
522
+ // Resizable panels
523
+
524
+ // Resizable panels
525
+ const [sidebarSize, setSidebarSize] = useState(() => {
526
+ try {
527
+ const saved = localStorage.getItem("rebase_storage_sidebar_size");
528
+ return saved !== null ? parseFloat(saved) : 18;
529
+ } catch {
530
+ return 18;
531
+ }
532
+ });
533
+
534
+ useEffect(() => {
535
+ try {
536
+ localStorage.setItem("rebase_storage_sidebar_size", sidebarSize.toString());
537
+ } catch { /* noop */ }
538
+ }, [sidebarSize]);
539
+
540
+ // ── Fetch directory contents ──
541
+ const fetchContents = useCallback(async (path: string) => {
542
+ setLoading(true);
543
+ setError(null);
544
+ try {
545
+ const result: StorageListResult = await storageSource.listObjects(path);
546
+
547
+ const folderItems: StorageFile[] = (result.prefixes ?? []).map(ref => ({
548
+ name: ref.name,
549
+ fullPath: ref.fullPath,
550
+ isFolder: true
551
+ }));
552
+
553
+ // Build file items and fetch metadata for each
554
+ const fileItems: StorageFile[] = await Promise.all(
555
+ (result.items ?? []).map(async (ref) => {
556
+ try {
557
+ const downloadConfig = await storageSource.getSignedUrl(ref.fullPath);
558
+ return {
559
+ name: ref.name,
560
+ fullPath: ref.fullPath,
561
+ isFolder: false,
562
+ size: downloadConfig.metadata?.size,
563
+ contentType: downloadConfig.metadata?.contentType,
564
+ downloadUrl: downloadConfig.url ?? undefined
565
+ };
566
+ } catch {
567
+ return {
568
+ name: ref.name,
569
+ fullPath: ref.fullPath,
570
+ isFolder: false
571
+ };
572
+ }
573
+ })
574
+ );
575
+
576
+ setFolders(folderItems);
577
+ setFiles(fileItems);
578
+ } catch (e) {
579
+ console.error("Storage list error:", e);
580
+ setError(e instanceof Error ? e.message : String(e));
581
+ } finally {
582
+ setLoading(false);
583
+ }
584
+ }, [storageSource]);
585
+
586
+ useEffect(() => {
587
+ fetchContents(currentPath);
588
+ }, [currentPath, fetchContents]);
589
+
590
+ // Navigate to path
591
+ const handleNavigate = useCallback((path: string) => {
592
+ setCurrentPath(path);
593
+ setSelectedFile(null);
594
+ setSelectedDownloadUrl(null);
595
+ }, []);
596
+
597
+ // Navigate up one level
598
+ const handleNavigateUp = useCallback(() => {
599
+ const parts = currentPath.split("/").filter(Boolean);
600
+ parts.pop();
601
+ handleNavigate(parts.join("/"));
602
+ }, [currentPath, handleNavigate]);
603
+
604
+ // Select a file for preview
605
+ const handleSelectFile = useCallback(async (file: StorageFile) => {
606
+ setSelectedFile(file);
607
+ if (file.downloadUrl) {
608
+ setSelectedDownloadUrl(file.downloadUrl);
609
+ } else {
610
+ try {
611
+ const config = await storageSource.getSignedUrl(file.fullPath);
612
+ setSelectedDownloadUrl(config.url);
613
+ } catch {
614
+ setSelectedDownloadUrl(null);
615
+ }
616
+ }
617
+ }, [storageSource]);
618
+
619
+ // Upload files
620
+ const handleUpload = useCallback(async (uploadFiles: File[]) => {
621
+ for (const file of uploadFiles) {
622
+ const key = currentPath ? `${currentPath}/${file.name}` : file.name;
623
+ await storageSource.putObject({
624
+ file,
625
+ key
626
+ });
627
+ }
628
+ snackbarController.open({
629
+ type: "success",
630
+ message: `${uploadFiles.length} file${uploadFiles.length > 1 ? "s" : ""} uploaded successfully`
631
+ });
632
+ fetchContents(currentPath);
633
+ }, [storageSource, currentPath, snackbarController, fetchContents]);
634
+
635
+ // Delete a file
636
+ const handleDeleteFile = useCallback(async (file: StorageFile) => {
637
+ try {
638
+ await storageSource.deleteObject(file.fullPath);
639
+ snackbarController.open({ type: "success",
640
+ message: `"${file.name}" deleted` });
641
+ setSelectedFile(null);
642
+ setSelectedDownloadUrl(null);
643
+ fetchContents(currentPath);
644
+ } catch (e) {
645
+ snackbarController.open({ type: "error",
646
+ message: e instanceof Error ? e.message : String(e) });
647
+ }
648
+ }, [storageSource, currentPath, snackbarController, fetchContents]);
649
+
650
+ // Handle refresh
651
+ const handleRefresh = useCallback(() => {
652
+ fetchContents(currentPath);
653
+ }, [currentPath, fetchContents]);
654
+
655
+ const segments = breadcrumbSegments(currentPath);
656
+
657
+ // ── Render file grid/list ──
658
+ const renderContents = () => {
659
+ if (loading) {
660
+ return (
661
+ <div className="flex-grow flex items-center justify-center">
662
+ <div className="text-center">
663
+ <CircularProgress size="medium"/>
664
+ <Typography variant="body2" className="mt-4 text-text-secondary dark:text-text-secondary-dark font-mono tracking-tight animate-pulse">
665
+ Loading...
666
+ </Typography>
667
+ </div>
668
+ </div>
669
+ );
670
+ }
671
+
672
+ if (error) {
673
+ return (
674
+ <div className="flex-grow flex items-center justify-center p-6 overflow-auto">
675
+ <ErrorView title="Error loading storage" error={error} onRetry={handleRefresh}/>
676
+ </div>
677
+ );
678
+ }
679
+
680
+ const allItems = [...folders, ...files];
681
+
682
+ if (allItems.length === 0) {
683
+ return (
684
+ <div className="flex-grow flex items-center justify-center text-text-disabled dark:text-text-disabled-dark">
685
+ <div className="text-center">
686
+ <svg className="w-12 h-12 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
687
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z"/>
688
+ </svg>
689
+ <Typography variant="body2">
690
+ This folder is empty
691
+ </Typography>
692
+ <Button className="mt-3" onClick={() => setUploadDialogOpen(true)}>
693
+ <PlusIcon size={iconSize.smallest}/>
694
+ Upload files
695
+ </Button>
696
+ </div>
697
+ </div>
698
+ );
699
+ }
700
+
701
+ if (viewMode === "list") {
702
+ return (
703
+ <div className="flex-grow overflow-auto">
704
+ <table className="w-full">
705
+ <thead>
706
+ <tr className={cls("border-b text-left text-[10px] uppercase tracking-wider text-text-disabled dark:text-text-disabled-dark", defaultBorderMixin)}>
707
+ <th className="px-4 py-2 font-bold">Name</th>
708
+ <th className="px-4 py-2 font-bold w-24">Type</th>
709
+ <th className="px-4 py-2 font-bold w-24 text-right">Size</th>
710
+ </tr>
711
+ </thead>
712
+ <tbody>
713
+ {folders.map(folder => (
714
+ <tr
715
+ key={folder.fullPath}
716
+ className="hover:bg-surface-100 dark:hover:bg-surface-950 cursor-pointer transition-colors border-b border-surface-100 dark:border-surface-950/50"
717
+ onClick={() => handleNavigate(folder.fullPath)}
718
+ >
719
+ <td className="px-4 py-2.5">
720
+ <div className="flex items-center gap-2">
721
+ <FolderIcon size={iconSize.smallest} className="text-amber-500 dark:text-amber-400 shrink-0"/>
722
+ <Typography variant="body2" className="text-[13px] font-medium truncate">
723
+ {folder.name}
724
+ </Typography>
725
+ </div>
726
+ </td>
727
+ <td className="px-4 py-2.5">
728
+ <Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark">
729
+ Folder
730
+ </Typography>
731
+ </td>
732
+ <td className="px-4 py-2.5 text-right">
733
+ <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark">
734
+
735
+ </Typography>
736
+ </td>
737
+ </tr>
738
+ ))}
739
+ {files.map(file => {
740
+ const FileIconComp = getFileIcon(file.contentType);
741
+ const isSelected = selectedFile?.fullPath === file.fullPath;
742
+ return (
743
+ <tr
744
+ key={file.fullPath}
745
+ className={cls(
746
+ "cursor-pointer transition-colors border-b border-surface-100 dark:border-surface-950/50",
747
+ isSelected
748
+ ? "bg-primary/5 dark:bg-primary/10"
749
+ : "hover:bg-surface-100 dark:hover:bg-surface-950"
750
+ )}
751
+ onClick={() => handleSelectFile(file)}
752
+ >
753
+ <td className="px-4 py-2.5">
754
+ <div className="flex items-center gap-2">
755
+ <FileIconComp size={iconSize.smallest} className="text-surface-accent-400 shrink-0"/>
756
+ <Typography variant="body2" className="text-[13px] truncate">
757
+ {file.name}
758
+ </Typography>
759
+ </div>
760
+ </td>
761
+ <td className="px-4 py-2.5">
762
+ <Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark">
763
+ {getExtension(file.name) || file.contentType?.split("/")[1]?.toUpperCase() || "—"}
764
+ </Typography>
765
+ </td>
766
+ <td className="px-4 py-2.5 text-right">
767
+ <Typography variant="caption" className="text-text-secondary dark:text-text-secondary-dark font-mono text-[11px]">
768
+ {file.size !== undefined ? formatFileSize(file.size) : "—"}
769
+ </Typography>
770
+ </td>
771
+ </tr>
772
+ );
773
+ })}
774
+ </tbody>
775
+ </table>
776
+ </div>
777
+ );
778
+ }
779
+
780
+ // Grid view
781
+ return (
782
+ <div className="flex-grow overflow-auto p-4">
783
+ {/* Folder cards */}
784
+ {folders.length > 0 && (
785
+ <div className="mb-4">
786
+ <Typography variant="caption" className="text-[10px] uppercase tracking-wider font-bold text-text-disabled dark:text-text-disabled-dark mb-2 block">
787
+ Folders
788
+ </Typography>
789
+ <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-3">
790
+ {folders.map(folder => (
791
+ <div
792
+ key={folder.fullPath}
793
+ className={cls(
794
+ "rounded-lg p-3 cursor-pointer transition-all duration-150 border",
795
+ defaultBorderMixin,
796
+ "hover:bg-surface-100 dark:hover:bg-surface-950 hover:shadow-sm",
797
+ "flex items-center gap-2"
798
+ )}
799
+ onClick={() => handleNavigate(folder.fullPath)}
800
+ >
801
+ <FolderIcon size={iconSize.smallest} className="text-amber-500 dark:text-amber-400 shrink-0"/>
802
+ <Typography variant="body2" className="text-[13px] font-medium truncate">
803
+ {folder.name}
804
+ </Typography>
805
+ </div>
806
+ ))}
807
+ </div>
808
+ </div>
809
+ )}
810
+
811
+ {/* FileIcon cards */}
812
+ {files.length > 0 && (
813
+ <div>
814
+ <Typography variant="caption" className="text-[10px] uppercase tracking-wider font-bold text-text-disabled dark:text-text-disabled-dark mb-2 block">
815
+ Files ({files.length})
816
+ </Typography>
817
+ <div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-3">
818
+ {files.map(file => {
819
+ const FileIconComp = getFileIcon(file.contentType);
820
+ const ext = getExtension(file.name)?.toLowerCase() || "";
821
+ const isImage = file.contentType?.startsWith("image/") || ["jpg", "jpeg", "png", "gif", "webp", "svg"].includes(ext);
822
+ const isSelected = selectedFile?.fullPath === file.fullPath;
823
+
824
+ return (
825
+ <div
826
+ key={file.fullPath}
827
+ className={cls(
828
+ "rounded-lg overflow-hidden cursor-pointer transition-all duration-150 border group",
829
+ defaultBorderMixin,
830
+ "hover:shadow-md hover:-translate-y-0.5",
831
+ isSelected && "ring-2 ring-primary"
832
+ )}
833
+ onClick={() => handleSelectFile(file)}
834
+ >
835
+ {/* Thumbnail or icon */}
836
+ <div className="aspect-square relative overflow-hidden bg-surface-100 dark:bg-surface-950 flex items-center justify-center">
837
+ {isImage && file.downloadUrl ? (
838
+ <img
839
+ src={file.downloadUrl}
840
+ alt={file.name}
841
+ className="w-full h-full object-cover transition-transform duration-200 group-hover:scale-105"
842
+ loading="lazy"
843
+ />
844
+ ) : (
845
+ <FileIconComp className="text-surface-accent-400 dark:text-surface-accent-500 w-8 h-8"/>
846
+ )}
847
+
848
+ {/* Extension badge */}
849
+ {getExtension(file.name) && (
850
+ <div className="absolute bottom-1.5 right-1.5 px-1.5 py-0.5 rounded text-[9px] font-bold uppercase bg-black/50 text-white backdrop-blur-sm">
851
+ {getExtension(file.name)}
852
+ </div>
853
+ )}
854
+
855
+ {/* Hover overlay */}
856
+ <div className={cls(
857
+ "absolute inset-0 bg-black/0 group-hover:bg-black/10",
858
+ "transition-colors duration-200"
859
+ )}/>
860
+ </div>
861
+
862
+ {/* Name & size */}
863
+ <div className="p-2.5">
864
+ <Typography variant="body2" className="text-[12px] font-medium truncate text-surface-900 dark:text-white">
865
+ {file.name}
866
+ </Typography>
867
+ <Typography variant="caption" color="secondary" className="truncate block mt-0.5 text-[11px]">
868
+ {file.size !== undefined ? formatFileSize(file.size) : "—"}
869
+ </Typography>
870
+ </div>
871
+ </div>
872
+ );
873
+ })}
874
+ </div>
875
+ </div>
876
+ )}
877
+ </div>
878
+ );
879
+ };
880
+
881
+ return (
882
+ <div className="flex h-full w-full bg-white dark:bg-surface-950 overflow-hidden text-text-primary dark:text-text-primary-dark">
883
+ <ResizablePanels
884
+ orientation="horizontal"
885
+ panelSizePercent={sidebarSize}
886
+ onPanelSizeChange={setSidebarSize}
887
+ minPanelSizePx={180}
888
+ firstPanel={
889
+ <StorageSidebar
890
+ folders={folders}
891
+ currentPath={currentPath}
892
+ onNavigate={handleNavigate}
893
+ loading={loading}
894
+ />
895
+ }
896
+ secondPanel={
897
+ <div className="flex h-full w-full">
898
+ {/* Main content */}
899
+ <div className="flex-grow flex flex-col min-w-0 h-full">
900
+ {/* Toolbar */}
901
+ <div className={cls("flex items-center justify-between pr-2 border-b bg-white dark:bg-surface-950 shrink-0", defaultBorderMixin)}>
902
+ <div className="flex items-center gap-1.5 flex-grow overflow-hidden px-3 py-2">
903
+ {/* Breadcrumbs */}
904
+ {currentPath && (
905
+ <Tooltip title="Go up">
906
+ <IconButton size="small" onClick={handleNavigateUp}>
907
+ <ArrowLeftIcon size={iconSize.smallest}/>
908
+ </IconButton>
909
+ </Tooltip>
910
+ )}
911
+ <div className="flex items-center gap-0.5 overflow-x-auto no-scrollbar">
912
+ {segments.map((seg, i) => (
913
+ <React.Fragment key={seg.path}>
914
+ {i > 0 && (
915
+ <Typography variant="caption" className="text-text-disabled dark:text-text-disabled-dark mx-0.5">/</Typography>
916
+ )}
917
+ <Button
918
+ variant="text"
919
+ size="small"
920
+ className={cls(
921
+ "px-1.5 py-0.5 min-h-0 min-w-0 h-6 text-xs whitespace-nowrap normal-case font-normal",
922
+ i === segments.length - 1
923
+ ? "text-text-primary dark:text-text-primary-dark font-medium"
924
+ : "text-text-secondary dark:text-text-secondary-dark"
925
+ )}
926
+ onClick={() => handleNavigate(seg.path)}
927
+ >
928
+ {seg.label}
929
+ </Button>
930
+ </React.Fragment>
931
+ ))}
932
+ </div>
933
+
934
+ <div className="flex-1"/>
935
+
936
+ {/* FileIcon count */}
937
+ {!loading && (
938
+ <Chip size="small" className="shrink-0 text-[10px]">
939
+ {files.length} file{files.length !== 1 ? "s" : ""}
940
+ {folders.length > 0 ? `, ${folders.length} folder${folders.length !== 1 ? "s" : ""}` : ""}
941
+ </Chip>
942
+ )}
943
+ </div>
944
+
945
+ <div className="flex shrink-0 items-center justify-end gap-1.5 pr-1">
946
+
947
+ <Tooltip title="Grid view">
948
+ <IconButton
949
+ size="small"
950
+ onClick={() => setViewMode("grid")}
951
+ className={cls(viewMode === "grid" && "bg-surface-100 dark:bg-surface-950")}
952
+ >
953
+ <LayoutGridIcon size={iconSize.smallest}/>
954
+ </IconButton>
955
+ </Tooltip>
956
+ <Tooltip title="List view">
957
+ <IconButton
958
+ size="small"
959
+ onClick={() => setViewMode("list")}
960
+ className={cls(viewMode === "list" && "bg-surface-100 dark:bg-surface-950")}
961
+ >
962
+ <ListIcon size={iconSize.smallest}/>
963
+ </IconButton>
964
+ </Tooltip>
965
+
966
+ <div className="h-4 w-px bg-surface-200 dark:bg-surface-950 mx-0.5"/>
967
+
968
+ <Tooltip title="Refresh">
969
+ <IconButton size="small" onClick={handleRefresh} disabled={loading}>
970
+ <RefreshCwIcon size={iconSize.smallest}/>
971
+ </IconButton>
972
+ </Tooltip>
973
+
974
+ <Button
975
+ size="small"
976
+ color="primary"
977
+ onClick={() => setUploadDialogOpen(true)}
978
+ >
979
+ <UploadCloudIcon size={iconSize.smallest} className="mr-1"/>
980
+ Upload
981
+ </Button>
982
+ </div>
983
+ </div>
984
+
985
+ {/* FileIcon grid / list */}
986
+ <div className="flex-grow flex flex-col overflow-hidden min-h-0">
987
+ {renderContents()}
988
+ </div>
989
+
990
+ {/* Status bar */}
991
+ <div className={cls("px-4 py-1.5 border-t bg-surface-50 dark:bg-surface-900 flex items-center justify-between shrink-0", defaultBorderMixin)}>
992
+ <div className="flex items-center gap-4 text-[11px]">
993
+ <span className="text-text-disabled dark:text-text-disabled-dark font-bold uppercase tracking-tighter">
994
+ Path
995
+ </span>
996
+ <span className="font-mono text-text-secondary dark:text-text-secondary-dark">
997
+ /{currentPath || ""}
998
+ </span>
999
+ </div>
1000
+ {selectedFile && (
1001
+ <div className="text-[11px] text-text-secondary dark:text-text-secondary-dark">
1002
+ Selected: <span className="font-mono">{selectedFile.name}</span>
1003
+ </div>
1004
+ )}
1005
+ </div>
1006
+ </div>
1007
+
1008
+ {/* Preview panel */}
1009
+ {selectedFile && (
1010
+ <div className="w-80 lg:w-96 shrink-0">
1011
+ <FilePreviewPanel
1012
+ file={selectedFile}
1013
+ downloadUrl={selectedDownloadUrl}
1014
+ onClose={() => {
1015
+ setSelectedFile(null);
1016
+ setSelectedDownloadUrl(null);
1017
+ }}
1018
+ onDelete={() => handleDeleteFile(selectedFile)}
1019
+ />
1020
+ </div>
1021
+ )}
1022
+ </div>
1023
+ }
1024
+ />
1025
+
1026
+ {/* Upload Dialog */}
1027
+ <UploadDialog
1028
+ open={uploadDialogOpen}
1029
+ currentPath={currentPath}
1030
+ onClose={() => setUploadDialogOpen(false)}
1031
+ onUpload={handleUpload}
1032
+ />
1033
+ </div>
1034
+ );
1035
+ };