@rebasepro/app 0.17.3 → 0.18.0
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/README.md +4 -1
- package/dist/components/index.d.ts +0 -3
- package/dist/contexts/AuthControllerContext.d.ts +2 -1
- package/dist/contexts/CustomizationControllerContext.d.ts +2 -1
- package/dist/contexts/DialogsProvider.d.ts +2 -1
- package/dist/contexts/RebaseDataContext.d.ts +10 -1
- package/dist/contexts/StorageSourceContext.d.ts +11 -1
- package/dist/core/RebaseProps.d.ts +2 -12
- package/dist/debug/index.d.ts +19 -0
- package/dist/debug.js +3631 -0
- package/dist/hooks/data/delete.d.ts +16 -7
- package/dist/hooks/data/save.d.ts +18 -4
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useAdminModeController.d.ts +5 -4
- package/dist/hooks/useNavigationGroupLabel.d.ts +17 -0
- package/dist/hooks/useStudioBridge.d.ts +33 -0
- package/dist/index.es.js +2599 -6980
- package/dist/index.es.js.map +1 -1
- package/dist/vitePlugin.d.ts +22 -0
- package/dist/vitePlugin.js +40 -1
- package/package.json +42 -31
- package/src/auth/api.ts +0 -134
- package/src/auth/index.ts +0 -26
- package/src/auth/types.ts +0 -115
- package/src/auth/useRebaseAuthController.ts +0 -345
- package/src/collections/collection_view_config.ts +0 -76
- package/src/collections/entity-display-cache.ts +0 -195
- package/src/collections/entity-display.ts +0 -77
- package/src/collections/entity_image_preview.ts +0 -61
- package/src/collections/filter-operator-resolution.ts +0 -119
- package/src/collections/form-layout.ts +0 -394
- package/src/collections/index.ts +0 -13
- package/src/collections/navigation_from_path.ts +0 -123
- package/src/collections/navigation_utils.ts +0 -171
- package/src/collections/parent_references_from_path.ts +0 -59
- package/src/collections/property-path.ts +0 -30
- package/src/collections/property_presentation.ts +0 -264
- package/src/collections/summary-property.ts +0 -114
- package/src/collections/title-property.ts +0 -312
- package/src/components/AIIcon.tsx +0 -47
- package/src/components/AuthSimulationMenu.tsx +0 -0
- package/src/components/ConfirmationDialog.tsx +0 -48
- package/src/components/Debug/UIReferenceView.tsx +0 -1053
- package/src/components/Debug/UIStyleGuide.tsx +0 -159
- package/src/components/Debug/collection-views/CardViewDemo.tsx +0 -87
- package/src/components/Debug/collection-views/CollectionTableDemo.tsx +0 -107
- package/src/components/Debug/collection-views/KanbanBoardDemo.tsx +0 -89
- package/src/components/Debug/collection-views/index.ts +0 -3
- package/src/components/Debug/collection-views/sample_data.ts +0 -44
- package/src/components/Debug/crm-dashboard/CalendarWidget.tsx +0 -387
- package/src/components/Debug/crm-dashboard/CrmDashboardDemo.tsx +0 -1100
- package/src/components/Debug/crm-dashboard/DashboardMetrics.tsx +0 -131
- package/src/components/Debug/crm-dashboard/PipelineOverview.tsx +0 -288
- package/src/components/Debug/crm-dashboard/RecentActivity.tsx +0 -213
- package/src/components/Debug/crm-dashboard/TaskFilters.tsx +0 -182
- package/src/components/Debug/crm-dashboard/TaskTable.tsx +0 -425
- package/src/components/Debug/crm-dashboard/TasksView.tsx +0 -234
- package/src/components/ErrorTooltip.tsx +0 -12
- package/src/components/ErrorView.tsx +0 -94
- package/src/components/LanguageToggle.tsx +0 -59
- package/src/components/LoginView/LoginView.tsx +0 -1235
- package/src/components/LoginView/index.ts +0 -2
- package/src/components/LoginView/oauth-redirect-flow.ts +0 -184
- package/src/components/NotFoundPage.tsx +0 -27
- package/src/components/RebaseAuth.tsx +0 -27
- package/src/components/RebaseLogo.tsx +0 -88
- package/src/components/SchemaDriftBanner.tsx +0 -102
- package/src/components/UnsavedChangesDialog.tsx +0 -46
- package/src/components/UserDisplay.tsx +0 -54
- package/src/components/UserSelectPopover.tsx +0 -399
- package/src/components/UserSettingsView.tsx +0 -318
- package/src/components/common/index.ts +0 -6
- package/src/components/common/table_height.tsx +0 -21
- package/src/components/common/types.tsx +0 -66
- package/src/components/common/useColumnsIds.tsx +0 -261
- package/src/components/common/useDataTableController.tsx +0 -571
- package/src/components/common/useDebouncedData.ts +0 -49
- package/src/components/common/useScrollRestoration.tsx +0 -68
- package/src/components/index.tsx +0 -26
- package/src/contexts/AdminModeController.tsx +0 -11
- package/src/contexts/AnalyticsContext.tsx +0 -4
- package/src/contexts/AuthControllerContext.tsx +0 -4
- package/src/contexts/CollectionResolverContext.tsx +0 -24
- package/src/contexts/CollectionScopeContext.tsx +0 -68
- package/src/contexts/ComponentOverrideContext.tsx +0 -81
- package/src/contexts/CustomizationControllerContext.tsx +0 -4
- package/src/contexts/DataDriverContext.tsx +0 -4
- package/src/contexts/DataSourcesContext.tsx +0 -36
- package/src/contexts/DatabaseAdminContext.tsx +0 -4
- package/src/contexts/DialogsProvider.tsx +0 -56
- package/src/contexts/EffectiveRoleController.tsx +0 -12
- package/src/contexts/ModeController.tsx +0 -11
- package/src/contexts/RebaseClientInstanceContext.tsx +0 -7
- package/src/contexts/RebaseDataContext.tsx +0 -4
- package/src/contexts/SnackbarProvider.tsx +0 -15
- package/src/contexts/StorageSourceContext.tsx +0 -4
- package/src/contexts/StorageSourcesContext.tsx +0 -30
- package/src/contexts/UserConfigurationPersistenceContext.tsx +0 -4
- package/src/contexts/index.ts +0 -18
- package/src/core/PluginLifecycleManager.tsx +0 -96
- package/src/core/PluginProviderStack.tsx +0 -40
- package/src/core/Rebase.tsx +0 -488
- package/src/core/RebaseProps.tsx +0 -333
- package/src/core/RebaseRouter.tsx +0 -18
- package/src/core/RebaseRoutes.tsx +0 -24
- package/src/core/index.tsx +0 -5
- package/src/hooks/ApiConfigContext.tsx +0 -83
- package/src/hooks/data/collectionQuery.ts +0 -55
- package/src/hooks/data/delete.ts +0 -56
- package/src/hooks/data/save.ts +0 -78
- package/src/hooks/data/useCollection.tsx +0 -183
- package/src/hooks/data/useData.tsx +0 -18
- package/src/hooks/data/useDataOrder.ts +0 -26
- package/src/hooks/data/useFetch.tsx +0 -156
- package/src/hooks/data/useRelationSelector.tsx +0 -280
- package/src/hooks/index.tsx +0 -45
- package/src/hooks/useAdminModeController.tsx +0 -23
- package/src/hooks/useAnalyticsController.tsx +0 -8
- package/src/hooks/useAuthController.tsx +0 -15
- package/src/hooks/useAuthSubscription.ts +0 -130
- package/src/hooks/useBackendStorageSource.ts +0 -295
- package/src/hooks/useBridgeRegistration.tsx +0 -32
- package/src/hooks/useBrowserTitleAndIcon.tsx +0 -23
- package/src/hooks/useBuildAdminModeController.tsx +0 -29
- package/src/hooks/useBuildEffectiveRoleController.tsx +0 -30
- package/src/hooks/useBuildLocalConfigurationPersistence.tsx +0 -83
- package/src/hooks/useBuildModeController.tsx +0 -73
- package/src/hooks/useClipboard.tsx +0 -158
- package/src/hooks/useCollapsedGroups.ts +0 -116
- package/src/hooks/useComponentOverride.tsx +0 -59
- package/src/hooks/useCustomizationController.tsx +0 -14
- package/src/hooks/useDialogsController.tsx +0 -15
- package/src/hooks/useEffectiveRoleController.tsx +0 -10
- package/src/hooks/useLargeLayout.tsx +0 -65
- package/src/hooks/useModeController.tsx +0 -23
- package/src/hooks/useNavigationBlocker.tsx +0 -108
- package/src/hooks/usePermissions.ts +0 -45
- package/src/hooks/useRebaseClient.tsx +0 -10
- package/src/hooks/useRebaseContext.tsx +0 -84
- package/src/hooks/useRebaseRegistry.tsx +0 -91
- package/src/hooks/useResolvedComponent.tsx +0 -195
- package/src/hooks/useSlot.tsx +0 -64
- package/src/hooks/useSnackbarController.tsx +0 -80
- package/src/hooks/useStorageSource.tsx +0 -11
- package/src/hooks/useStudioBridge.tsx +0 -258
- package/src/hooks/useTranslation.ts +0 -38
- package/src/hooks/useUnsavedChangesDialog.tsx +0 -67
- package/src/hooks/useUserConfigurationPersistence.tsx +0 -11
- package/src/i18n/RebaseI18nProvider.tsx +0 -159
- package/src/index.ts +0 -32
- package/src/internal/common.tsx +0 -28
- package/src/internal/useRestoreScroll.tsx +0 -69
- package/src/locales/de.ts +0 -941
- package/src/locales/en.ts +0 -1021
- package/src/locales/es.ts +0 -962
- package/src/locales/fr.ts +0 -941
- package/src/locales/hi.ts +0 -941
- package/src/locales/it.ts +0 -941
- package/src/locales/pt.ts +0 -949
- package/src/util/constants.ts +0 -7
- package/src/util/createFormexStub.tsx +0 -66
- package/src/util/entity_cache.ts +0 -242
- package/src/util/enums.ts +0 -34
- package/src/util/icon_list.ts +0 -22
- package/src/util/icons.tsx +0 -129
- package/src/util/index.ts +0 -13
- package/src/util/local_storage.ts +0 -66
- package/src/util/previews.ts +0 -127
- package/src/util/useStorageUploadController.tsx +0 -354
- package/src/vitePlugin.ts +0 -215
- /package/dist/{components/Debug → debug}/UIReferenceView.d.ts +0 -0
- /package/dist/{components/Debug → debug}/UIStyleGuide.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CardViewDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/CollectionTableDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/KanbanBoardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/index.d.ts +0 -0
- /package/dist/{components/Debug → debug}/collection-views/sample_data.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CalendarWidget.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/CrmDashboardDemo.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/DashboardMetrics.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/PipelineOverview.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/RecentActivity.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskFilters.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TaskTable.d.ts +0 -0
- /package/dist/{components/Debug → debug}/crm-dashboard/TasksView.d.ts +0 -0
package/src/hooks/index.tsx
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export * from "./data/useData";
|
|
2
|
-
export * from "./data/useCollection";
|
|
3
|
-
export * from "./data/useFetch";
|
|
4
|
-
export * from "./data/useRelationSelector";
|
|
5
|
-
export * from "./data/save";
|
|
6
|
-
export * from "./data/delete";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
export * from "./useUnsavedChangesDialog";
|
|
10
|
-
|
|
11
|
-
export * from "./useStorageSource";
|
|
12
|
-
export * from "./useAuthController";
|
|
13
|
-
export * from "./useAuthSubscription";
|
|
14
|
-
export * from "./useDialogsController";
|
|
15
|
-
export * from "./useAdminModeController";
|
|
16
|
-
export * from "./useBuildAdminModeController";
|
|
17
|
-
export * from "./useEffectiveRoleController";
|
|
18
|
-
export * from "./useBuildEffectiveRoleController";
|
|
19
|
-
export * from "./useRebaseContext";
|
|
20
|
-
export * from "./useSnackbarController";
|
|
21
|
-
export * from "./useModeController";
|
|
22
|
-
export * from "./useClipboard";
|
|
23
|
-
export * from "./useLargeLayout";
|
|
24
|
-
export * from "./useCollapsedGroups";
|
|
25
|
-
|
|
26
|
-
export * from "./useBrowserTitleAndIcon";
|
|
27
|
-
export * from "./useSlot";
|
|
28
|
-
export * from "./useCustomizationController";
|
|
29
|
-
|
|
30
|
-
export * from "./useBuildLocalConfigurationPersistence";
|
|
31
|
-
export * from "./useBuildModeController";
|
|
32
|
-
|
|
33
|
-
export * from "./useRebaseRegistry";
|
|
34
|
-
export * from "./useBackendStorageSource";
|
|
35
|
-
export * from "./usePermissions";
|
|
36
|
-
|
|
37
|
-
export * from "./ApiConfigContext";
|
|
38
|
-
|
|
39
|
-
export * from "./useTranslation";
|
|
40
|
-
export * from "./useRebaseClient";
|
|
41
|
-
|
|
42
|
-
export * from "./useAnalyticsController";
|
|
43
|
-
export * from "./useUserConfigurationPersistence";
|
|
44
|
-
export * from "./useResolvedComponent";
|
|
45
|
-
export * from "./useComponentOverride";
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { AdminModeControllerContext } from "../contexts/AdminModeController";
|
|
2
|
-
import { useContext } from "react";
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Use this controller to change the admin mode (developer vs editor)
|
|
6
|
-
* @group Hooks and utilities
|
|
7
|
-
*/
|
|
8
|
-
export interface AdminModeController {
|
|
9
|
-
mode: "cms" | "studio" | "settings";
|
|
10
|
-
setMode: (mode: "cms" | "studio" | "settings") => void;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Hook to retrieve the current admin mode ("developer" | "editor"), and `setMode`
|
|
15
|
-
* to change it.
|
|
16
|
-
*
|
|
17
|
-
* Consider that in order to use this hook you need to have a parent
|
|
18
|
-
* `Rebase`
|
|
19
|
-
*
|
|
20
|
-
* @see AdminModeController
|
|
21
|
-
* @group Hooks and utilities
|
|
22
|
-
*/
|
|
23
|
-
export const useAdminModeController = () => useContext(AdminModeControllerContext);
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { useContext } from "react";
|
|
2
|
-
import { AnalyticsController } from "@rebasepro/cms-types";
|
|
3
|
-
import { AnalyticsContext } from "../contexts/AnalyticsContext";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* @group Hooks and utilities
|
|
7
|
-
*/
|
|
8
|
-
export const useAnalyticsController = (): AnalyticsController => useContext(AnalyticsContext);
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { User } from "@rebasepro/types";
|
|
2
|
-
import { AuthController } from "@rebasepro/cms-types";
|
|
3
|
-
import { useContext } from "react";
|
|
4
|
-
import { AuthControllerContext } from "../contexts/AuthControllerContext";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Hook to retrieve the AuthContext.
|
|
8
|
-
*
|
|
9
|
-
* Consider that in order to use this hook you need to have a parent
|
|
10
|
-
* `Rebase`
|
|
11
|
-
*
|
|
12
|
-
* @see AuthController
|
|
13
|
-
* @group Hooks and utilities
|
|
14
|
-
*/
|
|
15
|
-
export const useAuthController = <USER extends User = User, AuthControllerType extends AuthController<USER> = AuthController<USER>>(): AuthControllerType => useContext(AuthControllerContext) as AuthControllerType;
|
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect, useMemo, useCallback } from "react";
|
|
2
|
-
import { AuthClient, User } from "@rebasepro/types";
|
|
3
|
-
import { AuthController } from "@rebasepro/cms-types";
|
|
4
|
-
|
|
5
|
-
export function useAuthSubscription(authClient?: AuthClient): AuthController {
|
|
6
|
-
|
|
7
|
-
// Check initial state
|
|
8
|
-
const currentSession = authClient?.getSession();
|
|
9
|
-
const [user, setUser] = useState<User | null>(currentSession?.user ?? null);
|
|
10
|
-
|
|
11
|
-
const [authLoading, setAuthLoading] = useState(false);
|
|
12
|
-
const [authError, setAuthError] = useState<Error>();
|
|
13
|
-
const [loginSkipped, setLoginSkipped] = useState(false);
|
|
14
|
-
const [extra, setExtra] = useState<any>();
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Whether asking the server "who am I?" could possibly answer.
|
|
18
|
-
*
|
|
19
|
-
* A client configured with `persistSession: false` and JSON auth — the
|
|
20
|
-
* shape used when one client borrows another's credential, as the hosted
|
|
21
|
-
* Studio does — has no stored session and no auth cookie, so the probe
|
|
22
|
-
* below is guaranteed to 401. Sending it anyway put three failing
|
|
23
|
-
* `GET /auth/me` calls on the wire per mount, each one landing in the
|
|
24
|
-
* *customer's* request log as an authentication failure against their own
|
|
25
|
-
* backend.
|
|
26
|
-
*
|
|
27
|
-
* A client that does not implement the capability is treated as "might
|
|
28
|
-
* work" — the historical behaviour.
|
|
29
|
-
*/
|
|
30
|
-
const mayHaveRestorableSession = authClient?.canRestoreSession?.() ?? true;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* `true` only while a probe is genuinely outstanding.
|
|
34
|
-
*
|
|
35
|
-
* It used to be seeded from `!currentSession` alone, which left a client
|
|
36
|
-
* that cannot restore a session — and therefore never runs the probe that
|
|
37
|
-
* clears the flag — reporting "still loading" forever. Anything gated on
|
|
38
|
-
* `initialLoading` (a spinner, a redirect to the login view) would never
|
|
39
|
-
* resolve.
|
|
40
|
-
*/
|
|
41
|
-
const [initialLoading, setInitialLoading] = useState(!currentSession && mayHaveRestorableSession);
|
|
42
|
-
|
|
43
|
-
useEffect(() => {
|
|
44
|
-
if (!authClient) return;
|
|
45
|
-
// If we don't have a session initially, try to get user which restores session if a persistent token exists
|
|
46
|
-
if (!currentSession && mayHaveRestorableSession) {
|
|
47
|
-
setInitialLoading(true);
|
|
48
|
-
authClient.getUser()
|
|
49
|
-
.then(user => {
|
|
50
|
-
if (user) {
|
|
51
|
-
setUser(user);
|
|
52
|
-
}
|
|
53
|
-
setAuthError(undefined);
|
|
54
|
-
})
|
|
55
|
-
.catch((e: unknown) => {
|
|
56
|
-
// A 401 or 403 is the ordinary first load of an anonymous
|
|
57
|
-
// visitor: there is no session, which is not an error.
|
|
58
|
-
//
|
|
59
|
-
// Everything else is. This used to swallow the lot with
|
|
60
|
-
// "Ignore, user just isn't logged in", leaving `authError`
|
|
61
|
-
// — a field this controller declares and `Rebase` renders a
|
|
62
|
-
// full-screen "Error loading auth" view for — permanently
|
|
63
|
-
// undefined. So the view was unreachable, and a backend that
|
|
64
|
-
// was down, misconfigured, or blocked by CORS looked exactly
|
|
65
|
-
// like being signed out: the app showed the login form.
|
|
66
|
-
const status = (e as { status?: number } | undefined)?.status;
|
|
67
|
-
if (status === 401 || status === 403) {
|
|
68
|
-
setAuthError(undefined);
|
|
69
|
-
return;
|
|
70
|
-
}
|
|
71
|
-
setAuthError(e instanceof Error ? e : new Error(String(e)));
|
|
72
|
-
})
|
|
73
|
-
.finally(() => {
|
|
74
|
-
setInitialLoading(false);
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
}, [authClient, currentSession, mayHaveRestorableSession]);
|
|
78
|
-
|
|
79
|
-
useEffect(() => {
|
|
80
|
-
if (!authClient) return;
|
|
81
|
-
const unsubscribe = authClient.onAuthStateChange((event, session) => {
|
|
82
|
-
if (event === "SIGNED_IN" || event === "USER_UPDATED" || event === "TOKEN_REFRESHED") {
|
|
83
|
-
setUser(session?.user ?? null);
|
|
84
|
-
} else if (event === "SIGNED_OUT") {
|
|
85
|
-
setUser(null);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
return unsubscribe;
|
|
89
|
-
}, [authClient]);
|
|
90
|
-
|
|
91
|
-
const signOut = useCallback(async () => {
|
|
92
|
-
if (!authClient) return;
|
|
93
|
-
setAuthLoading(true);
|
|
94
|
-
try {
|
|
95
|
-
await authClient.signOut();
|
|
96
|
-
} finally {
|
|
97
|
-
setAuthLoading(false);
|
|
98
|
-
}
|
|
99
|
-
}, [authClient]);
|
|
100
|
-
|
|
101
|
-
const getAuthToken = useCallback(async () => {
|
|
102
|
-
if (!authClient) return "";
|
|
103
|
-
const session = authClient.getSession();
|
|
104
|
-
if (!session) return "";
|
|
105
|
-
if (session.expiresAt < Date.now()) {
|
|
106
|
-
try {
|
|
107
|
-
const refreshed = await authClient.refreshSession();
|
|
108
|
-
return refreshed.accessToken;
|
|
109
|
-
} catch (e) {
|
|
110
|
-
return "";
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return session.accessToken;
|
|
114
|
-
}, [authClient]);
|
|
115
|
-
|
|
116
|
-
return useMemo(() => ({
|
|
117
|
-
user,
|
|
118
|
-
initialLoading: authClient ? initialLoading : false,
|
|
119
|
-
authLoading,
|
|
120
|
-
signOut,
|
|
121
|
-
authError,
|
|
122
|
-
getAuthToken,
|
|
123
|
-
loginSkipped,
|
|
124
|
-
extra,
|
|
125
|
-
setExtra
|
|
126
|
-
} as AuthController), [
|
|
127
|
-
user, initialLoading, authLoading, signOut, authError,
|
|
128
|
-
getAuthToken, loginSkipped, extra, setExtra, authClient
|
|
129
|
-
]);
|
|
130
|
-
}
|
|
@@ -1,295 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* React hook for using backend storage API as a StorageSource
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { useMemo, useCallback } from "react";
|
|
6
|
-
import {
|
|
7
|
-
StorageSource,
|
|
8
|
-
UploadFileProps,
|
|
9
|
-
UploadFileResult,
|
|
10
|
-
DownloadConfig,
|
|
11
|
-
StorageListResult
|
|
12
|
-
} from "@rebasepro/types";
|
|
13
|
-
import { DEFAULT_API_PATH } from "./ApiConfigContext";
|
|
14
|
-
|
|
15
|
-
export interface BackendStorageSourceProps {
|
|
16
|
-
/**
|
|
17
|
-
* Backend API URL (e.g., 'http://localhost:3001')
|
|
18
|
-
*/
|
|
19
|
-
apiUrl: string;
|
|
20
|
-
/**
|
|
21
|
-
* The path the backend mounts its API under. Defaults to the server's own
|
|
22
|
-
* default; pass the backend's `basePath` if it was configured otherwise.
|
|
23
|
-
*/
|
|
24
|
-
apiPath?: string;
|
|
25
|
-
/**
|
|
26
|
-
* Function to get the current auth token
|
|
27
|
-
*/
|
|
28
|
-
getAuthToken: () => Promise<string>;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Hook to create a StorageSource that uses the backend storage REST API.
|
|
33
|
-
* Use this for self-hosted Rebase with local or S3 storage.
|
|
34
|
-
*
|
|
35
|
-
* @example
|
|
36
|
-
* ```tsx
|
|
37
|
-
* const storageSource = useBackendStorageSource({
|
|
38
|
-
* apiUrl: 'http://localhost:3001',
|
|
39
|
-
* getAuthToken: authController.getAuthToken
|
|
40
|
-
* });
|
|
41
|
-
*
|
|
42
|
-
* // Then pass to Rebase:
|
|
43
|
-
* <Rebase storageSource={storageSource} ... />
|
|
44
|
-
* ```
|
|
45
|
-
*/
|
|
46
|
-
export function useBackendStorageSource({
|
|
47
|
-
apiUrl,
|
|
48
|
-
apiPath = DEFAULT_API_PATH,
|
|
49
|
-
getAuthToken
|
|
50
|
-
}: BackendStorageSourceProps): StorageSource {
|
|
51
|
-
|
|
52
|
-
const storageBasePath = `${apiUrl.replace(/\/+$/, "")}${apiPath}/storage`;
|
|
53
|
-
|
|
54
|
-
// Cache for download URLs to avoid redundant API calls
|
|
55
|
-
const urlsCache = useMemo(() => new Map<string, DownloadConfig>(), []);
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Make an authenticated request to the storage API
|
|
59
|
-
*/
|
|
60
|
-
const fetchWithAuth = useCallback(async (
|
|
61
|
-
url: string,
|
|
62
|
-
options: RequestInit = {}
|
|
63
|
-
): Promise<Response> => {
|
|
64
|
-
const token = await getAuthToken();
|
|
65
|
-
return fetch(url, {
|
|
66
|
-
...options,
|
|
67
|
-
headers: {
|
|
68
|
-
...options.headers,
|
|
69
|
-
"Authorization": `Bearer ${token}`
|
|
70
|
-
}
|
|
71
|
-
});
|
|
72
|
-
}, [getAuthToken]);
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Upload a file to storage
|
|
76
|
-
*/
|
|
77
|
-
const putObject = useCallback(async ({
|
|
78
|
-
file,
|
|
79
|
-
key,
|
|
80
|
-
metadata,
|
|
81
|
-
bucket
|
|
82
|
-
}: UploadFileProps): Promise<UploadFileResult> => {
|
|
83
|
-
const formData = new FormData();
|
|
84
|
-
formData.append("file", file);
|
|
85
|
-
|
|
86
|
-
if (key) {
|
|
87
|
-
formData.append("key", key);
|
|
88
|
-
}
|
|
89
|
-
if (bucket) {
|
|
90
|
-
formData.append("bucket", bucket);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
// Add metadata fields with prefix
|
|
94
|
-
if (metadata) {
|
|
95
|
-
for (const [key, value] of Object.entries(metadata)) {
|
|
96
|
-
if (value !== undefined && value !== null) {
|
|
97
|
-
formData.append(
|
|
98
|
-
`metadata_${key}`,
|
|
99
|
-
typeof value === "string" ? value : JSON.stringify(value)
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const response = await fetchWithAuth(`${storageBasePath}/upload`, {
|
|
106
|
-
method: "POST",
|
|
107
|
-
body: formData
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
if (!response.ok) {
|
|
111
|
-
const error = await response.json().catch(() => ({ error: "Upload failed" }));
|
|
112
|
-
throw new Error(error.error || "Upload failed");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const result = await response.json();
|
|
116
|
-
return result.data;
|
|
117
|
-
}, [fetchWithAuth, storageBasePath]);
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Get download URL for a file
|
|
121
|
-
*/
|
|
122
|
-
const getSignedUrl = useCallback(async (
|
|
123
|
-
keyOrUrl: string,
|
|
124
|
-
bucket?: string
|
|
125
|
-
): Promise<DownloadConfig> => {
|
|
126
|
-
// Check cache first
|
|
127
|
-
const cacheKey = bucket ? `${bucket}/${keyOrUrl}` : keyOrUrl;
|
|
128
|
-
const cached = urlsCache.get(cacheKey);
|
|
129
|
-
if (cached) {
|
|
130
|
-
return cached;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// Build the file path for the API
|
|
134
|
-
let filePath = keyOrUrl;
|
|
135
|
-
|
|
136
|
-
// Handle local:// and s3:// URLs
|
|
137
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
|
|
138
|
-
const withoutProtocol = filePath.substring(filePath.indexOf("://") + 3);
|
|
139
|
-
filePath = withoutProtocol;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// If bucket is provided separately, prepend it
|
|
143
|
-
if (bucket && filePath && !filePath.startsWith(bucket)) {
|
|
144
|
-
filePath = `${bucket}/${filePath}`;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (!filePath || filePath.trim() === "" || filePath === "/") {
|
|
148
|
-
return { url: null,
|
|
149
|
-
fileNotFound: true };
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
const response = await fetchWithAuth(`${storageBasePath}/metadata/${filePath}`);
|
|
153
|
-
|
|
154
|
-
if (response.status === 404) {
|
|
155
|
-
return {
|
|
156
|
-
url: null,
|
|
157
|
-
fileNotFound: true
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
if (!response.ok) {
|
|
162
|
-
const error = await response.json().catch(() => ({ error: "Failed to get download URL" }));
|
|
163
|
-
throw new Error(error.error || "Failed to get download URL");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
const result = await response.json();
|
|
167
|
-
|
|
168
|
-
const token = await getAuthToken();
|
|
169
|
-
const tokenQuery = token ? `?token=${token}` : "";
|
|
170
|
-
|
|
171
|
-
// The URL should point to the storage file endpoint
|
|
172
|
-
const downloadConfig: DownloadConfig = {
|
|
173
|
-
url: `${storageBasePath}/file/${filePath}${tokenQuery}`,
|
|
174
|
-
metadata: result.data
|
|
175
|
-
};
|
|
176
|
-
|
|
177
|
-
// Cache the result
|
|
178
|
-
urlsCache.set(cacheKey, downloadConfig);
|
|
179
|
-
|
|
180
|
-
return downloadConfig;
|
|
181
|
-
}, [fetchWithAuth, storageBasePath, urlsCache]);
|
|
182
|
-
|
|
183
|
-
/**
|
|
184
|
-
* Get file as a File object
|
|
185
|
-
*/
|
|
186
|
-
const getObject = useCallback(async (
|
|
187
|
-
key: string,
|
|
188
|
-
bucket?: string
|
|
189
|
-
): Promise<File | null> => {
|
|
190
|
-
let filePath = key;
|
|
191
|
-
|
|
192
|
-
// Handle protocol URLs
|
|
193
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
|
|
194
|
-
const withoutProtocol = filePath.substring(filePath.indexOf("://") + 3);
|
|
195
|
-
filePath = withoutProtocol;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (bucket && filePath && !filePath.startsWith(bucket)) {
|
|
199
|
-
filePath = `${bucket}/${filePath}`;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (!filePath || filePath.trim() === "" || filePath === "/") {
|
|
203
|
-
return null;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const response = await fetchWithAuth(`${storageBasePath}/file/${filePath}`);
|
|
207
|
-
|
|
208
|
-
if (response.status === 404) {
|
|
209
|
-
return null;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (!response.ok) {
|
|
213
|
-
throw new Error("Failed to get file");
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const blob = await response.blob();
|
|
217
|
-
const fileName = filePath.split("/").pop() || "file";
|
|
218
|
-
return new File([blob], fileName, { type: blob.type });
|
|
219
|
-
}, [fetchWithAuth, storageBasePath]);
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Delete a file
|
|
223
|
-
*/
|
|
224
|
-
const deleteObject = useCallback(async (
|
|
225
|
-
key: string,
|
|
226
|
-
bucket?: string
|
|
227
|
-
): Promise<void> => {
|
|
228
|
-
let filePath = key;
|
|
229
|
-
|
|
230
|
-
// Handle protocol URLs
|
|
231
|
-
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
|
|
232
|
-
const withoutProtocol = filePath.substring(filePath.indexOf("://") + 3);
|
|
233
|
-
filePath = withoutProtocol;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
if (bucket && filePath && !filePath.startsWith(bucket)) {
|
|
237
|
-
filePath = `${bucket}/${filePath}`;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (!filePath || filePath.trim() === "" || filePath === "/") {
|
|
241
|
-
return;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
const response = await fetchWithAuth(`${storageBasePath}/file/${filePath}`, {
|
|
245
|
-
method: "DELETE"
|
|
246
|
-
});
|
|
247
|
-
|
|
248
|
-
if (!response.ok && response.status !== 404) {
|
|
249
|
-
const error = await response.json().catch(() => ({ error: "Failed to delete file" }));
|
|
250
|
-
throw new Error(error.error || "Failed to delete file");
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Clear from cache
|
|
254
|
-
urlsCache.delete(bucket ? `${bucket}/${key}` : key);
|
|
255
|
-
}, [fetchWithAuth, storageBasePath, urlsCache]);
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* List files in a path
|
|
259
|
-
*/
|
|
260
|
-
const listObjects = useCallback(async (
|
|
261
|
-
prefix: string,
|
|
262
|
-
options?: {
|
|
263
|
-
bucket?: string;
|
|
264
|
-
maxResults?: number;
|
|
265
|
-
pageToken?: string;
|
|
266
|
-
}
|
|
267
|
-
): Promise<StorageListResult> => {
|
|
268
|
-
const params = new URLSearchParams();
|
|
269
|
-
if (prefix) params.set("prefix", prefix);
|
|
270
|
-
if (options?.bucket) params.set("bucket", options.bucket);
|
|
271
|
-
if (options?.maxResults) params.set("maxResults", String(options.maxResults));
|
|
272
|
-
if (options?.pageToken) params.set("pageToken", options.pageToken);
|
|
273
|
-
|
|
274
|
-
const response = await fetchWithAuth(
|
|
275
|
-
`${storageBasePath}/list?${params.toString()}`
|
|
276
|
-
);
|
|
277
|
-
|
|
278
|
-
if (!response.ok) {
|
|
279
|
-
const error = await response.json().catch(() => ({ error: "Failed to list files" }));
|
|
280
|
-
throw new Error(error.error || "Failed to list files");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
const result = await response.json();
|
|
284
|
-
return result.data;
|
|
285
|
-
}, [fetchWithAuth, storageBasePath]);
|
|
286
|
-
|
|
287
|
-
// Return memoized StorageSource
|
|
288
|
-
return useMemo<StorageSource>(() => ({
|
|
289
|
-
putObject,
|
|
290
|
-
getSignedUrl,
|
|
291
|
-
getObject,
|
|
292
|
-
deleteObject,
|
|
293
|
-
listObjects
|
|
294
|
-
}), [putObject, getSignedUrl, getObject, deleteObject, listObjects]);
|
|
295
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { useContext, useEffect } from "react";
|
|
2
|
-
import type { StudioBridge } from "./useStudioBridge";
|
|
3
|
-
import { StudioBridgeRegistryContext } from "./useStudioBridge";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Registers a value into the self-assembling Studio bridge.
|
|
7
|
-
*
|
|
8
|
-
* Each controller (collectionRegistry, sidePanel, url, navigation, breadcrumbs)
|
|
9
|
-
* calls this hook on mount to inject its real implementation into the bridge.
|
|
10
|
-
* On unmount the slice is automatically removed, reverting to the noop default.
|
|
11
|
-
*
|
|
12
|
-
* Usage:
|
|
13
|
-
* ```tsx
|
|
14
|
-
* function SomeProvider({ children }) {
|
|
15
|
-
* const controller = useBuildSomeController();
|
|
16
|
-
* useBridgeRegistration("sidePanelController", controller);
|
|
17
|
-
* return <SomeContext.Provider value={controller}>{children}</SomeContext.Provider>;
|
|
18
|
-
* }
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export function useBridgeRegistration<K extends keyof StudioBridge>(
|
|
22
|
-
key: K,
|
|
23
|
-
value: StudioBridge[K]
|
|
24
|
-
): void {
|
|
25
|
-
const registry = useContext(StudioBridgeRegistryContext);
|
|
26
|
-
|
|
27
|
-
useEffect(() => {
|
|
28
|
-
if (!registry) return;
|
|
29
|
-
registry.register(key, value);
|
|
30
|
-
return () => registry.unregister(key);
|
|
31
|
-
}, [registry, key, value]);
|
|
32
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { useEffect } from "react";
|
|
2
|
-
|
|
3
|
-
const rebaseLogo = "data:image/png;base64,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";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Internal hook to handle the browser title and icon
|
|
7
|
-
* @param name
|
|
8
|
-
* @param logo
|
|
9
|
-
*/
|
|
10
|
-
export function useBrowserTitleAndIcon(name: string, logo?: string) {
|
|
11
|
-
useEffect(() => {
|
|
12
|
-
if (document) {
|
|
13
|
-
document.title = `${name} - Rebase`;
|
|
14
|
-
let link = document.querySelector("link[rel~='icon']") as HTMLLinkElement | null;
|
|
15
|
-
if (!link) {
|
|
16
|
-
link = document.createElement("link");
|
|
17
|
-
link.rel = "icon";
|
|
18
|
-
document.getElementsByTagName("head")[0].appendChild(link);
|
|
19
|
-
}
|
|
20
|
-
link.href = logo ?? rebaseLogo;
|
|
21
|
-
}
|
|
22
|
-
}, [name, logo]);
|
|
23
|
-
}
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { useCallback, useMemo, useState } from "react";
|
|
2
|
-
import type { AdminModeController } from "./useAdminModeController";
|
|
3
|
-
import { readStoredString, writeStoredString } from "../util/local_storage";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Use this hook to build an admin mode controller that determines
|
|
7
|
-
* whether the UI shows Developer or Editor tools.
|
|
8
|
-
*/
|
|
9
|
-
export function useBuildAdminModeController(): AdminModeController {
|
|
10
|
-
|
|
11
|
-
// The content mode was renamed to "cms" when the panel's CMS half became a
|
|
12
|
-
// product name. The value is persisted, so a browser that used the panel
|
|
13
|
-
// before the rename still has "content" in local storage — read as-is it
|
|
14
|
-
// casts to a mode nothing matches, and the drawer renders neither half.
|
|
15
|
-
// Migrating on read is the exception the no-shims rule allows for stored data.
|
|
16
|
-
const stored = readStoredString("rebase-admin-mode");
|
|
17
|
-
const savedMode = (stored === "content" ? "cms" : stored) as "cms" | "studio" | "settings" | null;
|
|
18
|
-
const [mode, setMode] = useState<"cms" | "studio" | "settings">(savedMode ?? "cms");
|
|
19
|
-
|
|
20
|
-
const setModeInternal = useCallback((newMode: "cms" | "studio" | "settings") => {
|
|
21
|
-
writeStoredString("rebase-admin-mode", newMode);
|
|
22
|
-
setMode(newMode);
|
|
23
|
-
}, []);
|
|
24
|
-
|
|
25
|
-
return useMemo(() => ({
|
|
26
|
-
mode,
|
|
27
|
-
setMode: setModeInternal
|
|
28
|
-
}), [mode, setModeInternal]);
|
|
29
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import type { EffectiveRoleController } from "@rebasepro/types";
|
|
2
|
-
import { useCallback, useState, useMemo } from "react";
|
|
3
|
-
import { readStoredString, removeStoredString, writeStoredString } from "../util/local_storage";
|
|
4
|
-
;
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Use this hook to build an effective role controller that determines
|
|
8
|
-
* what role is simulated in Editor mode when Dev mode is active.
|
|
9
|
-
*
|
|
10
|
-
* It uses localStorage to persist the simulated role across reloads.
|
|
11
|
-
*/
|
|
12
|
-
export function useBuildEffectiveRoleController(): EffectiveRoleController {
|
|
13
|
-
|
|
14
|
-
const savedRole = readStoredString("rebase-effective-role");
|
|
15
|
-
const [effectiveRole, setEffectiveRole] = useState<string | null>(savedRole ?? null);
|
|
16
|
-
|
|
17
|
-
const setRoleInternal = useCallback((newRole: string | null) => {
|
|
18
|
-
if (newRole) {
|
|
19
|
-
writeStoredString("rebase-effective-role", newRole);
|
|
20
|
-
} else {
|
|
21
|
-
removeStoredString("rebase-effective-role");
|
|
22
|
-
}
|
|
23
|
-
setEffectiveRole(newRole);
|
|
24
|
-
}, []);
|
|
25
|
-
|
|
26
|
-
return useMemo(() => ({
|
|
27
|
-
effectiveRole,
|
|
28
|
-
setEffectiveRole: setRoleInternal
|
|
29
|
-
}), [effectiveRole, setRoleInternal]);
|
|
30
|
-
}
|