@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
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { StorageSource } from "@rebasepro/types";
|
|
2
|
-
import { StorageSourceContext } from "../contexts/StorageSourceContext";
|
|
3
|
-
import { useContext } from "react";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Use this hook to get the storage source being used
|
|
7
|
-
* @group Hooks and utilities
|
|
8
|
-
*/
|
|
9
|
-
export const useStorageSource = (): StorageSource => {
|
|
10
|
-
return useContext(StorageSourceContext);
|
|
11
|
-
};
|
|
@@ -1,258 +0,0 @@
|
|
|
1
|
-
import React, { createContext, useCallback, useContext, useMemo, useRef, useState } from "react";
|
|
2
|
-
import type {
|
|
3
|
-
CollectionRegistryController
|
|
4
|
-
} from "@rebasepro/types";
|
|
5
|
-
import type {
|
|
6
|
-
SidePanelController,
|
|
7
|
-
UrlController,
|
|
8
|
-
NavigationStateController,
|
|
9
|
-
BreadcrumbEntry,
|
|
10
|
-
BreadcrumbsController
|
|
11
|
-
} from "@rebasepro/cms-types";
|
|
12
|
-
|
|
13
|
-
export type { BreadcrumbEntry, BreadcrumbsController };
|
|
14
|
-
|
|
15
|
-
// ─── Breadcrumbs ──────────────────────────────────────────────────
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
// ─── Bridge interface ───────────────────────────────────────────────
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* StudioBridge provides optional admin capabilities to Studio components.
|
|
22
|
-
* When the admin is present, a bridge provider injects real implementations.
|
|
23
|
-
* When the admin is absent, noop defaults ensure Studio works standalone.
|
|
24
|
-
*/
|
|
25
|
-
export interface StudioBridge {
|
|
26
|
-
collectionRegistry: CollectionRegistryController;
|
|
27
|
-
sidePanelController: SidePanelController;
|
|
28
|
-
urlController: UrlController;
|
|
29
|
-
navigationState: NavigationStateController;
|
|
30
|
-
breadcrumbs: BreadcrumbsController;
|
|
31
|
-
capabilities: StudioCapabilities;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* What the *host* of these tools can do, as opposed to what the backend can.
|
|
36
|
-
*
|
|
37
|
-
* Studio is mounted in two very different places. In a project's own admin
|
|
38
|
-
* panel it runs next to the collection source files and can edit them through
|
|
39
|
-
* the schema-editor routes. In the hosted console it runs against somebody
|
|
40
|
-
* else's deployed container: there is no source to edit — the container is
|
|
41
|
-
* rebuilt from the customer's repository on every deploy — and the routes that
|
|
42
|
-
* would edit it are not mounted at all, because the framework switches the
|
|
43
|
-
* schema editor off under `NODE_ENV=production`.
|
|
44
|
-
*
|
|
45
|
-
* Tools that would otherwise offer a write into the codebase read this to
|
|
46
|
-
* decide whether that write is even meaningful.
|
|
47
|
-
*/
|
|
48
|
-
export interface StudioCapabilities {
|
|
49
|
-
/**
|
|
50
|
-
* Whether the host has the project's collection source at hand and can
|
|
51
|
-
* write to it.
|
|
52
|
-
*
|
|
53
|
-
* Defaults to `true`, which is what an admin panel running beside its own
|
|
54
|
-
* `collectionsDir` has always assumed.
|
|
55
|
-
*/
|
|
56
|
-
codebase: boolean;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
// ─── Noop defaults ──────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
const NOOP_COLLECTION_REGISTRY: CollectionRegistryController = {
|
|
62
|
-
getCollection: () => undefined,
|
|
63
|
-
getRawCollection: () => undefined,
|
|
64
|
-
getParentReferencesFromPath: () => [],
|
|
65
|
-
getParentCollectionSlugs: () => [],
|
|
66
|
-
getParentEntityIds: () => [],
|
|
67
|
-
convertIdsToPaths: () => [],
|
|
68
|
-
initialised: false
|
|
69
|
-
};
|
|
70
|
-
|
|
71
|
-
const NOOP_SIDE_PANEL: SidePanelController = {
|
|
72
|
-
open: () => {},
|
|
73
|
-
replace: () => {},
|
|
74
|
-
close: () => {}
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
const NOOP_URL_CONTROLLER: UrlController = {
|
|
78
|
-
basePath: "/",
|
|
79
|
-
baseCollectionPath: "/c",
|
|
80
|
-
urlPathToDataPath: () => "",
|
|
81
|
-
homeUrl: "/",
|
|
82
|
-
isUrlCollectionPath: () => false,
|
|
83
|
-
buildUrlCollectionPath: () => "",
|
|
84
|
-
buildAppUrlPath: () => "",
|
|
85
|
-
resolveDatabasePathsFrom: () => "",
|
|
86
|
-
navigate: () => {}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const NOOP_NAVIGATION_STATE: NavigationStateController = {
|
|
90
|
-
loading: false,
|
|
91
|
-
refreshNavigation: () => {}
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
const NOOP_BREADCRUMBS: BreadcrumbsController = {
|
|
95
|
-
breadcrumbs: [],
|
|
96
|
-
set: () => {}
|
|
97
|
-
};
|
|
98
|
-
|
|
99
|
-
const DEFAULT_CAPABILITIES: StudioCapabilities = {
|
|
100
|
-
codebase: true
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const NOOP_BRIDGE: StudioBridge = {
|
|
104
|
-
collectionRegistry: NOOP_COLLECTION_REGISTRY,
|
|
105
|
-
sidePanelController: NOOP_SIDE_PANEL,
|
|
106
|
-
urlController: NOOP_URL_CONTROLLER,
|
|
107
|
-
navigationState: NOOP_NAVIGATION_STATE,
|
|
108
|
-
breadcrumbs: NOOP_BREADCRUMBS,
|
|
109
|
-
capabilities: DEFAULT_CAPABILITIES
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
// ─── Context & Provider ─────────────────────────────────────────────
|
|
113
|
-
|
|
114
|
-
export const StudioBridgeContext = createContext<StudioBridge>(NOOP_BRIDGE);
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Provider that injects admin capabilities into Studio.
|
|
118
|
-
* Accepts partial overrides — any field not provided falls back to noop.
|
|
119
|
-
*
|
|
120
|
-
* Usage (in app wiring, when the admin is present):
|
|
121
|
-
* ```tsx
|
|
122
|
-
* <StudioBridgeProvider value={{
|
|
123
|
-
* collectionRegistry: useCollectionRegistryController(),
|
|
124
|
-
* sidePanelController: useSidePanel(),
|
|
125
|
-
* urlController: useUrlController(),
|
|
126
|
-
* navigationState: useNavigationStateController(),
|
|
127
|
-
* breadcrumbs: useBreadcrumbsController(),
|
|
128
|
-
* }}>
|
|
129
|
-
* <RebaseStudio ... />
|
|
130
|
-
* </StudioBridgeProvider>
|
|
131
|
-
* ```
|
|
132
|
-
*/
|
|
133
|
-
export function StudioBridgeProvider({
|
|
134
|
-
value,
|
|
135
|
-
children
|
|
136
|
-
}: {
|
|
137
|
-
value: Partial<StudioBridge>;
|
|
138
|
-
children: React.ReactNode;
|
|
139
|
-
}) {
|
|
140
|
-
// Keyed on the slices rather than on `value`: callers write the object
|
|
141
|
-
// inline (`value={{ collectionRegistry }}`), so a `[value]` dependency
|
|
142
|
-
// rebuilt the context on every render of the host and re-rendered every
|
|
143
|
-
// Studio consumer with it.
|
|
144
|
-
const merged = React.useMemo(
|
|
145
|
-
() => ({ ...NOOP_BRIDGE,
|
|
146
|
-
...value }),
|
|
147
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
148
|
-
[
|
|
149
|
-
value.collectionRegistry,
|
|
150
|
-
value.sidePanelController,
|
|
151
|
-
value.urlController,
|
|
152
|
-
value.navigationState,
|
|
153
|
-
value.breadcrumbs,
|
|
154
|
-
value.capabilities
|
|
155
|
-
]
|
|
156
|
-
);
|
|
157
|
-
return (
|
|
158
|
-
<StudioBridgeContext.Provider value={merged}>
|
|
159
|
-
{children}
|
|
160
|
-
</StudioBridgeContext.Provider>
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// ─── Convenience hooks ──────────────────────────────────────────────
|
|
165
|
-
|
|
166
|
-
/** Collection registry — returns noop if the admin is not present. */
|
|
167
|
-
export function useStudioCollectionRegistry(): CollectionRegistryController {
|
|
168
|
-
return useContext(StudioBridgeContext).collectionRegistry;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/** Side panel controller — returns noop if the admin is not present. */
|
|
172
|
-
export function useStudioSidePanelController(): SidePanelController {
|
|
173
|
-
return useContext(StudioBridgeContext).sidePanelController;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
/** URL controller — returns noop if the admin is not present. */
|
|
177
|
-
export function useStudioUrlController(): UrlController {
|
|
178
|
-
return useContext(StudioBridgeContext).urlController;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/** Navigation state — returns noop if the admin is not present. */
|
|
182
|
-
export function useStudioNavigationState(): NavigationStateController {
|
|
183
|
-
return useContext(StudioBridgeContext).navigationState;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/** Breadcrumbs controller — returns noop if the admin is not present. */
|
|
187
|
-
export function useStudioBreadcrumbs(): BreadcrumbsController {
|
|
188
|
-
return useContext(StudioBridgeContext).breadcrumbs;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/** What the host can do — see {@link StudioCapabilities}. */
|
|
192
|
-
export function useStudioCapabilities(): StudioCapabilities {
|
|
193
|
-
return useContext(StudioBridgeContext).capabilities;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// ─── Self-Assembling Bridge Registry ────────────────────────────────
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Registry that controllers use to self-register their implementations
|
|
200
|
-
* into the Studio bridge. Each controller calls `register(key, value)`
|
|
201
|
-
* on mount and `unregister(key)` on unmount.
|
|
202
|
-
*/
|
|
203
|
-
export interface StudioBridgeRegistry {
|
|
204
|
-
register: <K extends keyof StudioBridge>(key: K, value: StudioBridge[K]) => void;
|
|
205
|
-
unregister: (key: keyof StudioBridge) => void;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
export const StudioBridgeRegistryContext = createContext<StudioBridgeRegistry | null>(null);
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Provider that creates a self-assembling bridge.
|
|
212
|
-
*
|
|
213
|
-
* Mount this above the controller providers. Each controller calls
|
|
214
|
-
* `useBridgeRegistration(key, value)` to inject its implementation.
|
|
215
|
-
* The bridge context value is automatically kept in sync.
|
|
216
|
-
*
|
|
217
|
-
* ```tsx
|
|
218
|
-
* <StudioBridgeRegistryProvider>
|
|
219
|
-
* <CollectionRegistryProvider> // auto-registers
|
|
220
|
-
* <SidePanelProvider> // auto-registers
|
|
221
|
-
* <UrlProvider> // auto-registers
|
|
222
|
-
* <RebaseStudio /> // consumes bridge
|
|
223
|
-
* </UrlProvider>
|
|
224
|
-
* </SidePanelProvider>
|
|
225
|
-
* </CollectionRegistryProvider>
|
|
226
|
-
* </StudioBridgeRegistryProvider>
|
|
227
|
-
* ```
|
|
228
|
-
*/
|
|
229
|
-
export function StudioBridgeRegistryProvider({ children }: { children: React.ReactNode }) {
|
|
230
|
-
const [version, setVersion] = useState(0);
|
|
231
|
-
const slicesRef = useRef<Partial<StudioBridge>>({});
|
|
232
|
-
|
|
233
|
-
const register = useCallback(<K extends keyof StudioBridge>(key: K, value: StudioBridge[K]) => {
|
|
234
|
-
slicesRef.current[key] = value;
|
|
235
|
-
setVersion(v => v + 1);
|
|
236
|
-
}, []);
|
|
237
|
-
|
|
238
|
-
const unregister = useCallback((key: keyof StudioBridge) => {
|
|
239
|
-
delete slicesRef.current[key];
|
|
240
|
-
setVersion(v => v + 1);
|
|
241
|
-
}, []);
|
|
242
|
-
|
|
243
|
-
const registry = useMemo<StudioBridgeRegistry>(() => ({ register,
|
|
244
|
-
unregister }), [register, unregister]);
|
|
245
|
-
|
|
246
|
-
const bridgeValue = useMemo<StudioBridge>(() => ({
|
|
247
|
-
...NOOP_BRIDGE,
|
|
248
|
-
...slicesRef.current
|
|
249
|
-
}), [version]);
|
|
250
|
-
|
|
251
|
-
return (
|
|
252
|
-
<StudioBridgeRegistryContext.Provider value={registry}>
|
|
253
|
-
<StudioBridgeContext.Provider value={bridgeValue}>
|
|
254
|
-
{children}
|
|
255
|
-
</StudioBridgeContext.Provider>
|
|
256
|
-
</StudioBridgeRegistryContext.Provider>
|
|
257
|
-
);
|
|
258
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import { useTranslation as useI18nTranslation } from "react-i18next";
|
|
2
|
-
import { useCallback, useMemo } from "react";
|
|
3
|
-
|
|
4
|
-
const REBASE_NS = "rebase_core";
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Internal hook for translating Rebase UI strings.
|
|
8
|
-
*
|
|
9
|
-
* Uses the `rebase_core` i18next namespace that is initialised by
|
|
10
|
-
* `RebaseI18nProvider`. Do NOT use `react-i18next` directly in internal
|
|
11
|
-
* components — always go through this hook so the namespace is consistent.
|
|
12
|
-
*
|
|
13
|
-
* @example
|
|
14
|
-
* const { t } = useTranslation();
|
|
15
|
-
* <Button>{t("save")}</Button>
|
|
16
|
-
*
|
|
17
|
-
* @internal
|
|
18
|
-
*/
|
|
19
|
-
export function useTranslation() {
|
|
20
|
-
const { t, i18n } = useI18nTranslation(REBASE_NS);
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Typed translation function scoped to RebaseTranslations keys.
|
|
24
|
-
* Also supports i18next interpolation variables, e.g.
|
|
25
|
-
* t("add_to_field", { fieldName: "Tags" })
|
|
26
|
-
* t("error_deleting", { message: err.message })
|
|
27
|
-
* t("sort_key_position", { position: 2 })
|
|
28
|
-
*
|
|
29
|
-
* Numbers are accepted as well as strings: i18next interpolates them
|
|
30
|
-
* either way, and a signature that took only strings pushed a `String()`
|
|
31
|
-
* onto every call site with a count in it — `{{count}}`, `{{position}}`.
|
|
32
|
-
*/
|
|
33
|
-
const typedT = useCallback((key: string, vars?: Record<string, string | number>): string =>
|
|
34
|
-
t(key, vars) as string, [t]);
|
|
35
|
-
|
|
36
|
-
return useMemo(() => ({ t: typedT,
|
|
37
|
-
i18n }), [typedT, i18n]);
|
|
38
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { useCallback, useEffect, useState, useMemo } from "react";
|
|
2
|
-
import { UnsavedChangesDialogProps } from "../components/UnsavedChangesDialog";
|
|
3
|
-
import { useNavigationBlocker } from "./useNavigationBlocker";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* A single, unified hook to prevent navigation when there are unsaved changes.
|
|
7
|
-
*
|
|
8
|
-
* It automatically handles:
|
|
9
|
-
* 1. Internal React Router navigation using `useBlocker`.
|
|
10
|
-
* 2. External browser navigation (page refresh, tab close) using `beforeunload`.
|
|
11
|
-
*/
|
|
12
|
-
export function useUnsavedChangesDialog(
|
|
13
|
-
when: boolean,
|
|
14
|
-
onOk: () => void
|
|
15
|
-
): {
|
|
16
|
-
dialogProps: UnsavedChangesDialogProps;
|
|
17
|
-
triggerDialog: () => void;
|
|
18
|
-
} {
|
|
19
|
-
const [manualDialogOpen, setManualDialogOpen] = useState(false);
|
|
20
|
-
|
|
21
|
-
const blocker = useNavigationBlocker(
|
|
22
|
-
useCallback(
|
|
23
|
-
({ currentLocation, nextLocation }) =>
|
|
24
|
-
when && currentLocation.pathname !== nextLocation.pathname,
|
|
25
|
-
[when]
|
|
26
|
-
)
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
useEffect(() => {
|
|
30
|
-
if (!when) return;
|
|
31
|
-
|
|
32
|
-
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
|
33
|
-
e.preventDefault();
|
|
34
|
-
e.returnValue = "";
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
window.addEventListener("beforeunload", handleBeforeUnload);
|
|
38
|
-
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
|
39
|
-
}, [when]);
|
|
40
|
-
|
|
41
|
-
const handleOk = useCallback(() => {
|
|
42
|
-
onOk();
|
|
43
|
-
if (blocker.state === "blocked") {
|
|
44
|
-
blocker.proceed();
|
|
45
|
-
}
|
|
46
|
-
setManualDialogOpen(false);
|
|
47
|
-
}, [blocker, onOk]);
|
|
48
|
-
|
|
49
|
-
const handleCancel = useCallback(() => {
|
|
50
|
-
if (blocker.state === "blocked") {
|
|
51
|
-
blocker.reset();
|
|
52
|
-
}
|
|
53
|
-
setManualDialogOpen(false);
|
|
54
|
-
}, [blocker]);
|
|
55
|
-
|
|
56
|
-
const triggerDialog = useCallback(() => setManualDialogOpen(true), []);
|
|
57
|
-
|
|
58
|
-
return useMemo(() => ({
|
|
59
|
-
dialogProps: {
|
|
60
|
-
open: manualDialogOpen || blocker.state === "blocked",
|
|
61
|
-
handleOk,
|
|
62
|
-
handleCancel,
|
|
63
|
-
body: "There are unsaved changes in this collection"
|
|
64
|
-
},
|
|
65
|
-
triggerDialog
|
|
66
|
-
}), [manualDialogOpen, blocker.state, handleOk, handleCancel, triggerDialog]);
|
|
67
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { UserConfigurationPersistence } from "@rebasepro/cms-types";
|
|
2
|
-
import { useContext } from "react";
|
|
3
|
-
import { UserConfigurationPersistenceContext } from "../contexts/UserConfigurationPersistenceContext";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Use this controller to access the configuration that is stored externally,
|
|
7
|
-
* and not defined in code
|
|
8
|
-
*
|
|
9
|
-
* @group Hooks and utilities
|
|
10
|
-
*/
|
|
11
|
-
export const useUserConfigurationPersistence = (): UserConfigurationPersistence | undefined => useContext(UserConfigurationPersistenceContext);
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
import React, { PropsWithChildren, useEffect, useRef } from "react";
|
|
2
|
-
import i18next, { i18n } from "i18next";
|
|
3
|
-
import { I18nextProvider, initReactI18next } from "react-i18next";
|
|
4
|
-
import { en } from "../locales/en";
|
|
5
|
-
import { es } from "../locales/es";
|
|
6
|
-
import { de } from "../locales/de";
|
|
7
|
-
import { fr } from "../locales/fr";
|
|
8
|
-
import { it } from "../locales/it";
|
|
9
|
-
import { hi } from "../locales/hi";
|
|
10
|
-
import { pt } from "../locales/pt";
|
|
11
|
-
import { RebaseTranslations } from "@rebasepro/cms-types";
|
|
12
|
-
import { readStoredString, writeStoredString } from "../util/local_storage";
|
|
13
|
-
|
|
14
|
-
const REBASE_NS = "rebase_core";
|
|
15
|
-
|
|
16
|
-
export const REBASE_LOCALE_STORAGE_KEY = "rebase_locale";
|
|
17
|
-
|
|
18
|
-
/** DeepPartial helper — allows partial overrides at any nesting level */
|
|
19
|
-
type DeepPartial<T> = T extends object
|
|
20
|
-
? { [K in keyof T]?: DeepPartial<T[K]> }
|
|
21
|
-
: T;
|
|
22
|
-
|
|
23
|
-
export interface RebaseI18nProviderProps {
|
|
24
|
-
/** BCP-47 locale tag, e.g. "en", "es", "fr". Defaults to "en". */
|
|
25
|
-
locale?: string;
|
|
26
|
-
/**
|
|
27
|
-
* Override or extend any Rebase UI string, keyed by locale.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* translations={{
|
|
31
|
-
* en: { save: "Publish" },
|
|
32
|
-
* es: { save: "Publicar", discard: "Descartar" }
|
|
33
|
-
* }}
|
|
34
|
-
*/
|
|
35
|
-
translations?: {
|
|
36
|
-
[locale: string]: DeepPartial<RebaseTranslations>;
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Initialises a dedicated i18next instance for Rebase's internal UI strings.
|
|
42
|
-
*
|
|
43
|
-
* This instance is isolated from any app-level i18next configuration the
|
|
44
|
-
* consumer may have. Mount this at the top of the Rebase component tree.
|
|
45
|
-
*
|
|
46
|
-
* @internal
|
|
47
|
-
*/
|
|
48
|
-
export function RebaseI18nProvider({
|
|
49
|
-
locale = "en",
|
|
50
|
-
translations,
|
|
51
|
-
children
|
|
52
|
-
}: PropsWithChildren<RebaseI18nProviderProps>) {
|
|
53
|
-
const i18nRef = useRef<i18n | null>(null);
|
|
54
|
-
const [ready, setReady] = React.useState(false);
|
|
55
|
-
|
|
56
|
-
if (!i18nRef.current) {
|
|
57
|
-
const instance = i18next.createInstance();
|
|
58
|
-
|
|
59
|
-
// Build the initial resources: English baseline + any consumer overrides
|
|
60
|
-
const resources = buildResources(translations);
|
|
61
|
-
|
|
62
|
-
let initialLocale = locale;
|
|
63
|
-
const stored = readStoredString(REBASE_LOCALE_STORAGE_KEY);
|
|
64
|
-
if (stored) initialLocale = stored;
|
|
65
|
-
|
|
66
|
-
instance
|
|
67
|
-
.use(initReactI18next)
|
|
68
|
-
.init({
|
|
69
|
-
lng: initialLocale,
|
|
70
|
-
fallbackLng: "en",
|
|
71
|
-
ns: [REBASE_NS],
|
|
72
|
-
defaultNS: REBASE_NS,
|
|
73
|
-
resources,
|
|
74
|
-
interpolation: {
|
|
75
|
-
// React already escapes — don't double-escape
|
|
76
|
-
escapeValue: false
|
|
77
|
-
}
|
|
78
|
-
}, () => {
|
|
79
|
-
setReady(true);
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
instance.on("languageChanged", (lng) => {
|
|
83
|
-
if (typeof window !== "undefined") {
|
|
84
|
-
writeStoredString(REBASE_LOCALE_STORAGE_KEY, lng);
|
|
85
|
-
}
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
i18nRef.current = instance;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// When `locale` prop changes, switch language on the existing instance
|
|
92
|
-
// ONLY if the user hasn't explicitly set a preference
|
|
93
|
-
useEffect(() => {
|
|
94
|
-
if (i18nRef.current && i18nRef.current.language !== locale) {
|
|
95
|
-
const hasUserPreference = Boolean(readStoredString(REBASE_LOCALE_STORAGE_KEY));
|
|
96
|
-
if (!hasUserPreference) {
|
|
97
|
-
i18nRef.current.changeLanguage(locale);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}, [locale]);
|
|
101
|
-
|
|
102
|
-
// When consumer translations prop changes, update the resource bundles
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (!i18nRef.current) return;
|
|
105
|
-
const resources = buildResources(translations);
|
|
106
|
-
for (const [lang, bundle] of Object.entries(resources)) {
|
|
107
|
-
i18nRef.current.addResourceBundle(
|
|
108
|
-
lang,
|
|
109
|
-
REBASE_NS,
|
|
110
|
-
bundle[REBASE_NS],
|
|
111
|
-
true, // deep merge
|
|
112
|
-
true // overwrite existing keys
|
|
113
|
-
);
|
|
114
|
-
}
|
|
115
|
-
}, [translations]);
|
|
116
|
-
|
|
117
|
-
if (!ready || !i18nRef.current) return null;
|
|
118
|
-
|
|
119
|
-
return (
|
|
120
|
-
<I18nextProvider i18n={i18nRef.current}>
|
|
121
|
-
{children}
|
|
122
|
-
</I18nextProvider>
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Build an i18next resources object from the English baseline plus any
|
|
128
|
-
* consumer-provided overrides.
|
|
129
|
-
*/
|
|
130
|
-
function buildResources(
|
|
131
|
-
translations?: { [locale: string]: DeepPartial<RebaseTranslations> }
|
|
132
|
-
): Record<string, Record<string, object>> {
|
|
133
|
-
const resources: Record<string, Record<string, object>> = {
|
|
134
|
-
en: { [REBASE_NS]: { ...en } },
|
|
135
|
-
es: { [REBASE_NS]: { ...es } },
|
|
136
|
-
de: { [REBASE_NS]: { ...de } },
|
|
137
|
-
fr: { [REBASE_NS]: { ...fr } },
|
|
138
|
-
it: { [REBASE_NS]: { ...it } },
|
|
139
|
-
hi: { [REBASE_NS]: { ...hi } },
|
|
140
|
-
pt: { [REBASE_NS]: { ...pt } }
|
|
141
|
-
};
|
|
142
|
-
|
|
143
|
-
if (!translations) return resources;
|
|
144
|
-
|
|
145
|
-
for (const [lang, overrides] of Object.entries(translations)) {
|
|
146
|
-
if (!resources[lang]) {
|
|
147
|
-
// For non-English/Spanish locales, start from English as the fallback base
|
|
148
|
-
resources[lang] = { [REBASE_NS]: { ...en } };
|
|
149
|
-
}
|
|
150
|
-
// Merge consumer overrides (shallow merge is enough since translations
|
|
151
|
-
// is a flat record — deepMerge option in addResourceBundle handles deeper)
|
|
152
|
-
resources[lang][REBASE_NS] = {
|
|
153
|
-
...resources[lang][REBASE_NS],
|
|
154
|
-
...overrides
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
return resources;
|
|
159
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export * from "./core";
|
|
2
|
-
export * from "./hooks";
|
|
3
|
-
export * from "./auth";
|
|
4
|
-
export * from "./components";
|
|
5
|
-
export * from "./util";
|
|
6
|
-
export * from "./contexts";
|
|
7
|
-
|
|
8
|
-
// @internal — framework implementation details, exported only because
|
|
9
|
-
// @rebasepro/cms and @rebasepro/studio consume them. Named explicitly
|
|
10
|
-
// (not `export *`) so this file is the single place that grows or shrinks
|
|
11
|
-
// the internal surface — see the JSDoc on each symbol for details.
|
|
12
|
-
export { CONTAINER_FULL_WIDTH, FORM_CONTAINER_WIDTH, SIDE_PANEL_DEFAULT_WIDTH } from "./internal/common";
|
|
13
|
-
export { useRestoreScroll } from "./internal/useRestoreScroll";
|
|
14
|
-
export { useUnsavedChangesDialog } from "./hooks/useUnsavedChangesDialog";
|
|
15
|
-
export { NavigationBlockerProvider, useNavigationBlocker } from "./hooks/useNavigationBlocker";
|
|
16
|
-
export type { UnsavedChangesDialogProps } from "./components/UnsavedChangesDialog";
|
|
17
|
-
// The element type of the `storageSources` prop on `<Rebase>`. Anyone registering a
|
|
18
|
-
// direct-transport backend writes this shape by hand, and the docs have always told
|
|
19
|
-
// them to import it from here — it was simply never exported. Type-only, so it adds
|
|
20
|
-
// nothing to the bundle.
|
|
21
|
-
export type { RebaseStorageSource } from "./core/RebaseProps";
|
|
22
|
-
export { UnsavedChangesDialog } from "./components/UnsavedChangesDialog";
|
|
23
|
-
export * from "./i18n/RebaseI18nProvider";
|
|
24
|
-
export * from "./locales/en";
|
|
25
|
-
export * from "./locales/es";
|
|
26
|
-
|
|
27
|
-
// Studio Bridge — shared context for optional admin↔Studio integration
|
|
28
|
-
export * from "./hooks/useStudioBridge";
|
|
29
|
-
|
|
30
|
-
// Self-assembling bridge registration hook
|
|
31
|
-
export { useBridgeRegistration } from "./hooks/useBridgeRegistration";
|
|
32
|
-
export * from "./collections";
|
package/src/internal/common.tsx
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Layout width primitives used by the framework's own side-panel and form
|
|
3
|
-
* dialog sizing logic.
|
|
4
|
-
*
|
|
5
|
-
* @internal Not part of the stable public API — exported only because
|
|
6
|
-
* `@rebasepro/cms` and `@rebasepro/studio` need them to compute the same
|
|
7
|
-
* dialog widths as the framework. Do not depend on these for app code; they
|
|
8
|
-
* may change shape or be removed without a major version bump.
|
|
9
|
-
*/
|
|
10
|
-
export const CONTAINER_FULL_WIDTH = "100vw";
|
|
11
|
-
|
|
12
|
-
/** @internal See {@link CONTAINER_FULL_WIDTH}. */
|
|
13
|
-
export const FORM_CONTAINER_WIDTH = "768px";
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* How wide the side panel opens when neither the caller nor the collection
|
|
17
|
-
* says otherwise.
|
|
18
|
-
*
|
|
19
|
-
* Sized from what it has to hold rather than from a round number: the form's
|
|
20
|
-
* content column plus its metadata rail plus gutters, which is also the width
|
|
21
|
-
* at which the form stops being a single stack and picks up the same
|
|
22
|
-
* two-column layout it has in full screen. Capped against the viewport so a
|
|
23
|
-
* panel never becomes the whole window on a laptop — at that point you want
|
|
24
|
-
* full screen, and there is a button for it.
|
|
25
|
-
*
|
|
26
|
-
* @internal See {@link CONTAINER_FULL_WIDTH}.
|
|
27
|
-
*/
|
|
28
|
-
export const SIDE_PANEL_DEFAULT_WIDTH = "min(1160px, 88vw)";
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import React, { useCallback, useEffect, useRef } from "react";
|
|
2
|
-
import { useLocation } from "react-router";
|
|
3
|
-
|
|
4
|
-
const scrollsMap: Record<string, number> = {};
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Restores per-route scroll position on a scrollable container, keyed by
|
|
8
|
-
* router location. Used by the framework's own home page implementations.
|
|
9
|
-
*
|
|
10
|
-
* @internal Not part of the stable public API — exported only because
|
|
11
|
-
* `@rebasepro/cms` and `@rebasepro/studio` reuse it in their home page
|
|
12
|
-
* components. Its behavior (module-level scroll cache, router coupling) is
|
|
13
|
-
* an implementation detail and may change without a major version bump.
|
|
14
|
-
*/
|
|
15
|
-
export function useRestoreScroll() {
|
|
16
|
-
|
|
17
|
-
const location = useLocation();
|
|
18
|
-
|
|
19
|
-
const containerRef = useRef<HTMLDivElement>(null);
|
|
20
|
-
const [scroll, setScroll] = React.useState(0);
|
|
21
|
-
const [direction, setDirection] = React.useState<"up" | "down">("down");
|
|
22
|
-
|
|
23
|
-
// Use ref to track previous scroll for direction calculation
|
|
24
|
-
// This avoids recreating handleScroll on every scroll
|
|
25
|
-
const prevScrollRef = useRef(0);
|
|
26
|
-
|
|
27
|
-
const handleScroll = useCallback(() => {
|
|
28
|
-
if (!containerRef.current || !location.key) return;
|
|
29
|
-
const scrollTop = containerRef.current.scrollTop;
|
|
30
|
-
scrollsMap[location.key] = scrollTop;
|
|
31
|
-
setScroll(scrollTop);
|
|
32
|
-
setDirection(scrollTop > prevScrollRef.current ? "down" : "up");
|
|
33
|
-
prevScrollRef.current = scrollTop;
|
|
34
|
-
}, [location.key]);
|
|
35
|
-
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
const container = containerRef.current;
|
|
38
|
-
if (!container) return;
|
|
39
|
-
container.addEventListener("scroll", handleScroll, { passive: true });
|
|
40
|
-
|
|
41
|
-
return () => {
|
|
42
|
-
if (container)
|
|
43
|
-
container.removeEventListener("scroll", handleScroll);
|
|
44
|
-
};
|
|
45
|
-
}, [handleScroll]);
|
|
46
|
-
|
|
47
|
-
// Defer scroll restoration to next tick to allow async content to render
|
|
48
|
-
// This is necessary because DefaultHomePage content loads asynchronously
|
|
49
|
-
useEffect(() => {
|
|
50
|
-
const savedScroll = scrollsMap[location.key];
|
|
51
|
-
if (!containerRef.current || !savedScroll) return;
|
|
52
|
-
|
|
53
|
-
const timeoutId = setTimeout(() => {
|
|
54
|
-
if (!containerRef.current) return;
|
|
55
|
-
containerRef.current.scrollTo({
|
|
56
|
-
top: savedScroll,
|
|
57
|
-
behavior: "auto"
|
|
58
|
-
});
|
|
59
|
-
}, 0);
|
|
60
|
-
|
|
61
|
-
return () => clearTimeout(timeoutId);
|
|
62
|
-
}, [location.key]);
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
containerRef,
|
|
66
|
-
scroll,
|
|
67
|
-
direction
|
|
68
|
-
};
|
|
69
|
-
}
|