@nocobase/plugin-ui-layout 2.2.0-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +107 -0
- package/README.md +17 -0
- package/client-v2.d.ts +2 -0
- package/client-v2.js +1 -0
- package/client.d.ts +2 -0
- package/client.js +1 -0
- package/dist/client/index.d.ts +9 -0
- package/dist/client/index.js +10 -0
- package/dist/client/plugin.d.ts +12 -0
- package/dist/client-v2/647.3a2f92424b5ce814.js +10 -0
- package/dist/client-v2/983.f95caf8d9687987d.js +10 -0
- package/dist/client-v2/index.d.ts +11 -0
- package/dist/client-v2/index.js +10 -0
- package/dist/client-v2/layoutRegistration.d.ts +28 -0
- package/dist/client-v2/locale.d.ts +9 -0
- package/dist/client-v2/mobileOpenViewAction.d.ts +206 -0
- package/dist/client-v2/mobilePageModelResolution.d.ts +9 -0
- package/dist/client-v2/mobileRouteRepository.d.ts +44 -0
- package/dist/client-v2/models/MobileLayoutModel.d.ts +102 -0
- package/dist/client-v2/models/MobileMenuComponents.d.ts +10 -0
- package/dist/client-v2/models/MobileMenuModels.d.ts +97 -0
- package/dist/client-v2/models/MobileMenuUtils.d.ts +30 -0
- package/dist/client-v2/models/MobilePageModels.d.ts +26 -0
- package/dist/client-v2/models/mobileComponents.d.ts +18 -0
- package/dist/client-v2/models/mobileFlowCompat.d.ts +61 -0
- package/dist/client-v2/models/mobileThemeToken.d.ts +25 -0
- package/dist/client-v2/pages/RoutesPage.d.ts +22 -0
- package/dist/client-v2/pages/UiLayoutsPage.d.ts +80 -0
- package/dist/client-v2/permissions/LayoutAwareDesktopRoutesPermissionsTab.d.ts +22 -0
- package/dist/client-v2/permissions/layoutAwareDesktopRoutesPermissions.d.ts +22 -0
- package/dist/client-v2/plugin.d.ts +14 -0
- package/dist/constants.d.ts +29 -0
- package/dist/constants.js +64 -0
- package/dist/externalVersion.js +33 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +48 -0
- package/dist/locale/en-US.json +163 -0
- package/dist/locale/zh-CN.json +163 -0
- package/dist/server/collections/desktopRoutes.d.ts +14 -0
- package/dist/server/collections/desktopRoutes.js +48 -0
- package/dist/server/collections/uiLayouts.d.ts +10 -0
- package/dist/server/collections/uiLayouts.js +85 -0
- package/dist/server/ensureDefaultUiLayout.d.ts +10 -0
- package/dist/server/ensureDefaultUiLayout.js +129 -0
- package/dist/server/index.d.ts +9 -0
- package/dist/server/index.js +42 -0
- package/dist/server/migrations/20260601090000-ensure-default-admin-layout.d.ts +13 -0
- package/dist/server/migrations/20260601090000-ensure-default-admin-layout.js +39 -0
- package/dist/server/migrations/20260615090000-backfill-admin-layout-desktop-routes.d.ts +15 -0
- package/dist/server/migrations/20260615090000-backfill-admin-layout-desktop-routes.js +94 -0
- package/dist/server/plugin.d.ts +19 -0
- package/dist/server/plugin.js +662 -0
- package/package.json +37 -0
- package/server.d.ts +2 -0
- package/server.js +1 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
12
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
13
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
14
|
+
var __export = (target, all) => {
|
|
15
|
+
for (var name in all)
|
|
16
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
27
|
+
var plugin_exports = {};
|
|
28
|
+
__export(plugin_exports, {
|
|
29
|
+
PluginUiLayoutServer: () => PluginUiLayoutServer,
|
|
30
|
+
default: () => plugin_default
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(plugin_exports);
|
|
33
|
+
var import_server = require("@nocobase/server");
|
|
34
|
+
var import_constants = require("../constants");
|
|
35
|
+
var import_ensureDefaultUiLayout = require("./ensureDefaultUiLayout");
|
|
36
|
+
const EMPTY_DESKTOP_ROUTE_FILTER = {
|
|
37
|
+
id: {
|
|
38
|
+
$eq: null
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const UI_LAYOUT_RUNTIME_FIELDS = [
|
|
42
|
+
"uid",
|
|
43
|
+
"title",
|
|
44
|
+
"layoutType",
|
|
45
|
+
"routeName",
|
|
46
|
+
"routePath",
|
|
47
|
+
"authCheck",
|
|
48
|
+
"enabled"
|
|
49
|
+
];
|
|
50
|
+
const UI_LAYOUT_ROLE_PERMISSION_TARGET_FIELDS = ["uid", "title", "layoutType", "routeName", "enabled"];
|
|
51
|
+
const DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS = ["id", "title", "hidden", "parentId", "options"];
|
|
52
|
+
const ROUTES_MANAGEMENT_ACTIONS = ["desktopRoutes:list"];
|
|
53
|
+
const ROLE_LAYOUT_PERMISSION_TARGET_ACTIONS = [
|
|
54
|
+
"uiLayouts:listRolePermissionTargets",
|
|
55
|
+
"desktopRoutes:listRolePermissionTargets"
|
|
56
|
+
];
|
|
57
|
+
const DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG = "plugin-ui-layout:desktop-route-write-layout";
|
|
58
|
+
const DEFAULT_ADMIN_UI_LAYOUT_PROTECTED_FIELDS = [
|
|
59
|
+
"uid",
|
|
60
|
+
"layoutType",
|
|
61
|
+
"routeName",
|
|
62
|
+
"routePath",
|
|
63
|
+
"authCheck",
|
|
64
|
+
"enabled"
|
|
65
|
+
];
|
|
66
|
+
function getRequestedLayoutUid(layout) {
|
|
67
|
+
return getExplicitRequestedLayoutUid(layout) ?? import_constants.DEFAULT_ADMIN_UI_LAYOUT.uid;
|
|
68
|
+
}
|
|
69
|
+
function getExplicitRequestedLayoutUid(layout) {
|
|
70
|
+
const uid = Array.isArray(layout) ? layout[0] : layout;
|
|
71
|
+
if (typeof uid === "string" && uid.trim()) {
|
|
72
|
+
return uid;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function hasActionParam(params, key) {
|
|
76
|
+
return !!params && typeof params === "object" && Object.prototype.hasOwnProperty.call(params, key);
|
|
77
|
+
}
|
|
78
|
+
function getDesktopRouteLayoutFilterByUid(layoutUid) {
|
|
79
|
+
return {
|
|
80
|
+
"uiLayouts.uid": layoutUid
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function isDesktopRouteCreateValue(value) {
|
|
84
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
85
|
+
}
|
|
86
|
+
function isUiLayoutActionParams(value) {
|
|
87
|
+
return isDesktopRouteCreateValue(value);
|
|
88
|
+
}
|
|
89
|
+
function isDefaultAdminUiLayoutRecord(record) {
|
|
90
|
+
return record.get("uid") === import_constants.DEFAULT_ADMIN_UI_LAYOUT.uid;
|
|
91
|
+
}
|
|
92
|
+
function hasUnsafeDefaultAdminUiLayoutValues(values) {
|
|
93
|
+
if (!isDesktopRouteCreateValue(values)) {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
return DEFAULT_ADMIN_UI_LAYOUT_PROTECTED_FIELDS.some(
|
|
97
|
+
(field) => Object.prototype.hasOwnProperty.call(values, field) && values[field] !== import_constants.DEFAULT_ADMIN_UI_LAYOUT[field]
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
async function findUiLayoutActionTargets(ctx) {
|
|
101
|
+
var _a;
|
|
102
|
+
const params = (_a = ctx.action) == null ? void 0 : _a.params;
|
|
103
|
+
if (!isUiLayoutActionParams(params)) {
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
const repository = ctx.db.getRepository("uiLayouts");
|
|
107
|
+
const filterByTk = params.filterByTk ?? params.filterByTks;
|
|
108
|
+
if (filterByTk !== void 0 && filterByTk !== null) {
|
|
109
|
+
return repository.find({
|
|
110
|
+
filterByTk
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
if (params.filter && Object.keys(params.filter).length) {
|
|
114
|
+
return repository.find({
|
|
115
|
+
filter: params.filter
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
function hasUiLayout(uiLayout, layoutUid) {
|
|
121
|
+
if (uiLayout === layoutUid) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return isDesktopRouteCreateValue(uiLayout) && uiLayout.uid === layoutUid;
|
|
125
|
+
}
|
|
126
|
+
function mergeUiLayoutValue(uiLayouts, layoutUid) {
|
|
127
|
+
if (Array.isArray(uiLayouts)) {
|
|
128
|
+
if (uiLayouts.some((uiLayout) => hasUiLayout(uiLayout, layoutUid))) {
|
|
129
|
+
return uiLayouts;
|
|
130
|
+
}
|
|
131
|
+
return [...uiLayouts, layoutUid];
|
|
132
|
+
}
|
|
133
|
+
if (hasUiLayout(uiLayouts, layoutUid)) {
|
|
134
|
+
return uiLayouts;
|
|
135
|
+
}
|
|
136
|
+
return uiLayouts == null ? [layoutUid] : [uiLayouts, layoutUid];
|
|
137
|
+
}
|
|
138
|
+
function withDesktopRouteUiLayout(value, layoutUid) {
|
|
139
|
+
if (Array.isArray(value)) {
|
|
140
|
+
return value.map((item) => withDesktopRouteUiLayout(item, layoutUid));
|
|
141
|
+
}
|
|
142
|
+
if (!isDesktopRouteCreateValue(value)) {
|
|
143
|
+
return value;
|
|
144
|
+
}
|
|
145
|
+
return {
|
|
146
|
+
...value,
|
|
147
|
+
uiLayouts: mergeUiLayoutValue(value.uiLayouts, layoutUid),
|
|
148
|
+
...Array.isArray(value.children) ? { children: withDesktopRouteUiLayout(value.children, layoutUid) } : {}
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
async function getDesktopRouteLayoutContext(ctx) {
|
|
152
|
+
var _a;
|
|
153
|
+
const requestedLayout = (_a = ctx.action) == null ? void 0 : _a.params.layout;
|
|
154
|
+
const layoutUid = requestedLayout === void 0 ? import_constants.DEFAULT_ADMIN_UI_LAYOUT.uid : getExplicitRequestedLayoutUid(requestedLayout);
|
|
155
|
+
if (!layoutUid) {
|
|
156
|
+
return {
|
|
157
|
+
filter: EMPTY_DESKTOP_ROUTE_FILTER,
|
|
158
|
+
valid: false
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
162
|
+
filter: {
|
|
163
|
+
uid: layoutUid,
|
|
164
|
+
enabled: true
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
if (!uiLayout) {
|
|
168
|
+
return {
|
|
169
|
+
filter: EMPTY_DESKTOP_ROUTE_FILTER,
|
|
170
|
+
layoutUid,
|
|
171
|
+
valid: false
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return {
|
|
175
|
+
filter: getDesktopRouteLayoutFilterByUid(layoutUid),
|
|
176
|
+
layoutUid,
|
|
177
|
+
valid: true
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function getRouteValue(route, key) {
|
|
181
|
+
if (!route || typeof route !== "object") {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
const maybeModel = route;
|
|
185
|
+
if (typeof maybeModel.get === "function") {
|
|
186
|
+
return maybeModel.get(key);
|
|
187
|
+
}
|
|
188
|
+
return route[key];
|
|
189
|
+
}
|
|
190
|
+
function getRouteId(route) {
|
|
191
|
+
const id = getRouteValue(route, "id");
|
|
192
|
+
return id === null || id === void 0 ? void 0 : String(id);
|
|
193
|
+
}
|
|
194
|
+
function getRouteParentId(route) {
|
|
195
|
+
const parentId = getRouteValue(route, "parentId");
|
|
196
|
+
return parentId === null || parentId === void 0 ? void 0 : String(parentId);
|
|
197
|
+
}
|
|
198
|
+
function setRouteChildren(route, children) {
|
|
199
|
+
if (!route || typeof route !== "object") {
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const maybeModel = route;
|
|
203
|
+
if (typeof maybeModel.setDataValue === "function") {
|
|
204
|
+
maybeModel.setDataValue("children", children);
|
|
205
|
+
} else {
|
|
206
|
+
maybeModel.children = children;
|
|
207
|
+
}
|
|
208
|
+
if (!maybeModel._options) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (!maybeModel._options.includeNames) {
|
|
212
|
+
maybeModel._options.includeNames = ["children"];
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (!maybeModel._options.includeNames.includes("children")) {
|
|
216
|
+
maybeModel._options.includeNames.push("children");
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
function collectRouteIds(routes, ids) {
|
|
220
|
+
for (const route of routes) {
|
|
221
|
+
const id = getRouteId(route);
|
|
222
|
+
if (id) {
|
|
223
|
+
ids.add(id);
|
|
224
|
+
}
|
|
225
|
+
const children = getRouteValue(route, "children");
|
|
226
|
+
if (Array.isArray(children)) {
|
|
227
|
+
collectRouteIds(children, ids);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function removeNestedRootRoutes(routes) {
|
|
232
|
+
if (!Array.isArray(routes)) {
|
|
233
|
+
return [];
|
|
234
|
+
}
|
|
235
|
+
const routeIds = /* @__PURE__ */ new Set();
|
|
236
|
+
collectRouteIds(routes, routeIds);
|
|
237
|
+
return routes.filter((route) => {
|
|
238
|
+
const parentId = getRouteParentId(route);
|
|
239
|
+
return !parentId || !routeIds.has(parentId);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
async function removeRouteIdsWithUnauthorizedAncestors(ctx, routeIds) {
|
|
243
|
+
if (routeIds.size === 0) {
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
const parentIdByRouteId = /* @__PURE__ */ new Map();
|
|
247
|
+
let pendingRouteIds = new Set(routeIds);
|
|
248
|
+
while (pendingRouteIds.size > 0) {
|
|
249
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
250
|
+
fields: ["id", "parentId"],
|
|
251
|
+
filter: {
|
|
252
|
+
id: Array.from(pendingRouteIds)
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
pendingRouteIds = /* @__PURE__ */ new Set();
|
|
256
|
+
for (const route of routes) {
|
|
257
|
+
const routeId = route.get("id");
|
|
258
|
+
if (routeId === null || routeId === void 0) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const normalizedRouteId = String(routeId);
|
|
262
|
+
const parentId = route.get("parentId");
|
|
263
|
+
const normalizedParentId = parentId === null || parentId === void 0 ? void 0 : String(parentId);
|
|
264
|
+
parentIdByRouteId.set(normalizedRouteId, normalizedParentId);
|
|
265
|
+
if (normalizedParentId && !parentIdByRouteId.has(normalizedParentId)) {
|
|
266
|
+
pendingRouteIds.add(normalizedParentId);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
for (const routeId of Array.from(routeIds)) {
|
|
271
|
+
const visitedRouteIds = /* @__PURE__ */ new Set([routeId]);
|
|
272
|
+
let parentId = parentIdByRouteId.get(routeId);
|
|
273
|
+
while (parentId) {
|
|
274
|
+
if (!routeIds.has(parentId) || visitedRouteIds.has(parentId)) {
|
|
275
|
+
routeIds.delete(routeId);
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
visitedRouteIds.add(parentId);
|
|
279
|
+
parentId = parentIdByRouteId.get(parentId);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
async function addDesktopRouteWriteLayout(ctx, next) {
|
|
284
|
+
var _a, _b, _c;
|
|
285
|
+
const params = (_a = ctx.action) == null ? void 0 : _a.params;
|
|
286
|
+
const hasExplicitLayoutScope = hasActionParam(params, "layout");
|
|
287
|
+
const explicitLayoutUid = hasExplicitLayoutScope ? getExplicitRequestedLayoutUid(params.layout) : void 0;
|
|
288
|
+
if (hasExplicitLayoutScope && !explicitLayoutUid) {
|
|
289
|
+
ctx.throw(400, ctx.t("Invalid layout scope", { ns: import_constants.NAMESPACE }));
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
const layoutUid = explicitLayoutUid ?? import_constants.DEFAULT_ADMIN_UI_LAYOUT.uid;
|
|
293
|
+
const uiLayout = await ctx.db.getRepository("uiLayouts").findOne({
|
|
294
|
+
filter: {
|
|
295
|
+
uid: layoutUid,
|
|
296
|
+
enabled: true
|
|
297
|
+
}
|
|
298
|
+
});
|
|
299
|
+
if (!uiLayout && hasExplicitLayoutScope) {
|
|
300
|
+
ctx.throw(400, ctx.t("Invalid layout scope", { ns: import_constants.NAMESPACE }));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
if (uiLayout) {
|
|
304
|
+
(_c = ctx.action) == null ? void 0 : _c.mergeParams({
|
|
305
|
+
values: withDesktopRouteUiLayout((_b = ctx.action) == null ? void 0 : _b.params.values, layoutUid)
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
await next();
|
|
309
|
+
}
|
|
310
|
+
async function preventUiLayoutUidChange(ctx, next) {
|
|
311
|
+
var _a, _b;
|
|
312
|
+
const values = (_a = ctx.action) == null ? void 0 : _a.params.values;
|
|
313
|
+
if (!isDesktopRouteCreateValue(values) || !Object.prototype.hasOwnProperty.call(values, "uid")) {
|
|
314
|
+
await next();
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
const requestedUid = values.uid;
|
|
318
|
+
const filterByTk = (_b = ctx.action) == null ? void 0 : _b.params.filterByTk;
|
|
319
|
+
if (typeof requestedUid !== "string" || filterByTk === null || filterByTk === void 0 || Array.isArray(filterByTk)) {
|
|
320
|
+
ctx.throw(400, ctx.t("UID cannot be changed", { ns: import_constants.NAMESPACE }));
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
const record = await ctx.db.getRepository("uiLayouts").findOne({
|
|
324
|
+
filterByTk
|
|
325
|
+
});
|
|
326
|
+
if (record && record.get("uid") !== requestedUid) {
|
|
327
|
+
ctx.throw(400, ctx.t("UID cannot be changed", { ns: import_constants.NAMESPACE }));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
await next();
|
|
331
|
+
}
|
|
332
|
+
async function preventDefaultAdminUiLayoutUpdate(ctx, next) {
|
|
333
|
+
var _a;
|
|
334
|
+
if (!hasUnsafeDefaultAdminUiLayoutValues((_a = ctx.action) == null ? void 0 : _a.params.values)) {
|
|
335
|
+
await next();
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const targets = await findUiLayoutActionTargets(ctx);
|
|
339
|
+
if (targets.some(isDefaultAdminUiLayoutRecord)) {
|
|
340
|
+
ctx.throw(400, ctx.t("Default AdminLayout cannot be changed", { ns: import_constants.NAMESPACE }));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
await next();
|
|
344
|
+
}
|
|
345
|
+
async function preventDefaultAdminUiLayoutDestroy(ctx, next) {
|
|
346
|
+
const targets = await findUiLayoutActionTargets(ctx);
|
|
347
|
+
if (targets.some(isDefaultAdminUiLayoutRecord)) {
|
|
348
|
+
ctx.throw(400, ctx.t("Default AdminLayout cannot be deleted", { ns: import_constants.NAMESPACE }));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
await next();
|
|
352
|
+
}
|
|
353
|
+
function getCurrentRoles(ctx) {
|
|
354
|
+
const currentRoles = ctx.state.currentRoles;
|
|
355
|
+
if (!Array.isArray(currentRoles)) {
|
|
356
|
+
return [];
|
|
357
|
+
}
|
|
358
|
+
return currentRoles.filter((role) => typeof role === "string");
|
|
359
|
+
}
|
|
360
|
+
async function getLayoutAccessibleRouteIds(ctx, layoutUid) {
|
|
361
|
+
const currentRoles = getCurrentRoles(ctx);
|
|
362
|
+
if (currentRoles.includes("root")) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
const routePermissions = await ctx.db.getRepository("rolesDesktopRoutes").find({
|
|
366
|
+
fields: ["desktopRouteId"],
|
|
367
|
+
filter: {
|
|
368
|
+
roleName: currentRoles
|
|
369
|
+
}
|
|
370
|
+
});
|
|
371
|
+
const permittedRouteIds = /* @__PURE__ */ new Set();
|
|
372
|
+
for (const permission of routePermissions) {
|
|
373
|
+
const routeId = permission.get("desktopRouteId");
|
|
374
|
+
if (routeId !== null && routeId !== void 0) {
|
|
375
|
+
permittedRouteIds.add(String(routeId));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (permittedRouteIds.size === 0) {
|
|
379
|
+
return permittedRouteIds;
|
|
380
|
+
}
|
|
381
|
+
const layoutRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
382
|
+
fields: ["id"],
|
|
383
|
+
filter: {
|
|
384
|
+
...getDesktopRouteLayoutFilterByUid(layoutUid),
|
|
385
|
+
id: Array.from(permittedRouteIds)
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
const routeIds = /* @__PURE__ */ new Set();
|
|
389
|
+
for (const route of layoutRoutes) {
|
|
390
|
+
const routeId = route.get("id");
|
|
391
|
+
if (routeId !== null && routeId !== void 0) {
|
|
392
|
+
routeIds.add(String(routeId));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
await removeRouteIdsWithUnauthorizedAncestors(ctx, routeIds);
|
|
396
|
+
return routeIds;
|
|
397
|
+
}
|
|
398
|
+
function removeInaccessibleRoute(route, allowedRouteIds) {
|
|
399
|
+
if (!route || !allowedRouteIds) {
|
|
400
|
+
return route;
|
|
401
|
+
}
|
|
402
|
+
const id = getRouteId(route);
|
|
403
|
+
if (!id || !allowedRouteIds.has(id)) {
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
return route;
|
|
407
|
+
}
|
|
408
|
+
function buildAccessibleRouteTreeWithAncestors(routes, accessibleRouteIds) {
|
|
409
|
+
const routeById = /* @__PURE__ */ new Map();
|
|
410
|
+
const childrenByParentId = /* @__PURE__ */ new Map();
|
|
411
|
+
const roots = [];
|
|
412
|
+
for (const route of routes) {
|
|
413
|
+
const id = getRouteId(route);
|
|
414
|
+
if (!id) {
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
setRouteChildren(route, void 0);
|
|
418
|
+
routeById.set(id, route);
|
|
419
|
+
}
|
|
420
|
+
for (const route of routes) {
|
|
421
|
+
const id = getRouteId(route);
|
|
422
|
+
if (!id || !routeById.has(id)) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const parentId = getRouteParentId(route);
|
|
426
|
+
if (!parentId || !routeById.has(parentId)) {
|
|
427
|
+
roots.push(route);
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
const children = childrenByParentId.get(parentId) ?? [];
|
|
431
|
+
children.push(route);
|
|
432
|
+
childrenByParentId.set(parentId, children);
|
|
433
|
+
}
|
|
434
|
+
const visitRoute = (route, visitingRouteIds) => {
|
|
435
|
+
const id = getRouteId(route);
|
|
436
|
+
if (!id || visitingRouteIds.has(id)) {
|
|
437
|
+
return void 0;
|
|
438
|
+
}
|
|
439
|
+
visitingRouteIds.add(id);
|
|
440
|
+
const children = childrenByParentId.get(id) ?? [];
|
|
441
|
+
const visibleChildren = children.map((child) => visitRoute(child, visitingRouteIds)).filter((child) => child !== void 0);
|
|
442
|
+
visitingRouteIds.delete(id);
|
|
443
|
+
if (!accessibleRouteIds.has(id) && visibleChildren.length === 0) {
|
|
444
|
+
return void 0;
|
|
445
|
+
}
|
|
446
|
+
setRouteChildren(route, visibleChildren.length > 0 ? visibleChildren : void 0);
|
|
447
|
+
return route;
|
|
448
|
+
};
|
|
449
|
+
return roots.map((route) => visitRoute(route, /* @__PURE__ */ new Set())).filter((route) => route !== void 0);
|
|
450
|
+
}
|
|
451
|
+
async function includeRouteAncestorsForListAccessible(ctx, routes, layoutFilter) {
|
|
452
|
+
if (!Array.isArray(routes) || getCurrentRoles(ctx).includes("root")) {
|
|
453
|
+
return routes;
|
|
454
|
+
}
|
|
455
|
+
const accessibleRouteIds = /* @__PURE__ */ new Set();
|
|
456
|
+
collectRouteIds(routes, accessibleRouteIds);
|
|
457
|
+
if (accessibleRouteIds.size === 0) {
|
|
458
|
+
return routes;
|
|
459
|
+
}
|
|
460
|
+
const layoutRoutes = await ctx.db.getRepository("desktopRoutes").find({
|
|
461
|
+
sort: "sort",
|
|
462
|
+
filter: layoutFilter
|
|
463
|
+
});
|
|
464
|
+
return buildAccessibleRouteTreeWithAncestors(layoutRoutes, accessibleRouteIds);
|
|
465
|
+
}
|
|
466
|
+
async function addDesktopRouteLayoutFilter(ctx, next) {
|
|
467
|
+
var _a, _b;
|
|
468
|
+
const layoutContext = await getDesktopRouteLayoutContext(ctx);
|
|
469
|
+
(_a = ctx.action) == null ? void 0 : _a.mergeParams({
|
|
470
|
+
filter: layoutContext.filter
|
|
471
|
+
});
|
|
472
|
+
await next();
|
|
473
|
+
if (!layoutContext.valid || !layoutContext.layoutUid) {
|
|
474
|
+
ctx.body = [];
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
if (getCurrentRoles(ctx).includes("root")) {
|
|
478
|
+
ctx.body = removeNestedRootRoutes(
|
|
479
|
+
await includeRouteAncestorsForListAccessible(ctx, ctx.body, layoutContext.filter)
|
|
480
|
+
);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
const routeIds = await getLayoutAccessibleRouteIds(ctx, layoutContext.layoutUid);
|
|
484
|
+
if (!routeIds || routeIds.size === 0) {
|
|
485
|
+
ctx.body = [];
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
489
|
+
tree: true,
|
|
490
|
+
sort: "sort",
|
|
491
|
+
filter: {
|
|
492
|
+
...(_b = ctx.action) == null ? void 0 : _b.params.filter,
|
|
493
|
+
id: Array.from(routeIds)
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
ctx.body = removeNestedRootRoutes(await includeRouteAncestorsForListAccessible(ctx, routes, layoutContext.filter));
|
|
497
|
+
}
|
|
498
|
+
async function addDesktopRouteGetLayoutFilter(ctx, next) {
|
|
499
|
+
var _a, _b, _c;
|
|
500
|
+
const layoutContext = await getDesktopRouteLayoutContext(ctx);
|
|
501
|
+
(_a = ctx.action) == null ? void 0 : _a.mergeParams({
|
|
502
|
+
filter: layoutContext.filter
|
|
503
|
+
});
|
|
504
|
+
await next();
|
|
505
|
+
if (!layoutContext.valid || !layoutContext.layoutUid) {
|
|
506
|
+
ctx.status = 204;
|
|
507
|
+
ctx.body = void 0;
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
if (getCurrentRoles(ctx).includes("root")) {
|
|
511
|
+
if (ctx.body) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
ctx.status = 204;
|
|
515
|
+
ctx.body = void 0;
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
const routeIds = await getLayoutAccessibleRouteIds(ctx, layoutContext.layoutUid);
|
|
519
|
+
let route = removeInaccessibleRoute(ctx.body, routeIds);
|
|
520
|
+
if (!route && routeIds && routeIds.size > 0) {
|
|
521
|
+
route = await ctx.db.getRepository("desktopRoutes").findOne({
|
|
522
|
+
sort: "sort",
|
|
523
|
+
...(_b = ctx.action) == null ? void 0 : _b.params,
|
|
524
|
+
filter: {
|
|
525
|
+
...(_c = ctx.action) == null ? void 0 : _c.params.filter,
|
|
526
|
+
id: Array.from(routeIds)
|
|
527
|
+
}
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
if (route == null) {
|
|
531
|
+
ctx.status = 204;
|
|
532
|
+
ctx.body = void 0;
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
535
|
+
ctx.body = route;
|
|
536
|
+
}
|
|
537
|
+
function pickUiLayoutRuntimeFields(record) {
|
|
538
|
+
const result = {};
|
|
539
|
+
for (const field of UI_LAYOUT_RUNTIME_FIELDS) {
|
|
540
|
+
result[field] = record.get(field);
|
|
541
|
+
}
|
|
542
|
+
return result;
|
|
543
|
+
}
|
|
544
|
+
function pickUiLayoutRolePermissionTargetFields(record) {
|
|
545
|
+
const result = {};
|
|
546
|
+
for (const field of UI_LAYOUT_ROLE_PERMISSION_TARGET_FIELDS) {
|
|
547
|
+
result[field] = record.get(field);
|
|
548
|
+
}
|
|
549
|
+
return result;
|
|
550
|
+
}
|
|
551
|
+
function pickDesktopRouteRolePermissionTargetFields(route) {
|
|
552
|
+
const result = {};
|
|
553
|
+
for (const field of DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS) {
|
|
554
|
+
result[field] = getRouteValue(route, field) ?? null;
|
|
555
|
+
}
|
|
556
|
+
const children = getRouteValue(route, "children");
|
|
557
|
+
result.children = Array.isArray(children) ? children.map((child) => pickDesktopRouteRolePermissionTargetFields(child)) : [];
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
560
|
+
async function listAccessibleUiLayouts(ctx, next) {
|
|
561
|
+
const records = await ctx.db.getRepository("uiLayouts").find({
|
|
562
|
+
filter: {
|
|
563
|
+
enabled: true
|
|
564
|
+
},
|
|
565
|
+
fields: [...UI_LAYOUT_RUNTIME_FIELDS],
|
|
566
|
+
sort: ["uid"]
|
|
567
|
+
});
|
|
568
|
+
ctx.body = records.map((record) => pickUiLayoutRuntimeFields(record));
|
|
569
|
+
await next();
|
|
570
|
+
}
|
|
571
|
+
async function listEnabledUiLayouts(ctx, next) {
|
|
572
|
+
const records = await ctx.db.getRepository("uiLayouts").find({
|
|
573
|
+
filter: {
|
|
574
|
+
enabled: true
|
|
575
|
+
},
|
|
576
|
+
fields: [...UI_LAYOUT_RUNTIME_FIELDS],
|
|
577
|
+
sort: ["uid"]
|
|
578
|
+
});
|
|
579
|
+
ctx.body = records.map((record) => pickUiLayoutRuntimeFields(record));
|
|
580
|
+
await next();
|
|
581
|
+
}
|
|
582
|
+
async function listUiLayoutRolePermissionTargets(ctx, next) {
|
|
583
|
+
const records = await ctx.db.getRepository("uiLayouts").find({
|
|
584
|
+
filter: {
|
|
585
|
+
enabled: true
|
|
586
|
+
},
|
|
587
|
+
fields: [...UI_LAYOUT_ROLE_PERMISSION_TARGET_FIELDS],
|
|
588
|
+
sort: ["uid"]
|
|
589
|
+
});
|
|
590
|
+
ctx.body = records.map((record) => pickUiLayoutRolePermissionTargetFields(record));
|
|
591
|
+
await next();
|
|
592
|
+
}
|
|
593
|
+
async function listDesktopRouteRolePermissionTargets(ctx, next) {
|
|
594
|
+
const layoutContext = await getDesktopRouteLayoutContext(ctx);
|
|
595
|
+
if (!layoutContext.valid) {
|
|
596
|
+
ctx.body = [];
|
|
597
|
+
await next();
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
const routes = await ctx.db.getRepository("desktopRoutes").find({
|
|
601
|
+
tree: true,
|
|
602
|
+
sort: "sort",
|
|
603
|
+
filter: layoutContext.filter,
|
|
604
|
+
fields: [...DESKTOP_ROUTE_ROLE_PERMISSION_TARGET_FIELDS]
|
|
605
|
+
});
|
|
606
|
+
ctx.body = removeNestedRootRoutes(routes).map((route) => pickDesktopRouteRolePermissionTargetFields(route));
|
|
607
|
+
await next();
|
|
608
|
+
}
|
|
609
|
+
class PluginUiLayoutServer extends import_server.Plugin {
|
|
610
|
+
async afterAdd() {
|
|
611
|
+
}
|
|
612
|
+
async beforeLoad() {
|
|
613
|
+
}
|
|
614
|
+
async load() {
|
|
615
|
+
this.app.acl.registerSnippet({
|
|
616
|
+
name: "pm.routes",
|
|
617
|
+
actions: ROUTES_MANAGEMENT_ACTIONS
|
|
618
|
+
});
|
|
619
|
+
this.app.acl.registerSnippet({
|
|
620
|
+
name: "pm.acl.roles",
|
|
621
|
+
actions: ROLE_LAYOUT_PERMISSION_TARGET_ACTIONS
|
|
622
|
+
});
|
|
623
|
+
this.app.acl.allow("uiLayouts", "listEnabled", "public");
|
|
624
|
+
this.app.acl.allow("uiLayouts", "listAccessible", "loggedIn");
|
|
625
|
+
this.app.resourceManager.registerActionHandler("uiLayouts:listEnabled", listEnabledUiLayouts);
|
|
626
|
+
this.app.resourceManager.registerActionHandler("uiLayouts:listAccessible", listAccessibleUiLayouts);
|
|
627
|
+
this.app.resourceManager.registerActionHandler(
|
|
628
|
+
"uiLayouts:listRolePermissionTargets",
|
|
629
|
+
listUiLayoutRolePermissionTargets
|
|
630
|
+
);
|
|
631
|
+
this.app.resourceManager.registerActionHandler(
|
|
632
|
+
"desktopRoutes:listRolePermissionTargets",
|
|
633
|
+
listDesktopRouteRolePermissionTargets
|
|
634
|
+
);
|
|
635
|
+
this.app.resourceManager.registerPreActionHandler("uiLayouts:update", preventUiLayoutUidChange);
|
|
636
|
+
this.app.resourceManager.registerPreActionHandler("uiLayouts:update", preventDefaultAdminUiLayoutUpdate);
|
|
637
|
+
this.app.resourceManager.registerPreActionHandler("uiLayouts:destroy", preventDefaultAdminUiLayoutDestroy);
|
|
638
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:create", addDesktopRouteWriteLayout, {
|
|
639
|
+
tag: DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
640
|
+
});
|
|
641
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:updateOrCreate", addDesktopRouteWriteLayout, {
|
|
642
|
+
tag: DESKTOP_ROUTE_WRITE_LAYOUT_HANDLER_TAG
|
|
643
|
+
});
|
|
644
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:listAccessible", addDesktopRouteLayoutFilter);
|
|
645
|
+
this.app.resourceManager.registerPreActionHandler("desktopRoutes:getAccessible", addDesktopRouteGetLayoutFilter);
|
|
646
|
+
}
|
|
647
|
+
async install() {
|
|
648
|
+
await (0, import_ensureDefaultUiLayout.ensureDefaultUiLayout)(this.db);
|
|
649
|
+
}
|
|
650
|
+
async afterEnable() {
|
|
651
|
+
await (0, import_ensureDefaultUiLayout.ensureDefaultUiLayout)(this.db);
|
|
652
|
+
}
|
|
653
|
+
async afterDisable() {
|
|
654
|
+
}
|
|
655
|
+
async remove() {
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
var plugin_default = PluginUiLayoutServer;
|
|
659
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
660
|
+
0 && (module.exports = {
|
|
661
|
+
PluginUiLayoutServer
|
|
662
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nocobase/plugin-ui-layout",
|
|
3
|
+
"version": "2.2.0-alpha.2",
|
|
4
|
+
"license": "Apache-2.0",
|
|
5
|
+
"main": "dist/server/index.js",
|
|
6
|
+
"displayName": "UI layout",
|
|
7
|
+
"displayName.zh-CN": "UI 布局",
|
|
8
|
+
"description": "Provides desktop layout, mobile layout, and route management pages.",
|
|
9
|
+
"description.zh-CN": "提供桌面端布局、移动端布局、路由管理页面。",
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@ant-design/icons": "5.x",
|
|
12
|
+
"@dnd-kit/core": "^6.0.0",
|
|
13
|
+
"@emotion/css": "11.x",
|
|
14
|
+
"@formily/antd-v5": "1.x",
|
|
15
|
+
"@formily/json-schema": "2.x",
|
|
16
|
+
"@formily/react": "2.x",
|
|
17
|
+
"@formily/reactive": "2.x",
|
|
18
|
+
"@formily/shared": "2.x",
|
|
19
|
+
"ahooks": "3.x",
|
|
20
|
+
"antd": "5.x",
|
|
21
|
+
"lodash": "4.x",
|
|
22
|
+
"react": "^18.2.0",
|
|
23
|
+
"react-i18next": "11.x",
|
|
24
|
+
"react-router-dom": "6.x",
|
|
25
|
+
"sequelize": "^6.26.0"
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"@nocobase/client": "2.x",
|
|
29
|
+
"@nocobase/client-v2": "2.x",
|
|
30
|
+
"@nocobase/database": "2.x",
|
|
31
|
+
"@nocobase/flow-engine": "2.x",
|
|
32
|
+
"@nocobase/server": "2.x",
|
|
33
|
+
"@nocobase/test": "2.x",
|
|
34
|
+
"@nocobase/utils": "2.x"
|
|
35
|
+
},
|
|
36
|
+
"gitHead": "9dd5224bb8f6c1ac12d766b9f175bdc29bddd845"
|
|
37
|
+
}
|
package/server.d.ts
ADDED
package/server.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./dist/server/index.js');
|