@antelopejs/dms 0.7.15 → 0.7.17
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 +34 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js +16 -0
- package/dist/test/unit/interfaces/dms/page-layout-query.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js +79 -0
- package/dist/test/unit/interfaces/dms-base/layout-header-actions.test.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/composables/period/presetResolvers.ts +19 -1
- package/frontend-vue/layers/dms-core/app/composables/period/usePeriod.ts +52 -28
- package/frontend-vue/layers/dms-core/i18n/locales/core-en-GB.json +1 -0
- package/frontend-vue/layers/dms-core/i18n/locales/core-fr-FR.json +1 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionBar.vue +48 -4
- package/frontend-vue/layers/dms-layout/app/build/components/layout/PageHeaderActionsOutlet.vue +6 -2
- package/frontend-vue/layers/dms-layout/app/composables/layout/usePageHeaderActions.ts +32 -1
- package/frontend-vue/layers/dms-ui/app/build/components/period/PeriodRangeCalendar.vue +17 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/bulkSelection.ts +7 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +42 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/secondaryTarget.ts +1 -59
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +2 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/period/periodDisplay.ts +49 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/period/usePeriodLocation.ts +34 -11
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableUrlTab.ts +47 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/fileDownload.ts +69 -0
- package/frontend-vue/layers/dms-ui/app/components/period/PeriodSelector.vue +32 -6
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +14 -4
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +13 -0
- package/frontend-vue/tests/confirm-secondary-action.test.ts +2 -4
- package/frontend-vue/tests/download-action-target.test.ts +172 -0
- package/frontend-vue/tests/header-action-page-query.test.ts +120 -1
- package/frontend-vue/tests/period-day-rollover.test.ts +80 -0
- package/frontend-vue/tests/period-max-days.test.ts +128 -0
- package/frontend-vue/tests/period-selector-url.test.ts +44 -8
- package/frontend-vue/tests/table-view-url-tab.test.ts +115 -0
- package/package.json +2 -2
- package/skills/dms-pages/REFERENCE.md +9 -1
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
/**
|
|
3
|
+
* A table view opens the tab its URL names (`?<tableId>.tab=`, or `?tab=` on
|
|
4
|
+
* a page with a single table view), on arrival and afterwards: a link or a
|
|
5
|
+
* `router.replace` naming another tab switches to it in place, without
|
|
6
|
+
* remounting the page.
|
|
7
|
+
*/
|
|
8
|
+
import { readFileSync } from "node:fs";
|
|
9
|
+
import { resolve } from "node:path";
|
|
10
|
+
import { createApp, defineComponent, h, nextTick, reactive, ref } from "vue";
|
|
11
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
12
|
+
import {
|
|
13
|
+
readTableUrlTab,
|
|
14
|
+
useTableUrlTab,
|
|
15
|
+
} from "../layers/dms-ui/app/build/composables/table-view/useTableUrlTab";
|
|
16
|
+
import type { TableUrlScope } from "../layers/dms-ui/app/build/composables/table-view/utils/views";
|
|
17
|
+
|
|
18
|
+
const OPENABLE_TABS = new Set(["all", "active", "archived"]);
|
|
19
|
+
const mounted: Array<{ unmount: () => void }> = [];
|
|
20
|
+
|
|
21
|
+
function mountTable(scope: TableUrlScope, query: Record<string, string>) {
|
|
22
|
+
const route = reactive({ query });
|
|
23
|
+
const activeTabId = ref(readTableUrlTab(route.query, scope) ?? "all");
|
|
24
|
+
const app = createApp(
|
|
25
|
+
defineComponent({
|
|
26
|
+
setup() {
|
|
27
|
+
useTableUrlTab({
|
|
28
|
+
route,
|
|
29
|
+
urlScope: scope,
|
|
30
|
+
activeTabId,
|
|
31
|
+
isOpenable: (tabId) => OPENABLE_TABS.has(tabId),
|
|
32
|
+
});
|
|
33
|
+
return () => h("div");
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
);
|
|
37
|
+
app.mount(document.createElement("div"));
|
|
38
|
+
mounted.push(app);
|
|
39
|
+
return { route, activeTabId };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
afterEach(() => {
|
|
43
|
+
mounted.splice(0).forEach((app) => app.unmount());
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe("the tab a URL names", () => {
|
|
47
|
+
it("opens on arrival", () => {
|
|
48
|
+
const { activeTabId } = mountTable(
|
|
49
|
+
{ tableId: "runs" },
|
|
50
|
+
{ "runs.tab": "active" },
|
|
51
|
+
);
|
|
52
|
+
expect(activeTabId.value).toBe("active");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("switches the tab when the prefixed key changes after mount", async () => {
|
|
56
|
+
const { route, activeTabId } = mountTable({ tableId: "runs" }, {});
|
|
57
|
+
route.query = { "runs.tab": "archived" };
|
|
58
|
+
await nextTick();
|
|
59
|
+
expect(activeTabId.value).toBe("archived");
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it("follows the short key on a page with a single table view", async () => {
|
|
63
|
+
const { route, activeTabId } = mountTable(
|
|
64
|
+
{ tableId: "runs", isSoleTableView: true },
|
|
65
|
+
{ tab: "active" },
|
|
66
|
+
);
|
|
67
|
+
expect(activeTabId.value).toBe("active");
|
|
68
|
+
route.query = { tab: "archived" };
|
|
69
|
+
await nextTick();
|
|
70
|
+
expect(activeTabId.value).toBe("archived");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("ignores the short key on a page with several table views", async () => {
|
|
74
|
+
const { route, activeTabId } = mountTable({ tableId: "runs" }, {});
|
|
75
|
+
route.query = { tab: "archived" };
|
|
76
|
+
await nextTick();
|
|
77
|
+
expect(activeTabId.value).toBe("all");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("keeps the open tab when the key is dropped or unknown", async () => {
|
|
81
|
+
const { route, activeTabId } = mountTable(
|
|
82
|
+
{ tableId: "runs" },
|
|
83
|
+
{ "runs.tab": "active" },
|
|
84
|
+
);
|
|
85
|
+
route.query = {};
|
|
86
|
+
await nextTick();
|
|
87
|
+
expect(activeTabId.value).toBe("active");
|
|
88
|
+
route.query = { "runs.tab": "missing" };
|
|
89
|
+
await nextTick();
|
|
90
|
+
expect(activeTabId.value).toBe("active");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("lets the user change tabs while the URL stays put", async () => {
|
|
94
|
+
const { route, activeTabId } = mountTable(
|
|
95
|
+
{ tableId: "runs" },
|
|
96
|
+
{ "runs.tab": "active" },
|
|
97
|
+
);
|
|
98
|
+
activeTabId.value = "archived";
|
|
99
|
+
route.query = { "runs.tab": "active", other: "1" };
|
|
100
|
+
await nextTick();
|
|
101
|
+
expect(activeTabId.value).toBe("archived");
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("wires the URL tab into the table view", () => {
|
|
106
|
+
const source = readFileSync(
|
|
107
|
+
resolve(
|
|
108
|
+
__dirname,
|
|
109
|
+
"../layers/dms-ui/app/components/table-view/TableView.vue",
|
|
110
|
+
),
|
|
111
|
+
"utf8",
|
|
112
|
+
);
|
|
113
|
+
expect(source).toContain("readTableUrlTab(route.query, urlScope)");
|
|
114
|
+
expect(source).toContain("useTableUrlTab({");
|
|
115
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@antelopejs/dms",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.17",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/AntelopeJS/dms.git"
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"@antelopejs/interface-data-api": ">=0.2.0 <1.0.0",
|
|
38
38
|
"@antelopejs/interface-database": ">=0.1.8 <1.0.0",
|
|
39
39
|
"@antelopejs/interface-database-decorators": ">=0.1.7 <1.0.0",
|
|
40
|
-
"@antelopejs/interface-dms": ">=0.5.
|
|
40
|
+
"@antelopejs/interface-dms": ">=0.5.11 <0.6.0",
|
|
41
41
|
"@antelopejs/interface-email": ">=0.0.5 <1.0.0",
|
|
42
42
|
"@antelopejs/interface-file-storage": ">=0.1.3 <1.0.0",
|
|
43
43
|
"archiver": "^7.0.1",
|
|
@@ -39,7 +39,12 @@ A page's `headerActions` follow its URL: a target URL takes `{{params.X}}` / `{{
|
|
|
39
39
|
(`?workspace={{query.workspace}}`, a pair the page has no value for is dropped), and an
|
|
40
40
|
`availability` reads `context.query`, asked again when the query changes — no need to build
|
|
41
41
|
the header in Vue with `usePageHeaderActions` to carry the scope —
|
|
42
|
-
`docs/07.interfaces/3.base/6.layout.md#buttons-that-follow-the-page-url`.
|
|
42
|
+
`docs/07.interfaces/3.base/6.layout.md#buttons-that-follow-the-page-url`. An `availability`
|
|
43
|
+
answering `{ hidden: true }` leaves the button out for that query (`{ reason }` only
|
|
44
|
+
disables it): no Vue header to hide "Add key" on a read-only workspace. `order` on a
|
|
45
|
+
header button and `usePageHeaderActions(render, { order })` interleave served buttons with a
|
|
46
|
+
component's client-side ones (default `0`, served first on a tie: `order: -1` puts a Vue
|
|
47
|
+
"Export" before a served "Add key") — `docs/07.interfaces/3.base/6.layout.md#ordering-the-header`.
|
|
43
48
|
|
|
44
49
|
A button or row action (`customButtons`, `rowActions.custom`, a page's `headerActions`)
|
|
45
50
|
points at an `ActionTarget`. A `drawer` target opens from the bottom; give it
|
|
@@ -50,6 +55,9 @@ target instead: a non-modal panel docked on the right whose header the DMS draws
|
|
|
50
55
|
`rowTitle` naming the row from its fields, `positionLabel` "x of y", previous / next, J / K, close), `?record=` kept by default (`deepLink: false` opts out), a "not found" state on a 404 (`notFound: { title, description }`) and the table's
|
|
51
56
|
`rowClick: "single"` (a `TableView.fromSource` table reads a deep-linked row beyond its first page through `fetchRowUrl: "/api/x/{id}"`) — never bridge the table's DOM to build one —
|
|
52
57
|
`docs/04.components/06.tables/8.row-drawers.md#side-panel`.
|
|
58
|
+
A button downloading a file the user's session may read (`/api/...`) takes a `download`
|
|
59
|
+
target, `{ url, filename? }`, with the same `{field}` / `{{query.X}}` tokens — never a Vue
|
|
60
|
+
header button or an `api` target for it — `docs/07.interfaces/3.base/2.types.md`.
|
|
53
61
|
|
|
54
62
|
A row that must stay on top of a table under any sort (a base language, a default
|
|
55
63
|
workspace) takes `pinnedRows: { field: "isBase", equals: true }` — never sort on the
|