@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.
- package/LICENSE +114 -0
- package/README.md +159 -0
- package/dist/ApiExplorer-gMJt5JrS.js +1053 -0
- package/dist/ApiExplorer-gMJt5JrS.js.map +1 -0
- package/dist/AuthSimulationSelector-BF4rkRGp.js +118 -0
- package/dist/AuthSimulationSelector-BF4rkRGp.js.map +1 -0
- package/dist/BranchesView-DcHZtvXo.js +292 -0
- package/dist/BranchesView-DcHZtvXo.js.map +1 -0
- package/dist/CronJobsView-CijCToeK.js +456 -0
- package/dist/CronJobsView-CijCToeK.js.map +1 -0
- package/dist/JSEditor-D8nVp3Lp.js +1308 -0
- package/dist/JSEditor-D8nVp3Lp.js.map +1 -0
- package/dist/MonacoEditor-CMYEjiRf.js +161 -0
- package/dist/MonacoEditor-CMYEjiRf.js.map +1 -0
- package/dist/RLSEditor-DBH09u9v.js +1831 -0
- package/dist/RLSEditor-DBH09u9v.js.map +1 -0
- package/dist/SQLEditor-CkVx9vgr.js +1792 -0
- package/dist/SQLEditor-CkVx9vgr.js.map +1 -0
- package/dist/SchemaVisualizer-BgD5Zb77.js +1069 -0
- package/dist/SchemaVisualizer-BgD5Zb77.js.map +1 -0
- package/dist/StorageView-CTqGFhY9.js +907 -0
- package/dist/StorageView-CTqGFhY9.js.map +1 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +56 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +58 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +22 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/core/src/components/AIIcon.d.ts +16 -0
- package/dist/core/src/components/ConfirmationDialog.d.ts +9 -0
- package/dist/core/src/components/Debug/UIReferenceView.d.ts +1 -0
- package/dist/core/src/components/Debug/UIStyleGuide.d.ts +1 -0
- package/dist/core/src/components/ErrorTooltip.d.ts +2 -0
- package/dist/core/src/components/ErrorView.d.ts +21 -0
- package/dist/core/src/components/LanguageToggle.d.ts +1 -0
- package/dist/core/src/components/LoginView/LoginView.d.ts +68 -0
- package/dist/core/src/components/LoginView/index.d.ts +2 -0
- package/dist/core/src/components/NotFoundPage.d.ts +1 -0
- package/dist/core/src/components/RebaseAuth.d.ts +10 -0
- package/dist/core/src/components/RebaseLogo.d.ts +7 -0
- package/dist/core/src/components/UnsavedChangesDialog.d.ts +9 -0
- package/dist/core/src/components/UserDisplay.d.ts +7 -0
- package/dist/core/src/components/UserSelectPopover.d.ts +62 -0
- package/dist/core/src/components/UserSettingsView.d.ts +1 -0
- package/dist/core/src/components/common/index.d.ts +6 -0
- package/dist/core/src/components/common/table_height.d.ts +5 -0
- package/dist/core/src/components/common/types.d.ts +63 -0
- package/dist/core/src/components/common/useColumnsIds.d.ts +9 -0
- package/dist/core/src/components/common/useDataTableController.d.ts +45 -0
- package/dist/core/src/components/common/useDebouncedData.d.ts +9 -0
- package/dist/core/src/components/common/useScrollRestoration.d.ts +14 -0
- package/dist/core/src/components/index.d.ts +16 -0
- package/dist/core/src/contexts/AdminModeController.d.ts +4 -0
- package/dist/core/src/contexts/AnalyticsContext.d.ts +3 -0
- package/dist/core/src/contexts/AuthControllerContext.d.ts +3 -0
- package/dist/core/src/contexts/CustomizationControllerContext.d.ts +3 -0
- package/dist/core/src/contexts/DataDriverContext.d.ts +3 -0
- package/dist/core/src/contexts/DatabaseAdminContext.d.ts +3 -0
- package/dist/core/src/contexts/DialogsProvider.d.ts +4 -0
- package/dist/core/src/contexts/EffectiveRoleController.d.ts +4 -0
- package/dist/core/src/contexts/InternalUserManagementContext.d.ts +3 -0
- package/dist/core/src/contexts/ModeController.d.ts +4 -0
- package/dist/core/src/contexts/RebaseClientInstanceContext.d.ts +6 -0
- package/dist/core/src/contexts/RebaseDataContext.d.ts +3 -0
- package/dist/core/src/contexts/SnackbarProvider.d.ts +2 -0
- package/dist/core/src/contexts/StorageSourceContext.d.ts +3 -0
- package/dist/core/src/contexts/UserConfigurationPersistenceContext.d.ts +3 -0
- package/dist/core/src/contexts/index.d.ts +13 -0
- package/dist/core/src/core/PluginLifecycleManager.d.ts +17 -0
- package/dist/core/src/core/PluginProviderStack.d.ts +21 -0
- package/dist/core/src/core/Rebase.d.ts +14 -0
- package/dist/core/src/core/RebaseProps.d.ts +136 -0
- package/dist/core/src/core/RebaseRouter.d.ts +4 -0
- package/dist/core/src/core/RebaseRoutes.d.ts +17 -0
- package/dist/core/src/core/index.d.ts +4 -0
- package/dist/core/src/hooks/ApiConfigContext.d.ts +24 -0
- package/dist/core/src/hooks/data/delete.d.ts +31 -0
- package/dist/core/src/hooks/data/save.d.ts +34 -0
- package/dist/core/src/hooks/data/useCollectionFetch.d.ts +51 -0
- package/dist/core/src/hooks/data/useData.d.ts +13 -0
- package/dist/core/src/hooks/data/useDataOrder.d.ts +12 -0
- package/dist/core/src/hooks/data/useEntityFetch.d.ts +38 -0
- package/dist/core/src/hooks/data/useRelationSelector.d.ts +52 -0
- package/dist/core/src/hooks/data/useUserSelector.d.ts +31 -0
- package/dist/core/src/hooks/index.d.ts +37 -0
- package/dist/core/src/hooks/useAdminModeController.d.ts +19 -0
- package/dist/core/src/hooks/useAnalyticsController.d.ts +5 -0
- package/dist/core/src/hooks/useAuthController.d.ts +11 -0
- package/dist/core/src/hooks/useAuthSubscription.d.ts +2 -0
- package/dist/core/src/hooks/useBackendStorageSource.d.ts +30 -0
- package/dist/core/src/hooks/useBridgeRegistration.d.ts +18 -0
- package/dist/core/src/hooks/useBrowserTitleAndIcon.d.ts +6 -0
- package/dist/core/src/hooks/useBuildAdminModeController.d.ts +6 -0
- package/dist/core/src/hooks/useBuildEffectiveRoleController.d.ts +8 -0
- package/dist/core/src/hooks/useBuildLocalConfigurationPersistence.d.ts +2 -0
- package/dist/core/src/hooks/useBuildModeController.d.ts +6 -0
- package/dist/core/src/hooks/useClipboard.d.ts +57 -0
- package/dist/core/src/hooks/useCollapsedGroups.d.ts +12 -0
- package/dist/core/src/hooks/useCustomizationController.d.ts +11 -0
- package/dist/core/src/hooks/useDialogsController.d.ts +11 -0
- package/dist/core/src/hooks/useEffectiveRoleController.d.ts +7 -0
- package/dist/core/src/hooks/useInternalUserManagementController.d.ts +12 -0
- package/dist/core/src/hooks/useLargeLayout.d.ts +1 -0
- package/dist/core/src/hooks/useModeController.d.ts +19 -0
- package/dist/core/src/hooks/usePermissions.d.ts +12 -0
- package/dist/core/src/hooks/useRebaseClient.d.ts +5 -0
- package/dist/core/src/hooks/useRebaseContext.d.ts +11 -0
- package/dist/core/src/hooks/useRebaseRegistry.d.ts +34 -0
- package/dist/core/src/hooks/useSlot.d.ts +18 -0
- package/dist/core/src/hooks/useSnackbarController.d.ts +20 -0
- package/dist/core/src/hooks/useStorageSource.d.ts +7 -0
- package/dist/core/src/hooks/useStudioBridge.d.ts +91 -0
- package/dist/core/src/hooks/useTranslation.d.ts +17 -0
- package/dist/core/src/hooks/useUnsavedChangesDialog.d.ts +12 -0
- package/dist/core/src/hooks/useUserConfigurationPersistence.d.ts +8 -0
- package/dist/core/src/hooks/useValidateAuthenticator.d.ts +21 -0
- package/dist/core/src/i18n/RebaseI18nProvider.d.ts +33 -0
- package/dist/core/src/index.d.ts +15 -0
- package/dist/core/src/internal/common.d.ts +3 -0
- package/dist/core/src/internal/useRestoreScroll.d.ts +6 -0
- package/dist/core/src/locales/de.d.ts +2 -0
- package/dist/core/src/locales/en.d.ts +10 -0
- package/dist/core/src/locales/es.d.ts +10 -0
- package/dist/core/src/locales/fr.d.ts +2 -0
- package/dist/core/src/locales/hi.d.ts +2 -0
- package/dist/core/src/locales/it.d.ts +2 -0
- package/dist/core/src/locales/pt.d.ts +7 -0
- package/dist/core/src/util/constants.d.ts +1 -0
- package/dist/core/src/util/createFormexStub.d.ts +2 -0
- package/dist/core/src/util/entity_cache.d.ts +27 -0
- package/dist/core/src/util/enums.d.ts +5 -0
- package/dist/core/src/util/icon_list.d.ts +5 -0
- package/dist/core/src/util/icon_synonyms.d.ts +1 -0
- package/dist/core/src/util/icons.d.ts +20 -0
- package/dist/core/src/util/index.d.ts +10 -0
- package/dist/core/src/util/previews.d.ts +4 -0
- package/dist/core/src/util/useStorageUploadController.d.ts +38 -0
- package/dist/core/src/util/useTraceUpdate.d.ts +2 -0
- package/dist/formex/src/Field.d.ts +52 -0
- package/dist/formex/src/Formex.d.ts +7 -0
- package/dist/formex/src/index.d.ts +5 -0
- package/dist/formex/src/types.d.ts +40 -0
- package/dist/formex/src/useCreateFormex.d.ts +14 -0
- package/dist/formex/src/utils.d.ts +16 -0
- package/dist/index.es.js +726 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.umd.js +9647 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/studio/src/components/ApiExplorer/ApiExplorer.d.ts +9 -0
- package/dist/studio/src/components/ApiExplorer/EndpointDetail.d.ts +9 -0
- package/dist/studio/src/components/ApiExplorer/TryItPanel.d.ts +15 -0
- package/dist/studio/src/components/ApiExplorer/parseSpec.d.ts +16 -0
- package/dist/studio/src/components/ApiExplorer/types.d.ts +90 -0
- package/dist/studio/src/components/AuthSimulationSelector.d.ts +11 -0
- package/dist/studio/src/components/Branches/BranchesView.d.ts +1 -0
- package/dist/studio/src/components/CronJobs/CronJobsView.d.ts +1 -0
- package/dist/studio/src/components/JSEditor/JSEditor.d.ts +1 -0
- package/dist/studio/src/components/JSEditor/JSEditorSidebar.d.ts +21 -0
- package/dist/studio/src/components/JSEditor/JSMonacoEditor.d.ts +18 -0
- package/dist/studio/src/components/RLSEditor/PolicyEditor.d.ts +9 -0
- package/dist/studio/src/components/RLSEditor/RLSEditor.d.ts +19 -0
- package/dist/studio/src/components/RLSEditor/index.d.ts +1 -0
- package/dist/studio/src/components/RebaseStudio.d.ts +2 -0
- package/dist/studio/src/components/SQLEditor/ExplainVisualizer.d.ts +24 -0
- package/dist/studio/src/components/SQLEditor/MonacoEditor.d.ts +17 -0
- package/dist/studio/src/components/SQLEditor/SQLEditor.d.ts +11 -0
- package/dist/studio/src/components/SQLEditor/SQLEditorSidebar.d.ts +21 -0
- package/dist/studio/src/components/SQLEditor/SchemaBrowser.d.ts +8 -0
- package/dist/studio/src/components/SchemaVisualizer/RelationEdge.d.ts +3 -0
- package/dist/studio/src/components/SchemaVisualizer/SchemaVisualizer.d.ts +2 -0
- package/dist/studio/src/components/SchemaVisualizer/TableNode.d.ts +3 -0
- package/dist/studio/src/components/SchemaVisualizer/index.d.ts +5 -0
- package/dist/studio/src/components/SchemaVisualizer/schema-visualizer.utils.d.ts +42 -0
- package/dist/studio/src/components/SchemaVisualizer/useSchemaGraph.d.ts +37 -0
- package/dist/studio/src/components/StorageView/StorageView.d.ts +1 -0
- package/dist/studio/src/components/StudioHomePage.d.ts +9 -0
- package/dist/studio/src/index.d.ts +4 -0
- package/dist/studio/src/utils/entities.d.ts +0 -0
- package/dist/studio/src/utils/pgColumnToProperty.d.ts +6 -0
- package/dist/studio/src/utils/sql_utils.d.ts +52 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +119 -0
- package/dist/types/src/controllers/client.d.ts +170 -0
- package/dist/types/src/controllers/collection_registry.d.ts +45 -0
- package/dist/types/src/controllers/customization_controller.d.ts +60 -0
- package/dist/types/src/controllers/data.d.ts +168 -0
- package/dist/types/src/controllers/data_driver.d.ts +160 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/email.d.ts +34 -0
- package/dist/types/src/controllers/index.d.ts +18 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +54 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +90 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +171 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +105 -0
- package/dist/types/src/types/backend.d.ts +536 -0
- package/dist/types/src/types/builders.d.ts +15 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +856 -0
- package/dist/types/src/types/cron.d.ts +102 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +10 -0
- package/dist/types/src/types/entity_views.d.ts +61 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/index.d.ts +23 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +279 -0
- package/dist/types/src/types/properties.d.ts +1176 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +252 -0
- package/dist/types/src/types/translations.d.ts +870 -0
- package/dist/types/src/types/user_management_delegate.d.ts +121 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/dist/ui/src/components/Alert.d.ts +12 -0
- package/dist/ui/src/components/Autocomplete.d.ts +21 -0
- package/dist/ui/src/components/Avatar.d.ts +11 -0
- package/dist/ui/src/components/Badge.d.ts +8 -0
- package/dist/ui/src/components/BooleanSwitch.d.ts +14 -0
- package/dist/ui/src/components/BooleanSwitchWithLabel.d.ts +17 -0
- package/dist/ui/src/components/Button.d.ts +14 -0
- package/dist/ui/src/components/Card.d.ts +9 -0
- package/dist/ui/src/components/CenteredView.d.ts +9 -0
- package/dist/ui/src/components/Checkbox.d.ts +13 -0
- package/dist/ui/src/components/Chip.d.ts +26 -0
- package/dist/ui/src/components/CircularProgress.d.ts +5 -0
- package/dist/ui/src/components/CircularProgressCenter.d.ts +11 -0
- package/dist/ui/src/components/Collapse.d.ts +9 -0
- package/dist/ui/src/components/ColorPicker.d.ts +30 -0
- package/dist/ui/src/components/Container.d.ts +8 -0
- package/dist/ui/src/components/DateTimeField.d.ts +24 -0
- package/dist/ui/src/components/DebouncedTextField.d.ts +2 -0
- package/dist/ui/src/components/Dialog.d.ts +39 -0
- package/dist/ui/src/components/DialogActions.d.ts +7 -0
- package/dist/ui/src/components/DialogContent.d.ts +7 -0
- package/dist/ui/src/components/DialogTitle.d.ts +10 -0
- package/dist/ui/src/components/ErrorBoundary.d.ts +11 -0
- package/dist/ui/src/components/ExpandablePanel.d.ts +12 -0
- package/dist/ui/src/components/FileUpload.d.ts +23 -0
- package/dist/ui/src/components/IconButton.d.ts +12 -0
- package/dist/ui/src/components/InfoLabel.d.ts +5 -0
- package/dist/ui/src/components/InputLabel.d.ts +11 -0
- package/dist/ui/src/components/Label.d.ts +7 -0
- package/dist/ui/src/components/LoadingButton.d.ts +7 -0
- package/dist/ui/src/components/Markdown.d.ts +10 -0
- package/dist/ui/src/components/Menu.d.ts +23 -0
- package/dist/ui/src/components/Menubar.d.ts +80 -0
- package/dist/ui/src/components/MultiSelect.d.ts +48 -0
- package/dist/ui/src/components/Paper.d.ts +6 -0
- package/dist/ui/src/components/Popover.d.ts +24 -0
- package/dist/ui/src/components/RadioGroup.d.ts +28 -0
- package/dist/ui/src/components/ResizablePanels.d.ts +18 -0
- package/dist/ui/src/components/SearchBar.d.ts +22 -0
- package/dist/ui/src/components/Select.d.ts +43 -0
- package/dist/ui/src/components/Separator.d.ts +5 -0
- package/dist/ui/src/components/Sheet.d.ts +22 -0
- package/dist/ui/src/components/Skeleton.d.ts +6 -0
- package/dist/ui/src/components/Slider.d.ts +21 -0
- package/dist/ui/src/components/Table.d.ts +34 -0
- package/dist/ui/src/components/Tabs.d.ts +19 -0
- package/dist/ui/src/components/TextField.d.ts +58 -0
- package/dist/ui/src/components/TextareaAutosize.d.ts +43 -0
- package/dist/ui/src/components/ToggleButtonGroup.d.ts +30 -0
- package/dist/ui/src/components/Tooltip.d.ts +19 -0
- package/dist/ui/src/components/Typography.d.ts +36 -0
- package/dist/ui/src/components/VirtualTable/VirtualTable.d.ts +11 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableCell.d.ts +21 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableHeader.d.ts +29 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableHeaderRow.d.ts +2 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableProps.d.ts +243 -0
- package/dist/ui/src/components/VirtualTable/VirtualTableRow.d.ts +3 -0
- package/dist/ui/src/components/VirtualTable/index.d.ts +3 -0
- package/dist/ui/src/components/VirtualTable/types.d.ts +38 -0
- package/dist/ui/src/components/common/SelectInputLabel.d.ts +5 -0
- package/dist/ui/src/components/index.d.ts +53 -0
- package/dist/ui/src/hooks/PortalContainerContext.d.ts +31 -0
- package/dist/ui/src/hooks/index.d.ts +6 -0
- package/dist/ui/src/hooks/useDebounceCallback.d.ts +1 -0
- package/dist/ui/src/hooks/useDebounceValue.d.ts +1 -0
- package/dist/ui/src/hooks/useDebouncedCallback.d.ts +1 -0
- package/dist/ui/src/hooks/useInjectStyles.d.ts +7 -0
- package/dist/ui/src/hooks/useOutsideAlerter.d.ts +5 -0
- package/dist/ui/src/icons/GitHubIcon.d.ts +2 -0
- package/dist/ui/src/icons/HandleIcon.d.ts +1 -0
- package/dist/ui/src/icons/Icon.d.ts +20 -0
- package/dist/ui/src/icons/cool_icon_keys.d.ts +1 -0
- package/dist/ui/src/icons/icon_keys.d.ts +1 -0
- package/dist/ui/src/icons/index.d.ts +6 -0
- package/dist/ui/src/index.d.ts +5 -0
- package/dist/ui/src/styles.d.ts +12 -0
- package/dist/ui/src/util/chip_colors.d.ts +4 -0
- package/dist/ui/src/util/cls.d.ts +2 -0
- package/dist/ui/src/util/debounce.d.ts +10 -0
- package/dist/ui/src/util/hash.d.ts +1 -0
- package/dist/ui/src/util/index.d.ts +4 -0
- package/dist/ui/src/util/key_to_icon_component.d.ts +1 -0
- package/package.json +84 -0
- package/src/components/ApiExplorer/ApiExplorer.tsx +290 -0
- package/src/components/ApiExplorer/EndpointDetail.tsx +271 -0
- package/src/components/ApiExplorer/TryItPanel.tsx +510 -0
- package/src/components/ApiExplorer/parseSpec.ts +104 -0
- package/src/components/ApiExplorer/types.ts +84 -0
- package/src/components/AuthSimulationSelector.tsx +77 -0
- package/src/components/Branches/BranchesView.tsx +370 -0
- package/src/components/CronJobs/CronJobsView.tsx +346 -0
- package/src/components/JSEditor/JSEditor.tsx +1033 -0
- package/src/components/JSEditor/JSEditorSidebar.tsx +340 -0
- package/src/components/JSEditor/JSMonacoEditor.tsx +390 -0
- package/src/components/RLSEditor/PolicyEditor.tsx +444 -0
- package/src/components/RLSEditor/RLSEditor.tsx +692 -0
- package/src/components/RLSEditor/index.ts +1 -0
- package/src/components/RebaseStudio.tsx +121 -0
- package/src/components/SQLEditor/ExplainVisualizer.tsx +128 -0
- package/src/components/SQLEditor/MonacoEditor.tsx +203 -0
- package/src/components/SQLEditor/SQLEditor.tsx +1419 -0
- package/src/components/SQLEditor/SQLEditorSidebar.tsx +174 -0
- package/src/components/SQLEditor/SchemaBrowser.tsx +158 -0
- package/src/components/SchemaVisualizer/RelationEdge.tsx +102 -0
- package/src/components/SchemaVisualizer/SchemaVisualizer.tsx +665 -0
- package/src/components/SchemaVisualizer/TableNode.tsx +257 -0
- package/src/components/SchemaVisualizer/index.ts +5 -0
- package/src/components/SchemaVisualizer/schema-visualizer.utils.ts +140 -0
- package/src/components/SchemaVisualizer/useSchemaGraph.ts +397 -0
- package/src/components/StorageView/StorageView.tsx +1035 -0
- package/src/components/StudioHomePage.tsx +357 -0
- package/src/index.ts +31 -0
- package/src/utils/entities.ts +2 -0
- package/src/utils/pgColumnToProperty.test.ts +401 -0
- package/src/utils/pgColumnToProperty.ts +275 -0
- package/src/utils/sql_utils.test.ts +265 -0
- package/src/utils/sql_utils.ts +291 -0
- 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 "{file.name}"?
|
|
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
|
+
};
|