@richardmcquiston01/dashboard-widgets-toolkit 0.15.0 → 0.16.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/dist/core.cjs +1931 -1698
- package/dist/core.cjs.map +1 -1
- package/dist/core.d.cts +49 -3
- package/dist/core.d.ts +49 -3
- package/dist/core.js +1921 -1699
- package/dist/core.js.map +1 -1
- package/dist/index.cjs +1931 -1698
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1921 -1699
- package/dist/index.js.map +1 -1
- package/dist/react.cjs +276 -11
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +21 -2
- package/dist/react.d.ts +21 -2
- package/dist/react.js +276 -11
- package/dist/react.js.map +1 -1
- package/dist/{storage-DwJ-vGJg.d.cts → storage-oivwB7al.d.cts} +126 -55
- package/dist/{storage-DwJ-vGJg.d.ts → storage-oivwB7al.d.ts} +126 -55
- package/package.json +1 -1
package/dist/react.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode, ReactElement, RefObject } from 'react';
|
|
2
|
-
import { D as DashboardWidget, W as WidgetSize, a as WidgetFill, G as GraphWidgetData, N as NiceDomain, b as WidgetDefinition, c as DetailProviderOptions, d as DetailData, e as DetailQuery, O as OptionValues, L as Locale, f as DashboardLayout, g as WidgetViewerSettings, h as WidgetSettingsPatch, A as AlertListWidgetData, B as BarListWidgetData, i as GaugeWidgetData, K as KpiWidgetData, T as TableWidgetData, R as ResolvedTableControls, j as DetailSort, k as TextWidgetData, l as WidgetData, m as LayoutPersistence, S as StoreScope, P as PageItem, n as NormalizeOptions, o as ResolveOptions, p as WidgetContext, q as WidgetProviders } from './storage-
|
|
2
|
+
import { D as DashboardWidget, W as WidgetSize, a as WidgetFill, G as GraphWidgetData, N as NiceDomain, b as WidgetDefinition, c as DetailProviderOptions, d as DetailData, e as DetailQuery, O as OptionValues, L as Locale, f as DashboardLayout, g as WidgetViewerSettings, h as WidgetSettingsPatch, A as AlertListWidgetData, B as BarListWidgetData, i as GaugeWidgetData, K as KpiWidgetData, T as TableWidgetData, R as ResolvedTableControls, j as DetailSort, k as TextWidgetData, l as WidgetData, m as LayoutPersistence, S as StoreScope, P as PageItem, n as NormalizeOptions, o as ResolveOptions, p as WidgetContext, q as WidgetProviders } from './storage-oivwB7al.cjs';
|
|
3
3
|
|
|
4
4
|
interface WidgetCardProps {
|
|
5
5
|
readonly title: string;
|
|
@@ -216,6 +216,18 @@ interface DashboardLabels {
|
|
|
216
216
|
readonly menuHide: string;
|
|
217
217
|
/** The card menu's item that opens the Options dialog. */
|
|
218
218
|
readonly menuOptions: string;
|
|
219
|
+
/** Arrange menu item that makes a clone of the widget. */
|
|
220
|
+
readonly menuDuplicate: string;
|
|
221
|
+
readonly duplicate: (title: string) => string;
|
|
222
|
+
/** Accessible name of Duplicate when the page has no room; `reason` says why. */
|
|
223
|
+
readonly duplicateBlocked: (title: string, reason: string) => string;
|
|
224
|
+
readonly duplicated: (title: string) => string;
|
|
225
|
+
/** Options dialog button that deletes a clone. */
|
|
226
|
+
readonly optionsDelete: string;
|
|
227
|
+
readonly confirmDelete: string;
|
|
228
|
+
readonly deleted: (title: string) => string;
|
|
229
|
+
/** The title suggested for a clone shown with another view. */
|
|
230
|
+
readonly suggestedViewTitle: (title: string, viewLabel: string) => string;
|
|
219
231
|
/** The Options dialog: heading, fields and buttons. */
|
|
220
232
|
readonly optionsFor: (title: string) => string;
|
|
221
233
|
readonly optionsTitleField: string;
|
|
@@ -393,11 +405,18 @@ interface WidgetOptionsDialogProps {
|
|
|
393
405
|
};
|
|
394
406
|
/** Whether another widget already shows this title; the dialog refuses it. */
|
|
395
407
|
readonly isTitleInUse?: (title: string) => boolean;
|
|
408
|
+
/**
|
|
409
|
+
* For a clone: its original's title, so choosing another view can suggest
|
|
410
|
+
* a title like "Recent Orders Line Chart".
|
|
411
|
+
*/
|
|
412
|
+
readonly sourceTitle?: string;
|
|
413
|
+
/** For a clone: shows "Delete this widget"; called after the viewer confirms. */
|
|
414
|
+
readonly onDelete?: () => void;
|
|
396
415
|
/** Called after the viewer confirms Apply, Reset or Revert. */
|
|
397
416
|
readonly onApply: (patch: WidgetSettingsPatch) => void;
|
|
398
417
|
readonly onClose: () => void;
|
|
399
418
|
}
|
|
400
|
-
declare function WidgetOptionsDialog({ definition, settings, baseline, isTitleInUse, onApply, onClose, }: WidgetOptionsDialogProps): ReactElement;
|
|
419
|
+
declare function WidgetOptionsDialog({ definition, settings, baseline, isTitleInUse, sourceTitle, onDelete, onApply, onClose, }: WidgetOptionsDialogProps): ReactElement;
|
|
401
420
|
|
|
402
421
|
/**
|
|
403
422
|
* Default colors, used as `var(--dwt-*, fallback)` so charts read correctly
|
package/dist/react.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode, ReactElement, RefObject } from 'react';
|
|
2
|
-
import { D as DashboardWidget, W as WidgetSize, a as WidgetFill, G as GraphWidgetData, N as NiceDomain, b as WidgetDefinition, c as DetailProviderOptions, d as DetailData, e as DetailQuery, O as OptionValues, L as Locale, f as DashboardLayout, g as WidgetViewerSettings, h as WidgetSettingsPatch, A as AlertListWidgetData, B as BarListWidgetData, i as GaugeWidgetData, K as KpiWidgetData, T as TableWidgetData, R as ResolvedTableControls, j as DetailSort, k as TextWidgetData, l as WidgetData, m as LayoutPersistence, S as StoreScope, P as PageItem, n as NormalizeOptions, o as ResolveOptions, p as WidgetContext, q as WidgetProviders } from './storage-
|
|
2
|
+
import { D as DashboardWidget, W as WidgetSize, a as WidgetFill, G as GraphWidgetData, N as NiceDomain, b as WidgetDefinition, c as DetailProviderOptions, d as DetailData, e as DetailQuery, O as OptionValues, L as Locale, f as DashboardLayout, g as WidgetViewerSettings, h as WidgetSettingsPatch, A as AlertListWidgetData, B as BarListWidgetData, i as GaugeWidgetData, K as KpiWidgetData, T as TableWidgetData, R as ResolvedTableControls, j as DetailSort, k as TextWidgetData, l as WidgetData, m as LayoutPersistence, S as StoreScope, P as PageItem, n as NormalizeOptions, o as ResolveOptions, p as WidgetContext, q as WidgetProviders } from './storage-oivwB7al.js';
|
|
3
3
|
|
|
4
4
|
interface WidgetCardProps {
|
|
5
5
|
readonly title: string;
|
|
@@ -216,6 +216,18 @@ interface DashboardLabels {
|
|
|
216
216
|
readonly menuHide: string;
|
|
217
217
|
/** The card menu's item that opens the Options dialog. */
|
|
218
218
|
readonly menuOptions: string;
|
|
219
|
+
/** Arrange menu item that makes a clone of the widget. */
|
|
220
|
+
readonly menuDuplicate: string;
|
|
221
|
+
readonly duplicate: (title: string) => string;
|
|
222
|
+
/** Accessible name of Duplicate when the page has no room; `reason` says why. */
|
|
223
|
+
readonly duplicateBlocked: (title: string, reason: string) => string;
|
|
224
|
+
readonly duplicated: (title: string) => string;
|
|
225
|
+
/** Options dialog button that deletes a clone. */
|
|
226
|
+
readonly optionsDelete: string;
|
|
227
|
+
readonly confirmDelete: string;
|
|
228
|
+
readonly deleted: (title: string) => string;
|
|
229
|
+
/** The title suggested for a clone shown with another view. */
|
|
230
|
+
readonly suggestedViewTitle: (title: string, viewLabel: string) => string;
|
|
219
231
|
/** The Options dialog: heading, fields and buttons. */
|
|
220
232
|
readonly optionsFor: (title: string) => string;
|
|
221
233
|
readonly optionsTitleField: string;
|
|
@@ -393,11 +405,18 @@ interface WidgetOptionsDialogProps {
|
|
|
393
405
|
};
|
|
394
406
|
/** Whether another widget already shows this title; the dialog refuses it. */
|
|
395
407
|
readonly isTitleInUse?: (title: string) => boolean;
|
|
408
|
+
/**
|
|
409
|
+
* For a clone: its original's title, so choosing another view can suggest
|
|
410
|
+
* a title like "Recent Orders Line Chart".
|
|
411
|
+
*/
|
|
412
|
+
readonly sourceTitle?: string;
|
|
413
|
+
/** For a clone: shows "Delete this widget"; called after the viewer confirms. */
|
|
414
|
+
readonly onDelete?: () => void;
|
|
396
415
|
/** Called after the viewer confirms Apply, Reset or Revert. */
|
|
397
416
|
readonly onApply: (patch: WidgetSettingsPatch) => void;
|
|
398
417
|
readonly onClose: () => void;
|
|
399
418
|
}
|
|
400
|
-
declare function WidgetOptionsDialog({ definition, settings, baseline, isTitleInUse, onApply, onClose, }: WidgetOptionsDialogProps): ReactElement;
|
|
419
|
+
declare function WidgetOptionsDialog({ definition, settings, baseline, isTitleInUse, sourceTitle, onDelete, onApply, onClose, }: WidgetOptionsDialogProps): ReactElement;
|
|
401
420
|
|
|
402
421
|
/**
|
|
403
422
|
* Default colors, used as `var(--dwt-*, fallback)` so charts read correctly
|
package/dist/react.js
CHANGED
|
@@ -828,6 +828,14 @@ var DEFAULT_LABELS = {
|
|
|
828
828
|
menuMoveLater: "Move later",
|
|
829
829
|
menuHide: "Hide",
|
|
830
830
|
menuOptions: "Options\u2026",
|
|
831
|
+
menuDuplicate: "Duplicate",
|
|
832
|
+
duplicate: (title) => `Duplicate ${title}`,
|
|
833
|
+
duplicateBlocked: (title, reason) => `Duplicate ${title} (unavailable: ${reason})`,
|
|
834
|
+
duplicated: (title) => `Duplicated ${title}`,
|
|
835
|
+
optionsDelete: "Delete this widget",
|
|
836
|
+
confirmDelete: "Delete this widget? Its settings are lost.",
|
|
837
|
+
deleted: (title) => `Deleted ${title}`,
|
|
838
|
+
suggestedViewTitle: (title, viewLabel) => `${title} ${viewLabel}`,
|
|
831
839
|
optionsFor: (title) => `Options for ${title}`,
|
|
832
840
|
optionsTitleField: "Title",
|
|
833
841
|
titleInUse: "Another widget already uses this title.",
|
|
@@ -1937,13 +1945,15 @@ var NOT_LOCKED = Object.freeze({
|
|
|
1937
1945
|
move: false,
|
|
1938
1946
|
hide: false,
|
|
1939
1947
|
minimize: false,
|
|
1940
|
-
options: false
|
|
1948
|
+
options: false,
|
|
1949
|
+
clone: false
|
|
1941
1950
|
});
|
|
1942
1951
|
var FULLY_LOCKED = Object.freeze({
|
|
1943
1952
|
move: true,
|
|
1944
1953
|
hide: true,
|
|
1945
1954
|
minimize: true,
|
|
1946
|
-
options: true
|
|
1955
|
+
options: true,
|
|
1956
|
+
clone: true
|
|
1947
1957
|
});
|
|
1948
1958
|
function resolveWidgetLock(definition) {
|
|
1949
1959
|
const setting = definition.locked;
|
|
@@ -1953,7 +1963,8 @@ function resolveWidgetLock(definition) {
|
|
|
1953
1963
|
move: setting.move === true,
|
|
1954
1964
|
hide: setting.hide === true,
|
|
1955
1965
|
minimize: setting.minimize === true,
|
|
1956
|
-
options: setting.options === true
|
|
1966
|
+
options: setting.options === true,
|
|
1967
|
+
clone: setting.clone === true
|
|
1957
1968
|
};
|
|
1958
1969
|
}
|
|
1959
1970
|
function sortDefinitions(definitions) {
|
|
@@ -2403,6 +2414,62 @@ function isTitleInUse(definitions, layout, key, title) {
|
|
|
2403
2414
|
);
|
|
2404
2415
|
}
|
|
2405
2416
|
|
|
2417
|
+
// src/core/clones.ts
|
|
2418
|
+
function cloneNumber(key) {
|
|
2419
|
+
const match = /#(\d+)$/.exec(key);
|
|
2420
|
+
return match === null ? void 0 : Number(match[1]);
|
|
2421
|
+
}
|
|
2422
|
+
function cleanClones(definitions, clones, options = {}) {
|
|
2423
|
+
if (clones === void 0) return void 0;
|
|
2424
|
+
const byKey = new Map(definitions.map((d) => [d.key, d]));
|
|
2425
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2426
|
+
const kept = [];
|
|
2427
|
+
for (const clone of clones) {
|
|
2428
|
+
if (seen.has(clone.key)) continue;
|
|
2429
|
+
const original = byKey.get(clone.from);
|
|
2430
|
+
if (original === void 0 || original.sourceKey !== void 0) continue;
|
|
2431
|
+
const existing = byKey.get(clone.key);
|
|
2432
|
+
if (existing !== void 0 && existing.sourceKey !== clone.from) continue;
|
|
2433
|
+
if (options.overrideLocks !== true && resolveWidgetLock(original).clone) {
|
|
2434
|
+
continue;
|
|
2435
|
+
}
|
|
2436
|
+
seen.add(clone.key);
|
|
2437
|
+
kept.push(clone);
|
|
2438
|
+
}
|
|
2439
|
+
if (kept.length === 0) return void 0;
|
|
2440
|
+
return kept.length === clones.length && kept.every((c, i) => c === clones[i]) ? clones : kept;
|
|
2441
|
+
}
|
|
2442
|
+
function defaultCloneTitle(original, clone, index) {
|
|
2443
|
+
const base = original.title ?? original.key;
|
|
2444
|
+
const suffix = ` (#${cloneNumber(clone.key) ?? index + 1})`;
|
|
2445
|
+
return `${base.slice(0, MAX_WIDGET_TITLE_LENGTH - suffix.length)}${suffix}`;
|
|
2446
|
+
}
|
|
2447
|
+
function withClones(definitions, layout, options = {}) {
|
|
2448
|
+
const valid = cleanClones(
|
|
2449
|
+
definitions,
|
|
2450
|
+
layout.clones,
|
|
2451
|
+
options
|
|
2452
|
+
);
|
|
2453
|
+
if (valid === void 0) return definitions;
|
|
2454
|
+
const present = new Set(definitions.map((d) => d.key));
|
|
2455
|
+
const toAdd = valid.filter((c) => !present.has(c.key));
|
|
2456
|
+
if (toAdd.length === 0) return definitions;
|
|
2457
|
+
const out = [];
|
|
2458
|
+
for (const definition of definitions) {
|
|
2459
|
+
out.push(definition);
|
|
2460
|
+
toAdd.forEach((clone, index) => {
|
|
2461
|
+
if (clone.from !== definition.key) return;
|
|
2462
|
+
const copy = { ...definition };
|
|
2463
|
+
copy.key = clone.key;
|
|
2464
|
+
copy.sourceKey = definition.key;
|
|
2465
|
+
copy.title = defaultCloneTitle(definition, clone, index);
|
|
2466
|
+
delete copy.locked;
|
|
2467
|
+
out.push(copy);
|
|
2468
|
+
});
|
|
2469
|
+
}
|
|
2470
|
+
return out;
|
|
2471
|
+
}
|
|
2472
|
+
|
|
2406
2473
|
// src/core/layout.ts
|
|
2407
2474
|
var MAX_PAGE_TITLE_LENGTH = 30;
|
|
2408
2475
|
var MAX_PARSED_PAGES = 50;
|
|
@@ -2880,6 +2947,114 @@ function moveWidgetToPage(definitions, layout, widgetKey, targetKey, options = {
|
|
|
2880
2947
|
}
|
|
2881
2948
|
return ok(moved);
|
|
2882
2949
|
}
|
|
2950
|
+
|
|
2951
|
+
// src/core/duplicate.ts
|
|
2952
|
+
function canDuplicate(definitions, key, options = {}) {
|
|
2953
|
+
const definition = definitions.find(
|
|
2954
|
+
(d) => d.key === key
|
|
2955
|
+
);
|
|
2956
|
+
if (definition === void 0) return false;
|
|
2957
|
+
if (options.overrideLocks === true) return true;
|
|
2958
|
+
const sourceKey = definition.sourceKey ?? definition.key;
|
|
2959
|
+
const original = definitions.find(
|
|
2960
|
+
(d) => d.key === sourceKey
|
|
2961
|
+
);
|
|
2962
|
+
return original !== void 0 && !resolveWidgetLock(original).clone;
|
|
2963
|
+
}
|
|
2964
|
+
function nextCloneKey(definitions, layout, fromKey) {
|
|
2965
|
+
const taken = /* @__PURE__ */ new Set([
|
|
2966
|
+
...definitions.map((d) => d.key),
|
|
2967
|
+
...(layout.clones ?? []).map((c) => c.key)
|
|
2968
|
+
]);
|
|
2969
|
+
let highest = 0;
|
|
2970
|
+
for (const clone of layout.clones ?? []) {
|
|
2971
|
+
if (clone.from === fromKey) {
|
|
2972
|
+
highest = Math.max(highest, cloneNumber(clone.key) ?? 0);
|
|
2973
|
+
}
|
|
2974
|
+
}
|
|
2975
|
+
let n = highest + 1;
|
|
2976
|
+
while (taken.has(`${fromKey}#${n}`)) n += 1;
|
|
2977
|
+
return `${fromKey}#${n}`;
|
|
2978
|
+
}
|
|
2979
|
+
function duplicateWidget(definitions, layout, key, options = {}) {
|
|
2980
|
+
const widget = definitions.find((d) => d.key === key);
|
|
2981
|
+
if (widget === void 0) {
|
|
2982
|
+
return err(`Widget ${JSON.stringify(key)} isn't on this dashboard.`);
|
|
2983
|
+
}
|
|
2984
|
+
const originalKey = widget.sourceKey ?? widget.key;
|
|
2985
|
+
if (!canDuplicate(definitions, key, options)) {
|
|
2986
|
+
return err(
|
|
2987
|
+
`Widget ${JSON.stringify(originalKey)} is locked against duplicating.`
|
|
2988
|
+
);
|
|
2989
|
+
}
|
|
2990
|
+
const cloneKey = nextCloneKey(definitions, layout, originalKey);
|
|
2991
|
+
const copiedSettings = (() => {
|
|
2992
|
+
const mine = layout.settings?.[key];
|
|
2993
|
+
if (mine === void 0) return void 0;
|
|
2994
|
+
const { title: _title, ...rest } = mine;
|
|
2995
|
+
return Object.keys(rest).length === 0 ? void 0 : rest;
|
|
2996
|
+
})();
|
|
2997
|
+
const settings = copiedSettings === void 0 ? layout.settings : { ...layout.settings, [cloneKey]: copiedSettings };
|
|
2998
|
+
let next = {
|
|
2999
|
+
...layout,
|
|
3000
|
+
clones: [...layout.clones ?? [], { key: cloneKey, from: originalKey }],
|
|
3001
|
+
...settings === void 0 ? {} : { settings }
|
|
3002
|
+
};
|
|
3003
|
+
const all = withClones(definitions, next, options);
|
|
3004
|
+
const assigned = assignPages(all, next);
|
|
3005
|
+
const pageKey = [...assigned].find(
|
|
3006
|
+
([, widgets]) => widgets.some((w) => w.key === key)
|
|
3007
|
+
)?.[0];
|
|
3008
|
+
if (pageKey !== void 0) {
|
|
3009
|
+
const view = pageLayout(next, pageKey);
|
|
3010
|
+
const onPage = assigned.get(pageKey) ?? [];
|
|
3011
|
+
if (view !== void 0) {
|
|
3012
|
+
const shown = visibleWidgets(onPage, view).filter((d) => !resolveWidgetLock(d).move && d.key !== cloneKey).map((d) => d.key);
|
|
3013
|
+
const at = shown.indexOf(key);
|
|
3014
|
+
const order = at === -1 ? [...shown, cloneKey] : [...shown.slice(0, at + 1), cloneKey, ...shown.slice(at + 1)];
|
|
3015
|
+
next = withPageLayout(next, pageKey, { ...view, order });
|
|
3016
|
+
}
|
|
3017
|
+
}
|
|
3018
|
+
if (hasPages(next) && pageKey !== void 0) {
|
|
3019
|
+
const effective = effectiveDefinitions(all, next, options);
|
|
3020
|
+
const room = pageRoom(effective, next, pageKey, options);
|
|
3021
|
+
if (room !== void 0 && room.rowsUsed > room.maxRows) {
|
|
3022
|
+
const page = next.pages?.find(
|
|
3023
|
+
(p) => p.key === pageKey
|
|
3024
|
+
);
|
|
3025
|
+
const width = effective.find(
|
|
3026
|
+
(d) => d.key === cloneKey
|
|
3027
|
+
)?.width;
|
|
3028
|
+
return err(
|
|
3029
|
+
`Page "${page?.title ?? pageKey}" has no room for a widget${width === void 0 ? "" : ` ${width} wide`} (it holds ${room.maxRows} rows).`
|
|
3030
|
+
);
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
return ok({ layout: next, key: cloneKey });
|
|
3034
|
+
}
|
|
3035
|
+
function deleteClone(layout, key) {
|
|
3036
|
+
if (layout.clones?.some((clone) => clone.key === key) !== true) return layout;
|
|
3037
|
+
const without2 = (keys) => keys.includes(key) ? keys.filter((k) => k !== key) : keys;
|
|
3038
|
+
const { clones, settings, ...rest } = layout;
|
|
3039
|
+
const remaining = (clones ?? []).filter((c) => c.key !== key);
|
|
3040
|
+
const { [key]: _gone, ...otherSettings } = settings ?? {};
|
|
3041
|
+
return {
|
|
3042
|
+
...rest,
|
|
3043
|
+
order: without2(layout.order),
|
|
3044
|
+
hidden: without2(layout.hidden),
|
|
3045
|
+
minimized: without2(layout.minimized),
|
|
3046
|
+
...layout.pages === void 0 ? {} : {
|
|
3047
|
+
pages: layout.pages.map((page) => ({
|
|
3048
|
+
...page,
|
|
3049
|
+
order: without2(page.order),
|
|
3050
|
+
hidden: without2(page.hidden),
|
|
3051
|
+
minimized: without2(page.minimized)
|
|
3052
|
+
}))
|
|
3053
|
+
},
|
|
3054
|
+
...Object.keys(otherSettings).length === 0 ? {} : { settings: otherSettings },
|
|
3055
|
+
...remaining.length === 0 ? {} : { clones: remaining }
|
|
3056
|
+
};
|
|
3057
|
+
}
|
|
2883
3058
|
function InlineConfirm({
|
|
2884
3059
|
message,
|
|
2885
3060
|
onAnswer
|
|
@@ -3130,6 +3305,8 @@ function WidgetOptionsDialog({
|
|
|
3130
3305
|
settings,
|
|
3131
3306
|
baseline,
|
|
3132
3307
|
isTitleInUse: isTitleInUse2,
|
|
3308
|
+
sourceTitle,
|
|
3309
|
+
onDelete,
|
|
3133
3310
|
onApply,
|
|
3134
3311
|
onClose
|
|
3135
3312
|
}) {
|
|
@@ -3142,6 +3319,7 @@ function WidgetOptionsDialog({
|
|
|
3142
3319
|
const [confirming, setConfirming] = useState(
|
|
3143
3320
|
void 0
|
|
3144
3321
|
);
|
|
3322
|
+
const autoTitle = useRef(void 0);
|
|
3145
3323
|
useEffect(() => {
|
|
3146
3324
|
const dialog = dialogRef.current;
|
|
3147
3325
|
if (dialog === null) return void 0;
|
|
@@ -3184,6 +3362,9 @@ function WidgetOptionsDialog({
|
|
|
3184
3362
|
} else if (which === "reset") {
|
|
3185
3363
|
onApply(EMPTY_PATCH);
|
|
3186
3364
|
onClose();
|
|
3365
|
+
} else if (which === "delete") {
|
|
3366
|
+
onDelete?.();
|
|
3367
|
+
onClose();
|
|
3187
3368
|
} else if (baseline !== void 0) {
|
|
3188
3369
|
onApply(patchFrom(definition, draftFrom(baseline.settings)));
|
|
3189
3370
|
onClose();
|
|
@@ -3199,7 +3380,8 @@ function WidgetOptionsDialog({
|
|
|
3199
3380
|
apply: labels.confirmApply,
|
|
3200
3381
|
discard: labels.confirmDiscard,
|
|
3201
3382
|
reset: labels.confirmResetOptions,
|
|
3202
|
-
revert: labels.confirmRevertOptions
|
|
3383
|
+
revert: labels.confirmRevertOptions,
|
|
3384
|
+
delete: labels.confirmDelete
|
|
3203
3385
|
};
|
|
3204
3386
|
const widths = [];
|
|
3205
3387
|
for (let width = range.min; width <= range.max; width += 1)
|
|
@@ -3296,7 +3478,32 @@ function WidgetOptionsDialog({
|
|
|
3296
3478
|
value: effectiveViewKey(definition, draft.view),
|
|
3297
3479
|
onChange: (event) => {
|
|
3298
3480
|
const value = event.target.value;
|
|
3299
|
-
setDraft((current) =>
|
|
3481
|
+
setDraft((current) => {
|
|
3482
|
+
const next = { ...current, view: value };
|
|
3483
|
+
if (sourceTitle === void 0 || current.title.trim() !== "" && current.title !== autoTitle.current) {
|
|
3484
|
+
return next;
|
|
3485
|
+
}
|
|
3486
|
+
const label = views.find(
|
|
3487
|
+
(view) => view.key === value
|
|
3488
|
+
)?.label;
|
|
3489
|
+
const startView = effectiveViewKey(
|
|
3490
|
+
definition,
|
|
3491
|
+
settings?.view
|
|
3492
|
+
);
|
|
3493
|
+
if (label === void 0 || value === startView) {
|
|
3494
|
+
autoTitle.current = void 0;
|
|
3495
|
+
return { ...next, title: "" };
|
|
3496
|
+
}
|
|
3497
|
+
let suggestion = labels.suggestedViewTitle(
|
|
3498
|
+
sourceTitle,
|
|
3499
|
+
label
|
|
3500
|
+
);
|
|
3501
|
+
for (let n = 2; isTitleInUse2?.(suggestion) === true && n < 50; n += 1) {
|
|
3502
|
+
suggestion = `${labels.suggestedViewTitle(sourceTitle, label)} (#${n})`;
|
|
3503
|
+
}
|
|
3504
|
+
autoTitle.current = suggestion;
|
|
3505
|
+
return { ...next, title: suggestion };
|
|
3506
|
+
});
|
|
3300
3507
|
},
|
|
3301
3508
|
children: views.map((view) => /* @__PURE__ */ jsx("option", { value: view.key, children: view.key === effectiveViewKey(definition, void 0) ? labels.viewDefault(view.label) : view.label }, view.key))
|
|
3302
3509
|
}
|
|
@@ -3353,6 +3560,15 @@ function WidgetOptionsDialog({
|
|
|
3353
3560
|
onClick: () => setConfirming("revert"),
|
|
3354
3561
|
children: labels.revertToBeforeEditing
|
|
3355
3562
|
}
|
|
3563
|
+
),
|
|
3564
|
+
onDelete !== void 0 && /* @__PURE__ */ jsx(
|
|
3565
|
+
"button",
|
|
3566
|
+
{
|
|
3567
|
+
type: "button",
|
|
3568
|
+
className: slot("button", "dwt-button"),
|
|
3569
|
+
onClick: () => setConfirming("delete"),
|
|
3570
|
+
children: labels.optionsDelete
|
|
3571
|
+
}
|
|
3356
3572
|
)
|
|
3357
3573
|
] }) : /* @__PURE__ */ jsx(
|
|
3358
3574
|
InlineConfirm,
|
|
@@ -4357,6 +4573,29 @@ function DashboardInner({
|
|
|
4357
4573
|
setPageError(void 0);
|
|
4358
4574
|
change(withPageLayout(layout, activeKey, next));
|
|
4359
4575
|
}
|
|
4576
|
+
function duplicateItems(definition, key, title) {
|
|
4577
|
+
const sources = [...authorDefinitions.values()];
|
|
4578
|
+
if (!canDuplicate(sources, key, { overrideLocks })) return [];
|
|
4579
|
+
const trial = duplicateWidget(sources, layout, key, {
|
|
4580
|
+
overrideLocks,
|
|
4581
|
+
...roomOptions
|
|
4582
|
+
});
|
|
4583
|
+
return [
|
|
4584
|
+
{
|
|
4585
|
+
kind: "action",
|
|
4586
|
+
key: "duplicate",
|
|
4587
|
+
label: labels.menuDuplicate,
|
|
4588
|
+
ariaLabel: trial.ok ? labels.duplicate(title) : labels.duplicateBlocked(title, trial.error),
|
|
4589
|
+
disabled: !trial.ok,
|
|
4590
|
+
onSelect: () => {
|
|
4591
|
+
if (!trial.ok) return;
|
|
4592
|
+
change(trial.value.layout);
|
|
4593
|
+
setOptionsKey(trial.value.key);
|
|
4594
|
+
setAnnouncement(labels.duplicated(title));
|
|
4595
|
+
}
|
|
4596
|
+
}
|
|
4597
|
+
];
|
|
4598
|
+
}
|
|
4360
4599
|
function selectPage(pageKey) {
|
|
4361
4600
|
const index = pages.findIndex((page) => page.key === pageKey);
|
|
4362
4601
|
if (index === -1) return;
|
|
@@ -4486,10 +4725,16 @@ function DashboardInner({
|
|
|
4486
4725
|
definitions,
|
|
4487
4726
|
defaultLayout ?? EMPTY_LAYOUT
|
|
4488
4727
|
);
|
|
4489
|
-
const {
|
|
4490
|
-
|
|
4491
|
-
|
|
4492
|
-
|
|
4728
|
+
const {
|
|
4729
|
+
settings: _defaultSettings,
|
|
4730
|
+
clones: _defaultClones,
|
|
4731
|
+
...arrangement
|
|
4732
|
+
} = arranged;
|
|
4733
|
+
change({
|
|
4734
|
+
...arrangement,
|
|
4735
|
+
...layout.settings === void 0 ? {} : { settings: layout.settings },
|
|
4736
|
+
...layout.clones === void 0 ? {} : { clones: layout.clones }
|
|
4737
|
+
});
|
|
4493
4738
|
} else if (yes && which === "revert" && snapshot !== void 0) {
|
|
4494
4739
|
change(snapshot);
|
|
4495
4740
|
}
|
|
@@ -4795,6 +5040,7 @@ function DashboardInner({
|
|
|
4795
5040
|
onSelect: () => setOptionsKey(key)
|
|
4796
5041
|
}
|
|
4797
5042
|
],
|
|
5043
|
+
...duplicateItems(definition, key, title),
|
|
4798
5044
|
...pageTargets,
|
|
4799
5045
|
...lock.hide ? [] : [
|
|
4800
5046
|
{
|
|
@@ -4889,6 +5135,21 @@ function DashboardInner({
|
|
|
4889
5135
|
definition: authorDefinitions.get(optionsKey),
|
|
4890
5136
|
settings: layout.settings?.[optionsKey],
|
|
4891
5137
|
...snapshot === void 0 ? {} : { baseline: { settings: snapshot.settings?.[optionsKey] } },
|
|
5138
|
+
...authorDefinitions.get(optionsKey)?.sourceKey === void 0 ? {} : {
|
|
5139
|
+
sourceTitle: effectiveWidgets.find(
|
|
5140
|
+
(w) => w.definition.key === authorDefinitions.get(optionsKey)?.sourceKey
|
|
5141
|
+
)?.definition.title ?? authorDefinitions.get(optionsKey)?.sourceKey ?? "",
|
|
5142
|
+
onDelete: () => {
|
|
5143
|
+
change(deleteClone(layout, optionsKey));
|
|
5144
|
+
setAnnouncement(
|
|
5145
|
+
labels.deleted(
|
|
5146
|
+
effectiveWidgets.find(
|
|
5147
|
+
(w) => w.definition.key === optionsKey
|
|
5148
|
+
)?.definition.title ?? optionsKey
|
|
5149
|
+
)
|
|
5150
|
+
);
|
|
5151
|
+
}
|
|
5152
|
+
},
|
|
4892
5153
|
isTitleInUse: (title) => isTitleInUse(
|
|
4893
5154
|
widgets.map((w) => w.definition),
|
|
4894
5155
|
layout,
|
|
@@ -5436,7 +5697,7 @@ function loadingWidgets(definitions) {
|
|
|
5436
5697
|
return definitions.map((definition) => ({ definition, status: "loading" }));
|
|
5437
5698
|
}
|
|
5438
5699
|
function cacheKeyFor(definition, options, values) {
|
|
5439
|
-
const base = options.cacheKey === void 0 ? definition.key : options.cacheKey(definition);
|
|
5700
|
+
const base = options.cacheKey === void 0 ? definition.sourceKey ?? definition.key : options.cacheKey(definition);
|
|
5440
5701
|
return values === void 0 ? base : `${base}${optionsCacheSuffix(definition, values, options)}`;
|
|
5441
5702
|
}
|
|
5442
5703
|
function optionValuesFor(definition, options) {
|
|
@@ -5610,7 +5871,11 @@ function createWidgetLoader(options) {
|
|
|
5610
5871
|
}
|
|
5611
5872
|
}
|
|
5612
5873
|
if (!isCurrent()) return;
|
|
5613
|
-
const
|
|
5874
|
+
const providerKey = definition.sourceKey ?? key;
|
|
5875
|
+
const provider = Object.prototype.hasOwnProperty.call(
|
|
5876
|
+
providers,
|
|
5877
|
+
providerKey
|
|
5878
|
+
) ? providers[providerKey] : void 0;
|
|
5614
5879
|
const result = await resolveWidgetUnapplied(definition, provider, context, {
|
|
5615
5880
|
...runOptions,
|
|
5616
5881
|
signal: controller.signal
|