@proveanything/smartlinks-utils-ui 0.12.4 → 0.12.6
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/components/AssetPicker/index.css +17 -2
- package/dist/components/AssetPicker/index.css.map +1 -1
- package/dist/components/ConditionsEditor/index.css +17 -2
- package/dist/components/ConditionsEditor/index.css.map +1 -1
- package/dist/components/FontPicker/index.css +17 -2
- package/dist/components/FontPicker/index.css.map +1 -1
- package/dist/components/IconPicker/index.css +17 -2
- package/dist/components/IconPicker/index.css.map +1 -1
- package/dist/components/RecordsAdmin/index.css +17 -2
- package/dist/components/RecordsAdmin/index.css.map +1 -1
- package/dist/components/RecordsAdmin/index.d.ts +8 -0
- package/dist/components/RecordsAdmin/index.js +392 -34
- package/dist/components/RecordsAdmin/index.js.map +1 -1
- package/dist/index.css +17 -2
- package/dist/index.css.map +1 -1
- package/package.json +1 -1
|
@@ -455,6 +455,14 @@ interface RecordsAdminI18n {
|
|
|
455
455
|
duplicateAction: string;
|
|
456
456
|
/** Toast shown after a successful Duplicate. `{name}` = source label. */
|
|
457
457
|
duplicateToast: string;
|
|
458
|
+
/** Duplicate-to picker labels. */
|
|
459
|
+
duplicateDialogTitle: string;
|
|
460
|
+
duplicateTargetGlobal: string;
|
|
461
|
+
duplicateTargetSameRule: string;
|
|
462
|
+
duplicateTargetExistingRules: string;
|
|
463
|
+
duplicateTargetNewRule: string;
|
|
464
|
+
duplicateConfirm: string;
|
|
465
|
+
duplicateCancel: string;
|
|
458
466
|
/**
|
|
459
467
|
* Row-menu label for the "Copy and start new rule" action — shown on
|
|
460
468
|
* the rules tab in singleton mode. One-click flow: copies the row to
|
|
@@ -7,7 +7,7 @@ import { cn } from '../../chunk-L7FQ52F5.js';
|
|
|
7
7
|
import { parsedRefToTarget, parsedRefToScope, matchRecords, scopesEqual, getRecordById, listRecords, upsertRecord, updateRecord, createRecord, removeRecord } from '../../chunk-KA4MKRHL.js';
|
|
8
8
|
export { bulkDelete, bulkUpsert, createRecord, getRecordById, listRecords, matchRecords, parsedRefToScope, parsedRefToTarget, removeRecord, restoreRecord, scopesEqual, upsertRecord } from '../../chunk-KA4MKRHL.js';
|
|
9
9
|
import { createContext, useMemo, useState, useEffect, useCallback, useRef, isValidElement, useLayoutEffect, useContext, useSyncExternalStore, createElement } from 'react';
|
|
10
|
-
import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Settings2 } from 'lucide-react';
|
|
10
|
+
import { ChevronDown, Database, Lightbulb, SearchX, Inbox, LayoutGrid, Eye, MoreHorizontal, Download, Upload, Trash2, Copy, Pencil, Plus, CircleDashed, ArrowDownLeft, CheckCircle2, List, SlidersHorizontal, Globe, Tag, Boxes, Layers, Package, Target, Check, Rows3, ChevronRight, Eraser, FilePlus2, CopyPlus, ClipboardPaste, Box, X, Search, Image, Table, ArrowLeft, ChevronLeft, AlertTriangle, Info, HelpCircle, CornerDownLeft, Circle, ArrowUpDown, ArrowUp, ArrowDown, MinusCircle, XCircle, AlertCircle, Undo2, Save, Loader2, Archive, ArrowRight, Globe2, Sparkles, Settings2 } from 'lucide-react';
|
|
11
11
|
import { useQuery, useQueryClient, useInfiniteQuery } from '@tanstack/react-query';
|
|
12
12
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
13
|
import { createPortal } from 'react-dom';
|
|
@@ -240,6 +240,13 @@ var DEFAULT_I18N = {
|
|
|
240
240
|
pasteWarnContinue: "Continue",
|
|
241
241
|
duplicateAction: "Duplicate",
|
|
242
242
|
duplicateToast: "Duplicated {name}. Review and Save.",
|
|
243
|
+
duplicateDialogTitle: 'Duplicate "{name}" to:',
|
|
244
|
+
duplicateTargetGlobal: "Global",
|
|
245
|
+
duplicateTargetSameRule: "Same rule as source",
|
|
246
|
+
duplicateTargetExistingRules: "Existing rules",
|
|
247
|
+
duplicateTargetNewRule: "New rule\u2026",
|
|
248
|
+
duplicateConfirm: "Duplicate",
|
|
249
|
+
duplicateCancel: "Cancel",
|
|
243
250
|
copyAndNewRuleAction: "Copy and start new rule",
|
|
244
251
|
itemsAllLabel: "All items",
|
|
245
252
|
subtitleEmpty: "Not set",
|
|
@@ -1621,6 +1628,199 @@ var ClipboardToast = ({ message, variant = "copy", onDismiss }) => {
|
|
|
1621
1628
|
}
|
|
1622
1629
|
);
|
|
1623
1630
|
};
|
|
1631
|
+
var DEFAULT_I18N2 = {
|
|
1632
|
+
title: 'Duplicate "{name}" to:',
|
|
1633
|
+
global: "Global",
|
|
1634
|
+
sameRule: "Same rule as source",
|
|
1635
|
+
existingRules: "Existing rules",
|
|
1636
|
+
newRule: "New rule\u2026",
|
|
1637
|
+
confirm: "Duplicate",
|
|
1638
|
+
cancel: "Cancel"
|
|
1639
|
+
};
|
|
1640
|
+
function DuplicateTargetPicker({
|
|
1641
|
+
open,
|
|
1642
|
+
sourceLabel,
|
|
1643
|
+
sourceHasRule,
|
|
1644
|
+
rules,
|
|
1645
|
+
supportsRules,
|
|
1646
|
+
supportsGlobal = true,
|
|
1647
|
+
onCancel,
|
|
1648
|
+
onConfirm,
|
|
1649
|
+
i18n
|
|
1650
|
+
}) {
|
|
1651
|
+
const t = { ...DEFAULT_I18N2, ...i18n ?? {} };
|
|
1652
|
+
const [picked, setPicked] = useState(null);
|
|
1653
|
+
const containerRef = useRef(null);
|
|
1654
|
+
useEffect(() => {
|
|
1655
|
+
if (!open) {
|
|
1656
|
+
setPicked(null);
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1659
|
+
setPicked(sourceHasRule && supportsRules ? { kind: "sameRule" } : supportsGlobal ? { kind: "global" } : null);
|
|
1660
|
+
}, [open, sourceHasRule, supportsRules, supportsGlobal]);
|
|
1661
|
+
useEffect(() => {
|
|
1662
|
+
if (!open) return;
|
|
1663
|
+
const onKey = (e) => {
|
|
1664
|
+
if (e.key === "Escape") {
|
|
1665
|
+
e.preventDefault();
|
|
1666
|
+
onCancel();
|
|
1667
|
+
}
|
|
1668
|
+
};
|
|
1669
|
+
window.addEventListener("keydown", onKey);
|
|
1670
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
1671
|
+
}, [open, onCancel]);
|
|
1672
|
+
const sortedRules = useMemo(() => rules.slice().sort((a, b) => {
|
|
1673
|
+
if (b.count !== a.count) return b.count - a.count;
|
|
1674
|
+
return a.summary.localeCompare(b.summary);
|
|
1675
|
+
}), [rules]);
|
|
1676
|
+
if (!open || typeof document === "undefined") return null;
|
|
1677
|
+
const stop = (e) => {
|
|
1678
|
+
e.stopPropagation();
|
|
1679
|
+
};
|
|
1680
|
+
const isSame = (a, b) => {
|
|
1681
|
+
if (!a || a.kind !== b.kind) return false;
|
|
1682
|
+
if (a.kind === "existingRule" && b.kind === "existingRule") {
|
|
1683
|
+
return a.ruleHash === b.ruleHash;
|
|
1684
|
+
}
|
|
1685
|
+
return true;
|
|
1686
|
+
};
|
|
1687
|
+
const RadioRow = ({
|
|
1688
|
+
target,
|
|
1689
|
+
label,
|
|
1690
|
+
sublabel,
|
|
1691
|
+
icon: Icon
|
|
1692
|
+
}) => {
|
|
1693
|
+
const checked = isSame(picked, target);
|
|
1694
|
+
return /* @__PURE__ */ jsxs(
|
|
1695
|
+
"button",
|
|
1696
|
+
{
|
|
1697
|
+
type: "button",
|
|
1698
|
+
onClick: () => setPicked(target),
|
|
1699
|
+
className: "w-full text-left px-3 py-2 rounded-md border transition-colors flex items-start gap-2.5",
|
|
1700
|
+
style: {
|
|
1701
|
+
borderColor: checked ? "hsl(var(--ra-accent))" : "hsl(var(--ra-border))",
|
|
1702
|
+
background: checked ? "hsl(var(--ra-accent) / 0.08)" : "transparent",
|
|
1703
|
+
color: "hsl(var(--ra-text))"
|
|
1704
|
+
},
|
|
1705
|
+
children: [
|
|
1706
|
+
/* @__PURE__ */ jsx(
|
|
1707
|
+
"span",
|
|
1708
|
+
{
|
|
1709
|
+
className: "mt-0.5 inline-flex items-center justify-center h-4 w-4 rounded-full shrink-0",
|
|
1710
|
+
style: {
|
|
1711
|
+
border: `2px solid ${checked ? "hsl(var(--ra-accent))" : "hsl(var(--ra-border))"}`
|
|
1712
|
+
},
|
|
1713
|
+
children: checked && /* @__PURE__ */ jsx(
|
|
1714
|
+
"span",
|
|
1715
|
+
{
|
|
1716
|
+
className: "h-2 w-2 rounded-full",
|
|
1717
|
+
style: { background: "hsl(var(--ra-accent))" }
|
|
1718
|
+
}
|
|
1719
|
+
)
|
|
1720
|
+
}
|
|
1721
|
+
),
|
|
1722
|
+
Icon && /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4 mt-0.5 shrink-0" }),
|
|
1723
|
+
/* @__PURE__ */ jsxs("span", { className: "min-w-0 flex-1", children: [
|
|
1724
|
+
/* @__PURE__ */ jsx("span", { className: "block text-sm", children: label }),
|
|
1725
|
+
sublabel && /* @__PURE__ */ jsx("span", { className: "block text-xs truncate", style: { color: "hsl(var(--ra-muted-text))" }, children: sublabel })
|
|
1726
|
+
] })
|
|
1727
|
+
]
|
|
1728
|
+
}
|
|
1729
|
+
);
|
|
1730
|
+
};
|
|
1731
|
+
const showRuleSection = supportsRules && (sortedRules.length > 0 || sourceHasRule);
|
|
1732
|
+
return createPortal(
|
|
1733
|
+
/* @__PURE__ */ jsx(
|
|
1734
|
+
"div",
|
|
1735
|
+
{
|
|
1736
|
+
className: "fixed inset-0 z-[1000] flex items-center justify-center",
|
|
1737
|
+
style: { background: "hsl(0 0% 0% / 0.4)" },
|
|
1738
|
+
onClick: onCancel,
|
|
1739
|
+
onMouseDown: stop,
|
|
1740
|
+
onTouchStart: stop,
|
|
1741
|
+
children: /* @__PURE__ */ jsxs(
|
|
1742
|
+
"div",
|
|
1743
|
+
{
|
|
1744
|
+
ref: containerRef,
|
|
1745
|
+
role: "dialog",
|
|
1746
|
+
"aria-modal": "true",
|
|
1747
|
+
className: "w-[min(28rem,calc(100vw-2rem))] max-h-[min(36rem,calc(100vh-2rem))] flex flex-col rounded-lg shadow-xl",
|
|
1748
|
+
style: { background: "hsl(var(--ra-surface))", border: "1px solid hsl(var(--ra-border))" },
|
|
1749
|
+
onClick: stop,
|
|
1750
|
+
onMouseDown: stop,
|
|
1751
|
+
onTouchStart: stop,
|
|
1752
|
+
children: [
|
|
1753
|
+
/* @__PURE__ */ jsx("header", { className: "px-4 py-3 border-b", style: { borderColor: "hsl(var(--ra-border))" }, children: /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold", style: { color: "hsl(var(--ra-text))" }, children: t.title.replace("{name}", sourceLabel) }) }),
|
|
1754
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3", children: [
|
|
1755
|
+
supportsGlobal && /* @__PURE__ */ jsx(RadioRow, { target: { kind: "global" }, label: t.global, icon: Globe2 }),
|
|
1756
|
+
showRuleSection && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1757
|
+
sourceHasRule && /* @__PURE__ */ jsx(RadioRow, { target: { kind: "sameRule" }, label: t.sameRule, icon: Target }),
|
|
1758
|
+
sortedRules.length > 0 && /* @__PURE__ */ jsxs("div", { children: [
|
|
1759
|
+
/* @__PURE__ */ jsx(
|
|
1760
|
+
"div",
|
|
1761
|
+
{
|
|
1762
|
+
className: "text-[10px] uppercase tracking-wide mb-1.5 px-1",
|
|
1763
|
+
style: { color: "hsl(var(--ra-muted-text))" },
|
|
1764
|
+
children: t.existingRules
|
|
1765
|
+
}
|
|
1766
|
+
),
|
|
1767
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: sortedRules.map((r) => /* @__PURE__ */ jsx(
|
|
1768
|
+
RadioRow,
|
|
1769
|
+
{
|
|
1770
|
+
target: { kind: "existingRule", ruleHash: r.hash },
|
|
1771
|
+
label: r.summary,
|
|
1772
|
+
sublabel: `${r.count} record${r.count === 1 ? "" : "s"}`,
|
|
1773
|
+
icon: Target
|
|
1774
|
+
},
|
|
1775
|
+
r.hash
|
|
1776
|
+
)) })
|
|
1777
|
+
] }),
|
|
1778
|
+
/* @__PURE__ */ jsx(RadioRow, { target: { kind: "newRule" }, label: t.newRule, icon: Sparkles })
|
|
1779
|
+
] })
|
|
1780
|
+
] }),
|
|
1781
|
+
/* @__PURE__ */ jsxs(
|
|
1782
|
+
"footer",
|
|
1783
|
+
{
|
|
1784
|
+
className: "px-4 py-3 border-t flex items-center justify-end gap-2",
|
|
1785
|
+
style: { borderColor: "hsl(var(--ra-border))" },
|
|
1786
|
+
children: [
|
|
1787
|
+
/* @__PURE__ */ jsx(
|
|
1788
|
+
"button",
|
|
1789
|
+
{
|
|
1790
|
+
type: "button",
|
|
1791
|
+
onClick: onCancel,
|
|
1792
|
+
className: "text-xs px-3 py-1.5 rounded-md border hover:bg-[hsl(var(--ra-muted))]",
|
|
1793
|
+
style: { borderColor: "hsl(var(--ra-border))", color: "hsl(var(--ra-text))" },
|
|
1794
|
+
children: t.cancel
|
|
1795
|
+
}
|
|
1796
|
+
),
|
|
1797
|
+
/* @__PURE__ */ jsxs(
|
|
1798
|
+
"button",
|
|
1799
|
+
{
|
|
1800
|
+
type: "button",
|
|
1801
|
+
disabled: !picked,
|
|
1802
|
+
onClick: () => {
|
|
1803
|
+
if (picked) onConfirm(picked);
|
|
1804
|
+
},
|
|
1805
|
+
className: "text-xs px-3 py-1.5 rounded-md font-medium transition-opacity disabled:opacity-40 inline-flex items-center gap-1.5",
|
|
1806
|
+
style: { background: "hsl(var(--ra-accent))", color: "hsl(var(--ra-surface))" },
|
|
1807
|
+
children: [
|
|
1808
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
|
|
1809
|
+
t.confirm
|
|
1810
|
+
]
|
|
1811
|
+
}
|
|
1812
|
+
)
|
|
1813
|
+
]
|
|
1814
|
+
}
|
|
1815
|
+
)
|
|
1816
|
+
]
|
|
1817
|
+
}
|
|
1818
|
+
)
|
|
1819
|
+
}
|
|
1820
|
+
),
|
|
1821
|
+
document.body
|
|
1822
|
+
);
|
|
1823
|
+
}
|
|
1624
1824
|
function useShellClipboard(args) {
|
|
1625
1825
|
const {
|
|
1626
1826
|
enabled,
|
|
@@ -1640,7 +1840,11 @@ function useShellClipboard(args) {
|
|
|
1640
1840
|
onLeftSelectRef,
|
|
1641
1841
|
onCreateItemDraftRef,
|
|
1642
1842
|
onCreateRuleFromClipboardRef,
|
|
1643
|
-
isRuleTab
|
|
1843
|
+
isRuleTab,
|
|
1844
|
+
ruleCatalogue,
|
|
1845
|
+
ruleCatalogueRef,
|
|
1846
|
+
supportsRules = true,
|
|
1847
|
+
onCreateItemDraftAtScopeRef
|
|
1644
1848
|
} = args;
|
|
1645
1849
|
const clipboard = useRecordClipboard({
|
|
1646
1850
|
appId,
|
|
@@ -1770,17 +1974,102 @@ function useShellClipboard(args) {
|
|
|
1770
1974
|
// wired item-draft creation. Pushes the editor value to the clipboard
|
|
1771
1975
|
// (so the existing pendingPasteTarget effect lands it on the new
|
|
1772
1976
|
// draft) and mints a fresh draft.
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1977
|
+
// Collection-cardinality clone affordance — opens the Duplicate-To
|
|
1978
|
+
// picker so the admin chooses the destination scope (Global, an
|
|
1979
|
+
// existing rule, a new rule, or the source's own rule). We only show
|
|
1980
|
+
// this when the host wired the at-scope draft creator, since without
|
|
1981
|
+
// it we have nowhere to mint the duplicate.
|
|
1982
|
+
onDuplicate: isCollection && !!onCreateItemDraftAtScopeRef ? () => {
|
|
1983
|
+
if (!editingScope) return;
|
|
1984
|
+
openDuplicatePicker({
|
|
1985
|
+
value: editorCtx.value,
|
|
1986
|
+
scope: editingScope,
|
|
1987
|
+
label: editorHeaderLabel ?? editingScope.raw,
|
|
1988
|
+
facetRule: resolved.facetRule ?? null,
|
|
1989
|
+
recordId: resolved.recordId
|
|
1990
|
+
});
|
|
1781
1991
|
} : void 0
|
|
1782
1992
|
} : void 0;
|
|
1783
1993
|
const [pendingPasteTarget, setPendingPasteTarget] = useState(null);
|
|
1994
|
+
const [pendingSeed, setPendingSeed] = useState(null);
|
|
1995
|
+
const [pickerOpen, setPickerOpen] = useState(false);
|
|
1996
|
+
const pickerSourceRef = useRef(null);
|
|
1997
|
+
const openDuplicatePicker = useCallback((source) => {
|
|
1998
|
+
pickerSourceRef.current = {
|
|
1999
|
+
value: source.value,
|
|
2000
|
+
scope: source.scope,
|
|
2001
|
+
label: source.label,
|
|
2002
|
+
facetRule: source.facetRule ?? null,
|
|
2003
|
+
recordId: source.recordId
|
|
2004
|
+
};
|
|
2005
|
+
setPickerOpen(true);
|
|
2006
|
+
}, []);
|
|
2007
|
+
const onDuplicatePickerCancel = useCallback(() => {
|
|
2008
|
+
setPickerOpen(false);
|
|
2009
|
+
pickerSourceRef.current = null;
|
|
2010
|
+
}, []);
|
|
2011
|
+
const onDuplicatePickerConfirm = useCallback((target) => {
|
|
2012
|
+
const source = pickerSourceRef.current;
|
|
2013
|
+
setPickerOpen(false);
|
|
2014
|
+
pickerSourceRef.current = null;
|
|
2015
|
+
if (!source) return;
|
|
2016
|
+
const transformed = onCopyOverride ? onCopyOverride({ value: source.value, scope: source.scope }) : cloneValue(source.value);
|
|
2017
|
+
if (target.kind === "newRule") {
|
|
2018
|
+
clipboard.set({
|
|
2019
|
+
value: transformed,
|
|
2020
|
+
sourceScope: source.scope,
|
|
2021
|
+
sourceRecordId: source.recordId,
|
|
2022
|
+
sourceLabel: source.label
|
|
2023
|
+
});
|
|
2024
|
+
onTelemetry?.({
|
|
2025
|
+
type: "clipboard.copy",
|
|
2026
|
+
recordType,
|
|
2027
|
+
sourceRef: anchorKey(source.scope)
|
|
2028
|
+
});
|
|
2029
|
+
window.setTimeout(() => onCreateRuleFromClipboardRef?.current?.(), 0);
|
|
2030
|
+
return;
|
|
2031
|
+
}
|
|
2032
|
+
const create = onCreateItemDraftAtScopeRef?.current;
|
|
2033
|
+
if (!create) return;
|
|
2034
|
+
let scopeArg;
|
|
2035
|
+
if (target.kind === "global") {
|
|
2036
|
+
scopeArg = { kind: "global" };
|
|
2037
|
+
} else if (target.kind === "sameRule") {
|
|
2038
|
+
if (!source.facetRule) return;
|
|
2039
|
+
scopeArg = { kind: "rule", rule: source.facetRule };
|
|
2040
|
+
} else {
|
|
2041
|
+
const list = ruleCatalogueRef?.current ?? ruleCatalogue ?? [];
|
|
2042
|
+
const hit = list.find((r) => r.hash === target.ruleHash);
|
|
2043
|
+
if (!hit) return;
|
|
2044
|
+
scopeArg = { kind: "rule", rule: hit.rule };
|
|
2045
|
+
}
|
|
2046
|
+
const newId = create(scopeArg);
|
|
2047
|
+
if (!newId) return;
|
|
2048
|
+
setPendingPasteTarget({ kind: "record", recordId: newId });
|
|
2049
|
+
setPendingSeed({
|
|
2050
|
+
value: transformed,
|
|
2051
|
+
sourceScope: source.scope,
|
|
2052
|
+
sourceLabel: source.label
|
|
2053
|
+
});
|
|
2054
|
+
onTelemetry?.({
|
|
2055
|
+
type: "clipboard.copy",
|
|
2056
|
+
recordType,
|
|
2057
|
+
sourceRef: anchorKey(source.scope)
|
|
2058
|
+
});
|
|
2059
|
+
setNotice({
|
|
2060
|
+
message: i18n.duplicateToast.replace("{name}", source.label),
|
|
2061
|
+
variant: "copy"
|
|
2062
|
+
});
|
|
2063
|
+
}, [
|
|
2064
|
+
onCopyOverride,
|
|
2065
|
+
clipboard,
|
|
2066
|
+
onTelemetry,
|
|
2067
|
+
recordType,
|
|
2068
|
+
onCreateRuleFromClipboardRef,
|
|
2069
|
+
onCreateItemDraftAtScopeRef,
|
|
2070
|
+
ruleCatalogue,
|
|
2071
|
+
i18n.duplicateToast
|
|
2072
|
+
]);
|
|
1784
2073
|
useEffect(() => {
|
|
1785
2074
|
if (!pendingPasteTarget) return;
|
|
1786
2075
|
if (!editingScope) return;
|
|
@@ -1788,14 +2077,31 @@ function useShellClipboard(args) {
|
|
|
1788
2077
|
if (!matched) return;
|
|
1789
2078
|
const t = window.setTimeout(() => {
|
|
1790
2079
|
setPendingPasteTarget(null);
|
|
2080
|
+
if (pendingSeed) {
|
|
2081
|
+
const seed = pendingSeed;
|
|
2082
|
+
setPendingSeed(null);
|
|
2083
|
+
const finalValue = onPasteOverride ? onPasteOverride(
|
|
2084
|
+
{ value: seed.value, sourceScope: seed.sourceScope },
|
|
2085
|
+
{ scope: editingScope, currentValue: null }
|
|
2086
|
+
) ?? seed.value : seed.value;
|
|
2087
|
+
editorCtx.onChange(finalValue);
|
|
2088
|
+
onTelemetry?.({
|
|
2089
|
+
type: "clipboard.paste",
|
|
2090
|
+
recordType,
|
|
2091
|
+
sourceRef: anchorKey(editingScope),
|
|
2092
|
+
destinationRef: editingScope.raw,
|
|
2093
|
+
replaced: false
|
|
2094
|
+
});
|
|
2095
|
+
return;
|
|
2096
|
+
}
|
|
1791
2097
|
void pasteCurrent();
|
|
1792
2098
|
}, 0);
|
|
1793
2099
|
return () => window.clearTimeout(t);
|
|
1794
|
-
}, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent]);
|
|
2100
|
+
}, [pendingPasteTarget, editingScope, isCollection, selectedItemId, selectedRecordId, pasteCurrent, pendingSeed, editorCtx, onTelemetry, recordType, onPasteOverride]);
|
|
1795
2101
|
const rowClipboard = enabled ? (record) => {
|
|
1796
2102
|
const summaryHasData = record.data != null;
|
|
1797
2103
|
const sourceParsed = record.scope;
|
|
1798
|
-
const canDuplicate = summaryHasData && isCollection;
|
|
2104
|
+
const canDuplicate = summaryHasData && isCollection && !!onCreateItemDraftAtScopeRef;
|
|
1799
2105
|
const canCopyAndNewRule = summaryHasData && !isCollection && !!isRuleTab && !!onCreateRuleFromClipboardRef;
|
|
1800
2106
|
return {
|
|
1801
2107
|
onCopy: summaryHasData ? () => {
|
|
@@ -1826,24 +2132,12 @@ function useShellClipboard(args) {
|
|
|
1826
2132
|
}, 0);
|
|
1827
2133
|
} : void 0,
|
|
1828
2134
|
onDuplicate: canDuplicate ? () => {
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
});
|
|
1836
|
-
onTelemetry?.({ type: "clipboard.copy", recordType, sourceRef: anchorKey(record.scope) });
|
|
1837
|
-
onLeftSelectRef.current?.(record);
|
|
1838
|
-
const create = onCreateItemDraftRef?.current;
|
|
1839
|
-
if (!create) return;
|
|
1840
|
-
window.setTimeout(() => {
|
|
1841
|
-
const newId = create();
|
|
1842
|
-
if (newId) setPendingPasteTarget({ kind: "record", recordId: newId });
|
|
1843
|
-
}, 0);
|
|
1844
|
-
setNotice({
|
|
1845
|
-
message: i18n.duplicateToast.replace("{name}", record.label),
|
|
1846
|
-
variant: "copy"
|
|
2135
|
+
openDuplicatePicker({
|
|
2136
|
+
value: record.data,
|
|
2137
|
+
scope: sourceParsed,
|
|
2138
|
+
label: record.label,
|
|
2139
|
+
facetRule: record.facetRule ?? null,
|
|
2140
|
+
recordId: record.id ?? void 0
|
|
1847
2141
|
});
|
|
1848
2142
|
} : void 0
|
|
1849
2143
|
};
|
|
@@ -1856,10 +2150,36 @@ function useShellClipboard(args) {
|
|
|
1856
2150
|
onDismiss: () => setNotice(null)
|
|
1857
2151
|
}
|
|
1858
2152
|
) : null;
|
|
2153
|
+
const duplicatePicker = enabled ? /* @__PURE__ */ jsx(
|
|
2154
|
+
DuplicateTargetPicker,
|
|
2155
|
+
{
|
|
2156
|
+
open: pickerOpen,
|
|
2157
|
+
sourceLabel: pickerSourceRef.current?.label ?? "",
|
|
2158
|
+
sourceHasRule: !!pickerSourceRef.current?.facetRule,
|
|
2159
|
+
rules: (ruleCatalogueRef?.current ?? ruleCatalogue ?? []).map((r) => ({
|
|
2160
|
+
hash: r.hash,
|
|
2161
|
+
summary: r.summary,
|
|
2162
|
+
count: r.count
|
|
2163
|
+
})),
|
|
2164
|
+
supportsRules,
|
|
2165
|
+
onCancel: onDuplicatePickerCancel,
|
|
2166
|
+
onConfirm: onDuplicatePickerConfirm,
|
|
2167
|
+
i18n: {
|
|
2168
|
+
title: i18n.duplicateDialogTitle,
|
|
2169
|
+
global: i18n.duplicateTargetGlobal,
|
|
2170
|
+
sameRule: i18n.duplicateTargetSameRule,
|
|
2171
|
+
existingRules: i18n.duplicateTargetExistingRules,
|
|
2172
|
+
newRule: i18n.duplicateTargetNewRule,
|
|
2173
|
+
confirm: i18n.duplicateConfirm,
|
|
2174
|
+
cancel: i18n.duplicateCancel
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
) : null;
|
|
1859
2178
|
return {
|
|
1860
2179
|
editorClipboard,
|
|
1861
2180
|
rowClipboard,
|
|
1862
2181
|
confirmDialog: pasteConfirm.dialog,
|
|
2182
|
+
duplicatePicker,
|
|
1863
2183
|
toast,
|
|
1864
2184
|
store: clipboard
|
|
1865
2185
|
};
|
|
@@ -9178,6 +9498,8 @@ function RecordsAdminShellInner(props) {
|
|
|
9178
9498
|
]);
|
|
9179
9499
|
const onLeftSelectRef = useRef(null);
|
|
9180
9500
|
const onCreateItemDraftRef = useRef(null);
|
|
9501
|
+
const onCreateItemDraftAtScopeRef = useRef(null);
|
|
9502
|
+
const ruleCatalogueRef = useRef([]);
|
|
9181
9503
|
const onCreateRuleFromClipboardRef = useRef(null);
|
|
9182
9504
|
const previewReopenAnchorRef = useRef(null);
|
|
9183
9505
|
const { runWithGuard } = useDirtyNavigation({
|
|
@@ -9210,7 +9532,7 @@ function RecordsAdminShellInner(props) {
|
|
|
9210
9532
|
setDrawerOpen,
|
|
9211
9533
|
sidePreviewOpen,
|
|
9212
9534
|
setSidePreviewOpen,
|
|
9213
|
-
editorHeaderLabel,
|
|
9535
|
+
editorHeaderLabel: editorHeaderLabelRaw,
|
|
9214
9536
|
editorHeaderSubtitle,
|
|
9215
9537
|
editorHeaderMeta
|
|
9216
9538
|
} = useShellPreviewPane({
|
|
@@ -9219,6 +9541,14 @@ function RecordsAdminShellInner(props) {
|
|
|
9219
9541
|
selectedProductId,
|
|
9220
9542
|
productBrowseItems: productLookupItems
|
|
9221
9543
|
});
|
|
9544
|
+
const editorHeaderLabel = useMemo(() => {
|
|
9545
|
+
if (editorHeaderLabelRaw) return editorHeaderLabelRaw;
|
|
9546
|
+
if (isCollection && selectedItemId && !isDraftId3(selectedItemId)) {
|
|
9547
|
+
const hit = collectionItems.items.find((r) => r.id === selectedItemId || r.itemId === selectedItemId);
|
|
9548
|
+
if (hit?.label) return hit.label;
|
|
9549
|
+
}
|
|
9550
|
+
return void 0;
|
|
9551
|
+
}, [editorHeaderLabelRaw, isCollection, selectedItemId, collectionItems.items]);
|
|
9222
9552
|
const shellClipboard = useShellClipboard({
|
|
9223
9553
|
enabled: !!enableClipboard,
|
|
9224
9554
|
appId,
|
|
@@ -9230,14 +9560,17 @@ function RecordsAdminShellInner(props) {
|
|
|
9230
9560
|
isCollection,
|
|
9231
9561
|
selectedItemId,
|
|
9232
9562
|
selectedRecordId,
|
|
9233
|
-
resolved: { source: resolved.source, recordId: resolved.recordId },
|
|
9563
|
+
resolved: { source: resolved.source, recordId: resolved.recordId, facetRule: resolved.facetRule ?? null },
|
|
9234
9564
|
onTelemetry,
|
|
9235
9565
|
onCopyOverride,
|
|
9236
9566
|
onPasteOverride,
|
|
9237
9567
|
onLeftSelectRef,
|
|
9238
9568
|
onCreateItemDraftRef,
|
|
9239
9569
|
onCreateRuleFromClipboardRef,
|
|
9240
|
-
isRuleTab: activeScope === "rule" || activeScope === "collection"
|
|
9570
|
+
isRuleTab: activeScope === "rule" || activeScope === "collection",
|
|
9571
|
+
ruleCatalogueRef,
|
|
9572
|
+
supportsRules: effectiveTopLevelScopes.includes("rule"),
|
|
9573
|
+
onCreateItemDraftAtScopeRef
|
|
9241
9574
|
});
|
|
9242
9575
|
const editorClipboard = shellClipboard.editorClipboard;
|
|
9243
9576
|
const rowClipboard = shellClipboard.rowClipboard;
|
|
@@ -9736,6 +10069,10 @@ function RecordsAdminShellInner(props) {
|
|
|
9736
10069
|
const onRuleWizardNext = useCallback(() => {
|
|
9737
10070
|
if (cardinality === "collection") {
|
|
9738
10071
|
setRuleWizardStep(2);
|
|
10072
|
+
if (ruleWizardSeedMode) {
|
|
10073
|
+
const id = coerceDraftItemId2(generateItemId);
|
|
10074
|
+
setSelectedItemId(id);
|
|
10075
|
+
}
|
|
9739
10076
|
} else {
|
|
9740
10077
|
setRuleWizardStep(2);
|
|
9741
10078
|
if (ruleWizardSeedMode) {
|
|
@@ -9743,7 +10080,7 @@ function RecordsAdminShellInner(props) {
|
|
|
9743
10080
|
setSelectedRecordId(DRAFT_ID3);
|
|
9744
10081
|
}
|
|
9745
10082
|
}
|
|
9746
|
-
}, [cardinality, ruleWizardSeedMode, mintRuleWizardDraftKey]);
|
|
10083
|
+
}, [cardinality, ruleWizardSeedMode, mintRuleWizardDraftKey, generateItemId]);
|
|
9747
10084
|
const startRuleWizardDraft = useCallback((seed) => {
|
|
9748
10085
|
setRuleWizardSeedMode(seed);
|
|
9749
10086
|
setRuleWizardDraftKey(mintRuleWizardDraftKey());
|
|
@@ -10019,6 +10356,26 @@ function RecordsAdminShellInner(props) {
|
|
|
10019
10356
|
onLeftSelectRef.current = onLeftSelect;
|
|
10020
10357
|
onCreateItemDraftRef.current = onItemCreate;
|
|
10021
10358
|
onCreateRuleFromClipboardRef.current = () => onCreateRule("paste");
|
|
10359
|
+
ruleCatalogueRef.current = ruleCatalogue;
|
|
10360
|
+
onCreateItemDraftAtScopeRef.current = (target) => {
|
|
10361
|
+
if (!isCollection) return null;
|
|
10362
|
+
if (target.kind === "global") {
|
|
10363
|
+
if (activeScope !== "collection") setActiveScope("collection");
|
|
10364
|
+
setSelectedRecordId(null);
|
|
10365
|
+
setDraftKind(null);
|
|
10366
|
+
const id2 = onItemCreate();
|
|
10367
|
+
return id2 ?? null;
|
|
10368
|
+
}
|
|
10369
|
+
if (activeScope !== "rule") setActiveScope("rule");
|
|
10370
|
+
setRuleWizardRule(target.rule);
|
|
10371
|
+
setRuleWizardSeedMode(null);
|
|
10372
|
+
setRuleWizardDraftKey(null);
|
|
10373
|
+
setRuleWizardStep(2);
|
|
10374
|
+
setDraftKind("rule");
|
|
10375
|
+
setSelectedRecordId(null);
|
|
10376
|
+
const id = onItemCreate();
|
|
10377
|
+
return id ?? null;
|
|
10378
|
+
};
|
|
10022
10379
|
return /* @__PURE__ */ jsx(RuleLabelLookupProvider, { value: ruleLabelLookup, children: /* @__PURE__ */ jsxs(
|
|
10023
10380
|
"div",
|
|
10024
10381
|
{
|
|
@@ -10027,6 +10384,7 @@ function RecordsAdminShellInner(props) {
|
|
|
10027
10384
|
children: [
|
|
10028
10385
|
dirtyConfirm.dialog,
|
|
10029
10386
|
shellClipboard.confirmDialog,
|
|
10387
|
+
shellClipboard.duplicatePicker,
|
|
10030
10388
|
shellClipboard.toast,
|
|
10031
10389
|
(() => {
|
|
10032
10390
|
const showFloatHelp = !!intro && dismissed && resolvedReopenAffordance === "footer" && !headerWillRender;
|