@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,1033 @@
|
|
|
1
|
+
|
|
2
|
+
import React, { useState, useEffect, useCallback, useRef, useMemo } from "react";
|
|
3
|
+
import { Highlight, themes } from "prism-react-renderer";
|
|
4
|
+
import { toSnakeCase } from "@rebasepro/utils";
|
|
5
|
+
import { Button, Typography, CircularProgress, cls, IconButton, Dialog, DialogContent, DialogActions, DialogTitle, TextField, Tooltip, defaultBorderMixin, ResizablePanels, Chip, Menu, MenuItem, Tabs, Tab, VirtualTable, VirtualTableColumn, CellRendererParams , iconSize } from "@rebasepro/ui";
|
|
6
|
+
import { TerminalIcon, XIcon, PlusIcon, SaveIcon, DownloadIcon, PlayIcon, MoreVerticalIcon, PencilIcon, MenuIcon } from "lucide-react";
|
|
7
|
+
import { useStudioUrlController, useStudioCollectionRegistry, useStudioSideEntityController } from "@rebasepro/core";
|
|
8
|
+
import { useRebaseContext, useRebaseClient, useSnackbarController, useApiConfig, useTranslation, useModeController, ErrorView, SelectableUser, IconForView } from "@rebasepro/core";
|
|
9
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
10
|
+
import { createRebaseClient } from "@rebasepro/client";
|
|
11
|
+
import { JSMonacoEditor } from "./JSMonacoEditor";
|
|
12
|
+
import { JSEditorSidebar, JSSnippet } from "./JSEditorSidebar";
|
|
13
|
+
import { AuthSimulationSelector } from "../AuthSimulationSelector";
|
|
14
|
+
|
|
15
|
+
// ─── Types ───────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
interface ConsoleEntry {
|
|
18
|
+
type: "log" | "warn" | "error" | "info";
|
|
19
|
+
args: any[];
|
|
20
|
+
timestamp: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface ExecutionResult {
|
|
24
|
+
value: any;
|
|
25
|
+
console: ConsoleEntry[];
|
|
26
|
+
duration: number;
|
|
27
|
+
error?: string;
|
|
28
|
+
timestamp: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface EditorTab {
|
|
32
|
+
id: string;
|
|
33
|
+
name: string;
|
|
34
|
+
code: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// SelectedUser is now SelectableUser from @rebasepro/core
|
|
38
|
+
|
|
39
|
+
// ─── Constants ───────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
const STORAGE_PREFIX = "rebase_js_";
|
|
42
|
+
const MAX_HISTORY = 50;
|
|
43
|
+
|
|
44
|
+
const DEFAULT_CODE = `// Available: client (RebaseClient)
|
|
45
|
+
// Press Cmd+Enter (Ctrl+Enter) to run
|
|
46
|
+
//
|
|
47
|
+
// Examples:
|
|
48
|
+
// const result = await client.data.collection("your_collection").find({ limit: 10 });
|
|
49
|
+
// const users = await client.admin.listUsers();
|
|
50
|
+
// const session = client.auth.getSession();
|
|
51
|
+
|
|
52
|
+
const result = await client.data.collection("your_collection").find({ limit: 10 });
|
|
53
|
+
return result;
|
|
54
|
+
`;
|
|
55
|
+
|
|
56
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
function loadFromStorage<T>(key: string, fallback: T): T {
|
|
59
|
+
try {
|
|
60
|
+
const raw = localStorage.getItem(STORAGE_PREFIX + key);
|
|
61
|
+
return raw ? JSON.parse(raw) : fallback;
|
|
62
|
+
} catch {
|
|
63
|
+
return fallback;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function saveToStorage<T>(key: string, value: T) {
|
|
68
|
+
try {
|
|
69
|
+
localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value));
|
|
70
|
+
} catch { /* quota */ }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function formatJSON(value: any): string {
|
|
74
|
+
try {
|
|
75
|
+
return JSON.stringify(value, null, 2);
|
|
76
|
+
} catch {
|
|
77
|
+
return String(value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── Helpers: Collection matching for JS results ────────────────────
|
|
82
|
+
|
|
83
|
+
interface MatchedJSCollection {
|
|
84
|
+
collectionSlug: string;
|
|
85
|
+
collection: EntityCollection;
|
|
86
|
+
pkColumn: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Given the raw SDK result, try to detect which collections are present.
|
|
91
|
+
* JS SDK results typically come back as `{ data: [{ id, values }] }` or plain arrays.
|
|
92
|
+
* The heuristic: if the executed code contains `collection("<slug>")` or `client.data.<slug>`,
|
|
93
|
+
* and the result has rows with an "id" column, we match those slugs.
|
|
94
|
+
*/
|
|
95
|
+
function detectCollectionsInResult(
|
|
96
|
+
code: string,
|
|
97
|
+
resultValue: any,
|
|
98
|
+
collections: EntityCollection[]
|
|
99
|
+
): MatchedJSCollection[] {
|
|
100
|
+
if (!resultValue || !collections?.length) return [];
|
|
101
|
+
|
|
102
|
+
// Extract collection slugs mentioned in the code
|
|
103
|
+
const mentionedSlugs = new Set<string>();
|
|
104
|
+
|
|
105
|
+
// Match: collection("slug") or collection('slug')
|
|
106
|
+
const collectionCallRegex = /\.collection\(["']([^"']+)["']\)/g;
|
|
107
|
+
let m: RegExpExecArray | null;
|
|
108
|
+
while ((m = collectionCallRegex.exec(code)) !== null) {
|
|
109
|
+
mentionedSlugs.add(m[1]);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Match: client.data.<slug>. (dot-access pattern)
|
|
113
|
+
const dotAccessRegex = /client\.data\.([a-zA-Z_][a-zA-Z0-9_]*)\./g;
|
|
114
|
+
while ((m = dotAccessRegex.exec(code)) !== null) {
|
|
115
|
+
if (!["collection", "find", "findById", "create", "update", "delete"].includes(m[1])) {
|
|
116
|
+
mentionedSlugs.add(m[1]);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (mentionedSlugs.size === 0) return [];
|
|
121
|
+
|
|
122
|
+
// Check if result has rows with an "id" field
|
|
123
|
+
let rows: any[] = [];
|
|
124
|
+
if (resultValue?.data && Array.isArray(resultValue.data)) {
|
|
125
|
+
rows = resultValue.data;
|
|
126
|
+
} else if (Array.isArray(resultValue)) {
|
|
127
|
+
rows = resultValue;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const hasId = rows.length > 0 && rows.some(r => r?.id != null);
|
|
131
|
+
if (!hasId) return [];
|
|
132
|
+
|
|
133
|
+
const matched: MatchedJSCollection[] = [];
|
|
134
|
+
for (const slug of mentionedSlugs) {
|
|
135
|
+
const normalised = toSnakeCase(slug);
|
|
136
|
+
const col = collections.find(c => {
|
|
137
|
+
const tableName = (c as any).table || toSnakeCase(c.slug);
|
|
138
|
+
return c.slug === slug || tableName === normalised || toSnakeCase(c.slug) === normalised;
|
|
139
|
+
});
|
|
140
|
+
if (col) {
|
|
141
|
+
matched.push({
|
|
142
|
+
collectionSlug: col.slug,
|
|
143
|
+
collection: col,
|
|
144
|
+
pkColumn: "id"
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return matched;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ─── Main Component ─────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
export function JSEditor() {
|
|
155
|
+
// Contexts
|
|
156
|
+
const rebaseContext = useRebaseContext();
|
|
157
|
+
const rebaseClient = useRebaseClient();
|
|
158
|
+
const apiConfig = useApiConfig();
|
|
159
|
+
const snackbar = useSnackbarController();
|
|
160
|
+
const collectionRegistry = useStudioCollectionRegistry();
|
|
161
|
+
const sideEntityController = useStudioSideEntityController();
|
|
162
|
+
const { t } = useTranslation();
|
|
163
|
+
|
|
164
|
+
// User management for the "Run as" picker
|
|
165
|
+
const userManagement = rebaseContext.userManagement;
|
|
166
|
+
const currentUser = rebaseContext.authController?.user;
|
|
167
|
+
|
|
168
|
+
// State
|
|
169
|
+
const [tabs, setTabs] = useState<EditorTab[]>(() =>
|
|
170
|
+
loadFromStorage("tabs", [{ id: "1",
|
|
171
|
+
name: "Script 1",
|
|
172
|
+
code: DEFAULT_CODE }])
|
|
173
|
+
);
|
|
174
|
+
const [activeTabId, setActiveTabId] = useState<string>(() =>
|
|
175
|
+
loadFromStorage("activeTab", "1")
|
|
176
|
+
);
|
|
177
|
+
const [result, setResult] = useState<ExecutionResult | null>(null);
|
|
178
|
+
const [isRunning, setIsRunning] = useState(false);
|
|
179
|
+
const [snippets, setSnippets] = useState<JSSnippet[]>(() =>
|
|
180
|
+
loadFromStorage("snippets", [])
|
|
181
|
+
);
|
|
182
|
+
const [history, setHistory] = useState<string[]>(() =>
|
|
183
|
+
loadFromStorage("history", [])
|
|
184
|
+
);
|
|
185
|
+
const [showSaveDialog, setShowSaveDialog] = useState(false);
|
|
186
|
+
const [snippetName, setSnippetName] = useState("");
|
|
187
|
+
const [resultView, setResultView] = useState<"json" | "table" | "console">("json");
|
|
188
|
+
|
|
189
|
+
// "Run as" user state — null means "self" (current admin)
|
|
190
|
+
const [selectedUser, setSelectedUser] = useState<SelectableUser | null>(null);
|
|
191
|
+
const [authMode, setAuthMode] = useState<"jwt" | "none">("jwt");
|
|
192
|
+
|
|
193
|
+
const [sidebarSize, setSidebarSize] = useState<number>(() => {
|
|
194
|
+
try {
|
|
195
|
+
const stored = localStorage.getItem(STORAGE_PREFIX + "sidebar_size");
|
|
196
|
+
return stored ? Number(stored) : 18;
|
|
197
|
+
} catch { return 18; }
|
|
198
|
+
});
|
|
199
|
+
const [editorHeight, setEditorHeight] = useState<number>(() => {
|
|
200
|
+
try {
|
|
201
|
+
const stored = localStorage.getItem(STORAGE_PREFIX + "editor_height");
|
|
202
|
+
return stored ? Number(stored) : 55;
|
|
203
|
+
} catch { return 55; }
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
// Derived
|
|
207
|
+
const activeTab = tabs.find(t => t.id === activeTabId) ?? tabs[0];
|
|
208
|
+
|
|
209
|
+
// Collection info for the sidebar and Monaco
|
|
210
|
+
const collectionInfos = useMemo(() => {
|
|
211
|
+
const collections = collectionRegistry?.collections ?? [];
|
|
212
|
+
return collections.map(col => ({
|
|
213
|
+
slug: col.slug,
|
|
214
|
+
name: col.name,
|
|
215
|
+
properties: Object.keys(col.properties ?? {})
|
|
216
|
+
}));
|
|
217
|
+
}, [collectionRegistry?.collections]);
|
|
218
|
+
|
|
219
|
+
const collectionSlugs = useMemo(() => collectionInfos.map(c => c.slug), [collectionInfos]);
|
|
220
|
+
|
|
221
|
+
// Users list for the picker — mapped to SelectableUser shape
|
|
222
|
+
const users = useMemo((): SelectableUser[] => {
|
|
223
|
+
const managed = (userManagement?.users ?? []).map(u => ({
|
|
224
|
+
uid: u.uid,
|
|
225
|
+
displayName: u.displayName,
|
|
226
|
+
email: u.email,
|
|
227
|
+
photoURL: u.photoURL,
|
|
228
|
+
roles: u.roles
|
|
229
|
+
}));
|
|
230
|
+
// Ensure the current user is in the list
|
|
231
|
+
if (currentUser && !managed.some(u => u.uid === currentUser.uid)) {
|
|
232
|
+
managed.unshift({
|
|
233
|
+
uid: currentUser.uid,
|
|
234
|
+
displayName: currentUser.displayName,
|
|
235
|
+
email: currentUser.email,
|
|
236
|
+
photoURL: currentUser.photoURL,
|
|
237
|
+
roles: currentUser.roles
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return managed;
|
|
241
|
+
}, [userManagement?.users, currentUser]);
|
|
242
|
+
|
|
243
|
+
// Current user as SelectableUser for the popover
|
|
244
|
+
const currentSelectableUser = useMemo((): SelectableUser | null => {
|
|
245
|
+
if (!currentUser) return null;
|
|
246
|
+
return {
|
|
247
|
+
uid: currentUser.uid,
|
|
248
|
+
displayName: currentUser.displayName,
|
|
249
|
+
email: currentUser.email,
|
|
250
|
+
photoURL: currentUser.photoURL,
|
|
251
|
+
roles: currentUser.roles
|
|
252
|
+
};
|
|
253
|
+
}, [currentUser]);
|
|
254
|
+
|
|
255
|
+
// ─── Persistence ─────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
useEffect(() => { saveToStorage("tabs", tabs); }, [tabs]);
|
|
258
|
+
useEffect(() => { saveToStorage("activeTab", activeTabId); }, [activeTabId]);
|
|
259
|
+
useEffect(() => { saveToStorage("snippets", snippets); }, [snippets]);
|
|
260
|
+
useEffect(() => { saveToStorage("history", history); }, [history]);
|
|
261
|
+
|
|
262
|
+
useEffect(() => {
|
|
263
|
+
try { localStorage.setItem(STORAGE_PREFIX + "sidebar_size", sidebarSize.toString()); } catch { /* ignore */ }
|
|
264
|
+
}, [sidebarSize]);
|
|
265
|
+
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
try { localStorage.setItem(STORAGE_PREFIX + "editor_height", editorHeight.toString()); } catch { /* ignore */ }
|
|
268
|
+
}, [editorHeight]);
|
|
269
|
+
|
|
270
|
+
// ─── Tab management ──────────────────────────────────────────
|
|
271
|
+
|
|
272
|
+
const updateActiveCode = useCallback((code: string | undefined) => {
|
|
273
|
+
setTabs(prev => prev.map(t => t.id === activeTabId ? { ...t,
|
|
274
|
+
code: code ?? "" } : t));
|
|
275
|
+
}, [activeTabId]);
|
|
276
|
+
|
|
277
|
+
const addTab = useCallback(() => {
|
|
278
|
+
const id = String(Date.now());
|
|
279
|
+
const newTab: EditorTab = { id,
|
|
280
|
+
name: `Script ${tabs.length + 1}`,
|
|
281
|
+
code: DEFAULT_CODE };
|
|
282
|
+
setTabs(prev => [...prev, newTab]);
|
|
283
|
+
setActiveTabId(id);
|
|
284
|
+
}, [tabs.length]);
|
|
285
|
+
|
|
286
|
+
const closeTab = useCallback((tabId: string) => {
|
|
287
|
+
setTabs(prev => {
|
|
288
|
+
const filtered = prev.filter(t => t.id !== tabId);
|
|
289
|
+
if (filtered.length === 0) {
|
|
290
|
+
const fresh = { id: String(Date.now()),
|
|
291
|
+
name: "Script 1",
|
|
292
|
+
code: DEFAULT_CODE };
|
|
293
|
+
setActiveTabId(fresh.id);
|
|
294
|
+
return [fresh];
|
|
295
|
+
}
|
|
296
|
+
if (activeTabId === tabId) {
|
|
297
|
+
setActiveTabId(filtered[filtered.length - 1].id);
|
|
298
|
+
}
|
|
299
|
+
return filtered;
|
|
300
|
+
});
|
|
301
|
+
}, [activeTabId]);
|
|
302
|
+
|
|
303
|
+
// ─── Create an authenticated client for execution ────────────
|
|
304
|
+
|
|
305
|
+
const buildClient = useCallback(async () => {
|
|
306
|
+
const apiUrl = apiConfig?.apiUrl;
|
|
307
|
+
const getAuthToken = apiConfig?.getAuthToken;
|
|
308
|
+
|
|
309
|
+
if (!apiUrl) {
|
|
310
|
+
throw new Error("API URL not configured. Make sure apiUrl is set.");
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
if (authMode === "none") {
|
|
314
|
+
const client = createRebaseClient({
|
|
315
|
+
baseUrl: apiUrl,
|
|
316
|
+
token: undefined
|
|
317
|
+
});
|
|
318
|
+
return { client, isScoped: true };
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// If not running as another user safely reuse the application's global client.
|
|
322
|
+
if (!selectedUser || (currentUser && selectedUser.uid === currentUser.uid)) {
|
|
323
|
+
if (!rebaseClient) {
|
|
324
|
+
throw new Error("Application client is not initialized.");
|
|
325
|
+
}
|
|
326
|
+
return { client: rebaseClient, isScoped: false };
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Get the current auth token
|
|
330
|
+
let token: string | undefined;
|
|
331
|
+
if (getAuthToken) {
|
|
332
|
+
token = (await getAuthToken()) ?? undefined;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
if (!token) {
|
|
336
|
+
throw new Error("No auth token available. Please sign in first.");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Create a fresh 'scoped' client. This enables us to attach custom auth later for this specific execution
|
|
340
|
+
// without mutating the global client.
|
|
341
|
+
const client = createRebaseClient({
|
|
342
|
+
baseUrl: apiUrl,
|
|
343
|
+
token
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
return { client,
|
|
347
|
+
isScoped: true };
|
|
348
|
+
}, [apiConfig, rebaseClient, selectedUser, currentUser, authMode]);
|
|
349
|
+
|
|
350
|
+
// ─── Execution engine ────────────────────────────────────────
|
|
351
|
+
|
|
352
|
+
const executeCode = useCallback(async (codeOverride?: string) => {
|
|
353
|
+
const code = codeOverride ?? activeTab?.code ?? "";
|
|
354
|
+
if (!code.trim()) return;
|
|
355
|
+
|
|
356
|
+
setIsRunning(true);
|
|
357
|
+
setResult(null);
|
|
358
|
+
|
|
359
|
+
// Add to history
|
|
360
|
+
setHistory(prev => {
|
|
361
|
+
const deduped = prev.filter(h => h !== code);
|
|
362
|
+
return [...deduped, code].slice(-MAX_HISTORY);
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
const consoleEntries: ConsoleEntry[] = [];
|
|
366
|
+
const startTime = performance.now();
|
|
367
|
+
let scopedClientToCleanUp: any = null;
|
|
368
|
+
|
|
369
|
+
// Capture console methods
|
|
370
|
+
const originalConsole = {
|
|
371
|
+
log: console.log,
|
|
372
|
+
warn: console.warn,
|
|
373
|
+
error: console.error,
|
|
374
|
+
info: console.info
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
const captureConsole = (type: ConsoleEntry["type"]) => (...args: any[]) => {
|
|
378
|
+
consoleEntries.push({ type,
|
|
379
|
+
args,
|
|
380
|
+
timestamp: Date.now() });
|
|
381
|
+
originalConsole[type](...args);
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
try {
|
|
385
|
+
console.log = captureConsole("log");
|
|
386
|
+
console.warn = captureConsole("warn");
|
|
387
|
+
console.error = captureConsole("error");
|
|
388
|
+
console.info = captureConsole("info");
|
|
389
|
+
|
|
390
|
+
// Build an authenticated client
|
|
391
|
+
const { client, isScoped } = await buildClient();
|
|
392
|
+
if (isScoped) {
|
|
393
|
+
scopedClientToCleanUp = client;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// Build context object with useful info about selected user
|
|
397
|
+
const context = {
|
|
398
|
+
user: selectedUser ?? (currentUser ? {
|
|
399
|
+
uid: currentUser.uid,
|
|
400
|
+
displayName: currentUser.displayName,
|
|
401
|
+
email: currentUser.email,
|
|
402
|
+
roles: currentUser.roles
|
|
403
|
+
} : null),
|
|
404
|
+
collections: collectionInfos
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// Create async function with `client` and `context` injected
|
|
408
|
+
|
|
409
|
+
const AsyncFunction = Object.getPrototypeOf(async function () { }).constructor;
|
|
410
|
+
const fn = new AsyncFunction("client", "context", code);
|
|
411
|
+
|
|
412
|
+
const value = await fn(client, context);
|
|
413
|
+
const duration = performance.now() - startTime;
|
|
414
|
+
|
|
415
|
+
setResult({
|
|
416
|
+
value,
|
|
417
|
+
console: consoleEntries,
|
|
418
|
+
duration,
|
|
419
|
+
timestamp: Date.now()
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// Auto-detect best view
|
|
423
|
+
if (value?.data && Array.isArray(value.data)) {
|
|
424
|
+
setResultView("table");
|
|
425
|
+
} else if (consoleEntries.length > 0 && value === undefined) {
|
|
426
|
+
setResultView("console");
|
|
427
|
+
} else {
|
|
428
|
+
setResultView("json");
|
|
429
|
+
}
|
|
430
|
+
} catch (err: any) {
|
|
431
|
+
const duration = performance.now() - startTime;
|
|
432
|
+
setResult({
|
|
433
|
+
value: undefined,
|
|
434
|
+
console: consoleEntries,
|
|
435
|
+
duration,
|
|
436
|
+
error: err?.message || String(err),
|
|
437
|
+
timestamp: Date.now()
|
|
438
|
+
});
|
|
439
|
+
setResultView("json");
|
|
440
|
+
} finally {
|
|
441
|
+
console.log = originalConsole.log;
|
|
442
|
+
console.warn = originalConsole.warn;
|
|
443
|
+
console.error = originalConsole.error;
|
|
444
|
+
console.info = originalConsole.info;
|
|
445
|
+
setIsRunning(false);
|
|
446
|
+
|
|
447
|
+
if (scopedClientToCleanUp) {
|
|
448
|
+
// Ensure we disconnect WebSockets to prevent leaking connections
|
|
449
|
+
scopedClientToCleanUp.ws?.disconnect();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
}, [activeTab?.code, buildClient, selectedUser, currentUser, collectionInfos]);
|
|
453
|
+
|
|
454
|
+
// ─── Snippet management ──────────────────────────────────────
|
|
455
|
+
|
|
456
|
+
const saveSnippet = useCallback(() => {
|
|
457
|
+
if (!snippetName.trim() || !activeTab?.code.trim()) return;
|
|
458
|
+
const snippet: JSSnippet = {
|
|
459
|
+
id: String(Date.now()),
|
|
460
|
+
name: snippetName.trim(),
|
|
461
|
+
code: activeTab.code,
|
|
462
|
+
createdAt: Date.now()
|
|
463
|
+
};
|
|
464
|
+
setSnippets(prev => [snippet, ...prev]);
|
|
465
|
+
setShowSaveDialog(false);
|
|
466
|
+
setSnippetName("");
|
|
467
|
+
snackbar.open({ type: "success",
|
|
468
|
+
message: "Snippet saved" });
|
|
469
|
+
}, [snippetName, activeTab?.code, snackbar]);
|
|
470
|
+
|
|
471
|
+
const deleteSnippet = useCallback((id: string) => {
|
|
472
|
+
setSnippets(prev => prev.filter(s => s.id !== id));
|
|
473
|
+
}, []);
|
|
474
|
+
|
|
475
|
+
// ─── Export ──────────────────────────────────────────────────
|
|
476
|
+
|
|
477
|
+
const exportResult = useCallback(() => {
|
|
478
|
+
if (!result?.value) return;
|
|
479
|
+
const blob = new Blob([formatJSON(result.value)], { type: "application/json" });
|
|
480
|
+
const url = URL.createObjectURL(blob);
|
|
481
|
+
const a = document.createElement("a");
|
|
482
|
+
a.href = url;
|
|
483
|
+
a.download = `rebase-result-${Date.now()}.json`;
|
|
484
|
+
a.click();
|
|
485
|
+
URL.revokeObjectURL(url);
|
|
486
|
+
}, [result]);
|
|
487
|
+
|
|
488
|
+
// ─── Table columns for array data ────────────────────────────
|
|
489
|
+
|
|
490
|
+
const tableData = useMemo(() => {
|
|
491
|
+
if (!result?.value) return { columns: [] as VirtualTableColumn[],
|
|
492
|
+
data: [] as Record<string, unknown>[] };
|
|
493
|
+
|
|
494
|
+
let rows: any[] = [];
|
|
495
|
+
if (result.value?.data && Array.isArray(result.value.data)) {
|
|
496
|
+
rows = result.value.data.map((entity: any) => ({
|
|
497
|
+
id: entity.id,
|
|
498
|
+
...entity.values,
|
|
499
|
+
...(entity.values ? {} : entity)
|
|
500
|
+
}));
|
|
501
|
+
} else if (Array.isArray(result.value)) {
|
|
502
|
+
rows = result.value;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (rows.length === 0) return { columns: [] as VirtualTableColumn[],
|
|
506
|
+
data: [] as Record<string, unknown>[] };
|
|
507
|
+
|
|
508
|
+
const keys = new Set<string>();
|
|
509
|
+
rows.slice(0, 20).forEach(row => {
|
|
510
|
+
if (row && typeof row === "object") {
|
|
511
|
+
Object.keys(row).forEach(k => keys.add(k));
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
const columns: VirtualTableColumn[] = Array.from(keys).map(key => ({
|
|
516
|
+
key,
|
|
517
|
+
title: key,
|
|
518
|
+
width: key === "id" ? 100 : 200
|
|
519
|
+
}));
|
|
520
|
+
|
|
521
|
+
return { columns,
|
|
522
|
+
data: rows };
|
|
523
|
+
}, [result]);
|
|
524
|
+
|
|
525
|
+
// ─── Matched collections for entity actions ──────────────────
|
|
526
|
+
|
|
527
|
+
const matchedCollections = useMemo(() => {
|
|
528
|
+
if (!result?.value || result.error) return [];
|
|
529
|
+
return detectCollectionsInResult(
|
|
530
|
+
activeTab?.code ?? "",
|
|
531
|
+
result.value,
|
|
532
|
+
collectionRegistry?.collections ?? []
|
|
533
|
+
);
|
|
534
|
+
}, [result, activeTab?.code, collectionRegistry?.collections]);
|
|
535
|
+
|
|
536
|
+
const getRowEntityActions = useCallback((rowData: any): { collection: MatchedJSCollection; entityId: string | number }[] => {
|
|
537
|
+
if (!rowData || matchedCollections.length === 0) return [];
|
|
538
|
+
return matchedCollections
|
|
539
|
+
.filter(mc => rowData[mc.pkColumn] != null)
|
|
540
|
+
.map(mc => ({
|
|
541
|
+
collection: mc,
|
|
542
|
+
entityId: rowData[mc.pkColumn]
|
|
543
|
+
}));
|
|
544
|
+
}, [matchedCollections]);
|
|
545
|
+
|
|
546
|
+
// ─── Export handlers ─────────────────────────────────────────
|
|
547
|
+
|
|
548
|
+
const handleExportCSV = useCallback(() => {
|
|
549
|
+
if (tableData.data.length === 0) return;
|
|
550
|
+
const headers = tableData.columns.map(c => c.key).join(",");
|
|
551
|
+
const rows = tableData.data.map(row =>
|
|
552
|
+
tableData.columns.map(c => {
|
|
553
|
+
const val = row[c.key];
|
|
554
|
+
const str = val === null || val === undefined ? "" : String(val);
|
|
555
|
+
return str.includes(",") ? `"${str}"` : str;
|
|
556
|
+
}).join(",")
|
|
557
|
+
);
|
|
558
|
+
const csv = [headers, ...rows].join("\n");
|
|
559
|
+
const blob = new Blob([csv], { type: "text/csv" });
|
|
560
|
+
const url = URL.createObjectURL(blob);
|
|
561
|
+
const a = document.createElement("a");
|
|
562
|
+
a.href = url;
|
|
563
|
+
a.download = `js_results_${new Date().toISOString().slice(0, 19)}.csv`;
|
|
564
|
+
a.click();
|
|
565
|
+
URL.revokeObjectURL(url);
|
|
566
|
+
}, [tableData]);
|
|
567
|
+
|
|
568
|
+
const handleExportMarkdown = useCallback(() => {
|
|
569
|
+
if (tableData.data.length === 0) return;
|
|
570
|
+
const headers = tableData.columns.map(c => c.key);
|
|
571
|
+
const headerRow = `| ${headers.join(" | ")} |`;
|
|
572
|
+
const dividerRow = `| ${headers.map(() => "---").join(" | ")} |`;
|
|
573
|
+
const dataRows = tableData.data.map(row =>
|
|
574
|
+
`| ${headers.map(h => {
|
|
575
|
+
const val = row[h];
|
|
576
|
+
if (val === null || val === undefined) return "";
|
|
577
|
+
return String(val).replace(/\|/g, "\\|").replace(/\n/g, " ");
|
|
578
|
+
}).join(" | ")} |`
|
|
579
|
+
);
|
|
580
|
+
const markdown = [headerRow, dividerRow, ...dataRows].join("\n");
|
|
581
|
+
navigator.clipboard.writeText(markdown).then(() => {
|
|
582
|
+
snackbar.open({ type: "success",
|
|
583
|
+
message: t("studio_sql_markdown_copied") });
|
|
584
|
+
}).catch(() => {
|
|
585
|
+
snackbar.open({ type: "error",
|
|
586
|
+
message: t("studio_sql_markdown_copy_failed") });
|
|
587
|
+
});
|
|
588
|
+
}, [tableData, snackbar, t]);
|
|
589
|
+
|
|
590
|
+
// ─── Render ──────────────────────────────────────────────────
|
|
591
|
+
|
|
592
|
+
return (
|
|
593
|
+
<div className="flex flex-col h-full w-full">
|
|
594
|
+
{/* Main content */}
|
|
595
|
+
<div className="flex-grow overflow-hidden">
|
|
596
|
+
<ResizablePanels
|
|
597
|
+
orientation="horizontal"
|
|
598
|
+
panelSizePercent={sidebarSize}
|
|
599
|
+
onPanelSizeChange={setSidebarSize}
|
|
600
|
+
minPanelSizePx={200}
|
|
601
|
+
firstPanel={
|
|
602
|
+
<JSEditorSidebar
|
|
603
|
+
collections={collectionInfos}
|
|
604
|
+
snippets={snippets}
|
|
605
|
+
history={history}
|
|
606
|
+
onSelectSnippet={(code) => updateActiveCode(code)}
|
|
607
|
+
onDeleteSnippet={deleteSnippet}
|
|
608
|
+
onInsertCode={(code) => updateActiveCode(code)}
|
|
609
|
+
/>
|
|
610
|
+
}
|
|
611
|
+
secondPanel={
|
|
612
|
+
<div className="flex flex-col h-full overflow-hidden">
|
|
613
|
+
{/* Toolbar: matching SQL Editor layout */}
|
|
614
|
+
<div className={cls("flex items-center justify-between pr-2 border-b bg-white dark:bg-surface-950", defaultBorderMixin)}>
|
|
615
|
+
<div className="flex items-center flex-grow overflow-hidden mr-4">
|
|
616
|
+
<div className="flex items-center no-scrollbar overflow-x-auto min-w-0">
|
|
617
|
+
<Tabs value={activeTabId} onValueChange={setActiveTabId} variant="boxy" className="w-[unset] flex-shrink-0" innerClassName="bg-white dark:bg-surface-950">
|
|
618
|
+
{tabs.map(tab => (
|
|
619
|
+
<Tab key={tab.id} value={tab.id} className="flex items-center justify-between group max-w-[200px]">
|
|
620
|
+
<TerminalIcon size={iconSize.smallest} className="text-amber-500 mr-1.5 flex-shrink-0"/>
|
|
621
|
+
<span className="truncate">{tab.name}</span>
|
|
622
|
+
{tabs.length > 1 && (
|
|
623
|
+
<IconButton
|
|
624
|
+
size="smallest"
|
|
625
|
+
onClick={(e) => { e.stopPropagation(); closeTab(tab.id); }}
|
|
626
|
+
className="ml-1 !p-0.5 opacity-0 group-hover:opacity-100 hover:text-red-500 transition-opacity"
|
|
627
|
+
>
|
|
628
|
+
<XIcon size={iconSize.smallest}/>
|
|
629
|
+
</IconButton>
|
|
630
|
+
)}
|
|
631
|
+
</Tab>
|
|
632
|
+
))}
|
|
633
|
+
</Tabs>
|
|
634
|
+
<IconButton
|
|
635
|
+
size="small"
|
|
636
|
+
onClick={addTab}
|
|
637
|
+
className="ml-2 flex-shrink-0"
|
|
638
|
+
>
|
|
639
|
+
<PlusIcon size={iconSize.smallest}/>
|
|
640
|
+
</IconButton>
|
|
641
|
+
</div>
|
|
642
|
+
</div>
|
|
643
|
+
<div className="flex shrink-0 items-center justify-end gap-1.5">
|
|
644
|
+
<Tooltip title="SaveIcon as snippet">
|
|
645
|
+
<IconButton
|
|
646
|
+
size="small"
|
|
647
|
+
onClick={() => {
|
|
648
|
+
setSnippetName("");
|
|
649
|
+
setShowSaveDialog(true);
|
|
650
|
+
}}
|
|
651
|
+
disabled={!activeTab?.code.trim()}
|
|
652
|
+
>
|
|
653
|
+
<SaveIcon size={iconSize.smallest}/>
|
|
654
|
+
</IconButton>
|
|
655
|
+
</Tooltip>
|
|
656
|
+
|
|
657
|
+
{result?.value && (
|
|
658
|
+
<Tooltip title="Export result as JSON">
|
|
659
|
+
<IconButton size="small" onClick={exportResult}>
|
|
660
|
+
<DownloadIcon size={iconSize.smallest}/>
|
|
661
|
+
</IconButton>
|
|
662
|
+
</Tooltip>
|
|
663
|
+
)}
|
|
664
|
+
|
|
665
|
+
<Button
|
|
666
|
+
size="small"
|
|
667
|
+
color="primary"
|
|
668
|
+
disabled={isRunning || !activeTab?.code.trim()}
|
|
669
|
+
onClick={() => executeCode()}
|
|
670
|
+
>
|
|
671
|
+
{isRunning ? <CircularProgress size="smallest" className="mr-2"/> : <PlayIcon size={iconSize.smallest} className="mr-2"/>}
|
|
672
|
+
Run
|
|
673
|
+
</Button>
|
|
674
|
+
</div>
|
|
675
|
+
</div>
|
|
676
|
+
|
|
677
|
+
{/* Editor + Results split */}
|
|
678
|
+
<div className="flex-grow overflow-hidden">
|
|
679
|
+
<ResizablePanels
|
|
680
|
+
orientation="vertical"
|
|
681
|
+
panelSizePercent={editorHeight}
|
|
682
|
+
onPanelSizeChange={setEditorHeight}
|
|
683
|
+
minPanelSizePx={100}
|
|
684
|
+
firstPanel={
|
|
685
|
+
<div className="h-full w-full overflow-hidden flex flex-col">
|
|
686
|
+
{/* Auth Simulation UI */}
|
|
687
|
+
<div className="p-2 px-3 border-b border-surface-200 dark:border-surface-950 bg-surface-50 dark:bg-surface-900 flex items-center shrink-0">
|
|
688
|
+
<AuthSimulationSelector
|
|
689
|
+
authMode={authMode}
|
|
690
|
+
setAuthMode={setAuthMode}
|
|
691
|
+
selectedUser={selectedUser}
|
|
692
|
+
setSelectedUser={setSelectedUser}
|
|
693
|
+
users={users}
|
|
694
|
+
loading={userManagement?.loading}
|
|
695
|
+
currentUser={currentSelectableUser}
|
|
696
|
+
/>
|
|
697
|
+
</div>
|
|
698
|
+
<div className="flex-1 min-h-0">
|
|
699
|
+
<JSMonacoEditor
|
|
700
|
+
value={activeTab?.code ?? ""}
|
|
701
|
+
onChange={updateActiveCode}
|
|
702
|
+
onRun={(selectedText) => executeCode(selectedText)}
|
|
703
|
+
collectionSlugs={collectionSlugs}
|
|
704
|
+
collections={collectionInfos}
|
|
705
|
+
autoFocus
|
|
706
|
+
/>
|
|
707
|
+
</div>
|
|
708
|
+
</div>
|
|
709
|
+
}
|
|
710
|
+
secondPanel={
|
|
711
|
+
<div className="h-full w-full flex flex-col bg-surface-50 dark:bg-surface-950 overflow-hidden min-h-0">
|
|
712
|
+
{/* Result header — matches SQL editor */}
|
|
713
|
+
<div className={cls("p-2 px-4 bg-surface-100 dark:bg-surface-900 border-b shrink-0 flex items-center", defaultBorderMixin)}>
|
|
714
|
+
<Typography variant="caption" className="font-bold text-text-disabled dark:text-text-disabled-dark uppercase tracking-widest text-[10px]">
|
|
715
|
+
{t("studio_sql_query_results")}
|
|
716
|
+
</Typography>
|
|
717
|
+
|
|
718
|
+
{result && (
|
|
719
|
+
<>
|
|
720
|
+
<div className="flex-grow"/>
|
|
721
|
+
|
|
722
|
+
<Tabs value={resultView} onValueChange={(val) => setResultView(val as "json" | "table" | "console")} variant="pill" className="w-[unset] mr-2">
|
|
723
|
+
<Tab value="json">JSON</Tab>
|
|
724
|
+
{tableData.data.length > 0 && <Tab value="table">Table</Tab>}
|
|
725
|
+
{result.console.length > 0 && <Tab value="console">Console ({result.console.length})</Tab>}
|
|
726
|
+
</Tabs>
|
|
727
|
+
|
|
728
|
+
<Chip size="smallest" colorScheme={result.error ? "redDarker" : "greenDarker"}>
|
|
729
|
+
{result.error ? "Error" : `${result.duration.toFixed(0)}ms`}
|
|
730
|
+
</Chip>
|
|
731
|
+
</>
|
|
732
|
+
)}
|
|
733
|
+
</div>
|
|
734
|
+
|
|
735
|
+
{/* Result content */}
|
|
736
|
+
<div className="flex-grow flex flex-col min-h-0 overflow-hidden">
|
|
737
|
+
{isRunning && (
|
|
738
|
+
<div className="flex-grow flex items-center justify-center">
|
|
739
|
+
<div className="text-center">
|
|
740
|
+
<CircularProgress size="medium"/>
|
|
741
|
+
<Typography variant="body2" className="mt-4 text-text-secondary dark:text-text-secondary-dark font-mono tracking-tight animate-pulse">
|
|
742
|
+
EXECUTING SCRIPT...
|
|
743
|
+
</Typography>
|
|
744
|
+
</div>
|
|
745
|
+
</div>
|
|
746
|
+
)}
|
|
747
|
+
|
|
748
|
+
{!isRunning && !result && (
|
|
749
|
+
<div className="flex-grow flex items-center justify-center text-text-disabled dark:text-text-disabled-dark">
|
|
750
|
+
<div className="text-center">
|
|
751
|
+
<svg className="w-12 h-12 mx-auto mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1} d="M8 9l3 3-3 3m5 0h3M5 20h14a2 2 0 002-2V6a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/></svg>
|
|
752
|
+
<Typography variant="body2">Write JavaScript and press <kbd className="px-1.5 py-0.5 rounded bg-surface-200 dark:bg-surface-700 text-[11px] font-mono">⌘ Enter</kbd> to run</Typography>
|
|
753
|
+
</div>
|
|
754
|
+
</div>
|
|
755
|
+
)}
|
|
756
|
+
|
|
757
|
+
{!isRunning && result?.error && (
|
|
758
|
+
<div className="flex-grow flex items-center justify-center p-6 overflow-auto">
|
|
759
|
+
<ErrorView
|
|
760
|
+
title="Execution Error"
|
|
761
|
+
error={result.error}
|
|
762
|
+
/>
|
|
763
|
+
</div>
|
|
764
|
+
)}
|
|
765
|
+
|
|
766
|
+
{!isRunning && result && !result.error && resultView === "json" && (
|
|
767
|
+
<pre className="flex-grow overflow-auto p-4 text-[13px] font-mono leading-relaxed text-text-primary dark:text-text-primary-dark whitespace-pre-wrap break-words">
|
|
768
|
+
{result.value === undefined ? (
|
|
769
|
+
<span className="text-text-disabled italic">undefined (no return value)</span>
|
|
770
|
+
) : (
|
|
771
|
+
<JSONHighlight value={result.value}/>
|
|
772
|
+
)}
|
|
773
|
+
</pre>
|
|
774
|
+
)}
|
|
775
|
+
|
|
776
|
+
{!isRunning && result && !result.error && resultView === "table" && tableData.data.length > 0 && (
|
|
777
|
+
<div className="flex-grow flex flex-col overflow-hidden min-h-0">
|
|
778
|
+
{/* Collection badges bar — matching SQL editor */}
|
|
779
|
+
{matchedCollections.length > 0 && (
|
|
780
|
+
<div className={cls("px-4 py-1.5 border-b flex items-center gap-2 shrink-0 bg-surface-50 dark:bg-surface-900", defaultBorderMixin)}>
|
|
781
|
+
<Tooltip title={t("studio_sql_cms_collections_tooltip")}>
|
|
782
|
+
<Typography variant="caption" className="text-[10px] font-bold uppercase tracking-widest text-text-disabled dark:text-text-disabled-dark mr-1 shrink-0 cursor-help">{t("studio_sql_cms")}</Typography>
|
|
783
|
+
</Tooltip>
|
|
784
|
+
<div className="flex items-center gap-1.5 overflow-x-auto no-scrollbar">
|
|
785
|
+
{matchedCollections.map(mc => (
|
|
786
|
+
<Tooltip key={mc.collectionSlug} title={`${mc.collection.name} (${mc.collectionSlug})`}>
|
|
787
|
+
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded-full text-[11px] font-medium bg-primary/10 dark:bg-primary-dark/15 text-primary dark:text-primary-dark whitespace-nowrap border border-primary/20 dark:border-primary-dark/20">
|
|
788
|
+
{typeof mc.collection.icon === "string" && (
|
|
789
|
+
<IconForView collectionOrView={{ icon: mc.collection.icon } as never} className="text-[12px]"/>
|
|
790
|
+
)}
|
|
791
|
+
{mc.collection.name}
|
|
792
|
+
</span>
|
|
793
|
+
</Tooltip>
|
|
794
|
+
))}
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
797
|
+
)}
|
|
798
|
+
<div className="flex-grow relative h-full min-h-0 min-w-0">
|
|
799
|
+
<VirtualTable
|
|
800
|
+
data={tableData.data}
|
|
801
|
+
columns={
|
|
802
|
+
matchedCollections.length > 0
|
|
803
|
+
? [{ key: "__entity_action__",
|
|
804
|
+
title: "",
|
|
805
|
+
width: 36,
|
|
806
|
+
sortable: false,
|
|
807
|
+
resizable: false }, ...tableData.columns]
|
|
808
|
+
: tableData.columns
|
|
809
|
+
}
|
|
810
|
+
rowHeight={32}
|
|
811
|
+
headerHeight={32}
|
|
812
|
+
cellRenderer={({ rowData, column, rowIndex }: CellRendererParams<Record<string, unknown>>) => {
|
|
813
|
+
// Entity action column
|
|
814
|
+
if (column.key === "__entity_action__") {
|
|
815
|
+
const rowActions = getRowEntityActions(rowData);
|
|
816
|
+
if (rowActions.length === 0) return <div className="h-full w-full"/>;
|
|
817
|
+
if (rowActions.length === 1) {
|
|
818
|
+
const ra = rowActions[0];
|
|
819
|
+
return (
|
|
820
|
+
<div className="h-full flex items-center justify-center">
|
|
821
|
+
<Tooltip title={t("studio_sql_edit_entity", { name: ra.collection.collection.name,
|
|
822
|
+
id: String(ra.entityId) })}>
|
|
823
|
+
<IconButton
|
|
824
|
+
size="small"
|
|
825
|
+
className="text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300"
|
|
826
|
+
onClick={(e) => {
|
|
827
|
+
e.stopPropagation();
|
|
828
|
+
sideEntityController.open({
|
|
829
|
+
path: ra.collection.collectionSlug,
|
|
830
|
+
entityId: ra.entityId,
|
|
831
|
+
collection: ra.collection.collection,
|
|
832
|
+
updateUrl: false
|
|
833
|
+
});
|
|
834
|
+
}}
|
|
835
|
+
>
|
|
836
|
+
<PencilIcon size={iconSize.smallest}/>
|
|
837
|
+
</IconButton>
|
|
838
|
+
</Tooltip>
|
|
839
|
+
</div>
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
// Multiple matched collections
|
|
843
|
+
return (
|
|
844
|
+
<div className="h-full flex items-center justify-center">
|
|
845
|
+
<Menu
|
|
846
|
+
trigger={
|
|
847
|
+
<IconButton
|
|
848
|
+
size={"small"}
|
|
849
|
+
className="text-surface-400 dark:text-surface-500 hover:text-surface-600 dark:hover:text-surface-300"
|
|
850
|
+
onClick={(e) => e.stopPropagation()}
|
|
851
|
+
>
|
|
852
|
+
<MoreVerticalIcon size={iconSize.smallest}/>
|
|
853
|
+
</IconButton>
|
|
854
|
+
}
|
|
855
|
+
>
|
|
856
|
+
{rowActions.map(ra => (
|
|
857
|
+
<MenuItem
|
|
858
|
+
key={ra.collection.collectionSlug}
|
|
859
|
+
dense
|
|
860
|
+
onClick={() => {
|
|
861
|
+
sideEntityController.open({
|
|
862
|
+
path: ra.collection.collectionSlug,
|
|
863
|
+
entityId: ra.entityId,
|
|
864
|
+
collection: ra.collection.collection,
|
|
865
|
+
updateUrl: false
|
|
866
|
+
});
|
|
867
|
+
}}
|
|
868
|
+
>
|
|
869
|
+
{t("studio_sql_edit_entity", { name: ra.collection.collection.name,
|
|
870
|
+
id: String(ra.entityId) })}
|
|
871
|
+
</MenuItem>
|
|
872
|
+
))}
|
|
873
|
+
</Menu>
|
|
874
|
+
</div>
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Regular data cell
|
|
879
|
+
if (!rowData) return null;
|
|
880
|
+
const val = rowData[column.key];
|
|
881
|
+
const displayValue = typeof val === "object" && val !== null ? JSON.stringify(val) : String(val ?? "");
|
|
882
|
+
return (
|
|
883
|
+
<div className="px-4 py-1.5 h-full flex items-center whitespace-nowrap text-[13px] text-text-primary dark:text-text-primary-dark font-mono">
|
|
884
|
+
<div className="truncate flex-grow" title={displayValue}>
|
|
885
|
+
{displayValue === "" ? <span className="text-text-disabled dark:text-text-disabled-dark italic text-[11px]">NULL</span> : displayValue}
|
|
886
|
+
</div>
|
|
887
|
+
</div>
|
|
888
|
+
);
|
|
889
|
+
}}
|
|
890
|
+
/>
|
|
891
|
+
</div>
|
|
892
|
+
</div>
|
|
893
|
+
)}
|
|
894
|
+
|
|
895
|
+
{!isRunning && result && resultView === "console" && (
|
|
896
|
+
<div className="flex-grow overflow-auto p-2 space-y-1 font-mono text-[12px]">
|
|
897
|
+
{result.console.length === 0 ? (
|
|
898
|
+
<Typography variant="caption" className="text-text-disabled p-2">No console output</Typography>
|
|
899
|
+
) : (
|
|
900
|
+
result.console.map((entry, i) => (
|
|
901
|
+
<div
|
|
902
|
+
key={i}
|
|
903
|
+
className={cls(
|
|
904
|
+
"px-2 py-1 rounded flex items-start gap-2",
|
|
905
|
+
entry.type === "error" && "bg-red-50 dark:bg-red-950/30 text-red-700 dark:text-red-400",
|
|
906
|
+
entry.type === "warn" && "bg-amber-50 dark:bg-amber-950/30 text-amber-700 dark:text-amber-400",
|
|
907
|
+
entry.type === "log" && "text-text-primary dark:text-text-primary-dark",
|
|
908
|
+
entry.type === "info" && "text-blue-700 dark:text-blue-400"
|
|
909
|
+
)}
|
|
910
|
+
>
|
|
911
|
+
<span className="text-[10px] opacity-50 flex-shrink-0 mt-0.5">
|
|
912
|
+
{entry.type === "error" ? "❌" : entry.type === "warn" ? "⚠️" : entry.type === "info" ? "ℹ️" : "›"}
|
|
913
|
+
</span>
|
|
914
|
+
<span className="whitespace-pre-wrap break-words">
|
|
915
|
+
{entry.args.map(a => typeof a === "object" ? JSON.stringify(a, null, 2) : String(a)).join(" ")}
|
|
916
|
+
</span>
|
|
917
|
+
</div>
|
|
918
|
+
))
|
|
919
|
+
)}
|
|
920
|
+
|
|
921
|
+
{result.value !== undefined && (
|
|
922
|
+
<div className={cls("px-2 py-1 mt-2 border-t pt-2", defaultBorderMixin)}>
|
|
923
|
+
<Typography variant="caption" className="text-text-disabled text-[10px] uppercase tracking-wider mb-1 block">Return Value</Typography>
|
|
924
|
+
<pre className="text-text-primary dark:text-text-primary-dark whitespace-pre-wrap break-words text-[12px]">
|
|
925
|
+
{formatJSON(result.value)}
|
|
926
|
+
</pre>
|
|
927
|
+
</div>
|
|
928
|
+
)}
|
|
929
|
+
</div>
|
|
930
|
+
)}
|
|
931
|
+
</div>
|
|
932
|
+
|
|
933
|
+
{/* Footer bar — matching SQL editor */}
|
|
934
|
+
{!isRunning && result && !result.error && resultView === "table" && tableData.data.length > 0 && (
|
|
935
|
+
<div className={cls("p-2 px-4 border-t bg-surface-50 dark:bg-surface-900 flex justify-between items-center shrink-0", defaultBorderMixin)}>
|
|
936
|
+
<div className="flex space-x-4">
|
|
937
|
+
<div className="flex items-center text-[11px]">
|
|
938
|
+
<span className="font-bold text-text-disabled dark:text-text-disabled-dark mr-2 uppercase tracking-tighter">{t("studio_sql_rows")}</span>
|
|
939
|
+
<span className="font-mono text-text-secondary dark:text-text-secondary-dark">{tableData.data.length}</span>
|
|
940
|
+
</div>
|
|
941
|
+
<div className="flex items-center text-[11px]">
|
|
942
|
+
<span className="font-bold text-text-disabled dark:text-text-disabled-dark mr-2 uppercase tracking-tighter">{t("studio_sql_time")}</span>
|
|
943
|
+
<span className="font-mono text-text-secondary dark:text-text-secondary-dark">{result.duration.toFixed(0)}ms</span>
|
|
944
|
+
</div>
|
|
945
|
+
</div>
|
|
946
|
+
<div className="flex gap-2 overflow-x-auto no-scrollbar items-center px-2">
|
|
947
|
+
<Button
|
|
948
|
+
size="small"
|
|
949
|
+
variant="text"
|
|
950
|
+
className="text-[10px] uppercase font-bold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap"
|
|
951
|
+
onClick={handleExportMarkdown}
|
|
952
|
+
>
|
|
953
|
+
{t("studio_sql_copy_markdown")}
|
|
954
|
+
</Button>
|
|
955
|
+
<Button
|
|
956
|
+
size="small"
|
|
957
|
+
variant="text"
|
|
958
|
+
className="text-[10px] uppercase font-bold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap"
|
|
959
|
+
onClick={exportResult}
|
|
960
|
+
>
|
|
961
|
+
{t("studio_sql_export_json")}
|
|
962
|
+
</Button>
|
|
963
|
+
<Button
|
|
964
|
+
size="small"
|
|
965
|
+
variant="text"
|
|
966
|
+
className="text-[10px] uppercase font-bold text-text-secondary dark:text-text-secondary-dark whitespace-nowrap"
|
|
967
|
+
onClick={handleExportCSV}
|
|
968
|
+
>
|
|
969
|
+
{t("studio_sql_export_csv")}
|
|
970
|
+
</Button>
|
|
971
|
+
</div>
|
|
972
|
+
</div>
|
|
973
|
+
)}
|
|
974
|
+
</div>
|
|
975
|
+
}
|
|
976
|
+
/>
|
|
977
|
+
</div>
|
|
978
|
+
</div>
|
|
979
|
+
}
|
|
980
|
+
/>
|
|
981
|
+
</div>
|
|
982
|
+
|
|
983
|
+
{/* SaveIcon snippet dialog */}
|
|
984
|
+
<Dialog open={showSaveDialog} onOpenChange={setShowSaveDialog}>
|
|
985
|
+
<DialogTitle>SaveIcon Snippet</DialogTitle>
|
|
986
|
+
<DialogContent>
|
|
987
|
+
<TextField
|
|
988
|
+
label="Snippet name"
|
|
989
|
+
value={snippetName}
|
|
990
|
+
onChange={(e) => setSnippetName(e.target.value)}
|
|
991
|
+
placeholder="e.g. List all products"
|
|
992
|
+
autoFocus
|
|
993
|
+
onKeyDown={(e) => {
|
|
994
|
+
if (e.key === "Enter") saveSnippet();
|
|
995
|
+
}}
|
|
996
|
+
/>
|
|
997
|
+
</DialogContent>
|
|
998
|
+
<DialogActions>
|
|
999
|
+
<Button variant="text" onClick={() => setShowSaveDialog(false)}>Cancel</Button>
|
|
1000
|
+
<Button variant="filled" color="primary" onClick={saveSnippet} disabled={!snippetName.trim()}>Save</Button>
|
|
1001
|
+
</DialogActions>
|
|
1002
|
+
</Dialog>
|
|
1003
|
+
</div>
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
// ─── JSON Syntax Highlighting ────────────────────────────────────────
|
|
1008
|
+
|
|
1009
|
+
function JSONHighlight({ value }: { value: any }) {
|
|
1010
|
+
const json = formatJSON(value);
|
|
1011
|
+
const { mode } = useModeController();
|
|
1012
|
+
|
|
1013
|
+
return (
|
|
1014
|
+
<Highlight
|
|
1015
|
+
theme={mode === "dark" ? themes.vsDark : themes.github}
|
|
1016
|
+
code={json}
|
|
1017
|
+
language="json"
|
|
1018
|
+
>
|
|
1019
|
+
{({ style, tokens, getLineProps, getTokenProps }) => (
|
|
1020
|
+
<span style={{ ...style,
|
|
1021
|
+
backgroundColor: "transparent" }}>
|
|
1022
|
+
{tokens.map((line, i) => (
|
|
1023
|
+
<div key={i} {...getLineProps({ line })}>
|
|
1024
|
+
{line.map((token, key) => (
|
|
1025
|
+
<span key={key} {...getTokenProps({ token })}/>
|
|
1026
|
+
))}
|
|
1027
|
+
</div>
|
|
1028
|
+
))}
|
|
1029
|
+
</span>
|
|
1030
|
+
)}
|
|
1031
|
+
</Highlight>
|
|
1032
|
+
);
|
|
1033
|
+
}
|