@antelopejs/dms 0.3.6 → 0.3.8
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/CHANGELOG.md +48 -0
- package/dist/implementations/dms/page.js +2 -2
- package/dist/implementations/dms/page.js.map +1 -1
- package/dist/test/antelope.test.js +16 -5
- package/dist/test/antelope.test.js.map +1 -1
- package/dist/test/api-endpoint/index.d.ts +5 -0
- package/dist/test/api-endpoint/index.js +8 -0
- package/dist/test/api-endpoint/index.js.map +1 -0
- package/dist/test/helpers/http.d.ts +1 -0
- package/dist/test/helpers/http.js +16 -4
- package/dist/test/helpers/http.js.map +1 -1
- package/dist/test/unit/implementations/dms/notify-menu-changed.test.js +21 -0
- package/dist/test/unit/implementations/dms/notify-menu-changed.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-form-page-urls.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-page-urls.test.js +308 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-page-urls.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js +241 -0
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-route-param-filter-defaults.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-route-param-filter-defaults.test.js +254 -0
- package/dist/test/unit/interfaces/dms-base/table-view-route-param-filter-defaults.test.js.map +1 -0
- package/frontend-vue/layers/dms-layout/app/composables/page/useSiteLayout.ts +49 -20
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +6 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTable.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table/useTableColumns.ts +13 -7
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/registerDefaultFunctions.ts +49 -20
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +43 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +23 -29
- package/frontend-vue/layers/dms-ui/app/components/grid/Grid.vue +10 -1
- package/frontend-vue/layers/dms-ui/app/components/grid/GridRow.vue +16 -6
- package/frontend-vue/layers/dms-ui/app/components/grid/columns.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/components/grid/constants.ts +1 -0
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +3 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +35 -25
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +5 -1
- package/frontend-vue/pnpm-lock.yaml +4 -4
- package/frontend-vue/tests/form-page-url-fill.test.ts +63 -0
- package/frontend-vue/tests/form-redirect-placeholders.test.ts +69 -0
- package/frontend-vue/tests/grid-responsive-columns.test.ts +105 -0
- package/frontend-vue/tests/route-param-filter-defaults.test.ts +59 -0
- package/frontend-vue/tests/route-param-occurrences.test.ts +109 -0
- package/package.json +2 -2
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
19
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
20
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
21
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
22
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
23
|
+
};
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
42
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
const interface_api_1 = require("@antelopejs/interface-api");
|
|
46
|
+
const interface_core_1 = require("@antelopejs/interface-core");
|
|
47
|
+
const interface_data_api_1 = require("@antelopejs/interface-data-api");
|
|
48
|
+
const metadata_1 = require("@antelopejs/interface-data-api/metadata");
|
|
49
|
+
const interface_database_decorators_1 = require("@antelopejs/interface-database-decorators");
|
|
50
|
+
const chai_1 = require("chai");
|
|
51
|
+
const pageImpl = __importStar(require("../../../../implementations/dms/page"));
|
|
52
|
+
const permissionsImpl = __importStar(require("../../../../implementations/dms/permissions"));
|
|
53
|
+
const realtimeImpl = __importStar(require("../../../../implementations/dms/realtime"));
|
|
54
|
+
const constants_1 = require("@antelopejs/interface-dms/constants");
|
|
55
|
+
const pageInterface = __importStar(require("@antelopejs/interface-dms/page"));
|
|
56
|
+
const page_1 = require("@antelopejs/interface-dms/page");
|
|
57
|
+
const registry_1 = require("@antelopejs/interface-dms/page/registry");
|
|
58
|
+
const permissionsInterface = __importStar(require("@antelopejs/interface-dms/permissions"));
|
|
59
|
+
const realtimeInterface = __importStar(require("@antelopejs/interface-dms/realtime"));
|
|
60
|
+
const default_types_1 = require("@antelopejs/interface-dms/base/data-types/default-types");
|
|
61
|
+
const table_view_1 = require("@antelopejs/interface-dms/base/table-view");
|
|
62
|
+
const TABLE = "route-param-filter-documents";
|
|
63
|
+
const LOCATION = "/api/route-param-filter-defaults";
|
|
64
|
+
function settle() {
|
|
65
|
+
return new Promise((resolve) => setImmediate(resolve));
|
|
66
|
+
}
|
|
67
|
+
let Document = class Document extends interface_database_decorators_1.Table {
|
|
68
|
+
};
|
|
69
|
+
__decorate([
|
|
70
|
+
(0, interface_database_decorators_1.Field)("string"),
|
|
71
|
+
__metadata("design:type", String)
|
|
72
|
+
], Document.prototype, "name", void 0);
|
|
73
|
+
__decorate([
|
|
74
|
+
(0, interface_database_decorators_1.Field)("string"),
|
|
75
|
+
__metadata("design:type", String)
|
|
76
|
+
], Document.prototype, "_instance", void 0);
|
|
77
|
+
Document = __decorate([
|
|
78
|
+
(0, interface_database_decorators_1.RegisterTable)(TABLE, constants_1.CORE_SCHEMA_NAME)
|
|
79
|
+
], Document);
|
|
80
|
+
class DocumentModel extends (0, interface_database_decorators_1.BasicDataModel)(Document, TABLE) {
|
|
81
|
+
}
|
|
82
|
+
let DocumentAPI = class DocumentAPI extends (0, interface_data_api_1.DataController)(Document, { new: table_view_1.TableViewRoutes.New, edit: table_view_1.TableViewRoutes.Edit }, (0, interface_api_1.Controller)(LOCATION)) {
|
|
83
|
+
};
|
|
84
|
+
__decorate([
|
|
85
|
+
(0, metadata_1.ModelReference)(),
|
|
86
|
+
(0, interface_database_decorators_1.Model)(DocumentModel),
|
|
87
|
+
__metadata("design:type", DocumentModel)
|
|
88
|
+
], DocumentAPI.prototype, "model", void 0);
|
|
89
|
+
__decorate([
|
|
90
|
+
(0, table_view_1.Column)({ name: "Name", type: new default_types_1.DefaultDataTypes.StringType() }),
|
|
91
|
+
(0, metadata_1.Access)(metadata_1.AccessMode.ReadWrite),
|
|
92
|
+
__metadata("design:type", String)
|
|
93
|
+
], DocumentAPI.prototype, "name", void 0);
|
|
94
|
+
DocumentAPI = __decorate([
|
|
95
|
+
(0, interface_data_api_1.RegisterDataController)()
|
|
96
|
+
], DocumentAPI);
|
|
97
|
+
const ROUTE_FILTER = { id: { field: "_instance" } };
|
|
98
|
+
const QUERY_FILTER = { status: { field: "status" } };
|
|
99
|
+
const detailTable = (0, table_view_1.TableView)(DocumentAPI, {
|
|
100
|
+
formContainer: { type: "page" },
|
|
101
|
+
routeParamFilters: ROUTE_FILTER,
|
|
102
|
+
queryParamFilters: QUERY_FILTER,
|
|
103
|
+
realtime: false,
|
|
104
|
+
});
|
|
105
|
+
const namedParamTable = (0, table_view_1.TableView)(DocumentAPI, {
|
|
106
|
+
formContainer: { type: "page" },
|
|
107
|
+
routeParamFilters: { workspace: { field: "_instance" } },
|
|
108
|
+
realtime: false,
|
|
109
|
+
});
|
|
110
|
+
const deepTable = (0, table_view_1.TableView)(DocumentAPI, {
|
|
111
|
+
formContainer: { type: "page" },
|
|
112
|
+
routeParamFilters: ROUTE_FILTER,
|
|
113
|
+
realtime: false,
|
|
114
|
+
});
|
|
115
|
+
const topLevelTable = (0, table_view_1.TableView)(DocumentAPI, {
|
|
116
|
+
formContainer: { type: "page" },
|
|
117
|
+
routeParamFilters: ROUTE_FILTER,
|
|
118
|
+
realtime: false,
|
|
119
|
+
});
|
|
120
|
+
const unfilteredTable = (0, table_view_1.TableView)(DocumentAPI, {
|
|
121
|
+
formContainer: { type: "page" },
|
|
122
|
+
realtime: false,
|
|
123
|
+
});
|
|
124
|
+
const drawerTable = (0, table_view_1.TableView)(DocumentAPI, {
|
|
125
|
+
formContainer: { type: "drawer" },
|
|
126
|
+
routeParamFilters: ROUTE_FILTER,
|
|
127
|
+
realtime: false,
|
|
128
|
+
});
|
|
129
|
+
let DetailPage = class DetailPage extends (0, page_1.RootPageController)("rpf-detail", {
|
|
130
|
+
displayName: "Detail of a workspace",
|
|
131
|
+
urlSlug: "rpf-detail/:id",
|
|
132
|
+
}) {
|
|
133
|
+
static rows = detailTable;
|
|
134
|
+
};
|
|
135
|
+
DetailPage = __decorate([
|
|
136
|
+
(0, page_1.RegisterPage)()
|
|
137
|
+
], DetailPage);
|
|
138
|
+
let NamedParamPage = class NamedParamPage extends (0, page_1.RootPageController)("rpf-named", {
|
|
139
|
+
displayName: "Detail with a placeholder of its own name",
|
|
140
|
+
urlSlug: "rpf-named/:workspace",
|
|
141
|
+
}) {
|
|
142
|
+
static rows = namedParamTable;
|
|
143
|
+
};
|
|
144
|
+
NamedParamPage = __decorate([
|
|
145
|
+
(0, page_1.RegisterPage)()
|
|
146
|
+
], NamedParamPage);
|
|
147
|
+
let DeepPage = class DeepPage extends (0, page_1.RootPageController)("rpf-deep", {
|
|
148
|
+
displayName: "Page repeating its own placeholder",
|
|
149
|
+
urlSlug: "rpf-deep/:id/sub/:id",
|
|
150
|
+
}) {
|
|
151
|
+
static rows = deepTable;
|
|
152
|
+
};
|
|
153
|
+
DeepPage = __decorate([
|
|
154
|
+
(0, page_1.RegisterPage)()
|
|
155
|
+
], DeepPage);
|
|
156
|
+
let TopLevelPage = class TopLevelPage extends (0, page_1.RootPageController)("rpf-top", {
|
|
157
|
+
displayName: "Page without a route parameter",
|
|
158
|
+
}) {
|
|
159
|
+
static rows = topLevelTable;
|
|
160
|
+
};
|
|
161
|
+
TopLevelPage = __decorate([
|
|
162
|
+
(0, page_1.RegisterPage)()
|
|
163
|
+
], TopLevelPage);
|
|
164
|
+
let UnfilteredPage = class UnfilteredPage extends (0, page_1.RootPageController)("rpf-unfiltered", {
|
|
165
|
+
displayName: "Table view without URL filters",
|
|
166
|
+
}) {
|
|
167
|
+
static rows = unfilteredTable;
|
|
168
|
+
};
|
|
169
|
+
UnfilteredPage = __decorate([
|
|
170
|
+
(0, page_1.RegisterPage)()
|
|
171
|
+
], UnfilteredPage);
|
|
172
|
+
let DrawerPage = class DrawerPage extends (0, page_1.RootPageController)("rpf-drawer", {
|
|
173
|
+
displayName: "Drawer form container",
|
|
174
|
+
urlSlug: "rpf-drawer/:id",
|
|
175
|
+
}) {
|
|
176
|
+
static rows = drawerTable;
|
|
177
|
+
};
|
|
178
|
+
DrawerPage = __decorate([
|
|
179
|
+
(0, page_1.RegisterPage)()
|
|
180
|
+
], DrawerPage);
|
|
181
|
+
const pages = [
|
|
182
|
+
DetailPage,
|
|
183
|
+
NamedParamPage,
|
|
184
|
+
DeepPage,
|
|
185
|
+
TopLevelPage,
|
|
186
|
+
UnfilteredPage,
|
|
187
|
+
DrawerPage,
|
|
188
|
+
];
|
|
189
|
+
async function formPageOptions(formPageFullId) {
|
|
190
|
+
const form = registry_1.pageMetadataByFullId.get(formPageFullId)?.components.form;
|
|
191
|
+
if (!form)
|
|
192
|
+
throw new Error(`No form page registered as ${formPageFullId}`);
|
|
193
|
+
return (await form.serialize()).options;
|
|
194
|
+
}
|
|
195
|
+
async function formComponents(table) {
|
|
196
|
+
return (await table.serialize()).options?.formComponents;
|
|
197
|
+
}
|
|
198
|
+
describe("[unit] interfaces/dms-base — route param filter form defaults", () => {
|
|
199
|
+
before(async () => {
|
|
200
|
+
(0, interface_core_1.ImplementInterface)(permissionsInterface, permissionsImpl);
|
|
201
|
+
(0, interface_core_1.ImplementInterface)(realtimeInterface, realtimeImpl);
|
|
202
|
+
(0, interface_core_1.ImplementInterface)(pageInterface, pageImpl);
|
|
203
|
+
await settle();
|
|
204
|
+
});
|
|
205
|
+
after(() => {
|
|
206
|
+
for (const page of pages) {
|
|
207
|
+
const { pageInfo } = (0, interface_core_1.GetMetadata)(page, page_1.PageMetadata);
|
|
208
|
+
if (pageInfo)
|
|
209
|
+
pageInterface.internal.RegisterPage.unregister(pageInfo);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
it("numbers the page parameter on an edit route repeating its name", async () => {
|
|
213
|
+
const edit = await formPageOptions("rpf-detail.rows.edit");
|
|
214
|
+
(0, chai_1.expect)(edit?.submitDefaults).to.deep.equal({
|
|
215
|
+
status: "{{query.status}}",
|
|
216
|
+
_instance: "{{params.id:1}}",
|
|
217
|
+
});
|
|
218
|
+
(0, chai_1.expect)(edit?.submitUrl).to.equal(`${LOCATION}/edit?id={{params.id}}`);
|
|
219
|
+
});
|
|
220
|
+
it("keeps the bare name on a new route carrying the name once", async () => {
|
|
221
|
+
(0, chai_1.expect)((await formPageOptions("rpf-detail.rows.new"))?.submitDefaults).to.deep.equal({
|
|
222
|
+
status: "{{query.status}}",
|
|
223
|
+
_instance: "{{params.id}}",
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
it("gives the view form no defaults", async () => {
|
|
227
|
+
(0, chai_1.expect)((await formPageOptions("rpf-detail.rows.view"))?.submitDefaults).to.equal(undefined);
|
|
228
|
+
});
|
|
229
|
+
it("keeps the bare name when the page parameter is not named id", async () => {
|
|
230
|
+
(0, chai_1.expect)((await formPageOptions("rpf-named.rows.edit"))?.submitDefaults).to.deep.equal({ _instance: "{{params.workspace}}" });
|
|
231
|
+
});
|
|
232
|
+
it("numbers the last occurrence of a name the page slug repeats", async () => {
|
|
233
|
+
(0, chai_1.expect)((await formPageOptions("rpf-deep.rows.new"))?.submitDefaults).to.deep.equal({ _instance: "{{params.id:2}}" });
|
|
234
|
+
(0, chai_1.expect)((await formPageOptions("rpf-deep.rows.edit"))?.submitDefaults).to.deep.equal({ _instance: "{{params.id:2}}" });
|
|
235
|
+
});
|
|
236
|
+
it("leaves the forms of a page without route parameter as they were", async () => {
|
|
237
|
+
for (const kind of ["new", "edit"]) {
|
|
238
|
+
(0, chai_1.expect)((await formPageOptions(`rpf-top.rows.${kind}`))?.submitDefaults).to.deep.equal({ _instance: "{{params.id}}" });
|
|
239
|
+
}
|
|
240
|
+
const unfiltered = await formPageOptions("rpf-unfiltered.rows.edit");
|
|
241
|
+
(0, chai_1.expect)(unfiltered?.submitDefaults).to.equal(undefined);
|
|
242
|
+
});
|
|
243
|
+
it("keeps the bare name on the forms the table view embeds", async () => {
|
|
244
|
+
const embedded = await formComponents(drawerTable);
|
|
245
|
+
(0, chai_1.expect)(embedded?.edit?.options?.submitDefaults).to.deep.equal({
|
|
246
|
+
_instance: "{{params.id}}",
|
|
247
|
+
});
|
|
248
|
+
(0, chai_1.expect)((await formComponents(detailTable))?.edit?.options?.submitDefaults).to.deep.equal({
|
|
249
|
+
status: "{{query.status}}",
|
|
250
|
+
_instance: "{{params.id}}",
|
|
251
|
+
});
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
//# sourceMappingURL=table-view-route-param-filter-defaults.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table-view-route-param-filter-defaults.test.js","sourceRoot":"","sources":["../../../../../src/test/unit/interfaces/dms-base/table-view-route-param-filter-defaults.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,36 +37,65 @@ interface MatchRouteResult {
|
|
|
37
37
|
params: Record<string, string>;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
// A route pattern can carry the same placeholder name twice: a page-mode form
|
|
41
|
+
// sub-page under a page whose own slug is `:id` resolves to
|
|
42
|
+
// `/workspaces/:id/invoiceTable/:id/edit`. The bare name holds the LAST
|
|
43
|
+
// occurrence, so `params.id` keeps meaning the row id the way it does on every
|
|
44
|
+
// single-placeholder form route, and each occurrence of a repeated name is
|
|
45
|
+
// exposed as well under `<name>:<n>`, numbered from 1 in pattern order, so the
|
|
46
|
+
// id of the carrying page stays reachable as `id:1`. A name that occurs only
|
|
47
|
+
// once gets no indexed key: those bags are exactly what they were.
|
|
48
|
+
const OCCURRENCE_KEY_SEPARATOR = ":";
|
|
49
|
+
|
|
50
|
+
const occurrenceKey = (name: string, occurrence: number): string =>
|
|
51
|
+
`${name}${OCCURRENCE_KEY_SEPARATOR}${occurrence}`;
|
|
52
|
+
|
|
53
|
+
function countByName(names: string[]): Record<string, number> {
|
|
54
|
+
const totals: Record<string, number> = {};
|
|
55
|
+
for (const name of names) {
|
|
56
|
+
totals[name] = (totals[name] ?? 0) + 1;
|
|
57
|
+
}
|
|
58
|
+
return totals;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function buildParamBag(
|
|
62
|
+
names: string[],
|
|
63
|
+
values: string[],
|
|
64
|
+
): Record<string, string> {
|
|
65
|
+
const totals = countByName(names);
|
|
66
|
+
const seen: Record<string, number> = {};
|
|
67
|
+
const params: Record<string, string> = {};
|
|
68
|
+
|
|
69
|
+
names.forEach((name, index) => {
|
|
70
|
+
const value = values[index]!;
|
|
71
|
+
const occurrence = (seen[name] ?? 0) + 1;
|
|
72
|
+
seen[name] = occurrence;
|
|
73
|
+
params[name] = value;
|
|
74
|
+
if (totals[name]! > 1) {
|
|
75
|
+
params[occurrenceKey(name, occurrence)] = value;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return params;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function extractRouteParams(
|
|
41
83
|
pattern: string,
|
|
42
84
|
path: string,
|
|
43
85
|
): Record<string, string> | null {
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
.map((segment) =>
|
|
47
|
-
if (segment.startsWith(":")) {
|
|
48
|
-
return "([^/]+)";
|
|
49
|
-
}
|
|
50
|
-
return segment;
|
|
51
|
-
})
|
|
86
|
+
const segments = pattern.split("/");
|
|
87
|
+
const regexPattern = segments
|
|
88
|
+
.map((segment) => (segment.startsWith(":") ? "([^/]+)" : segment))
|
|
52
89
|
.join("/");
|
|
53
90
|
|
|
54
|
-
const
|
|
55
|
-
const match = path.match(regex);
|
|
56
|
-
|
|
91
|
+
const match = path.match(new RegExp(`^${regexPattern}$`));
|
|
57
92
|
if (!match) return null;
|
|
58
93
|
|
|
59
|
-
const paramNames =
|
|
60
|
-
.split("/")
|
|
94
|
+
const paramNames = segments
|
|
61
95
|
.filter((segment) => segment.startsWith(":"))
|
|
62
96
|
.map((segment) => segment.substring(1));
|
|
63
97
|
|
|
64
|
-
|
|
65
|
-
paramNames.forEach((name, index) => {
|
|
66
|
-
params[name] = match[index + 1]!;
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
return params;
|
|
98
|
+
return buildParamBag(paramNames, match.slice(1));
|
|
70
99
|
}
|
|
71
100
|
|
|
72
101
|
export const useSiteLayout = () => {
|
|
@@ -26,7 +26,10 @@ import type {
|
|
|
26
26
|
CustomButton,
|
|
27
27
|
TableViewDisplayCapabilities,
|
|
28
28
|
} from "../../../composables/table-view/types";
|
|
29
|
-
import type {
|
|
29
|
+
import type {
|
|
30
|
+
FormContainer,
|
|
31
|
+
FormPageUrls,
|
|
32
|
+
} from "../../composables/table-view/useTableViewConfig";
|
|
30
33
|
import type { CustomRowAction } from "../../../types/row-action";
|
|
31
34
|
import type { RowActionConfig } from "#dms-core/app/types/row-action";
|
|
32
35
|
import type { TableTabItem } from "./Tabs.vue";
|
|
@@ -97,6 +100,8 @@ export interface TableProps<T> {
|
|
|
97
100
|
onCustomRowAction?: (action: CustomRowAction, rowData: T) => void;
|
|
98
101
|
componentId?: string;
|
|
99
102
|
formContainer?: FormContainer;
|
|
103
|
+
formPages?: FormPageUrls;
|
|
104
|
+
routeParams?: Record<string, string>;
|
|
100
105
|
defaultSort?: { field: string; desc?: boolean };
|
|
101
106
|
initialColumnVisibility?: VisibilityState;
|
|
102
107
|
presenceByRow?: TableRowPresenceMap;
|
|
@@ -92,6 +92,8 @@ export const useTable = <T extends Data>(props: UseTableProps<T>) => {
|
|
|
92
92
|
ui: props.ui,
|
|
93
93
|
componentId: props.tableProps.componentId,
|
|
94
94
|
formContainer: props.tableProps.formContainer,
|
|
95
|
+
formPages: props.tableProps.formPages,
|
|
96
|
+
routeParams: props.tableProps.routeParams,
|
|
95
97
|
onCustomRowAction: props.tableProps.onCustomRowAction,
|
|
96
98
|
});
|
|
97
99
|
|
|
@@ -28,7 +28,11 @@ import {
|
|
|
28
28
|
FormContainerType,
|
|
29
29
|
DEFAULT_FORM_CONTAINER_TYPE,
|
|
30
30
|
} from "../table-view/types";
|
|
31
|
-
import
|
|
31
|
+
import {
|
|
32
|
+
fillFormPageUrl,
|
|
33
|
+
type FormContainer,
|
|
34
|
+
type FormPageUrls,
|
|
35
|
+
} from "../table-view/useTableViewConfig";
|
|
32
36
|
import type { DmsAppConfig } from "#dms-core/shared/types/app-config";
|
|
33
37
|
|
|
34
38
|
const SORT_DIRECTION_ICON: Record<SortDirection, string> = {
|
|
@@ -84,6 +88,8 @@ interface ColumnConfig<T> {
|
|
|
84
88
|
ui: ComputedRef<TableStyleSlots>;
|
|
85
89
|
componentId?: string;
|
|
86
90
|
formContainer?: FormContainer;
|
|
91
|
+
formPages?: FormPageUrls;
|
|
92
|
+
routeParams?: Record<string, string>;
|
|
87
93
|
onCustomRowAction?: (action: CustomRowAction, rowData: T) => void;
|
|
88
94
|
}
|
|
89
95
|
|
|
@@ -353,13 +359,13 @@ export const useTableColumns = <T extends Data>(config: ColumnConfig<T>) => {
|
|
|
353
359
|
|
|
354
360
|
const containerType =
|
|
355
361
|
config.formContainer?.type ?? DEFAULT_FORM_CONTAINER_TYPE;
|
|
356
|
-
|
|
362
|
+
const viewPageUrl =
|
|
363
|
+
containerType === FormContainerType.page
|
|
364
|
+
? config.formPages?.view
|
|
365
|
+
: undefined;
|
|
366
|
+
if (viewPageUrl) {
|
|
357
367
|
const itemId = get(rowData, config.rowIdKey);
|
|
358
|
-
|
|
359
|
-
config.formContainer?.pages?.view?.urlSlug ||
|
|
360
|
-
`${currentRoute.path}/${itemId}/view`;
|
|
361
|
-
const viewUrl = viewSlug.replace(":id", itemId);
|
|
362
|
-
itemUrl = `${window.location.origin}${viewUrl}`;
|
|
368
|
+
itemUrl = `${window.location.origin}${fillFormPageUrl(viewPageUrl, config.routeParams, itemId)}`;
|
|
363
369
|
} else {
|
|
364
370
|
const updatedQuery = {
|
|
365
371
|
...currentRoute.query,
|
package/frontend-vue/layers/dms-ui/app/build/composables/table-view/registerDefaultFunctions.ts
CHANGED
|
@@ -1,32 +1,61 @@
|
|
|
1
1
|
import type { LocationQueryValue } from "#dms/frontend-module";
|
|
2
|
+
import { replaceUrlVariables } from "../../../composables/form/useForm";
|
|
3
|
+
|
|
4
|
+
type RouteQuery = Record<string, LocationQueryValue | LocationQueryValue[]>;
|
|
5
|
+
|
|
6
|
+
export interface FormSuccessParams {
|
|
7
|
+
url: string;
|
|
8
|
+
preserveQuery?: string[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface FormSuccessRoute {
|
|
12
|
+
query: RouteQuery;
|
|
13
|
+
params?: Record<string, string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Where a page-mode form sends the user on submit: the page carrying the table
|
|
18
|
+
* view. Its URL holds a `{{params.X}}` token per placeholder of that page's
|
|
19
|
+
* slug, resolved against the parameters of the form route being left, the way
|
|
20
|
+
* `redirectOnSuccess` is.
|
|
21
|
+
*/
|
|
22
|
+
export function resolveFormSuccessTarget(
|
|
23
|
+
{ url, preserveQuery }: FormSuccessParams,
|
|
24
|
+
route: FormSuccessRoute,
|
|
25
|
+
): { path: string; query?: RouteQuery } {
|
|
26
|
+
const path = replaceUrlVariables(url, {
|
|
27
|
+
routeParams: route.params,
|
|
28
|
+
routeQuery: route.query,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// Restore the filtered table view: carry back the query params the form
|
|
32
|
+
// was opened with (those declared in `queryParamFilters`).
|
|
33
|
+
const query: RouteQuery = {};
|
|
34
|
+
for (const key of preserveQuery ?? []) {
|
|
35
|
+
if (route.query[key] !== undefined) {
|
|
36
|
+
query[key] = route.query[key];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return { path, ...(Object.keys(query).length > 0 ? { query } : {}) };
|
|
41
|
+
}
|
|
2
42
|
|
|
3
43
|
export function registerDefaultFunctions() {
|
|
4
44
|
const { registerFunction } = useDefinedFunctions();
|
|
5
45
|
const route = useDmsRoute();
|
|
46
|
+
const { findMatchingRoute } = useSiteLayout();
|
|
6
47
|
|
|
7
48
|
registerFunction(
|
|
8
49
|
TableViewFunctions.CUSTOM_PAGE_FORM_SUCCESS,
|
|
9
50
|
(event: CustomEvent) => {
|
|
10
|
-
const {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const query: Record<string, LocationQueryValue | LocationQueryValue[]> =
|
|
19
|
-
{};
|
|
20
|
-
for (const key of preserveQuery ?? []) {
|
|
21
|
-
if (route.query[key] !== undefined) {
|
|
22
|
-
query[key] = route.query[key];
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
navigateDms({
|
|
27
|
-
path: url,
|
|
28
|
-
...(Object.keys(query).length > 0 ? { query } : {}),
|
|
29
|
-
});
|
|
51
|
+
const { params } = event as CustomEvent & { params: FormSuccessParams };
|
|
52
|
+
|
|
53
|
+
navigateDms(
|
|
54
|
+
resolveFormSuccessTarget(params, {
|
|
55
|
+
query: route.query,
|
|
56
|
+
params: findMatchingRoute(route.path)?.params,
|
|
57
|
+
}),
|
|
58
|
+
);
|
|
30
59
|
},
|
|
31
60
|
);
|
|
32
61
|
}
|
|
@@ -35,16 +35,51 @@ const withBuiltinDefaults = (
|
|
|
35
35
|
};
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
+
// The per-page slugs the table view declares are resolved server-side and
|
|
39
|
+
// arrive as `formPages`; the frontend never reads them.
|
|
38
40
|
export interface FormContainer {
|
|
39
41
|
type: FormContainerType;
|
|
40
42
|
size?: ModalSize;
|
|
41
|
-
pages?: {
|
|
42
|
-
new?: { urlSlug?: string; displayName?: string; description?: string };
|
|
43
|
-
edit?: { urlSlug?: string; displayName?: string; description?: string };
|
|
44
|
-
view?: { urlSlug?: string; displayName?: string; description?: string };
|
|
45
|
-
};
|
|
46
43
|
}
|
|
47
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Page-mode form URLs resolved by the server, placeholders included: the `:id`
|
|
47
|
+
* of the row, and those the slug of the carrying page contributes.
|
|
48
|
+
*/
|
|
49
|
+
export interface FormPageUrls {
|
|
50
|
+
new: string;
|
|
51
|
+
edit: string;
|
|
52
|
+
view: string;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const ROW_ID_PLACEHOLDER = ":id";
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Turn a serialized form page URL into the one to navigate to. The row id
|
|
59
|
+
* takes the last `:id` — the form slug contributes it after the slug of the
|
|
60
|
+
* carrying page, so an earlier one belongs to that page — and the params of
|
|
61
|
+
* the current route fill whatever placeholder is left.
|
|
62
|
+
*/
|
|
63
|
+
export const fillFormPageUrl = (
|
|
64
|
+
url: string,
|
|
65
|
+
routeParams?: Record<string, string>,
|
|
66
|
+
itemId?: string,
|
|
67
|
+
): string => {
|
|
68
|
+
let filled = url;
|
|
69
|
+
const rowIdAt =
|
|
70
|
+
itemId === undefined ? -1 : url.lastIndexOf(ROW_ID_PLACEHOLDER);
|
|
71
|
+
if (rowIdAt >= 0) {
|
|
72
|
+
filled =
|
|
73
|
+
url.slice(0, rowIdAt) +
|
|
74
|
+
itemId +
|
|
75
|
+
url.slice(rowIdAt + ROW_ID_PLACEHOLDER.length);
|
|
76
|
+
}
|
|
77
|
+
for (const [param, value] of Object.entries(routeParams ?? {})) {
|
|
78
|
+
filled = filled.replace(new RegExp(`:${param}(?![\\w])`, "g"), value);
|
|
79
|
+
}
|
|
80
|
+
return filled;
|
|
81
|
+
};
|
|
82
|
+
|
|
48
83
|
export const useTableViewConfig = <T extends Data>(
|
|
49
84
|
config: TableViewConfig<T>,
|
|
50
85
|
) => {
|
|
@@ -90,6 +125,8 @@ export const useTableViewConfig = <T extends Data>(
|
|
|
90
125
|
customNavItems: config.customNavItems,
|
|
91
126
|
componentId: config.componentId,
|
|
92
127
|
formContainer: config.formContainer,
|
|
128
|
+
formPages: config.formPages,
|
|
129
|
+
routeParams: config.routeParams,
|
|
93
130
|
data: config.data,
|
|
94
131
|
loading: config.loading,
|
|
95
132
|
orderOptions: config.orderOptions,
|
|
@@ -117,6 +154,7 @@ export const useTableViewConfig = <T extends Data>(
|
|
|
117
154
|
customButtons: config.customButtons,
|
|
118
155
|
formComponents: config.formComponents,
|
|
119
156
|
formContainer: config.formContainer,
|
|
157
|
+
formPages: config.formPages,
|
|
120
158
|
componentId: config.componentId,
|
|
121
159
|
pageId: config.pageId,
|
|
122
160
|
defaultSort: config.defaultSort,
|