@antelopejs/dms 0.7.20 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/dist/implementations/dms-auth/account-emails.d.ts +4 -0
- package/dist/implementations/dms-auth/account-emails.js +62 -0
- package/dist/implementations/dms-auth/account-emails.js.map +1 -0
- package/dist/implementations/dms-auth/index.d.ts +1 -3
- package/dist/implementations/dms-auth/index.js +15 -61
- package/dist/implementations/dms-auth/index.js.map +1 -1
- package/dist/pages/settings/users/email-change.js +3 -4
- package/dist/pages/settings/users/email-change.js.map +1 -1
- package/dist/pages/settings/users/invites.js +1 -1
- package/dist/pages/settings/users/invites.js.map +1 -1
- package/dist/pages/settings/users/members.js +1 -1
- package/dist/pages/settings/users/members.js.map +1 -1
- package/dist/pages/settings/users/notifications.js +1 -0
- package/dist/pages/settings/users/notifications.js.map +1 -1
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.d.ts +1 -0
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js +34 -0
- package/dist/test/unit/implementations/dms-html-render/built-in-emails.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.js +49 -0
- package/dist/test/unit/interfaces/dms/hook-handler-results.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js +17 -0
- package/dist/test/unit/interfaces/dms/page-topics.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js +2 -2
- package/dist/test/unit/interfaces/dms-base/confirm-dialog-secondary-action.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js +29 -0
- package/dist/test/unit/interfaces/dms-base/query-response-delta.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/resource-form.test.js +5 -0
- package/dist/test/unit/interfaces/dms-base/resource-form.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js +12 -2
- package/dist/test/unit/interfaces/dms-base/table-view-form-redirect.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js +29 -4
- package/dist/test/unit/interfaces/dms-base/table-view-reduced-chrome.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-row-menu-label.test.d.ts +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-row-menu-label.test.js +27 -0
- package/dist/test/unit/interfaces/dms-base/table-view-row-menu-label.test.js.map +1 -0
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js +18 -4
- package/dist/test/unit/interfaces/dms-base/table-view-source-reorder.test.js.map +1 -1
- package/dist/test/unit/interfaces/dms-base/table-view-views.test.js +8 -5
- package/dist/test/unit/interfaces/dms-base/table-view-views.test.js.map +1 -1
- package/dist/test/unit/utils/email-subjects.test.d.ts +1 -0
- package/dist/test/unit/utils/email-subjects.test.js +27 -0
- package/dist/test/unit/utils/email-subjects.test.js.map +1 -0
- package/dist/utils/admin-invite-email.js +2 -4
- package/dist/utils/admin-invite-email.js.map +1 -1
- package/dist/utils/email-change-mail.d.ts +1 -1
- package/dist/utils/email-change-mail.js +11 -6
- package/dist/utils/email-change-mail.js.map +1 -1
- package/dist/utils/email-subjects.d.ts +3 -0
- package/dist/utils/email-subjects.js +32 -0
- package/dist/utils/email-subjects.js.map +1 -0
- package/dist/utils/export-jobs.js +15 -10
- package/dist/utils/export-jobs.js.map +1 -1
- package/frontend-vue/layers/dms-core/app/utils/url-interpolation.ts +29 -11
- package/frontend-vue/layers/dms-core/shared/types/app-config.ts +17 -0
- package/frontend-vue/layers/dms-layout/app/app.config.ts +5 -0
- package/frontend-vue/layers/dms-layout/app/build/components/layout/DashboardHeader.vue +18 -2
- package/frontend-vue/layers/dms-layout/app/build/components/layout/Footer.vue +16 -1
- package/frontend-vue/layers/dms-layout/app/build/composables/layout/useHeaderActionControls.ts +14 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/HStack.vue +16 -0
- package/frontend-vue/layers/dms-layout/app/components/layout/VStack.vue +2 -0
- package/frontend-vue/layers/dms-layout/app/composables/useFooterLinks.ts +11 -1
- package/frontend-vue/layers/dms-layout/app/custom-pages/settings/roles.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/emails/EmailAdminInvite.vue +1 -1
- package/frontend-vue/layers/dms-layout/app/emails/EmailChangeNotice.vue +13 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailChangeVerification.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailExportReady.vue +23 -12
- package/frontend-vue/layers/dms-layout/app/emails/EmailResetPassword.vue +18 -7
- package/frontend-vue/layers/dms-layout/app/emails/EmailTwoFactor.vue +23 -4
- package/frontend-vue/layers/dms-layout/app/emails/EmailUserValidation.vue +18 -6
- package/frontend-vue/layers/dms-layout/app/emails/components/EmailOTP.vue +8 -2
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-en-GB.json +40 -0
- package/frontend-vue/layers/dms-layout/i18n/locales/layout-fr-FR.json +40 -0
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormFieldControl.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/build/components/form/FormLoadError.vue +11 -3
- package/frontend-vue/layers/dms-ui/app/build/components/table/Table.vue +13 -3
- package/frontend-vue/layers/dms-ui/app/build/components/table-view/CardsDisplay.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/rowNavigation.ts +14 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/actions/useActionTargets.ts +90 -43
- package/frontend-vue/layers/dms-ui/app/build/composables/chart/apexTheme.ts +59 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/confirmDialogTexts.ts +27 -4
- package/frontend-vue/layers/dms-ui/app/build/composables/confirm/useActionConfirm.ts +10 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/data-types/registerDefaults.ts +0 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formEntryOrder.ts +23 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formFooter.ts +19 -15
- package/frontend-vue/layers/dms-ui/app/build/composables/form/formRecordLoad.ts +26 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/page/pageUrlValues.ts +41 -0
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/registerDefaultFunctions.ts +2 -2
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableRows.ts +3 -5
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableUrlTab.ts +42 -12
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewConfig.ts +7 -11
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/recordLink.ts +10 -11
- package/frontend-vue/layers/dms-ui/app/build/composables/table-view/utils/views.ts +29 -0
- package/frontend-vue/layers/dms-ui/app/build/utils/link.ts +7 -3
- package/frontend-vue/layers/dms-ui/app/build/utils/urlVariables.ts +32 -9
- package/frontend-vue/layers/dms-ui/app/components/activity-feed/ActivityFeed.vue +8 -1
- package/frontend-vue/layers/dms-ui/app/components/banner/Banner.vue +9 -4
- package/frontend-vue/layers/dms-ui/app/components/blocks/BannerBlock.vue +5 -11
- package/frontend-vue/layers/dms-ui/app/components/blocks/NavCardGridBlock.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/chart/ApexChartHost.vue +32 -1
- package/frontend-vue/layers/dms-ui/app/components/check-list/CheckList.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/form/Form.vue +12 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Cascader.vue +7 -3
- package/frontend-vue/layers/dms-ui/app/components/form/components/File.vue +7 -0
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputAddress.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputCode.vue +4 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/InputSecret.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/form/components/Relation.vue +4 -1
- package/frontend-vue/layers/dms-ui/app/components/grid/columns.ts +15 -3
- package/frontend-vue/layers/dms-ui/app/components/key-value-list/KeyValueList.vue +2 -1
- package/frontend-vue/layers/dms-ui/app/components/list-row/ListRow.vue +9 -2
- package/frontend-vue/layers/dms-ui/app/components/save-bar/InstantSaveBadge.vue +13 -5
- package/frontend-vue/layers/dms-ui/app/components/save-bar/SaveBar.vue +1 -1
- package/frontend-vue/layers/dms-ui/app/components/stat-group/StatGroup.vue +3 -2
- package/frontend-vue/layers/dms-ui/app/components/table-view/CascaderPath.vue +53 -31
- package/frontend-vue/layers/dms-ui/app/components/table-view/TableView.vue +14 -6
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexPlotOptions.ts +15 -4
- package/frontend-vue/layers/dms-ui/app/composables/chart/apexYRange.ts +123 -0
- package/frontend-vue/layers/dms-ui/app/composables/chart/useApexChart.ts +49 -49
- package/frontend-vue/layers/dms-ui/app/composables/chart/useChartTheme.ts +20 -1
- package/frontend-vue/layers/dms-ui/app/composables/confirm/types.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/form/types/props.ts +2 -0
- package/frontend-vue/layers/dms-ui/app/composables/form/useForm.ts +8 -6
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/action-target.ts +1 -1
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/config.ts +3 -3
- package/frontend-vue/layers/dms-ui/app/composables/table-view/types/display.ts +6 -0
- package/frontend-vue/layers/dms-ui/app/utils/bannerToneIcons.ts +13 -0
- package/frontend-vue/layers/dms-ui/app/utils/cascader.ts +26 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-en-GB.json +4 -0
- package/frontend-vue/layers/dms-ui/i18n/locales/ui-fr-FR.json +4 -0
- package/frontend-vue/tests/activity-feed-fill-height.test.ts +7 -0
- package/frontend-vue/tests/banner-tone-icon.test.ts +55 -0
- package/frontend-vue/tests/builtin-emails.test.ts +141 -0
- package/frontend-vue/tests/cascader.test.ts +27 -0
- package/frontend-vue/tests/chart-auto-y-range.test.ts +25 -9
- package/frontend-vue/tests/chart-colors.test.ts +8 -0
- package/frontend-vue/tests/chart-resize-after-animation.test.ts +100 -0
- package/frontend-vue/tests/chart-type-defaults.test.ts +128 -0
- package/frontend-vue/tests/check-list-marker.test.ts +58 -0
- package/frontend-vue/tests/confirm-from.test.ts +1 -1
- package/frontend-vue/tests/confirm-secondary-action.test.ts +2 -2
- package/frontend-vue/tests/context-links.test.ts +2 -2
- package/frontend-vue/tests/custom-action-confirm-from.test.ts +2 -2
- package/frontend-vue/tests/download-action-target.test.ts +2 -2
- package/frontend-vue/tests/file-drop-prompt.test.ts +72 -0
- package/frontend-vue/tests/footer-config.test.ts +135 -0
- package/frontend-vue/tests/form-actions.test.ts +7 -0
- package/frontend-vue/tests/form-entry-order.test.ts +45 -0
- package/frontend-vue/tests/form-footer-kind.test.ts +11 -6
- package/frontend-vue/tests/form-load-error.test.ts +38 -1
- package/frontend-vue/tests/grid-responsive-columns.test.ts +9 -0
- package/frontend-vue/tests/header-actions.test.ts +55 -9
- package/frontend-vue/tests/instant-save-badge.test.ts +11 -4
- package/frontend-vue/tests/list-row-truncate.test.ts +46 -0
- package/frontend-vue/tests/roles-name-errors.test.ts +2 -3
- package/frontend-vue/tests/row-side-panel.test.ts +93 -2
- package/frontend-vue/tests/save-bar.test.ts +15 -3
- package/frontend-vue/tests/save-status-error.test.ts +2 -2
- package/frontend-vue/tests/table-row-action-errors.test.ts +5 -5
- package/frontend-vue/tests/table-row-hover.test.ts +2 -2
- package/frontend-vue/tests/table-view-bulk-drawer.test.ts +114 -0
- package/frontend-vue/tests/table-view-cards-reread.test.ts +77 -0
- package/frontend-vue/tests/table-view-sources.test.ts +1 -1
- package/frontend-vue/tests/table-view-url-tab.test.ts +84 -2
- package/frontend-vue/tests/template-tokens.test.ts +235 -0
- package/frontend-vue/tests/url-interpolation.test.ts +6 -6
- package/package.json +2 -2
- package/skills/dms/SKILL.md +2 -1
- package/skills/dms-pages/REFERENCE.md +2 -2
- package/skills/dms-project/SKILL.md +0 -1
package/frontend-vue/layers/dms-ui/app/build/composables/table-view/registerDefaultFunctions.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { LocationQueryValue } from "#dms/frontend-module";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveLinkVariables } from "../../utils/urlVariables";
|
|
3
3
|
|
|
4
4
|
type RouteQuery = Record<string, LocationQueryValue | LocationQueryValue[]>;
|
|
5
5
|
|
|
@@ -23,7 +23,7 @@ export function resolveFormSuccessTarget(
|
|
|
23
23
|
{ url, preserveQuery }: FormSuccessParams,
|
|
24
24
|
route: FormSuccessRoute,
|
|
25
25
|
): { path: string; query?: RouteQuery } {
|
|
26
|
-
const path =
|
|
26
|
+
const path = resolveLinkVariables(url, {
|
|
27
27
|
routeParams: route.params,
|
|
28
28
|
routeQuery: route.query,
|
|
29
29
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { get } from "@nuxt/ui/runtime/utils/index.js";
|
|
2
2
|
import { computed } from "vue";
|
|
3
|
+
import { interpolateUrl } from "#dms-core/app/utils/url-interpolation";
|
|
3
4
|
import type {
|
|
4
5
|
TableViewColumn,
|
|
5
6
|
TableViewListResponse,
|
|
@@ -54,7 +55,6 @@ const DESCENDING = "desc";
|
|
|
54
55
|
*/
|
|
55
56
|
export const LIVE_HELD_AT_QUERY_KEY = "liveHeldAt";
|
|
56
57
|
const NO_ROUTE_VALUES: ReplaceUrlVariablesContext = { routeQuery: {} };
|
|
57
|
-
const ROW_ID_TOKEN = "{id}";
|
|
58
58
|
|
|
59
59
|
type SourceCapabilities = TableViewSourceConfig["capabilities"];
|
|
60
60
|
|
|
@@ -251,10 +251,8 @@ export function useTableRows<T extends Record<string, unknown>>(
|
|
|
251
251
|
id: string,
|
|
252
252
|
): Promise<T | undefined> => {
|
|
253
253
|
const url = resolveUrlVariables(
|
|
254
|
-
sourceConfig.fetchRowUrl
|
|
255
|
-
|
|
256
|
-
encodeURIComponent(id),
|
|
257
|
-
),
|
|
254
|
+
sourceConfig.fetchRowUrl &&
|
|
255
|
+
interpolateUrl(sourceConfig.fetchRowUrl, { id }),
|
|
258
256
|
options.route?.() ?? NO_ROUTE_VALUES,
|
|
259
257
|
);
|
|
260
258
|
return url ? api<T>(url) : Promise.resolve(undefined);
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { router as inertiaRouter } from "@inertiajs/vue3";
|
|
2
|
+
import { onBeforeUnmount, onMounted, watch, type Ref } from "vue";
|
|
3
|
+
import { queryOfUrl } from "./utils/urlFilters";
|
|
4
|
+
import {
|
|
5
|
+
readTableUrlKey,
|
|
6
|
+
type TableUrlScope,
|
|
7
|
+
writeTableUrlKey,
|
|
8
|
+
} from "./utils/views";
|
|
3
9
|
|
|
4
10
|
const TAB_URL_KEY = "tab";
|
|
5
11
|
|
|
@@ -30,18 +36,42 @@ export function readTableUrlTab(
|
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
39
|
+
* Keeps the open tab and the URL in step after arrival. A tab the user opens
|
|
40
|
+
* is written to the URL in place (no visit, no history entry), under the key
|
|
41
|
+
* it is read from, so a reload or a shared link opens it again. A link or a
|
|
42
|
+
* `router.replace` naming another tab of the same page switches to it without
|
|
43
|
+
* remounting the page. A URL dropping the key, or naming a tab the table
|
|
44
|
+
* cannot open in place (an unknown id, a link tab), leaves the open tab alone.
|
|
37
45
|
*/
|
|
38
46
|
export function useTableUrlTab(options: TableUrlTabOptions): void {
|
|
47
|
+
const { activeTabId, urlScope } = options;
|
|
48
|
+
|
|
49
|
+
const follow = (query: Record<string, unknown>) => {
|
|
50
|
+
const tabId = readTableUrlTab(query, urlScope);
|
|
51
|
+
if (!tabId || tabId === activeTabId.value) return;
|
|
52
|
+
if (!options.isOpenable(tabId)) return;
|
|
53
|
+
activeTabId.value = tabId;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// The tab is written in place, so the route keeps the value of the last
|
|
57
|
+
// visit: a link back to that tab leaves it unchanged, and only the visit
|
|
58
|
+
// tells it was followed. A `router.replace`, which Inertia does not
|
|
59
|
+
// announce, still moves the route.
|
|
39
60
|
watch(
|
|
40
|
-
() => readTableUrlTab(options.route.query,
|
|
41
|
-
(
|
|
42
|
-
if (!tabId || tabId === options.activeTabId.value) return;
|
|
43
|
-
if (!options.isOpenable(tabId)) return;
|
|
44
|
-
options.activeTabId.value = tabId;
|
|
45
|
-
},
|
|
61
|
+
() => readTableUrlTab(options.route.query, urlScope),
|
|
62
|
+
() => follow(options.route.query),
|
|
46
63
|
);
|
|
64
|
+
let stopFollowingVisits: (() => void) | undefined;
|
|
65
|
+
onMounted(() => {
|
|
66
|
+
stopFollowingVisits = inertiaRouter.on("navigate", (event) =>
|
|
67
|
+
follow(queryOfUrl(event.detail.page.url)),
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
onBeforeUnmount(() => stopFollowingVisits?.());
|
|
71
|
+
|
|
72
|
+
watch(activeTabId, (tabId) => {
|
|
73
|
+
if (options.isOpenable(tabId)) {
|
|
74
|
+
writeTableUrlKey(urlScope, TAB_URL_KEY, tabId);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
47
77
|
}
|
|
@@ -102,19 +102,15 @@ export const useTableViewConfig = <T extends Data>(
|
|
|
102
102
|
.map((col) => ({ ...col, header: processI18n(col.header) })),
|
|
103
103
|
);
|
|
104
104
|
|
|
105
|
-
// Config-derived (SSR-safe; the registry is client-only): the
|
|
106
|
-
//
|
|
105
|
+
// Config-derived (SSR-safe; the registry is client-only): the listed
|
|
106
|
+
// `displays`, else the built-in `table` alone. Built-in defaults
|
|
107
107
|
// (selfManagedData/capabilities) are merged per id.
|
|
108
|
-
const resolvedDisplays: TableViewDisplayConfig[] = (
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (display.id === TABLE_DISPLAY_ID) continue;
|
|
112
|
-
list.push(display);
|
|
113
|
-
}
|
|
114
|
-
return list.map(withBuiltinDefaults);
|
|
115
|
-
})();
|
|
108
|
+
const resolvedDisplays: TableViewDisplayConfig[] = (
|
|
109
|
+
config.displays ?? [{ id: TABLE_DISPLAY_ID }]
|
|
110
|
+
).map(withBuiltinDefaults);
|
|
116
111
|
|
|
117
|
-
const defaultDisplay =
|
|
112
|
+
const defaultDisplay =
|
|
113
|
+
config.defaultDisplay ?? resolvedDisplays[0]?.id ?? TABLE_DISPLAY_ID;
|
|
118
114
|
|
|
119
115
|
const initialVisibility = buildInitialColumnVisibility(config.columns ?? []);
|
|
120
116
|
|
package/frontend-vue/layers/dms-ui/app/build/composables/table-view/useTableViewRowActions.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { namesRowField } from "#dms-core/app/utils/url-interpolation";
|
|
1
2
|
import {
|
|
2
3
|
fillFormPageUrl,
|
|
3
4
|
type FormContainer,
|
|
@@ -91,7 +92,6 @@ interface BulkActionConfig {
|
|
|
91
92
|
/** Runs a confirmed action from inside its dialog (see `onConfirm`). */
|
|
92
93
|
type ConfirmedRun = NonNullable<ConfirmOptions["onConfirm"]>;
|
|
93
94
|
|
|
94
|
-
const ROW_URL_PLACEHOLDER = /\{[^}]+\}/;
|
|
95
95
|
/** Options of a delete: from the archive, it is a permanent one. */
|
|
96
96
|
interface DeleteRowsOptions {
|
|
97
97
|
permanently?: boolean;
|
|
@@ -634,7 +634,7 @@ export const useTableRowActions = <T extends Data>(
|
|
|
634
634
|
* reaches: a `from` dialog is worded for one row, so several rows keep the
|
|
635
635
|
* generic confirmation. Undefined when none applies.
|
|
636
636
|
*/
|
|
637
|
-
// A `from` URL naming a row (`{id}`, `{_id}`…) is worded for that row
|
|
637
|
+
// A `from` URL naming a row (`{{row.id}}`, `{{row._id}}`…) is worded for that row
|
|
638
638
|
// alone; one naming none receives the rows as `?ids=`, one or several, like
|
|
639
639
|
// the `from` of a bulk custom action.
|
|
640
640
|
const declaredBulkConfirm = (
|
|
@@ -643,7 +643,7 @@ export const useTableRowActions = <T extends Data>(
|
|
|
643
643
|
): ActionConfirm | undefined => {
|
|
644
644
|
const declared = normalizeActionConfig(actionConfig).confirm;
|
|
645
645
|
if (!declared || !isConfirmFrom(declared)) return declared;
|
|
646
|
-
if (
|
|
646
|
+
if (namesRowField(declared.from)) {
|
|
647
647
|
return ids.length === 1 ? declared : undefined;
|
|
648
648
|
}
|
|
649
649
|
return {
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import type { CustomRowAction } from "../../../../types/row-action";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
readTableUrlKey,
|
|
4
|
+
tableUrlKey,
|
|
5
|
+
type TableUrlScope,
|
|
6
|
+
writeTableUrlKey,
|
|
7
|
+
} from "./views";
|
|
3
8
|
import { readFetchNotFound } from "../../../utils/fetchNotFound";
|
|
4
9
|
|
|
5
10
|
/** Query key of the row a `deepLink` action has open. */
|
|
@@ -7,9 +12,7 @@ export const RECORD_URL_KEY = "record";
|
|
|
7
12
|
|
|
8
13
|
/** The key this table writes the open row under: short on a sole table. */
|
|
9
14
|
export function recordUrlKey(scope: TableUrlScope): string {
|
|
10
|
-
return scope
|
|
11
|
-
? RECORD_URL_KEY
|
|
12
|
-
: `${scope.tableId}.${RECORD_URL_KEY}`;
|
|
15
|
+
return tableUrlKey(scope, RECORD_URL_KEY);
|
|
13
16
|
}
|
|
14
17
|
|
|
15
18
|
/** The row the URL names for this table, if any. */
|
|
@@ -29,13 +32,9 @@ export function writeRecordId(
|
|
|
29
32
|
scope: TableUrlScope,
|
|
30
33
|
id?: string,
|
|
31
34
|
): WrittenRecord | undefined {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
if (id) url.searchParams.set(key, id);
|
|
36
|
-
else url.searchParams.delete(key);
|
|
37
|
-
window.history.replaceState(window.history.state, "", url);
|
|
38
|
-
return id ? { pathname: url.pathname, key, id } : undefined;
|
|
35
|
+
const url = writeTableUrlKey(scope, RECORD_URL_KEY, id);
|
|
36
|
+
if (!url || !id) return undefined;
|
|
37
|
+
return { pathname: url.pathname, key: recordUrlKey(scope), id };
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
/**
|
|
@@ -183,6 +183,35 @@ export interface UrlKeyReading {
|
|
|
183
183
|
ignoredShortKey?: boolean;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
+
/**
|
|
187
|
+
* The key this table writes `key` (`tab`, `record`) under: the short one on
|
|
188
|
+
* its page's only table, else prefixed with its id (`content.tab`).
|
|
189
|
+
*/
|
|
190
|
+
export function tableUrlKey(scope: TableUrlScope, key: string): string {
|
|
191
|
+
return scope.isSoleTableView || !scope.tableId
|
|
192
|
+
? key
|
|
193
|
+
: `${scope.tableId}.${key}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Writes (or, without a value, removes) `key` for this table in the browser's
|
|
198
|
+
* URL in place: no visit, no history entry. Returns the URL written, or
|
|
199
|
+
* `undefined` on a server render.
|
|
200
|
+
*/
|
|
201
|
+
export function writeTableUrlKey(
|
|
202
|
+
scope: TableUrlScope,
|
|
203
|
+
key: string,
|
|
204
|
+
value?: string,
|
|
205
|
+
): URL | undefined {
|
|
206
|
+
if (typeof window === "undefined") return undefined;
|
|
207
|
+
const url = new URL(window.location.href);
|
|
208
|
+
const scopedKey = tableUrlKey(scope, key);
|
|
209
|
+
if (value) url.searchParams.set(scopedKey, value);
|
|
210
|
+
else url.searchParams.delete(scopedKey);
|
|
211
|
+
window.history.replaceState(window.history.state, "", url);
|
|
212
|
+
return url;
|
|
213
|
+
}
|
|
214
|
+
|
|
186
215
|
/**
|
|
187
216
|
* The value the URL gives `key` (`view`, `tab`) for this table: under its
|
|
188
217
|
* prefixed key (`content.view`) or, when it is its page's only table, the
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
// bare `<a href="#…">` (an Inertia visit would reload the page to scroll), an
|
|
4
4
|
// absolute URL opens in a new tab, and everything else is a DMS route.
|
|
5
5
|
|
|
6
|
+
import { pageLink } from "../composables/page/pageUrlValues";
|
|
7
|
+
|
|
6
8
|
export type LinkKind = "internal" | "anchor" | "external";
|
|
7
9
|
|
|
8
10
|
const EXTERNAL_LINK_PATTERN = /^(?:[a-z][a-z\d+.-]*:|\/\/)/i;
|
|
@@ -20,12 +22,14 @@ export const EXTERNAL_LINK_ATTRIBUTES = {
|
|
|
20
22
|
} as const;
|
|
21
23
|
|
|
22
24
|
/**
|
|
23
|
-
* Props for a `UButton` (or `ULink`) following `to
|
|
24
|
-
*
|
|
25
|
+
* Props for a `UButton` (or `ULink`) following `to`, its page tokens
|
|
26
|
+
* (`{{params.X}}`, `{{query.X}}`) filled: an external target opens in a new
|
|
27
|
+
* tab, the others navigate in place.
|
|
25
28
|
*/
|
|
26
29
|
export function buttonLinkProps(
|
|
27
|
-
|
|
30
|
+
declared: string | undefined,
|
|
28
31
|
): Record<string, string> {
|
|
32
|
+
const to = pageLink(declared);
|
|
29
33
|
if (!to) return {};
|
|
30
34
|
return linkKind(to) === "external"
|
|
31
35
|
? { to, ...EXTERNAL_LINK_ATTRIBUTES }
|
|
@@ -1,11 +1,24 @@
|
|
|
1
|
+
import { get } from "@nuxt/ui/runtime/utils/index.js";
|
|
2
|
+
|
|
3
|
+
// Every token is `{{<scope>.<key>}}`, spaces inside the braces allowed, as the
|
|
4
|
+
// server's filter tokens (`{{ user.id }}`) are.
|
|
5
|
+
//
|
|
1
6
|
// `{{params.id}}` takes the bare name, which on a route repeating a placeholder
|
|
2
7
|
// is its last occurrence — the row id of a form page. The `:<n>` suffix reaches
|
|
3
8
|
// a specific occurrence, `{{params.id:1}}` being the id of the page carrying the
|
|
4
9
|
// table view (see extractRouteParams).
|
|
5
|
-
const
|
|
10
|
+
const PARAM_KEY = String.raw`params\.(\w+(?::\d+)?)`;
|
|
6
11
|
// A query key may carry dots and dashes: a period selector writes `<id>.period`.
|
|
7
|
-
const
|
|
8
|
-
|
|
12
|
+
const QUERY_KEY = String.raw`query\.([\w.-]+)`;
|
|
13
|
+
// A response key is a path into the answered body: `{{response.data.id}}`.
|
|
14
|
+
const RESPONSE_KEY = String.raw`response\.([\w.-]+)`;
|
|
15
|
+
|
|
16
|
+
const tokenPattern = (key: string, flags = "g"): RegExp =>
|
|
17
|
+
new RegExp(String.raw`\{\{\s*${key}\s*\}\}`, flags);
|
|
18
|
+
|
|
19
|
+
const PARAM_TOKEN = tokenPattern(PARAM_KEY);
|
|
20
|
+
const QUERY_TOKEN = tokenPattern(QUERY_KEY);
|
|
21
|
+
const RESPONSE_TOKEN = tokenPattern(RESPONSE_KEY);
|
|
9
22
|
const TOKEN_OPENING = "{{";
|
|
10
23
|
|
|
11
24
|
export interface ReplaceUrlVariablesContext {
|
|
@@ -63,8 +76,8 @@ function fillResponseTokens(
|
|
|
63
76
|
response: Record<string, unknown> | undefined,
|
|
64
77
|
): string {
|
|
65
78
|
if (!response) return text;
|
|
66
|
-
return text.replace(RESPONSE_TOKEN, (match, key) => {
|
|
67
|
-
const value = response
|
|
79
|
+
return text.replace(RESPONSE_TOKEN, (match, key: string) => {
|
|
80
|
+
const value: unknown = get(response, key);
|
|
68
81
|
return typeof value === "string" || typeof value === "number"
|
|
69
82
|
? String(value)
|
|
70
83
|
: match;
|
|
@@ -122,10 +135,20 @@ export function resolveTextVariables(
|
|
|
122
135
|
|
|
123
136
|
// A route token left without a value: `{{params.id}}`, `{{query.workspace}}`,
|
|
124
137
|
// `{{query.sales.period}}` (a query key may carry dots and dashes, as above).
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const UNRESOLVED_ROUTE_VALUE =
|
|
128
|
-
|
|
138
|
+
const ROUTE_KEY = `(?:${PARAM_KEY}|${QUERY_KEY})`;
|
|
139
|
+
const UNRESOLVED_ROUTE_TOKEN = tokenPattern(ROUTE_KEY);
|
|
140
|
+
const UNRESOLVED_ROUTE_VALUE = new RegExp(
|
|
141
|
+
`^${tokenPattern(ROUTE_KEY, "").source}$`,
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Whether a text names a value of the page (`{{params.X}}`, `{{query.X}}`):
|
|
146
|
+
* the route is only read for one that does.
|
|
147
|
+
*/
|
|
148
|
+
export function namesPageValue(text: string): boolean {
|
|
149
|
+
return new RegExp(UNRESOLVED_ROUTE_TOKEN.source).test(text);
|
|
150
|
+
}
|
|
151
|
+
|
|
129
152
|
const QUERY_SEPARATOR = "?";
|
|
130
153
|
const FRAGMENT_SEPARATOR = "#";
|
|
131
154
|
const PAIR_SEPARATOR = "&";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { pageLink } from "../../build/composables/page/pageUrlValues";
|
|
2
3
|
import { computed, ref, resolveComponent } from "vue";
|
|
3
4
|
import DmsActivityItem from "../activity/ActivityItem.vue";
|
|
4
5
|
import DmsBlockActions, {
|
|
@@ -236,6 +237,11 @@ const scrollArea = computed(() =>
|
|
|
236
237
|
const dayHeadClass = computed(
|
|
237
238
|
() => props.fillHeight && "sticky top-0 z-[1] bg-(--dms-surface-card)",
|
|
238
239
|
);
|
|
240
|
+
// …and the "Show N more" button at its foot, where entries taller than the
|
|
241
|
+
// row would otherwise push it out of sight.
|
|
242
|
+
const foldClass = computed(
|
|
243
|
+
() => props.fillHeight && "sticky bottom-0 z-[1] bg-(--dms-surface-card)",
|
|
244
|
+
);
|
|
239
245
|
|
|
240
246
|
const Wrapper = props.card ? resolveComponent("DmsCard") : "div";
|
|
241
247
|
const wrapperProps = computed(() =>
|
|
@@ -365,13 +371,14 @@ const wrapperProps = computed(() =>
|
|
|
365
371
|
:unread="item.unread"
|
|
366
372
|
:mono="props.mono"
|
|
367
373
|
:wrap="props.wrap"
|
|
368
|
-
:to="item.to"
|
|
374
|
+
:to="pageLink(item.to)"
|
|
369
375
|
:interactive="!!item.to"
|
|
370
376
|
/>
|
|
371
377
|
</section>
|
|
372
378
|
<div
|
|
373
379
|
v-if="foldedCount > 0"
|
|
374
380
|
class="dms-activity-feed__fold border-t border-(--ui-border-muted) px-[18px] pt-2 pb-1"
|
|
381
|
+
:class="foldClass"
|
|
375
382
|
>
|
|
376
383
|
<UButton
|
|
377
384
|
:label="foldLabel"
|
|
@@ -1,19 +1,22 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { computed } from "vue";
|
|
3
3
|
import DmsIconWell, { type IconWellSize } from "../icon-well/IconWell.vue";
|
|
4
|
-
import
|
|
4
|
+
import {
|
|
5
|
+
BANNER_TONE_ICONS,
|
|
6
|
+
type BannerTone,
|
|
7
|
+
} from "../../utils/bannerToneIcons";
|
|
5
8
|
|
|
6
9
|
// DMS-styled notice banner (design .banner): a left-to-right tinted wash over
|
|
7
10
|
// the card surface with a boxed leading icon, used for module beta / info
|
|
8
11
|
// notices. Wraps UAlert so we keep its a11y + slots while pinning the exact
|
|
9
12
|
// reference look. The `cli` slot hosts the optional inset command box
|
|
10
13
|
// (compose with DmsCopyButton), `actions` the right-aligned buttons.
|
|
11
|
-
type BannerTone = Exclude<SemanticColor, "neutral">;
|
|
12
14
|
type BannerSize = "sm" | "md";
|
|
13
15
|
|
|
14
16
|
interface BannerProps {
|
|
15
17
|
title?: string;
|
|
16
18
|
description?: string;
|
|
19
|
+
/** Defaults to the tone's icon. */
|
|
17
20
|
icon?: string;
|
|
18
21
|
/** Colour of the wash and the icon well. */
|
|
19
22
|
tone?: BannerTone;
|
|
@@ -39,7 +42,7 @@ interface BannerSlots {
|
|
|
39
42
|
const props = withDefaults(defineProps<BannerProps>(), {
|
|
40
43
|
title: undefined,
|
|
41
44
|
description: undefined,
|
|
42
|
-
icon:
|
|
45
|
+
icon: undefined,
|
|
43
46
|
tone: "warning",
|
|
44
47
|
size: "md",
|
|
45
48
|
dismissible: false,
|
|
@@ -99,6 +102,8 @@ const alertUi = computed(() => ({
|
|
|
99
102
|
actions: `items-center gap-2 mt-0 ${slots.actions ? "max-sm:basis-full max-sm:flex-wrap" : ""}`,
|
|
100
103
|
}));
|
|
101
104
|
|
|
105
|
+
const leadingIcon = computed(() => props.icon ?? BANNER_TONE_ICONS[props.tone]);
|
|
106
|
+
|
|
102
107
|
function onOpenChange(open: boolean) {
|
|
103
108
|
if (!open) emit("dismiss");
|
|
104
109
|
}
|
|
@@ -116,7 +121,7 @@ function onOpenChange(open: boolean) {
|
|
|
116
121
|
@update:open="onOpenChange"
|
|
117
122
|
>
|
|
118
123
|
<template #leading>
|
|
119
|
-
<DmsIconWell :icon="
|
|
124
|
+
<DmsIconWell :icon="leadingIcon" :tone="tone" :size="SIZE_WELL[size]" />
|
|
120
125
|
</template>
|
|
121
126
|
|
|
122
127
|
<template v-if="slots.description || slots.cli" #description>
|
|
@@ -8,14 +8,16 @@ import DmsBlockActions, {
|
|
|
8
8
|
import { useActionTargets } from "../../build/composables/actions/useActionTargets";
|
|
9
9
|
import { useChartFetch } from "../../composables/chart/useChartFetch";
|
|
10
10
|
import { refreshPageBlocks } from "../../utils/blockRefresh";
|
|
11
|
+
import {
|
|
12
|
+
BANNER_TONE_ICONS,
|
|
13
|
+
type BannerTone,
|
|
14
|
+
} from "../../utils/bannerToneIcons";
|
|
11
15
|
import type { CustomButton } from "../../composables/table-view/types/custom-button";
|
|
12
16
|
import { useWatch } from "../../../../dms-core/app/composables/watch/useWatch";
|
|
13
17
|
import { useComposedText } from "../../../../dms-core/app/composables/translation/useComposedText";
|
|
14
18
|
import type { DefaultComponentProps } from "../../../../dms-core/app/types/component";
|
|
15
19
|
import type { BlockText } from "../../../../dms-core/app/types/composed-text";
|
|
16
20
|
|
|
17
|
-
type BannerTone = "info" | "success" | "warning" | "error" | "primary";
|
|
18
|
-
|
|
19
21
|
/** What a banner shows (interface-dms `BannerContent`). */
|
|
20
22
|
interface BannerContent {
|
|
21
23
|
/** A string (`$` for an i18n key) or a composed text. */
|
|
@@ -83,14 +85,6 @@ const DISMISSED_MAX_KEYS = 40;
|
|
|
83
85
|
const FALLBACK_COMPONENT_ID = "banner";
|
|
84
86
|
const FALLBACK_PAGE_ID = "page";
|
|
85
87
|
|
|
86
|
-
const TONE_ICONS: Record<BannerTone, string> = {
|
|
87
|
-
info: "i-ph-info",
|
|
88
|
-
success: "i-ph-check-circle",
|
|
89
|
-
warning: "i-ph-warning",
|
|
90
|
-
error: "i-ph-warning-circle",
|
|
91
|
-
primary: "i-ph-sparkle",
|
|
92
|
-
};
|
|
93
|
-
|
|
94
88
|
const { state: watchState } = useWatch(
|
|
95
89
|
props.watchActions || [],
|
|
96
90
|
props.componentId,
|
|
@@ -183,7 +177,7 @@ function press(action: BlockButtonAction): void {
|
|
|
183
177
|
:description="
|
|
184
178
|
banner.description ? processText(banner.description) : undefined
|
|
185
179
|
"
|
|
186
|
-
:icon="banner.icon ??
|
|
180
|
+
:icon="banner.icon ?? BANNER_TONE_ICONS[banner.tone]"
|
|
187
181
|
:tone="banner.tone"
|
|
188
182
|
:size="banner.size"
|
|
189
183
|
:dismissible="banner.dismissible"
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { pageLink } from "../../build/composables/page/pageUrlValues";
|
|
2
3
|
import { gridColumnsTemplate } from "../grid/columns";
|
|
3
4
|
import {
|
|
4
5
|
useCategoryNavCards,
|
|
@@ -217,7 +218,7 @@ const { t } = useI18n();
|
|
|
217
218
|
>
|
|
218
219
|
<DmsNavCard
|
|
219
220
|
class="h-full"
|
|
220
|
-
:to="card.to"
|
|
221
|
+
:to="pageLink(card.to)"
|
|
221
222
|
:icon="card.icon"
|
|
222
223
|
:icon-tone="card.iconTone"
|
|
223
224
|
:title="card.title"
|
|
@@ -24,7 +24,14 @@ interface Emits {
|
|
|
24
24
|
(event: "drawn"): void;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** The ApexCharts instance the wrapper exposes as `chart`. */
|
|
28
|
+
interface ApexCore {
|
|
29
|
+
/** Apex's own (debounced) window resize handler: redraws at the container width. */
|
|
30
|
+
windowResizeHandler: () => void;
|
|
31
|
+
}
|
|
32
|
+
|
|
27
33
|
interface ApexChartInstance {
|
|
34
|
+
chart: ApexCore | null;
|
|
28
35
|
updateOptions: (
|
|
29
36
|
options: Record<string, unknown>,
|
|
30
37
|
redrawPaths?: boolean,
|
|
@@ -39,6 +46,7 @@ const emit = defineEmits<Emits>();
|
|
|
39
46
|
const REDRAW_PATHS = false;
|
|
40
47
|
const ANIMATE = true;
|
|
41
48
|
const UPDATE_SYNCED_CHARTS = false;
|
|
49
|
+
const WIDTH_TOLERANCE_PX = 1;
|
|
42
50
|
|
|
43
51
|
const { locale } = useI18n();
|
|
44
52
|
|
|
@@ -57,6 +65,7 @@ onMounted(() => {
|
|
|
57
65
|
void ensureApexLocale(locale.value).catch(() => undefined);
|
|
58
66
|
});
|
|
59
67
|
|
|
68
|
+
const hostRef = ref<HTMLElement | null>(null);
|
|
60
69
|
const chartRef = ref<ApexChartInstance | null>(null);
|
|
61
70
|
|
|
62
71
|
/**
|
|
@@ -123,10 +132,31 @@ watch(
|
|
|
123
132
|
function onDrawn() {
|
|
124
133
|
emit("drawn");
|
|
125
134
|
}
|
|
135
|
+
|
|
136
|
+
function isDrawnAtStaleWidth(host: HTMLElement): boolean {
|
|
137
|
+
const svg = host.querySelector("svg.apexcharts-svg");
|
|
138
|
+
if (!svg) return false;
|
|
139
|
+
const drawnWidth = svg.getBoundingClientRect().width;
|
|
140
|
+
const hostWidth = host.getBoundingClientRect().width;
|
|
141
|
+
return Math.abs(drawnWidth - hostWidth) > WIDTH_TOLERANCE_PX;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Apex drops its container's resizes while an animation runs, so a sidebar
|
|
146
|
+
* collapsed or expanded during one leaves the chart at its old width, past
|
|
147
|
+
* its card when the container shrank, until the next window resize. Once the
|
|
148
|
+
* animation is over, a chart drawn at another width than its host redraws.
|
|
149
|
+
*/
|
|
150
|
+
function redrawIfResizedWhileAnimating() {
|
|
151
|
+
const host = hostRef.value;
|
|
152
|
+
const chart = chartRef.value?.chart;
|
|
153
|
+
if (!host || !chart || !isDrawnAtStaleWidth(host)) return;
|
|
154
|
+
chart.windowResizeHandler();
|
|
155
|
+
}
|
|
126
156
|
</script>
|
|
127
157
|
|
|
128
158
|
<template>
|
|
129
|
-
<div class="dms-apex">
|
|
159
|
+
<div ref="hostRef" class="dms-apex">
|
|
130
160
|
<ApexChart
|
|
131
161
|
ref="chartRef"
|
|
132
162
|
:type="apexType"
|
|
@@ -135,6 +165,7 @@ function onDrawn() {
|
|
|
135
165
|
:series="apexSeries"
|
|
136
166
|
@mounted="onDrawn"
|
|
137
167
|
@updated="onDrawn"
|
|
168
|
+
@animation-end="redrawIfResizedWhileAnimating"
|
|
138
169
|
/>
|
|
139
170
|
</div>
|
|
140
171
|
</template>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { pageLink } from "../../build/composables/page/pageUrlValues";
|
|
2
3
|
import type { FormProps } from "../../composables/form/types/props";
|
|
3
4
|
import type {
|
|
4
5
|
FormData,
|
|
@@ -64,6 +65,7 @@ import DmsFormErrorSummary from "../../build/components/form/FormErrorSummary.vu
|
|
|
64
65
|
import DmsFormLoadError from "../../build/components/form/FormLoadError.vue";
|
|
65
66
|
import {
|
|
66
67
|
type FormRecordLoad,
|
|
68
|
+
missingRecordFailure,
|
|
67
69
|
trackFormRecordLoad,
|
|
68
70
|
} from "../../build/composables/form/formRecordLoad";
|
|
69
71
|
import DmsFormRequiredLegend from "./FormRequiredLegend.vue";
|
|
@@ -492,7 +494,7 @@ function cancelForm(): void {
|
|
|
492
494
|
window.history.back();
|
|
493
495
|
return;
|
|
494
496
|
}
|
|
495
|
-
if (props.backTo) void router.push(props.backTo);
|
|
497
|
+
if (props.backTo) void router.push(pageLink(props.backTo));
|
|
496
498
|
}
|
|
497
499
|
|
|
498
500
|
const validators = new Set<FormValidator>();
|
|
@@ -573,7 +575,15 @@ if (props.fetchUrl) {
|
|
|
573
575
|
});
|
|
574
576
|
}
|
|
575
577
|
|
|
576
|
-
const loadFailure = computed(
|
|
578
|
+
const loadFailure = computed(
|
|
579
|
+
() =>
|
|
580
|
+
recordLoad?.failure.value ??
|
|
581
|
+
missingRecordFailure({
|
|
582
|
+
requiresRecord: props.requiresRecord,
|
|
583
|
+
fetchUrl: props.fetchUrl,
|
|
584
|
+
resolvedFetchUrl: resolvedFetchUrl.value,
|
|
585
|
+
}),
|
|
586
|
+
);
|
|
577
587
|
const isRetryingLoad = computed(() => recordLoad?.retrying.value ?? false);
|
|
578
588
|
|
|
579
589
|
/** Load the record again after a failure; it is what the form starts from. */
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
|
+
import { pageRequestUrl } from "../../../build/composables/page/pageUrlValues";
|
|
2
3
|
import {
|
|
3
4
|
buildCascaderTree,
|
|
4
5
|
CASCADER_FETCH_LIMIT,
|
|
@@ -68,8 +69,10 @@ interface CascaderResponse {
|
|
|
68
69
|
|
|
69
70
|
const { data, status, execute } = await useDmsAsyncData(
|
|
70
71
|
`cascader-${instanceId}`,
|
|
71
|
-
() =>
|
|
72
|
-
|
|
72
|
+
() => {
|
|
73
|
+
const url = pageRequestUrl(props.searchUrl);
|
|
74
|
+
if (!url) return Promise.resolve({ results: [], total: 0 });
|
|
75
|
+
return $authFetch<CascaderResponse>(url, {
|
|
73
76
|
params: { limit: CASCADER_FETCH_LIMIT },
|
|
74
77
|
onRequestError: ({ error }: { error: Error }) => {
|
|
75
78
|
toast.add({
|
|
@@ -78,7 +81,8 @@ const { data, status, execute } = await useDmsAsyncData(
|
|
|
78
81
|
description: error.message,
|
|
79
82
|
});
|
|
80
83
|
},
|
|
81
|
-
})
|
|
84
|
+
});
|
|
85
|
+
},
|
|
82
86
|
{ lazy: true, immediate: false },
|
|
83
87
|
);
|
|
84
88
|
|
|
@@ -349,6 +349,12 @@ const acceptString = computed(() =>
|
|
|
349
349
|
props.constraints?.allowedMimetypes?.join(","),
|
|
350
350
|
);
|
|
351
351
|
|
|
352
|
+
const dropPromptKey = computed(() =>
|
|
353
|
+
props.multiple
|
|
354
|
+
? "dms.form.file.drop_prompt_multiple"
|
|
355
|
+
: "dms.form.file.drop_prompt",
|
|
356
|
+
);
|
|
357
|
+
|
|
352
358
|
const maxSizeFormatted = computed(() => {
|
|
353
359
|
if (!props.constraints?.maxSize) return null;
|
|
354
360
|
return formatFileSize(props.constraints.maxSize);
|
|
@@ -373,6 +379,7 @@ onScopeDispose(() => {
|
|
|
373
379
|
:disabled="disabled || isUploading"
|
|
374
380
|
:color="color"
|
|
375
381
|
:highlight="highlight"
|
|
382
|
+
:label="$t(dropPromptKey)"
|
|
376
383
|
v-bind="dropZoneAria"
|
|
377
384
|
>
|
|
378
385
|
<template v-if="maxSizeFormatted" #description>
|