@rebasepro/core 0.0.1-canary.eae7889 → 0.1.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/dist/components/BootstrapAdminBanner.d.ts +4 -0
- package/dist/components/LoginView/LoginView.d.ts +22 -0
- package/dist/components/common/useDataTableController.d.ts +3 -3
- package/dist/components/index.d.ts +1 -0
- package/dist/hooks/data/useRelationSelector.d.ts +2 -2
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/useCollapsedGroups.d.ts +16 -1
- package/dist/hooks/useResolvedComponent.d.ts +47 -0
- package/dist/index.es.js +333 -121
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +330 -118
- package/dist/index.umd.js.map +1 -1
- package/dist/vitePlugin.d.ts +28 -1
- package/dist/vitePlugin.js +42 -0
- package/package.json +7 -8
- package/src/components/BootstrapAdminBanner.tsx +66 -0
- package/src/components/LoginView/LoginView.tsx +73 -42
- package/src/components/UserSelectPopover.tsx +8 -34
- package/src/components/common/useColumnsIds.tsx +16 -16
- package/src/components/common/useDataTableController.tsx +30 -18
- package/src/components/index.tsx +1 -1
- package/src/core/Rebase.tsx +20 -14
- package/src/hooks/data/useRelationSelector.tsx +6 -6
- package/src/hooks/index.tsx +1 -0
- package/src/hooks/useCollapsedGroups.ts +48 -6
- package/src/hooks/useRebaseContext.tsx +11 -6
- package/src/hooks/useResolvedComponent.tsx +157 -0
- package/src/hooks/useStudioBridge.tsx +2 -1
- package/src/locales/de.ts +4 -0
- package/src/locales/en.ts +6 -0
- package/src/locales/es.ts +4 -0
- package/src/locales/fr.ts +4 -0
- package/src/locales/hi.ts +4 -0
- package/src/locales/it.ts +4 -0
- package/src/locales/pt.ts +4 -0
- package/src/util/previews.ts +16 -7
- package/src/vitePlugin.ts +87 -1
package/src/core/Rebase.tsx
CHANGED
|
@@ -116,20 +116,21 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
// 2. Auto-derive from the client's WebSocket connection (Rebase backend)
|
|
119
|
-
const ws =
|
|
120
|
-
if (ws && typeof ws.executeSql === "function") {
|
|
119
|
+
const ws = client?.ws;
|
|
120
|
+
if (ws && typeof (ws as Record<string, unknown>).executeSql === "function") {
|
|
121
|
+
const wsAdmin = ws as import("@rebasepro/types").DatabaseAdmin;
|
|
121
122
|
return {
|
|
122
|
-
executeSql:
|
|
123
|
-
fetchAvailableDatabases:
|
|
124
|
-
fetchAvailableRoles:
|
|
125
|
-
fetchCurrentDatabase:
|
|
126
|
-
fetchUnmappedTables:
|
|
127
|
-
fetchTableMetadata:
|
|
123
|
+
executeSql: wsAdmin.executeSql!.bind(wsAdmin),
|
|
124
|
+
fetchAvailableDatabases: wsAdmin.fetchAvailableDatabases?.bind(wsAdmin),
|
|
125
|
+
fetchAvailableRoles: wsAdmin.fetchAvailableRoles?.bind(wsAdmin),
|
|
126
|
+
fetchCurrentDatabase: wsAdmin.fetchCurrentDatabase?.bind(wsAdmin),
|
|
127
|
+
fetchUnmappedTables: wsAdmin.fetchUnmappedTables?.bind(wsAdmin),
|
|
128
|
+
fetchTableMetadata: wsAdmin.fetchTableMetadata?.bind(wsAdmin),
|
|
128
129
|
// Branch admin capabilities
|
|
129
|
-
...(typeof
|
|
130
|
-
createBranch:
|
|
131
|
-
deleteBranch:
|
|
132
|
-
listBranches:
|
|
130
|
+
...(typeof wsAdmin.createBranch === "function" ? {
|
|
131
|
+
createBranch: wsAdmin.createBranch.bind(wsAdmin),
|
|
132
|
+
deleteBranch: wsAdmin.deleteBranch!.bind(wsAdmin),
|
|
133
|
+
listBranches: wsAdmin.listBranches!.bind(wsAdmin)
|
|
133
134
|
} : {})
|
|
134
135
|
};
|
|
135
136
|
}
|
|
@@ -224,7 +225,7 @@ export function Rebase<USER extends User>(props: RebaseProps<USER>) {
|
|
|
224
225
|
</RebaseI18nProvider>
|
|
225
226
|
);
|
|
226
227
|
|
|
227
|
-
const resolvedApiUrl = apiUrl ??
|
|
228
|
+
const resolvedApiUrl = apiUrl ?? client?.baseUrl;
|
|
228
229
|
|
|
229
230
|
if (resolvedApiUrl) {
|
|
230
231
|
return (
|
|
@@ -255,7 +256,12 @@ function RebaseInternal({
|
|
|
255
256
|
}) : children;
|
|
256
257
|
|
|
257
258
|
const plugins = customizationController.plugins;
|
|
258
|
-
|
|
259
|
+
const authController = context.authController;
|
|
260
|
+
const authReady = !loading
|
|
261
|
+
&& !authController.authLoading
|
|
262
|
+
&& (Boolean(authController.user) || authController.loginSkipped);
|
|
263
|
+
|
|
264
|
+
if (authReady && plugins && plugins.length > 0) {
|
|
259
265
|
return (
|
|
260
266
|
<PluginProviderStack
|
|
261
267
|
plugins={plugins}
|
|
@@ -22,7 +22,7 @@ export interface UseRelationSelectorProps<M extends Record<string, any> = any> {
|
|
|
22
22
|
/**
|
|
23
23
|
* Force filter to be applied to the relation search
|
|
24
24
|
*/
|
|
25
|
-
|
|
25
|
+
fixedFilter?: FilterValues<string>;
|
|
26
26
|
/**
|
|
27
27
|
* Page size for pagination
|
|
28
28
|
*/
|
|
@@ -60,7 +60,7 @@ export function useRelationSelector<M extends Record<string, any> = any>(
|
|
|
60
60
|
{
|
|
61
61
|
path,
|
|
62
62
|
collection,
|
|
63
|
-
|
|
63
|
+
fixedFilter,
|
|
64
64
|
pageSize = DEFAULT_PAGE_SIZE,
|
|
65
65
|
getLabelFromEntity,
|
|
66
66
|
getDescriptionFromEntity,
|
|
@@ -138,10 +138,10 @@ export function useRelationSelector<M extends Record<string, any> = any>(
|
|
|
138
138
|
setError(undefined);
|
|
139
139
|
setLoading(true);
|
|
140
140
|
|
|
141
|
-
// Convert
|
|
141
|
+
// Convert fixedFilter to PostgREST where clause
|
|
142
142
|
const whereMap: Record<string, string> = {};
|
|
143
|
-
if (
|
|
144
|
-
Object.entries(
|
|
143
|
+
if (fixedFilter) {
|
|
144
|
+
Object.entries(fixedFilter).forEach(([key, value]) => {
|
|
145
145
|
if (value && Array.isArray(value)) {
|
|
146
146
|
const [op, val] = value;
|
|
147
147
|
const postgrestOp = op === "==" ? "eq" : op === "!=" ? "neq" : op === ">" ? "gt" : op === ">=" ? "gte" : op === "<" ? "lt" : op === "<=" ? "lte" : op === "in" ? "in" : op === "not-in" ? "nin" : op === "array-contains" ? "cs" : op === "array-contains-any" ? "csa" : "eq";
|
|
@@ -196,7 +196,7 @@ export function useRelationSelector<M extends Record<string, any> = any>(
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
unsubscribeRef.current = unsubscribe || null;
|
|
199
|
-
}, [dataClient, path,
|
|
199
|
+
}, [dataClient, path, fixedFilter, limit, currentSearch, entityToRelationItem, cleanupSubscription, setLoading]);
|
|
200
200
|
|
|
201
201
|
// Search function with debouncing
|
|
202
202
|
const search = useCallback((searchString: string) => {
|
package/src/hooks/index.tsx
CHANGED
|
@@ -7,10 +7,18 @@ const STORAGE_KEY_PREFIX = "rebase-collapsed-groups";
|
|
|
7
7
|
* with localStorage persistence. Automatically cleans up stale group entries
|
|
8
8
|
* when groups are removed from the navigation.
|
|
9
9
|
*
|
|
10
|
+
* Groups that have never been toggled by the user fall back to
|
|
11
|
+
* `defaults[groupName]` (driven by `collapsedByDefault` in config).
|
|
12
|
+
*
|
|
10
13
|
* @param groupNames - Array of group names to track
|
|
11
14
|
* @param namespace - Namespace for localStorage key (e.g., "home", "drawer") to allow independent state
|
|
15
|
+
* @param defaults - Optional map of group name → collapsed boolean from config
|
|
12
16
|
*/
|
|
13
|
-
export function useCollapsedGroups(
|
|
17
|
+
export function useCollapsedGroups(
|
|
18
|
+
groupNames: string[],
|
|
19
|
+
namespace = "default",
|
|
20
|
+
defaults?: Record<string, boolean>
|
|
21
|
+
) {
|
|
14
22
|
const storageKey = `${STORAGE_KEY_PREFIX}-${namespace}`;
|
|
15
23
|
|
|
16
24
|
// Load collapsed groups from localStorage on mount
|
|
@@ -57,13 +65,23 @@ export function useCollapsedGroups(groupNames: string[], namespace = "default")
|
|
|
57
65
|
}, [groupNames]);
|
|
58
66
|
|
|
59
67
|
const isGroupCollapsed = useCallback((name: string) => {
|
|
60
|
-
|
|
61
|
-
|
|
68
|
+
// If the user has explicitly toggled this group, use that value
|
|
69
|
+
if (name in collapsedGroups) {
|
|
70
|
+
return collapsedGroups[name];
|
|
71
|
+
}
|
|
72
|
+
// Otherwise fall back to the config default
|
|
73
|
+
return defaults?.[name] ?? false;
|
|
74
|
+
}, [collapsedGroups, defaults]);
|
|
62
75
|
|
|
63
76
|
const toggleGroupCollapsed = useCallback((name: string) => {
|
|
64
|
-
setCollapsedGroups(prev =>
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
setCollapsedGroups(prev => {
|
|
78
|
+
// Resolve current effective state (explicit or default)
|
|
79
|
+
const currentlyCollapsed = name in prev
|
|
80
|
+
? prev[name]
|
|
81
|
+
: (defaults?.[name] ?? false);
|
|
82
|
+
return { ...prev, [name]: !currentlyCollapsed };
|
|
83
|
+
});
|
|
84
|
+
}, [defaults]);
|
|
67
85
|
|
|
68
86
|
return useMemo(() => ({
|
|
69
87
|
isGroupCollapsed,
|
|
@@ -71,3 +89,27 @@ export function useCollapsedGroups(groupNames: string[], namespace = "default")
|
|
|
71
89
|
}), [isGroupCollapsed, toggleGroupCollapsed]);
|
|
72
90
|
}
|
|
73
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Build a defaults map from navigationGroupMappings for a given namespace.
|
|
94
|
+
* Returns a Record<groupName, collapsed> that can be passed to useCollapsedGroups.
|
|
95
|
+
*/
|
|
96
|
+
export function buildCollapsedDefaults(
|
|
97
|
+
mappings: Array<{ name: string; collapsedByDefault?: boolean | { drawer?: boolean; home?: boolean } }> | undefined,
|
|
98
|
+
namespace: "drawer" | "home"
|
|
99
|
+
): Record<string, boolean> {
|
|
100
|
+
if (!mappings) return {};
|
|
101
|
+
const result: Record<string, boolean> = {};
|
|
102
|
+
for (const mapping of mappings) {
|
|
103
|
+
const val = mapping.collapsedByDefault;
|
|
104
|
+
if (val === undefined) continue;
|
|
105
|
+
if (typeof val === "boolean") {
|
|
106
|
+
result[mapping.name] = val;
|
|
107
|
+
} else {
|
|
108
|
+
const ns = val[namespace];
|
|
109
|
+
if (ns !== undefined) {
|
|
110
|
+
result[mapping.name] = ns;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuthController, RebaseContext, User } from "@rebasepro/types";
|
|
2
2
|
import { useAuthController } from "./useAuthController";
|
|
3
|
+
import { useRebaseClient } from "./useRebaseClient";
|
|
3
4
|
import { useData } from "./data/useData";
|
|
4
5
|
import { useStorageSource } from "./useStorageSource";
|
|
5
6
|
import { useSnackbarController } from "./useSnackbarController";
|
|
@@ -11,9 +12,7 @@ import { useEffectiveRoleController } from "./useEffectiveRoleController";
|
|
|
11
12
|
import React, { useEffect, useContext } from "react";
|
|
12
13
|
import { useInternalUserManagementController } from "./useInternalUserManagementController";
|
|
13
14
|
|
|
14
|
-
//
|
|
15
|
-
// Wait, `databaseAdmin` hasn't been added to a context yet. Let's add it to a context in Rebase.tsx later.
|
|
16
|
-
// Let's create a placeholder for databaseAdmin context access.
|
|
15
|
+
// DatabaseAdmin is provided by <Rebase> via DatabaseAdminContext.
|
|
17
16
|
import { DatabaseAdminContext } from "../contexts/DatabaseAdminContext";
|
|
18
17
|
|
|
19
18
|
/**
|
|
@@ -27,6 +26,8 @@ import { DatabaseAdminContext } from "../contexts/DatabaseAdminContext";
|
|
|
27
26
|
*/
|
|
28
27
|
export const useRebaseContext = <USER extends User = User, AuthControllerType extends AuthController<USER> = AuthController<USER>>(): RebaseContext<USER, AuthControllerType> => {
|
|
29
28
|
|
|
29
|
+
const client = useRebaseClient<any>();
|
|
30
|
+
|
|
30
31
|
const authController = useAuthController<USER, AuthControllerType>();
|
|
31
32
|
const data = useData();
|
|
32
33
|
const storageSource = useStorageSource();
|
|
@@ -52,7 +53,8 @@ export const useRebaseContext = <USER extends User = User, AuthControllerType ex
|
|
|
52
53
|
analyticsController,
|
|
53
54
|
userManagement,
|
|
54
55
|
effectiveRoleController,
|
|
55
|
-
databaseAdmin
|
|
56
|
+
databaseAdmin,
|
|
57
|
+
client: client! // Client should be provided
|
|
56
58
|
});
|
|
57
59
|
|
|
58
60
|
React.useEffect(() => {
|
|
@@ -67,9 +69,12 @@ export const useRebaseContext = <USER extends User = User, AuthControllerType ex
|
|
|
67
69
|
analyticsController,
|
|
68
70
|
userManagement,
|
|
69
71
|
effectiveRoleController,
|
|
70
|
-
databaseAdmin
|
|
72
|
+
databaseAdmin,
|
|
73
|
+
client: client!
|
|
71
74
|
};
|
|
72
|
-
}, [authController,
|
|
75
|
+
}, [authController, data, storageSource, snackbarController, userConfigPersistence,
|
|
76
|
+
dialogsController, customizationController, analyticsController, userManagement,
|
|
77
|
+
effectiveRoleController, databaseAdmin, client]);
|
|
73
78
|
|
|
74
79
|
return rebaseContextRef.current;
|
|
75
80
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import React, { lazy, useMemo } from "react";
|
|
2
|
+
import type { ComponentRef, LazyComponentRef } from "@rebasepro/types";
|
|
3
|
+
import { isLazyComponentRef } from "@rebasepro/types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Internal cache for resolved lazy components.
|
|
7
|
+
*
|
|
8
|
+
* `React.lazy()` must return the SAME wrapper across renders — if we call
|
|
9
|
+
* `lazy(loader)` on every render, React will treat each result as a new
|
|
10
|
+
* component type and unmount/remount on each render cycle.
|
|
11
|
+
*
|
|
12
|
+
* This WeakMap keeps a stable mapping from a `ComponentRef` (object/function)
|
|
13
|
+
* to the `React.lazy()` wrapper it produced. Strings are keyed by a separate
|
|
14
|
+
* plain Map since they can't be WeakMap keys.
|
|
15
|
+
*/
|
|
16
|
+
const lazyCache = new WeakMap<object | Function, React.ComponentType<any>>();
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolves a `ComponentRef` into a renderable `React.ComponentType`.
|
|
20
|
+
*
|
|
21
|
+
* This hook handles all three forms of `ComponentRef`:
|
|
22
|
+
*
|
|
23
|
+
* 1. **`LazyComponentRef`** (produced by the Vite plugin from string paths):
|
|
24
|
+
* Wraps the lazy loader with `React.lazy()` for automatic code-splitting.
|
|
25
|
+
*
|
|
26
|
+
* 2. **`() => Promise<{ default: ComponentType }>`** (manual lazy import):
|
|
27
|
+
* Also wraps with `React.lazy()`. Distinguished from regular components
|
|
28
|
+
* by checking that the function has no parameters and is not a known
|
|
29
|
+
* React internal (no `$$typeof`).
|
|
30
|
+
*
|
|
31
|
+
* 3. **Direct `React.ComponentType`**:
|
|
32
|
+
* Returned as-is.
|
|
33
|
+
*
|
|
34
|
+
* If the ref is `undefined` or a raw string (which should never happen at
|
|
35
|
+
* runtime in the browser since the Vite plugin transforms strings), returns `undefined`.
|
|
36
|
+
*
|
|
37
|
+
* The returned component is stable across re-renders as long as the `ref`
|
|
38
|
+
* is referentially stable.
|
|
39
|
+
*
|
|
40
|
+
* **Usage:** Wrap the rendered component in `<Suspense>` to handle the
|
|
41
|
+
* loading state of lazy components.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* const ResolvedField = useResolvedComponent(property.ui?.Field);
|
|
46
|
+
* if (!ResolvedField) return null;
|
|
47
|
+
* return (
|
|
48
|
+
* <Suspense fallback={<CircularProgress />}>
|
|
49
|
+
* <ResolvedField {...fieldProps} />
|
|
50
|
+
* </Suspense>
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function useResolvedComponent<P = unknown>(
|
|
55
|
+
ref: ComponentRef<P> | undefined
|
|
56
|
+
): React.ComponentType<P> | undefined {
|
|
57
|
+
return useMemo(() => resolveComponentRef(ref), [ref]);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Wraps a loader function with `React.lazy()`, caching the result so the
|
|
62
|
+
* same loader always returns the same lazy component identity.
|
|
63
|
+
*/
|
|
64
|
+
function getOrCreateLazy<P>(
|
|
65
|
+
key: object | Function,
|
|
66
|
+
loader: () => Promise<{ default: React.ComponentType<P> }>
|
|
67
|
+
): React.ComponentType<P> {
|
|
68
|
+
const cached = lazyCache.get(key);
|
|
69
|
+
if (cached) return cached as React.ComponentType<P>;
|
|
70
|
+
|
|
71
|
+
const LazyComponent = lazy(loader) as unknown as React.ComponentType<P>;
|
|
72
|
+
lazyCache.set(key, LazyComponent);
|
|
73
|
+
return LazyComponent;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Pure function version of the resolver, for use outside React components.
|
|
78
|
+
* Same resolution logic as `useResolvedComponent`.
|
|
79
|
+
*
|
|
80
|
+
* Results are cached per reference identity — calling this multiple times
|
|
81
|
+
* with the same `ref` object returns the same `React.ComponentType`.
|
|
82
|
+
*/
|
|
83
|
+
export function resolveComponentRef<P = unknown>(
|
|
84
|
+
ref: ComponentRef<P> | undefined
|
|
85
|
+
): React.ComponentType<P> | undefined {
|
|
86
|
+
if (ref == null) return undefined;
|
|
87
|
+
|
|
88
|
+
// 1. String — should not happen at runtime (Vite transforms them).
|
|
89
|
+
// Log a warning and bail out.
|
|
90
|
+
if (typeof ref === "string") {
|
|
91
|
+
console.warn(
|
|
92
|
+
`[Rebase] Encountered a raw string ComponentRef ("${ref}") at runtime. ` +
|
|
93
|
+
"This usually means the Vite transform plugin did not process this file. " +
|
|
94
|
+
"Ensure the file is inside the configured collectionsDir."
|
|
95
|
+
);
|
|
96
|
+
return undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 2. LazyComponentRef — produced by the Vite plugin from string paths.
|
|
100
|
+
// The object has { __rebaseLazy: true, load: () => import(...) }.
|
|
101
|
+
if (isLazyComponentRef(ref)) {
|
|
102
|
+
return getOrCreateLazy<P>(
|
|
103
|
+
ref as unknown as object,
|
|
104
|
+
() => (ref as LazyComponentRef<P>).load()
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 3. Function — either a React component or a lazy import loader.
|
|
109
|
+
if (typeof ref === "function") {
|
|
110
|
+
const fn = ref as Function;
|
|
111
|
+
|
|
112
|
+
// Class components (React.Component / PureComponent) have this flag
|
|
113
|
+
if (fn.prototype?.isReactComponent) {
|
|
114
|
+
return ref as React.ComponentType<P>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// React internals (forwardRef, memo, etc.) carry $$typeof
|
|
118
|
+
if ("$$typeof" in fn) {
|
|
119
|
+
return ref as React.ComponentType<P>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// A function with declared parameters (fn.length > 0) is a regular
|
|
123
|
+
// component that accepts props — return as-is.
|
|
124
|
+
if (fn.length > 0) {
|
|
125
|
+
return ref as React.ComponentType<P>;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Zero-parameter function — could be a React component with no props
|
|
129
|
+
// OR a lazy loader `() => import(...)`. We distinguish by checking
|
|
130
|
+
// if `fn.name` looks like a component (starts with uppercase) or is
|
|
131
|
+
// an anonymous arrow.
|
|
132
|
+
//
|
|
133
|
+
// Convention: named function components always start with an
|
|
134
|
+
// uppercase letter. Dynamic import wrappers are typically anonymous
|
|
135
|
+
// arrows or have lowercase names.
|
|
136
|
+
const name = fn.name;
|
|
137
|
+
if (name && /^[A-Z]/.test(name)) {
|
|
138
|
+
// Named component (e.g. `function MyField() { ... }`) — return as-is
|
|
139
|
+
return ref as React.ComponentType<P>;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Treat as a lazy loader — wrap with React.lazy, cached by identity.
|
|
143
|
+
return getOrCreateLazy<P>(
|
|
144
|
+
fn,
|
|
145
|
+
ref as () => Promise<{ default: React.ComponentType<P> }>
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// 4. React wrapper objects (React.forwardRef, React.memo) —
|
|
150
|
+
// These are objects with $$typeof but are not functions.
|
|
151
|
+
if (typeof ref === "object" && "$$typeof" in (ref as object)) {
|
|
152
|
+
return ref as unknown as React.ComponentType<P>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// 5. Unknown shape — return undefined to be safe
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
@@ -42,7 +42,8 @@ const NOOP_COLLECTION_REGISTRY: CollectionRegistryController = {
|
|
|
42
42
|
getCollection: () => undefined,
|
|
43
43
|
getRawCollection: () => undefined,
|
|
44
44
|
getParentReferencesFromPath: () => [],
|
|
45
|
-
|
|
45
|
+
getParentCollectionSlugs: () => [],
|
|
46
|
+
getParentEntityIds: () => [],
|
|
46
47
|
convertIdsToPaths: () => [],
|
|
47
48
|
initialised: false
|
|
48
49
|
};
|
package/src/locales/de.ts
CHANGED
|
@@ -44,6 +44,7 @@ export const de: RebaseTranslations = {
|
|
|
44
44
|
all_entries_loaded: "Alle {{count}} Einträge geladen",
|
|
45
45
|
create_your_first_entry: "Erstellen Sie Ihren ersten Eintrag",
|
|
46
46
|
no_results_filter_sort: "Keine Ergebnisse mit angewendetem Filter/Sortierung",
|
|
47
|
+
no_results_search: "Keine Ergebnisse gefunden für \"{{search}}\"",
|
|
47
48
|
add: "Hinzufügen",
|
|
48
49
|
remove: "Entfernen",
|
|
49
50
|
multiple_entities: "Mehrere Entitäten",
|
|
@@ -127,6 +128,9 @@ export const de: RebaseTranslations = {
|
|
|
127
128
|
delete_user_confirmation: "Sind Sie sicher, dass Sie diesen Benutzer löschen möchten?",
|
|
128
129
|
create_your_users_and_roles: "Erstellen Sie Ihre Benutzer und Rollen",
|
|
129
130
|
no_users_or_roles_defined: "Sie haben keine Benutzer oder Rollen definiert. Sie können Standardrollen erstellen und den aktuellen Benutzer als Administrator hinzufügen.",
|
|
131
|
+
no_permission_to_view_users: "Sie haben keine Berechtigung, Benutzer anzuzeigen",
|
|
132
|
+
no_permission_to_view_roles: "Sie haben keine Berechtigung, Rollen anzuzeigen",
|
|
133
|
+
no_permission_description: "Wenden Sie sich an einen Administrator, wenn Sie Zugang zu diesem Bereich benötigen.",
|
|
130
134
|
save_before_changing_schema: "Sie müssen das Dokument speichern, bevor Sie das Schema ändern können",
|
|
131
135
|
edit_schema_for_this_form: "Schema für dieses Formular bearbeiten",
|
|
132
136
|
no_permissions_to_edit_collection: "Sie haben keine Berechtigung, diese Sammlung zu bearbeiten",
|
package/src/locales/en.ts
CHANGED
|
@@ -52,6 +52,7 @@ export const en: RebaseTranslations = {
|
|
|
52
52
|
all_entries_loaded: "All {{count}} entries loaded",
|
|
53
53
|
create_your_first_entry: "Create your first entry",
|
|
54
54
|
no_results_filter_sort: "No results with the applied filter/sort",
|
|
55
|
+
no_results_search: "No results found for \"{{search}}\"",
|
|
55
56
|
add: "Add",
|
|
56
57
|
remove: "Remove",
|
|
57
58
|
multiple_entities: "Multiple entities",
|
|
@@ -487,6 +488,11 @@ export const en: RebaseTranslations = {
|
|
|
487
488
|
reset_password: "Reset Password",
|
|
488
489
|
reset_password_success: "Password reset successfully",
|
|
489
490
|
reset_password_confirmation: "Are you sure you want to reset this user's password?",
|
|
491
|
+
|
|
492
|
+
/** Permission-denied empty states */
|
|
493
|
+
no_permission_to_view_users: "You don't have permission to view users",
|
|
494
|
+
no_permission_to_view_roles: "You don't have permission to view roles",
|
|
495
|
+
no_permission_description: "Contact an administrator if you need access to this section.",
|
|
490
496
|
error_resetting_password: "Error resetting password",
|
|
491
497
|
|
|
492
498
|
/** Editor table-bubble */
|
package/src/locales/es.ts
CHANGED
|
@@ -52,6 +52,7 @@ export const es: RebaseTranslations = {
|
|
|
52
52
|
all_entries_loaded: "Todas las {{count}} entradas cargadas",
|
|
53
53
|
create_your_first_entry: "Crea tu primera entrada",
|
|
54
54
|
no_results_filter_sort: "No hay resultados con el filtro/orden aplicado",
|
|
55
|
+
no_results_search: "No se encontraron resultados para \"{{search}}\"",
|
|
55
56
|
add: "Añadir",
|
|
56
57
|
remove: "Quitar",
|
|
57
58
|
multiple_entities: "Múltiples entidades",
|
|
@@ -133,6 +134,9 @@ export const es: RebaseTranslations = {
|
|
|
133
134
|
delete_user_confirmation: "¿Estás seguro de que quieres eliminar a este usuario?",
|
|
134
135
|
create_your_users_and_roles: "Crea tus usuarios y roles",
|
|
135
136
|
no_users_or_roles_defined: "No tienes usuarios ni roles. Puedes crear los roles por defecto y añadirte a ti mismo como administrador.",
|
|
137
|
+
no_permission_to_view_users: "No tienes permisos para ver los usuarios",
|
|
138
|
+
no_permission_to_view_roles: "No tienes permisos para ver los roles",
|
|
139
|
+
no_permission_description: "Contacta a un administrador si necesitas acceso a esta sección.",
|
|
136
140
|
save_before_changing_schema: "Debes guardar el documento antes de cambiar el esquema",
|
|
137
141
|
edit_schema_for_this_form: "Editar esquema para este formulario",
|
|
138
142
|
no_permissions_to_edit_collection: "No tienes permisos para editar esta colección",
|
package/src/locales/fr.ts
CHANGED
|
@@ -44,6 +44,7 @@ export const fr: RebaseTranslations = {
|
|
|
44
44
|
all_entries_loaded: "Toutes les {{count}} entrées chargées",
|
|
45
45
|
create_your_first_entry: "Créez votre première entrée",
|
|
46
46
|
no_results_filter_sort: "Aucun résultat avec le filtre/tri appliqué",
|
|
47
|
+
no_results_search: "Aucun résultat trouvé pour \"{{search}}\"",
|
|
47
48
|
add: "Ajouter",
|
|
48
49
|
remove: "Supprimer",
|
|
49
50
|
multiple_entities: "Entités multiples",
|
|
@@ -127,6 +128,9 @@ export const fr: RebaseTranslations = {
|
|
|
127
128
|
delete_user_confirmation: "Êtes-vous sûr de vouloir supprimer cet utilisateur ?",
|
|
128
129
|
create_your_users_and_roles: "Créez vos utilisateurs et rôles",
|
|
129
130
|
no_users_or_roles_defined: "Vous n'avez défini aucun utilisateur ni rôle. Vous pouvez créer les rôles par défaut et ajouter l'utilisateur actuel en tant qu'administrateur.",
|
|
131
|
+
no_permission_to_view_users: "Vous n'avez pas la permission de voir les utilisateurs",
|
|
132
|
+
no_permission_to_view_roles: "Vous n'avez pas la permission de voir les rôles",
|
|
133
|
+
no_permission_description: "Contactez un administrateur si vous avez besoin d'accéder à cette section.",
|
|
130
134
|
save_before_changing_schema: "Vous devez enregistrer le document avant de modifier le schéma",
|
|
131
135
|
edit_schema_for_this_form: "Modifier le schéma de ce formulaire",
|
|
132
136
|
no_permissions_to_edit_collection: "Vous n'avez pas l'autorisation de modifier cette collection",
|
package/src/locales/hi.ts
CHANGED
|
@@ -44,6 +44,7 @@ export const hi: RebaseTranslations = {
|
|
|
44
44
|
all_entries_loaded: "सभी {{count}} प्रविष्टियाँ लोड हो गईं",
|
|
45
45
|
create_your_first_entry: "अपनी पहली प्रविष्टि बनाएं",
|
|
46
46
|
no_results_filter_sort: "लागू किए गए फ़िल्टर/सॉर्ट के साथ कोई परिणाम नहीं",
|
|
47
|
+
no_results_search: "\"{{search}}\" के लिए कोई परिणाम नहीं मिला",
|
|
47
48
|
add: "जोड़ें",
|
|
48
49
|
remove: "हटाएं",
|
|
49
50
|
multiple_entities: "एकाधिक इकाइयां",
|
|
@@ -127,6 +128,9 @@ export const hi: RebaseTranslations = {
|
|
|
127
128
|
delete_user_confirmation: "क्या आप वाकई इस उपयोगकर्ता को हटाना चाहते हैं?",
|
|
128
129
|
create_your_users_and_roles: "अपने उपयोगकर्ता और भूमिकाएँ बनाएँ",
|
|
129
130
|
no_users_or_roles_defined: "आपके पास कोई उपयोगकर्ता या भूमिका परिभाषित नहीं है। आप डिफ़ॉल्ट भूमिकाएँ बना सकते हैं और वर्तमान उपयोगकर्ता को एडमिन के रूप में जोड़ सकते हैं।",
|
|
131
|
+
no_permission_to_view_users: "आपको उपयोगकर्ताओं को देखने की अनुमति नहीं है",
|
|
132
|
+
no_permission_to_view_roles: "आपको भूमिकाओं को देखने की अनुमति नहीं है",
|
|
133
|
+
no_permission_description: "यदि आपको इस अनुभाग तक पहुँच की आवश्यकता है तो किसी व्यवस्थापक से संपर्क करें।",
|
|
130
134
|
save_before_changing_schema: "स्कीमा बदलने से पहले आपको दस्तावेज़ सहेजना होगा",
|
|
131
135
|
edit_schema_for_this_form: "इस फॉर्म के लिए स्कीमा संपादित करें",
|
|
132
136
|
no_permissions_to_edit_collection: "आपके पास इस संग्रह को संपादित करने की अनुमति नहीं है",
|
package/src/locales/it.ts
CHANGED
|
@@ -44,6 +44,7 @@ export const it: RebaseTranslations = {
|
|
|
44
44
|
all_entries_loaded: "Tutte le {{count}} voci caricate",
|
|
45
45
|
create_your_first_entry: "Crea la tua prima voce",
|
|
46
46
|
no_results_filter_sort: "Nessun risultato con il filtro/ordinamento applicato",
|
|
47
|
+
no_results_search: "Nessun risultato trovato per \"{{search}}\"",
|
|
47
48
|
add: "Aggiungi",
|
|
48
49
|
remove: "Rimuovi",
|
|
49
50
|
multiple_entities: "Entità multiple",
|
|
@@ -127,6 +128,9 @@ export const it: RebaseTranslations = {
|
|
|
127
128
|
delete_user_confirmation: "Sei sicuro di voler eliminare questo utente?",
|
|
128
129
|
create_your_users_and_roles: "Crea i tuoi utenti e ruoli",
|
|
129
130
|
no_users_or_roles_defined: "Non hai definito né utenti né ruoli. Puoi creare ruoli predefiniti e aggiungere l'utente corrente come amministratore.",
|
|
131
|
+
no_permission_to_view_users: "Non hai i permessi per visualizzare gli utenti",
|
|
132
|
+
no_permission_to_view_roles: "Non hai i permessi per visualizzare i ruoli",
|
|
133
|
+
no_permission_description: "Contatta un amministratore se hai bisogno di accedere a questa sezione.",
|
|
130
134
|
save_before_changing_schema: "Devi salvare il documento prima di modificare lo schema",
|
|
131
135
|
edit_schema_for_this_form: "Modifica lo schema per questo modulo",
|
|
132
136
|
no_permissions_to_edit_collection: "Non hai i permessi per modificare questa collezione",
|
package/src/locales/pt.ts
CHANGED
|
@@ -49,6 +49,7 @@ export const pt: RebaseTranslations = {
|
|
|
49
49
|
all_entries_loaded: "Todos os {{count}} registos carregados",
|
|
50
50
|
create_your_first_entry: "Crie o seu primeiro registo",
|
|
51
51
|
no_results_filter_sort: "Sem resultados com o filtro/ordenação aplicado",
|
|
52
|
+
no_results_search: "Nenhum resultado encontrado para \"{{search}}\"",
|
|
52
53
|
add: "Adicionar",
|
|
53
54
|
remove: "Remover",
|
|
54
55
|
multiple_entities: "Múltiplas entidades",
|
|
@@ -132,6 +133,9 @@ export const pt: RebaseTranslations = {
|
|
|
132
133
|
delete_user_confirmation: "Tem a certeza de que quer eliminar este utilizador?",
|
|
133
134
|
create_your_users_and_roles: "Crie os seus utilizadores e funções",
|
|
134
135
|
no_users_or_roles_defined: "Não tem utilizadores ou funções definidas. Pode criar funções predefinidas e adicionar o utilizador atual como administrador.",
|
|
136
|
+
no_permission_to_view_users: "Não tem permissão para ver os utilizadores",
|
|
137
|
+
no_permission_to_view_roles: "Não tem permissão para ver as funções",
|
|
138
|
+
no_permission_description: "Contacte um administrador se precisar de acesso a esta secção.",
|
|
135
139
|
save_before_changing_schema: "Precisa de guardar o documento antes de alterar o esquema",
|
|
136
140
|
edit_schema_for_this_form: "Editar esquema para este formulário",
|
|
137
141
|
no_permissions_to_edit_collection: "Não tem permissões para editar esta coleção",
|
package/src/util/previews.ts
CHANGED
|
@@ -36,7 +36,7 @@ export function getEntityPreviewKeys(
|
|
|
36
36
|
if (listProperties && listProperties.length > 0) {
|
|
37
37
|
return listProperties;
|
|
38
38
|
} else {
|
|
39
|
-
listProperties = allProperties;
|
|
39
|
+
listProperties = (targetCollection.propertiesOrder as string[]) || allProperties;
|
|
40
40
|
return listProperties
|
|
41
41
|
.filter(key => {
|
|
42
42
|
const prop = targetCollection.properties[key];
|
|
@@ -54,16 +54,25 @@ export function getEntityTitlePropertyKey<M extends Record<string, any>>(collect
|
|
|
54
54
|
if (collection.titleProperty) {
|
|
55
55
|
return collection.titleProperty as string;
|
|
56
56
|
}
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
|
|
58
|
+
const orderToSearch = (collection.propertiesOrder as string[]) || Object.keys(collection.properties);
|
|
59
|
+
let firstStringCandidate: string | undefined;
|
|
60
|
+
|
|
61
|
+
for (const key of orderToSearch) {
|
|
59
62
|
const property = collection.properties[key];
|
|
60
|
-
if (!isPropertyBuilder(property)) {
|
|
63
|
+
if (property && !isPropertyBuilder(property)) {
|
|
61
64
|
const prop = property as Property;
|
|
62
|
-
if (prop.type === "string" && !prop.multiline && !prop.markdown && !prop.storage && !prop.isId) {
|
|
63
|
-
|
|
65
|
+
if (prop.type === "string" && !prop.ui?.multiline && !prop.ui?.markdown && !prop.storage && !prop.isId) {
|
|
66
|
+
if (!firstStringCandidate) {
|
|
67
|
+
firstStringCandidate = key;
|
|
68
|
+
}
|
|
69
|
+
const lowerKey = key.toLowerCase();
|
|
70
|
+
if (["name", "title", "label", "displayname", "username"].includes(lowerKey)) {
|
|
71
|
+
return key; // Immediate return if it's a strong title candidate
|
|
72
|
+
}
|
|
64
73
|
}
|
|
65
74
|
}
|
|
66
75
|
}
|
|
67
|
-
return
|
|
76
|
+
return firstStringCandidate;
|
|
68
77
|
}
|
|
69
78
|
|