@questpie/admin 3.3.0 → 3.4.1
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 -6
- package/dist/client/blocks/block-renderer.d.mts +2 -2
- package/dist/client/builder/admin-types.d.mts +3 -3
- package/dist/client/builder/types/action-types.d.mts +1 -1
- package/dist/client/builder/types/collection-types.d.mts +59 -2
- package/dist/client/modules/admin.d.mts +3 -0
- package/dist/client/modules/admin.mjs +3 -0
- package/dist/client/preview/block-scope-context.d.mts +2 -2
- package/dist/client/preview/preview-banner.d.mts +2 -2
- package/dist/client/preview/preview-field.d.mts +4 -4
- package/dist/client/views/auth/accept-invite-form.d.mts +2 -2
- package/dist/client/views/auth/auth-layout.d.mts +3 -3
- package/dist/client/views/auth/reset-password-form.d.mts +2 -2
- package/dist/client/views/auth/setup-form.d.mts +2 -2
- package/dist/client/views/collection/list-view.mjs +830 -0
- package/dist/client/views/collection/outline.mjs +363 -0
- package/dist/client/views/collection/table-view.mjs +6 -3
- package/dist/client/views/layout/admin-layout.d.mts +15 -1
- package/dist/client/views/layout/admin-layout.mjs +95 -31
- package/dist/client/views/pages/accept-invite-page.d.mts +2 -2
- package/dist/client/views/pages/dashboard-page.d.mts +2 -2
- package/dist/client/views/pages/forgot-password-page.d.mts +2 -2
- package/dist/client/views/pages/invite-page.d.mts +2 -2
- package/dist/client/views/pages/login-page.d.mts +2 -2
- package/dist/client.d.mts +6 -6
- package/dist/components/rich-text/rich-text-renderer.d.mts +2 -2
- package/dist/factories.d.mts +21 -0
- package/dist/factories.mjs +11 -0
- package/dist/fields.d.mts +4 -0
- package/dist/fields.mjs +5 -0
- package/dist/index.d.mts +6 -6
- package/dist/modules/admin.d.mts +10 -0
- package/dist/modules/admin.mjs +9 -0
- package/dist/modules/audit.d.mts +5 -0
- package/dist/modules/audit.mjs +5 -0
- package/dist/server/adapters/index.d.mts +2 -0
- package/dist/server/adapters/nextjs.d.mts +1 -0
- package/dist/server/augmentation/form-layout.d.mts +57 -2
- package/dist/server/augmentation/index.d.mts +3 -1
- package/dist/server/augmentation/shell.d.mts +48 -0
- package/dist/server/augmentation.d.mts +2 -1
- package/dist/server/auth-helpers.d.mts +1 -0
- package/dist/server/codegen/admin-client-template.mjs +11 -4
- package/dist/server/fields/blocks.d.mts +9 -2
- package/dist/server/fields/blocks.mjs +1 -1
- package/dist/server/fields/index.d.mts +2 -2
- package/dist/server/fields/index.mjs +2 -2
- package/dist/server/fields/rich-text.d.mts +9 -2
- package/dist/server/fields/rich-text.mjs +1 -1
- package/dist/server/modules/admin/.generated/module.d.mts +24 -19
- package/dist/server/modules/admin/.generated/module.mjs +5 -1
- package/dist/server/modules/admin/.generated/registries.d.mts +6 -4
- package/dist/server/modules/admin/client/.generated/module.d.mts +70 -70
- package/dist/server/modules/admin/client/.generated/module.mjs +3 -1
- package/dist/server/modules/admin/client/views/collection-form.d.mts +6 -0
- package/dist/server/modules/admin/client/views/collection-table.d.mts +6 -0
- package/dist/server/modules/admin/client/views/global-form.d.mts +6 -0
- package/dist/server/modules/admin/client/views/list-view.d.mts +6 -0
- package/dist/server/modules/admin/client/views/list-view.mjs +10 -0
- package/dist/server/modules/admin/collections/account.d.mts +50 -50
- package/dist/server/modules/admin/collections/admin-locks.d.mts +54 -54
- package/dist/server/modules/admin/collections/admin-preferences.d.mts +39 -39
- package/dist/server/modules/admin/collections/admin-saved-views.d.mts +47 -47
- package/dist/server/modules/admin/collections/apikey.d.mts +68 -68
- package/dist/server/modules/admin/collections/session.d.mts +42 -42
- package/dist/server/modules/admin/collections/user.d.mts +14 -14
- package/dist/server/modules/admin/collections/verification.d.mts +23 -23
- package/dist/server/modules/admin/dto/admin-config.dto.mjs +17 -0
- package/dist/server/modules/admin/index.d.mts +30 -31
- package/dist/server/modules/admin/routes/admin-config.d.mts +2 -17
- package/dist/server/modules/admin/routes/admin-config.mjs +21 -5
- package/dist/server/modules/admin/routes/execute-action.d.mts +9 -9
- package/dist/server/modules/admin/routes/execute-action.mjs +18 -12
- package/dist/server/modules/admin/routes/i18n-helpers.d.mts +4 -0
- package/dist/server/modules/admin/routes/preview.d.mts +11 -11
- package/dist/server/modules/admin/routes/reactive.d.mts +9 -9
- package/dist/server/modules/admin/routes/route-helpers.mjs +36 -1
- package/dist/server/modules/admin/routes/setup.d.mts +7 -14
- package/dist/server/modules/admin/routes/setup.mjs +16 -3
- package/dist/server/modules/admin/routes/widget-data.d.mts +5 -5
- package/dist/server/modules/admin/views/list-view.d.mts +8 -0
- package/dist/server/modules/admin/views/list-view.mjs +7 -0
- package/dist/server/modules/admin-preferences/collections/saved-views.d.mts +27 -27
- package/dist/server/modules/audit/collections/audit-log.d.mts +7 -2
- package/dist/server/modules/audit/index.d.mts +1 -1
- package/dist/server/plugin.d.mts +1 -1
- package/dist/server/plugin.mjs +28 -28
- package/dist/server.d.mts +7 -4
- package/dist/server.mjs +7 -7
- package/package.json +13 -3
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
//#region src/client/views/collection/outline.ts
|
|
2
|
+
const DEFAULT_MAX_DEPTH = 12;
|
|
3
|
+
const NO_VALUE = "No value";
|
|
4
|
+
function getId(value) {
|
|
5
|
+
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
6
|
+
if (value && typeof value === "object") {
|
|
7
|
+
const id = value.id;
|
|
8
|
+
if (typeof id === "string" || typeof id === "number") return String(id);
|
|
9
|
+
}
|
|
10
|
+
return null;
|
|
11
|
+
}
|
|
12
|
+
function getPathValue(source, path) {
|
|
13
|
+
if (!path) return source;
|
|
14
|
+
const parts = path.split(".").filter(Boolean);
|
|
15
|
+
let current = source;
|
|
16
|
+
for (const part of parts) {
|
|
17
|
+
if (!current || typeof current !== "object") return void 0;
|
|
18
|
+
current = current[part];
|
|
19
|
+
}
|
|
20
|
+
return current;
|
|
21
|
+
}
|
|
22
|
+
function defaultLabelForValue(value) {
|
|
23
|
+
if (value === null || value === void 0 || value === "") return NO_VALUE;
|
|
24
|
+
if (Array.isArray(value)) return value.length ? value.map((item) => defaultLabelForValue(item)).join(", ") : NO_VALUE;
|
|
25
|
+
if (typeof value === "object") {
|
|
26
|
+
const record = value;
|
|
27
|
+
return String(record.title ?? record.name ?? record.label ?? record.id ?? NO_VALUE);
|
|
28
|
+
}
|
|
29
|
+
return String(value);
|
|
30
|
+
}
|
|
31
|
+
function stableGroupKey(value) {
|
|
32
|
+
if (value === null || value === void 0 || value === "") return "__empty__";
|
|
33
|
+
if (Array.isArray(value)) return value.map(stableGroupKey).join(",");
|
|
34
|
+
if (typeof value === "object") return getId(value) ?? JSON.stringify(value);
|
|
35
|
+
return String(value);
|
|
36
|
+
}
|
|
37
|
+
function shouldExpand(key, depth, outline, collapsedKeys) {
|
|
38
|
+
if (collapsedKeys.has(key)) return false;
|
|
39
|
+
const defaultExpanded = outline?.defaultExpanded ?? true;
|
|
40
|
+
if (defaultExpanded === true) return true;
|
|
41
|
+
if (defaultExpanded === false) return false;
|
|
42
|
+
return depth === 0;
|
|
43
|
+
}
|
|
44
|
+
function compareByOrder(a, b, order) {
|
|
45
|
+
if (Array.isArray(order)) {
|
|
46
|
+
const ai = order.indexOf(a.key);
|
|
47
|
+
const bi = order.indexOf(b.key);
|
|
48
|
+
if (ai !== -1 || bi !== -1) {
|
|
49
|
+
if (ai === -1) return 1;
|
|
50
|
+
if (bi === -1) return -1;
|
|
51
|
+
return ai - bi;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const result = a.label.localeCompare(b.label);
|
|
55
|
+
return order === "desc" ? -result : result;
|
|
56
|
+
}
|
|
57
|
+
function matchesWhere(edge, where) {
|
|
58
|
+
if (!where) return true;
|
|
59
|
+
return Object.entries(where).every(([key, expected]) => {
|
|
60
|
+
const actual = getPathValue(edge, key);
|
|
61
|
+
if (expected && typeof expected === "object" && !Array.isArray(expected)) {
|
|
62
|
+
if ("in" in expected) return (expected.in ?? []).map(String).includes(String(actual));
|
|
63
|
+
}
|
|
64
|
+
return String(actual) === String(expected);
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function isRecord(value) {
|
|
68
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
69
|
+
}
|
|
70
|
+
function buildFieldRows(level, docs, depth, levelIndex, ctx, scopeKey) {
|
|
71
|
+
const groups = /* @__PURE__ */ new Map();
|
|
72
|
+
for (const doc of docs) {
|
|
73
|
+
const value = getPathValue(doc, level.field);
|
|
74
|
+
const key = stableGroupKey(value);
|
|
75
|
+
const group = groups.get(key);
|
|
76
|
+
if (group) {
|
|
77
|
+
group.docs.push(doc);
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
groups.set(key, {
|
|
81
|
+
key,
|
|
82
|
+
label: ctx.labelForValue(value, level.labelField ?? level.field),
|
|
83
|
+
value,
|
|
84
|
+
docs: [doc]
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
return Array.from(groups.values()).sort((a, b) => compareByOrder(a, b, level.order)).flatMap((group) => {
|
|
88
|
+
const rowKey = `${scopeKey}/field:${level.field}:${group.key}`;
|
|
89
|
+
const expanded = shouldExpand(rowKey, depth, ctx.outline, ctx.collapsedKeys);
|
|
90
|
+
return [{
|
|
91
|
+
kind: "group",
|
|
92
|
+
key: rowKey,
|
|
93
|
+
label: group.label,
|
|
94
|
+
depth,
|
|
95
|
+
count: group.docs.length,
|
|
96
|
+
expandable: true,
|
|
97
|
+
collapsed: !expanded
|
|
98
|
+
}, ...expanded ? buildLevelRows(group.docs, depth + 1, levelIndex + 1, ctx, rowKey) : []];
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function buildRelationFieldRows(level, docs, depth, levelIndex, ctx, scopeKey) {
|
|
102
|
+
const fieldPath = level.field ? `${level.relation}.${level.field}` : level.relation;
|
|
103
|
+
const groups = /* @__PURE__ */ new Map();
|
|
104
|
+
for (const doc of docs) {
|
|
105
|
+
const relationValue = getPathValue(doc, level.relation);
|
|
106
|
+
const value = level.field ? getPathValue(relationValue, level.field) : relationValue;
|
|
107
|
+
const key = stableGroupKey(value);
|
|
108
|
+
const group = groups.get(key);
|
|
109
|
+
if (group) {
|
|
110
|
+
group.docs.push(doc);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
groups.set(key, {
|
|
114
|
+
key,
|
|
115
|
+
label: ctx.labelForValue(value, level.labelField ?? fieldPath),
|
|
116
|
+
docs: [doc]
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return Array.from(groups.values()).sort((a, b) => compareByOrder(a, b, level.order)).flatMap((group) => {
|
|
120
|
+
const rowKey = `${scopeKey}/relation-field:${fieldPath}:${group.key}`;
|
|
121
|
+
const expanded = shouldExpand(rowKey, depth, ctx.outline, ctx.collapsedKeys);
|
|
122
|
+
return [{
|
|
123
|
+
kind: "group",
|
|
124
|
+
key: rowKey,
|
|
125
|
+
label: group.label,
|
|
126
|
+
depth,
|
|
127
|
+
count: group.docs.length,
|
|
128
|
+
expandable: true,
|
|
129
|
+
collapsed: !expanded
|
|
130
|
+
}, ...expanded ? buildLevelRows(group.docs, depth + 1, levelIndex + 1, ctx, rowKey) : []];
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function getRepeatMaxDepth(repeat, fallback) {
|
|
134
|
+
if (!repeat) return 1;
|
|
135
|
+
if (typeof repeat === "object") return repeat.maxDepth ?? fallback;
|
|
136
|
+
return fallback;
|
|
137
|
+
}
|
|
138
|
+
function buildEdgeRows(level, docs, depth, levelIndex, ctx, scopeKey) {
|
|
139
|
+
const docsById = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const doc of docs) {
|
|
141
|
+
const id = getId(doc);
|
|
142
|
+
if (id) docsById.set(id, doc);
|
|
143
|
+
}
|
|
144
|
+
const edgeDocs = (ctx.edgesByCollection[level.collection] ?? []).filter((edge) => matchesWhere(edge, level.where));
|
|
145
|
+
if (ctx.outline?.preserveMatchingBranches !== false) for (let pass = 0; pass < ctx.maxDepth; pass++) {
|
|
146
|
+
let changed = false;
|
|
147
|
+
for (const edge of edgeDocs) {
|
|
148
|
+
const parentValue = getPathValue(edge, level.parentField);
|
|
149
|
+
const childValue = getPathValue(edge, level.childField);
|
|
150
|
+
const parentId = getId(parentValue);
|
|
151
|
+
const childId = getId(childValue);
|
|
152
|
+
if (!parentId || !childId || !docsById.has(childId)) continue;
|
|
153
|
+
if (docsById.has(parentId) || !isRecord(parentValue)) continue;
|
|
154
|
+
docsById.set(parentId, parentValue);
|
|
155
|
+
changed = true;
|
|
156
|
+
}
|
|
157
|
+
if (!changed) break;
|
|
158
|
+
}
|
|
159
|
+
const outlineDocs = Array.from(docsById.values());
|
|
160
|
+
const childrenByParent = /* @__PURE__ */ new Map();
|
|
161
|
+
const childIds = /* @__PURE__ */ new Set();
|
|
162
|
+
const edgeSeen = /* @__PURE__ */ new Set();
|
|
163
|
+
for (const edge of edgeDocs) {
|
|
164
|
+
const parentId = getId(getPathValue(edge, level.parentField));
|
|
165
|
+
const childId = getId(getPathValue(edge, level.childField));
|
|
166
|
+
if (!parentId || !childId || parentId === childId) continue;
|
|
167
|
+
if (!docsById.has(childId)) continue;
|
|
168
|
+
const dedupeKey = `${parentId}:${childId}:${JSON.stringify(level.where ?? {})}`;
|
|
169
|
+
if (edgeSeen.has(dedupeKey)) continue;
|
|
170
|
+
edgeSeen.add(dedupeKey);
|
|
171
|
+
childIds.add(childId);
|
|
172
|
+
const children = childrenByParent.get(parentId) ?? [];
|
|
173
|
+
children.push({
|
|
174
|
+
childId,
|
|
175
|
+
edge
|
|
176
|
+
});
|
|
177
|
+
childrenByParent.set(parentId, children);
|
|
178
|
+
}
|
|
179
|
+
const roots = outlineDocs.filter((doc) => {
|
|
180
|
+
const id = getId(doc);
|
|
181
|
+
return !id || !childIds.has(id) || !docsById.has(id);
|
|
182
|
+
});
|
|
183
|
+
const maxRepeatDepth = getRepeatMaxDepth(level.repeat, ctx.maxDepth);
|
|
184
|
+
const rows = [];
|
|
185
|
+
const visited = /* @__PURE__ */ new Set();
|
|
186
|
+
function pushChildRows(parentId, nextDepth, branchDepth, ancestorIds) {
|
|
187
|
+
const children = childrenByParent.get(parentId) ?? [];
|
|
188
|
+
if (children.length === 0) return;
|
|
189
|
+
if (branchDepth >= maxRepeatDepth) return;
|
|
190
|
+
if (level.groupByEdgeField) {
|
|
191
|
+
const groups = /* @__PURE__ */ new Map();
|
|
192
|
+
for (const child of children) {
|
|
193
|
+
const value = getPathValue(child.edge, level.groupByEdgeField);
|
|
194
|
+
const key = stableGroupKey(value);
|
|
195
|
+
const group = groups.get(key);
|
|
196
|
+
if (group) group.children.push(child);
|
|
197
|
+
else groups.set(key, {
|
|
198
|
+
label: ctx.labelForValue(value, level.groupByEdgeField),
|
|
199
|
+
children: [child]
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
for (const [key, group] of groups) {
|
|
203
|
+
const rowKey = `${scopeKey}/edge-group:${level.collection}:${parentId}:${key}`;
|
|
204
|
+
const expanded = shouldExpand(rowKey, nextDepth, ctx.outline, ctx.collapsedKeys);
|
|
205
|
+
rows.push({
|
|
206
|
+
kind: "group",
|
|
207
|
+
key: rowKey,
|
|
208
|
+
label: group.label,
|
|
209
|
+
depth: nextDepth,
|
|
210
|
+
count: group.children.length,
|
|
211
|
+
expandable: true,
|
|
212
|
+
collapsed: !expanded
|
|
213
|
+
});
|
|
214
|
+
if (!expanded) continue;
|
|
215
|
+
for (const child of group.children) pushEdgeRecord(child.childId, nextDepth + 1, branchDepth + 1, ancestorIds);
|
|
216
|
+
}
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
for (const child of children) pushEdgeRecord(child.childId, nextDepth, branchDepth + 1, ancestorIds);
|
|
220
|
+
}
|
|
221
|
+
function pushEdgeRecord(id, rowDepth, branchDepth, ancestorIds) {
|
|
222
|
+
const doc = docsById.get(id);
|
|
223
|
+
if (!doc || ancestorIds.has(id)) return;
|
|
224
|
+
const rowKey = `${scopeKey}/record:${id}`;
|
|
225
|
+
const hasChildren = (childrenByParent.get(id) ?? []).some((child) => docsById.has(child.childId));
|
|
226
|
+
const expanded = hasChildren && shouldExpand(rowKey, rowDepth, ctx.outline, ctx.collapsedKeys);
|
|
227
|
+
rows.push({
|
|
228
|
+
kind: "record",
|
|
229
|
+
key: rowKey,
|
|
230
|
+
id,
|
|
231
|
+
doc,
|
|
232
|
+
depth: rowDepth,
|
|
233
|
+
expandable: hasChildren,
|
|
234
|
+
collapsed: hasChildren ? !expanded : void 0
|
|
235
|
+
});
|
|
236
|
+
visited.add(id);
|
|
237
|
+
if (hasChildren && expanded) {
|
|
238
|
+
const nextAncestors = new Set(ancestorIds);
|
|
239
|
+
nextAncestors.add(id);
|
|
240
|
+
pushChildRows(id, rowDepth + 1, branchDepth, nextAncestors);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
for (const doc of roots) {
|
|
244
|
+
const id = getId(doc);
|
|
245
|
+
if (!id) {
|
|
246
|
+
rows.push({
|
|
247
|
+
kind: "record",
|
|
248
|
+
key: `${scopeKey}/record:${rows.length}`,
|
|
249
|
+
id: String(rows.length),
|
|
250
|
+
doc,
|
|
251
|
+
depth
|
|
252
|
+
});
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
pushEdgeRecord(id, depth, 0, /* @__PURE__ */ new Set());
|
|
256
|
+
}
|
|
257
|
+
for (const doc of outlineDocs) {
|
|
258
|
+
const id = getId(doc);
|
|
259
|
+
if (id && !visited.has(id)) pushEdgeRecord(id, depth, 0, /* @__PURE__ */ new Set());
|
|
260
|
+
}
|
|
261
|
+
if (levelIndex + 1 >= ctx.levels.length) return rows;
|
|
262
|
+
return rows.flatMap((row) => row.kind === "record" ? [row, ...buildLevelRows([row.doc], row.depth + 1, levelIndex + 1, ctx, row.key)] : [row]);
|
|
263
|
+
}
|
|
264
|
+
function createPathNode(key, label) {
|
|
265
|
+
return {
|
|
266
|
+
key,
|
|
267
|
+
label,
|
|
268
|
+
children: /* @__PURE__ */ new Map(),
|
|
269
|
+
docs: []
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
function countPathNode(node) {
|
|
273
|
+
let count = node.docs.length;
|
|
274
|
+
for (const child of node.children.values()) count += countPathNode(child);
|
|
275
|
+
return count;
|
|
276
|
+
}
|
|
277
|
+
function buildPathRows(level, docs, depth, levelIndex, ctx, scopeKey) {
|
|
278
|
+
const root = createPathNode("path:root", "Root");
|
|
279
|
+
const separator = level.separator ?? "/";
|
|
280
|
+
const maxPathDepth = getRepeatMaxDepth(level.repeat, ctx.maxDepth);
|
|
281
|
+
for (const doc of docs) {
|
|
282
|
+
const segments = String(getPathValue(doc, level.field) ?? "").trim().split(separator).filter(Boolean).slice(0, maxPathDepth);
|
|
283
|
+
const folderSegments = level.syntheticFolders && segments.length > 0 ? segments.slice(0, Math.max(segments.length - 1, 0)) : segments;
|
|
284
|
+
let node = root;
|
|
285
|
+
let currentPath = "";
|
|
286
|
+
for (const segment of folderSegments) {
|
|
287
|
+
currentPath = currentPath ? `${currentPath}${separator}${segment}` : segment;
|
|
288
|
+
let child = node.children.get(segment);
|
|
289
|
+
if (!child) {
|
|
290
|
+
child = createPathNode(`${scopeKey}/path:${level.field}:${currentPath}`, segment);
|
|
291
|
+
node.children.set(segment, child);
|
|
292
|
+
}
|
|
293
|
+
node = child;
|
|
294
|
+
}
|
|
295
|
+
node.docs.push(doc);
|
|
296
|
+
}
|
|
297
|
+
function walk(node, rowDepth) {
|
|
298
|
+
const rows = [];
|
|
299
|
+
for (const child of Array.from(node.children.values()).sort((a, b) => a.label.localeCompare(b.label))) {
|
|
300
|
+
const expanded = shouldExpand(child.key, rowDepth, ctx.outline, ctx.collapsedKeys);
|
|
301
|
+
rows.push({
|
|
302
|
+
kind: "synthetic",
|
|
303
|
+
key: child.key,
|
|
304
|
+
label: child.label,
|
|
305
|
+
depth: rowDepth,
|
|
306
|
+
count: countPathNode(child),
|
|
307
|
+
expandable: true,
|
|
308
|
+
collapsed: !expanded
|
|
309
|
+
});
|
|
310
|
+
if (expanded) rows.push(...walk(child, rowDepth + 1));
|
|
311
|
+
}
|
|
312
|
+
for (const doc of node.docs) {
|
|
313
|
+
const id = getId(doc) ?? `${node.key}:${rows.length}`;
|
|
314
|
+
rows.push({
|
|
315
|
+
kind: "record",
|
|
316
|
+
key: `${scopeKey}/record:${id}`,
|
|
317
|
+
id,
|
|
318
|
+
doc,
|
|
319
|
+
depth: rowDepth
|
|
320
|
+
});
|
|
321
|
+
if (levelIndex + 1 < ctx.levels.length) rows.push(...buildLevelRows([doc], rowDepth + 1, levelIndex + 1, ctx, `${scopeKey}/record:${id}`));
|
|
322
|
+
}
|
|
323
|
+
return rows;
|
|
324
|
+
}
|
|
325
|
+
return walk(root, depth);
|
|
326
|
+
}
|
|
327
|
+
function buildLevelRows(docs, depth, levelIndex, ctx, scopeKey = "root") {
|
|
328
|
+
if (docs.length === 0) return [];
|
|
329
|
+
if (depth > ctx.maxDepth || levelIndex >= ctx.levels.length) return docs.map((doc, index) => {
|
|
330
|
+
const id = getId(doc) ?? `${depth}:${index}`;
|
|
331
|
+
return {
|
|
332
|
+
kind: "record",
|
|
333
|
+
key: `${scopeKey}/record:${id}`,
|
|
334
|
+
id,
|
|
335
|
+
doc,
|
|
336
|
+
depth
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
const level = ctx.levels[levelIndex];
|
|
340
|
+
if (level.kind === "field") return buildFieldRows(level, docs, depth, levelIndex, ctx, scopeKey);
|
|
341
|
+
if (level.kind === "relation-field") return buildRelationFieldRows(level, docs, depth, levelIndex, ctx, scopeKey);
|
|
342
|
+
if (level.kind === "edge") return buildEdgeRows(level, docs, depth, levelIndex, ctx, scopeKey);
|
|
343
|
+
return buildPathRows(level, docs, depth, levelIndex, ctx, scopeKey);
|
|
344
|
+
}
|
|
345
|
+
function buildOutlineRows({ docs, outline, edgesByCollection = {}, collapsedKeys, labelForValue = defaultLabelForValue, maxDepth }) {
|
|
346
|
+
const levels = outline?.levels?.filter(Boolean) ?? [];
|
|
347
|
+
const ctx = {
|
|
348
|
+
levels,
|
|
349
|
+
outline,
|
|
350
|
+
edgesByCollection,
|
|
351
|
+
collapsedKeys: new Set(collapsedKeys ?? []),
|
|
352
|
+
labelForValue,
|
|
353
|
+
maxDepth: maxDepth ?? outline?.maxDepth ?? DEFAULT_MAX_DEPTH
|
|
354
|
+
};
|
|
355
|
+
if (levels.length === 0) return buildLevelRows(docs, 0, 0, {
|
|
356
|
+
...ctx,
|
|
357
|
+
levels: []
|
|
358
|
+
});
|
|
359
|
+
return buildLevelRows(docs, 0, 0, ctx);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
//#endregion
|
|
363
|
+
export { buildOutlineRows };
|
|
@@ -252,11 +252,14 @@ function mapListSchemaToConfig(list) {
|
|
|
252
252
|
if (list.columns?.length) config.columns = list.columns;
|
|
253
253
|
if (list.defaultSort) config.defaultSort = list.defaultSort;
|
|
254
254
|
if (list.orderable) config.orderable = list.orderable;
|
|
255
|
-
if (list.searchable
|
|
255
|
+
if (Array.isArray(list.searchable) && list.searchable.length) {
|
|
256
256
|
config.searchFields = list.searchable;
|
|
257
257
|
config.searchable = true;
|
|
258
|
-
}
|
|
258
|
+
} else if (typeof list.searchable === "boolean") config.searchable = list.searchable;
|
|
259
|
+
if (list.filterable?.length) config.filterable = list.filterable;
|
|
259
260
|
if (list.grouping?.fields?.length) config.grouping = list.grouping;
|
|
261
|
+
if (list.layout) config.layout = list.layout;
|
|
262
|
+
if (list.outline?.levels?.length) config.outline = list.outline;
|
|
260
263
|
config.actions = mapListActionsToDefinitions(list.actions);
|
|
261
264
|
return config;
|
|
262
265
|
}
|
|
@@ -1543,4 +1546,4 @@ function TableViewInner({ collection, config, viewConfig, navigate, basePath = "
|
|
|
1543
1546
|
}
|
|
1544
1547
|
|
|
1545
1548
|
//#endregion
|
|
1546
|
-
export { TableView as default };
|
|
1549
|
+
export { UploadCollectionButton, TableView as default, mapListSchemaToConfig, stringifyGroupValue };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { ServerAdminShellRailConfig } from "../../../server/augmentation/shell.mjs";
|
|
2
|
+
import "../../../server/augmentation.mjs";
|
|
1
3
|
import { AdminToasterProps } from "../../components/ui/sonner.mjs";
|
|
2
4
|
import { AdminSidebarProps } from "./admin-sidebar.mjs";
|
|
3
5
|
import { AdminTheme } from "./admin-theme.mjs";
|
|
@@ -73,6 +75,18 @@ interface AdminLayoutSharedProps {
|
|
|
73
75
|
*/
|
|
74
76
|
layoutMode?: LayoutMode;
|
|
75
77
|
}
|
|
78
|
+
interface AdminShellRailProps {
|
|
79
|
+
/** Current route path, usually the browser pathname. */
|
|
80
|
+
activeRoute?: string;
|
|
81
|
+
/** Admin base path, usually "/admin". */
|
|
82
|
+
basePath: string;
|
|
83
|
+
/** Resolved rail placement. */
|
|
84
|
+
placement: "left" | "right";
|
|
85
|
+
/** Raw rail config from server admin config. */
|
|
86
|
+
config: ServerAdminShellRailConfig;
|
|
87
|
+
/** Navigate function from the admin runtime. */
|
|
88
|
+
navigate: (path: string) => void;
|
|
89
|
+
}
|
|
76
90
|
interface AdminLayoutProps extends AdminLayoutSharedProps {
|
|
77
91
|
/**
|
|
78
92
|
* Brand name for sidebar.
|
|
@@ -127,4 +141,4 @@ declare function AdminLayout({
|
|
|
127
141
|
layoutMode
|
|
128
142
|
}: AdminLayoutProps): React.ReactElement;
|
|
129
143
|
//#endregion
|
|
130
|
-
export { AdminLayout, AdminLayoutSharedProps };
|
|
144
|
+
export { AdminLayout, AdminLayoutSharedProps, AdminShellRailProps };
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { useSafeI18n } from "../../i18n/hooks.mjs";
|
|
2
2
|
import { cn } from "../../lib/utils.mjs";
|
|
3
3
|
import { useAdminStore } from "../../runtime/provider.mjs";
|
|
4
|
+
import { ComponentRenderer } from "../../components/component-renderer.mjs";
|
|
5
|
+
import { useAdminConfig } from "../../hooks/use-admin-config.mjs";
|
|
4
6
|
import { shouldHandleAdminShortcut } from "../../utils/keyboard-shortcuts.mjs";
|
|
5
7
|
import { SidebarInset, SidebarProvider } from "../../components/ui/sidebar.mjs";
|
|
6
8
|
import { Toaster } from "../../components/ui/sonner.mjs";
|
|
@@ -29,6 +31,54 @@ function useLayoutProps(props) {
|
|
|
29
31
|
navigate: props.navigate ?? storeNavigate
|
|
30
32
|
};
|
|
31
33
|
}
|
|
34
|
+
function normalizeRoute(route) {
|
|
35
|
+
return route.replace(/\/+$/, "") || "/";
|
|
36
|
+
}
|
|
37
|
+
function resolveRouteRule(rule, basePath) {
|
|
38
|
+
if (rule.startsWith("/")) return normalizeRoute(rule);
|
|
39
|
+
return normalizeRoute(`${basePath}/${rule.replace(/^\/+/, "")}`);
|
|
40
|
+
}
|
|
41
|
+
function routeMatchesRule(activeRoute, rule, basePath, match) {
|
|
42
|
+
if (!activeRoute) return false;
|
|
43
|
+
const active = normalizeRoute(activeRoute);
|
|
44
|
+
const target = resolveRouteRule(rule, basePath);
|
|
45
|
+
if (match === "exact") return active === target;
|
|
46
|
+
return active === target || active.startsWith(`${target}/`);
|
|
47
|
+
}
|
|
48
|
+
function shouldRenderShellRail(config, activeRoute, basePath) {
|
|
49
|
+
const routes = config.routes;
|
|
50
|
+
if (!routes) return true;
|
|
51
|
+
const match = routes.match ?? "prefix";
|
|
52
|
+
if (!(!routes.include?.length || routes.include.some((rule) => routeMatchesRule(activeRoute, rule, basePath, match)))) return false;
|
|
53
|
+
return !routes.exclude?.some((rule) => routeMatchesRule(activeRoute, rule, basePath, match));
|
|
54
|
+
}
|
|
55
|
+
function toCssLength(value) {
|
|
56
|
+
if (value === void 0) return void 0;
|
|
57
|
+
return typeof value === "number" ? `${value}px` : value;
|
|
58
|
+
}
|
|
59
|
+
function AdminShellRail({ config, activeRoute, basePath, navigate }) {
|
|
60
|
+
const placement = config.placement ?? "left";
|
|
61
|
+
const style = {
|
|
62
|
+
width: toCssLength(config.width ?? 320),
|
|
63
|
+
minWidth: toCssLength(config.minWidth ?? config.width ?? 280),
|
|
64
|
+
maxWidth: toCssLength(config.maxWidth)
|
|
65
|
+
};
|
|
66
|
+
return /* @__PURE__ */ jsx("aside", {
|
|
67
|
+
className: cn("qa-admin-layout__secondary-rail bg-background h-svh min-h-0 shrink-0 flex-col overflow-hidden", config.hiddenOnMobile === false ? "flex" : "hidden md:flex", placement === "left" ? "border-border-subtle border-r" : "border-border-subtle border-l", config.className),
|
|
68
|
+
"data-placement": placement,
|
|
69
|
+
style,
|
|
70
|
+
children: /* @__PURE__ */ jsx(ComponentRenderer, {
|
|
71
|
+
reference: config.component,
|
|
72
|
+
additionalProps: {
|
|
73
|
+
activeRoute,
|
|
74
|
+
basePath,
|
|
75
|
+
placement,
|
|
76
|
+
config,
|
|
77
|
+
navigate
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
}
|
|
32
82
|
/**
|
|
33
83
|
* AdminLayout Component
|
|
34
84
|
*
|
|
@@ -61,6 +111,15 @@ function AdminLayout({ LinkComponent, activeRoute, basePath = "/admin", brandNam
|
|
|
61
111
|
const [isSearchOpen, setIsSearchOpen] = React.useState(false);
|
|
62
112
|
const openSearch = React.useCallback(() => setIsSearchOpen(true), []);
|
|
63
113
|
const closeSearch = React.useCallback(() => setIsSearchOpen(false), []);
|
|
114
|
+
const { data: serverConfig } = useAdminConfig();
|
|
115
|
+
const currentActiveRoute = activeRoute ?? (typeof window !== "undefined" ? window.location.pathname : void 0);
|
|
116
|
+
const secondaryRailConfig = serverConfig?.shell?.secondaryRail;
|
|
117
|
+
const secondaryRail = !!secondaryRailConfig && shouldRenderShellRail(secondaryRailConfig, currentActiveRoute, basePath) && secondaryRailConfig ? /* @__PURE__ */ jsx(AdminShellRail, {
|
|
118
|
+
config: secondaryRailConfig,
|
|
119
|
+
activeRoute: currentActiveRoute,
|
|
120
|
+
basePath,
|
|
121
|
+
navigate
|
|
122
|
+
}) : null;
|
|
64
123
|
React.useEffect(() => {
|
|
65
124
|
const down = (e) => {
|
|
66
125
|
if (shouldHandleAdminShortcut(e, { key: "k" })) {
|
|
@@ -90,38 +149,43 @@ function AdminLayout({ LinkComponent, activeRoute, basePath = "/admin", brandNam
|
|
|
90
149
|
/* @__PURE__ */ jsxs(SidebarProvider, {
|
|
91
150
|
defaultOpen: !sidebarCollapsedProp,
|
|
92
151
|
className: "qa-admin-layout__sidebar-wrapper bg-sidebar mx-auto h-svh max-w-[1920px] overflow-hidden",
|
|
93
|
-
children: [
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
className:
|
|
116
|
-
|
|
152
|
+
children: [
|
|
153
|
+
/* @__PURE__ */ jsx(AdminSidebar, {
|
|
154
|
+
LinkComponent,
|
|
155
|
+
activeRoute: currentActiveRoute,
|
|
156
|
+
basePath,
|
|
157
|
+
brandName,
|
|
158
|
+
theme,
|
|
159
|
+
setTheme,
|
|
160
|
+
showThemeToggle,
|
|
161
|
+
onSearchOpen: openSearch,
|
|
162
|
+
...sidebarProps
|
|
163
|
+
}),
|
|
164
|
+
secondaryRailConfig?.placement !== "right" && secondaryRail,
|
|
165
|
+
/* @__PURE__ */ jsxs(SidebarInset, {
|
|
166
|
+
className: "qa-admin-layout__content bg-background flex h-svh flex-col overflow-hidden md:rounded-tl-2xl",
|
|
167
|
+
children: [
|
|
168
|
+
shouldShowHeader && header && /* @__PURE__ */ jsx("header", {
|
|
169
|
+
className: "qa-admin-layout__header border-border-subtle border-b",
|
|
170
|
+
children: header
|
|
171
|
+
}),
|
|
172
|
+
/* @__PURE__ */ jsx("main", {
|
|
173
|
+
id: "main-content",
|
|
174
|
+
className: "qa-admin-layout__main min-w-0 flex-1 overflow-y-auto",
|
|
175
|
+
tabIndex: -1,
|
|
176
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
177
|
+
className: cn("qa-admin-layout__main-content min-w-0", layoutMode === "default" && "mx-auto max-w-5xl px-3 pt-1 pb-6 md:px-4 md:pt-2 md:pb-8", layoutMode === "wide" && "px-3 pt-1 pb-6 md:px-4 md:pt-2 md:pb-8", layoutMode === "full" && "px-2 pt-1 pb-6 md:px-3 md:pb-8", layoutMode === "immersive" && "p-0"),
|
|
178
|
+
children
|
|
179
|
+
})
|
|
180
|
+
}),
|
|
181
|
+
shouldShowFooter && footer && /* @__PURE__ */ jsx("footer", {
|
|
182
|
+
className: "qa-admin-layout__footer border-border-subtle border-t",
|
|
183
|
+
children: footer
|
|
117
184
|
})
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
})
|
|
123
|
-
]
|
|
124
|
-
})]
|
|
185
|
+
]
|
|
186
|
+
}),
|
|
187
|
+
secondaryRailConfig?.placement === "right" && secondaryRail
|
|
188
|
+
]
|
|
125
189
|
}),
|
|
126
190
|
/* @__PURE__ */ jsx(Toaster, {
|
|
127
191
|
theme,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime5 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/client/views/pages/accept-invite-page.d.ts
|
|
5
5
|
|
|
@@ -61,6 +61,6 @@ declare function AcceptInvitePage({
|
|
|
61
61
|
redirectTo,
|
|
62
62
|
loginPath,
|
|
63
63
|
minPasswordLength
|
|
64
|
-
}: AcceptInvitePageProps):
|
|
64
|
+
}: AcceptInvitePageProps): react_jsx_runtime5.JSX.Element;
|
|
65
65
|
//#endregion
|
|
66
66
|
export { AcceptInvitePage };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as react_jsx_runtime9 from "react/jsx-runtime";
|
|
2
2
|
|
|
3
3
|
//#region src/client/views/pages/dashboard-page.d.ts
|
|
4
4
|
|
|
@@ -38,6 +38,6 @@ declare function DashboardPage({
|
|
|
38
38
|
title,
|
|
39
39
|
description,
|
|
40
40
|
className
|
|
41
|
-
}: DashboardPageProps):
|
|
41
|
+
}: DashboardPageProps): react_jsx_runtime9.JSX.Element;
|
|
42
42
|
//#endregion
|
|
43
43
|
export { DashboardPage };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime10 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/client/views/pages/forgot-password-page.d.ts
|
|
5
5
|
|
|
@@ -51,6 +51,6 @@ declare function ForgotPasswordPage({
|
|
|
51
51
|
logo,
|
|
52
52
|
loginPath,
|
|
53
53
|
resetPasswordRedirectUrl
|
|
54
|
-
}: ForgotPasswordPageProps):
|
|
54
|
+
}: ForgotPasswordPageProps): react_jsx_runtime10.JSX.Element;
|
|
55
55
|
//#endregion
|
|
56
56
|
export { ForgotPasswordPage };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime11 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/client/views/pages/invite-page.d.ts
|
|
5
5
|
|
|
@@ -65,6 +65,6 @@ declare function InvitePage({
|
|
|
65
65
|
defaultRole,
|
|
66
66
|
showMessage,
|
|
67
67
|
onSuccess
|
|
68
|
-
}: InvitePageProps):
|
|
68
|
+
}: InvitePageProps): react_jsx_runtime11.JSX.Element;
|
|
69
69
|
//#endregion
|
|
70
70
|
export { InvitePage };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import * as
|
|
2
|
+
import * as react_jsx_runtime8 from "react/jsx-runtime";
|
|
3
3
|
|
|
4
4
|
//#region src/client/views/pages/login-page.d.ts
|
|
5
5
|
|
|
@@ -64,6 +64,6 @@ declare function LoginPage({
|
|
|
64
64
|
signUpPath,
|
|
65
65
|
showForgotPassword,
|
|
66
66
|
showSignUp
|
|
67
|
-
}: LoginPageProps):
|
|
67
|
+
}: LoginPageProps): react_jsx_runtime8.JSX.Element;
|
|
68
68
|
//#endregion
|
|
69
69
|
export { LoginPage };
|