synapse-react-client
Version:
[](https://badge.fury.io/js/synapse-react-client) [](https://github.com/prettier/prettie
563 lines (562 loc) • 17.4 kB
JavaScript
import { jsxs as C, jsx as i } from "react/jsx-runtime";
import { WideButton as ve } from "../../styled/WideButton.js";
import { datasetItemToReference as De } from "./DatasetEditorUtils.js";
import "../../../synapse-client/SynapseClient.js";
import "@sage-bionetworks/synapse-client/generated/models/ErrorResponseCode";
import "@sage-bionetworks/synapse-client/generated/models/TwoFactorAuthErrorResponse";
import "@sage-bionetworks/synapse-client/util/SynapseClientError";
import "@sage-bionetworks/synapse-types";
import { entityTypeToFriendlyName as P, convertToEntityType as _e, isDataset as M, isDatasetCollection as Q } from "../../../utils/functions/EntityTypeUtils.js";
import "../../../utils/SynapseConstants.js";
import { isEqual as q, upperFirst as J } from "lodash-es";
import "@sage-bionetworks/synapse-client/util/synapseClientFetch";
import "@tanstack/react-query";
import "../../../utils/PermissionLevelToAccessType.js";
import { useState as S, useMemo as g, useEffect as w, useRef as Oe } from "react";
import "../../../utils/context/SynapseContext.js";
import "use-deep-compare-effect";
import { Typography as K, Skeleton as Ae, Button as A, Alert as Re, Tooltip as be, Checkbox as $e } from "@mui/material";
import { useSet as ze } from "../../../utils/hooks/useSet.js";
import "../../../utils/hooks/useCookiePreferences.js";
import "../../../utils/hooks/useSourceAppConfigs.js";
import "universal-cookie";
import "../../../utils/AppUtils/session/ApplicationSessionContext.js";
import "../../../utils/context/FullContextProvider.js";
import "../../../utils/context/DocumentMetadataContext.js";
import { BlockingLoader as Pe } from "../../LoadingScreen/LoadingScreen.js";
import "@tanstack/query-core";
import "lodash-es/isEmpty";
import "lodash-es/isEqual";
import "lodash-es/xorWith";
import "react-router";
import { EntityType as $ } from "@sage-bionetworks/synapse-client";
import "../../../utils/types/IsType.js";
import { useGetEntity as we, useGetEntityPath as Me, useUpdateEntity as Fe } from "../../../synapse-queries/entity/useEntity.js";
import Ve from "@mui/icons-material/AddCircleTwoTone";
import { useReactTable as ke, getCoreRowModel as xe, createColumnHelper as Be } from "@tanstack/react-table";
import { useVirtualizer as He } from "@tanstack/react-virtual";
import f from "pluralize";
import { CreatedOnCell as Le } from "../../EntityFinder/details/view/table/CreatedOnCell.js";
import { EntityBadgeIconsCell as Ue } from "../../EntityFinder/details/view/table/EntityBadgeIconsCell.js";
import { EntityNameCell as Ye } from "../../EntityFinder/details/view/table/EntityNameCell.js";
import { ModifiedByCell as Ge } from "../../EntityFinder/details/view/table/ModifiedByCell.js";
import { ModifiedOnCell as je } from "../../EntityFinder/details/view/table/ModifiedOnCell.js";
import { ParentProjectCell as We } from "../../EntityFinder/details/view/table/ParentProjectCell.js";
import { EntityFinderModal as Ze } from "../../EntityFinder/EntityFinderModal.js";
import { FinderScope as qe } from "../../EntityFinder/tree/EntityTree.js";
import { VersionSelectionType as Je } from "../../EntityFinder/VersionSelectionType.js";
import "@mui/material/Skeleton";
import { SkeletonTable as Qe } from "../../Skeleton/SkeletonTable.js";
import { WarningDialog as Ke } from "../../SynapseForm/WarningDialog.js";
import E from "../../TanStackTable/ColumnHeader.js";
import Xe from "../../TanStackTable/StyledVirtualTanStackTable.js";
import { displayToast as R } from "../../ToastMessage/ToastMessage.js";
import { DatasetEditorCheckboxCell as et } from "./DatasetEditorCheckboxCell.js";
import { DatasetEditorVersionCell as tt } from "./DatasetEditorVersionCell.js";
import { EntityFetchErrorCell as ot } from "./EntityFetchErrorCell.js";
const u = Be();
function it(n) {
const {
datasetToUpdate: o,
selectedIds: t,
clearSelectedIds: a,
addSelectedId: l,
allItemsAreSelected: m,
changeVersionOnItem: d
} = n;
return [
u.display({
id: "errorState",
minSize: 35,
maxSize: 35,
size: 35,
enableResizing: !1,
header: () => null,
cell: ot
}),
u.display({
id: "isSelected",
minSize: 40,
maxSize: 40,
size: 40,
enableResizing: !1,
header: () => /* @__PURE__ */ i(
at,
{
datasetToUpdate: o,
selectedIds: t,
clearSelectedIds: a,
addSelectedId: l,
allItemsAreSelected: m
}
),
cell: et
}),
u.display({
id: "name",
minSize: 50,
size: 300,
header: (r) => /* @__PURE__ */ i(E, { ...r, title: "Name" }),
cell: Ye
}),
u.accessor("entityId", {
minSize: 50,
size: 130,
header: (r) => /* @__PURE__ */ i(E, { ...r, title: "ID" }),
enableColumnFilter: !1
}),
u.display({
id: "badges",
minSize: 80,
size: 80,
enableResizing: !0,
cell: Ue
}),
u.display({
id: "version",
minSize: 150,
size: 150,
header: (r) => /* @__PURE__ */ i(E, { ...r, title: "Version" }),
cell: (r) => /* @__PURE__ */ i(
tt,
{
...r,
toggleSelection: (h) => {
d(h.entityId, h.versionNumber);
}
}
)
}),
u.display({
id: "createdOn",
header: (r) => /* @__PURE__ */ i(E, { ...r, title: "Created On" }),
size: 220,
minSize: 170,
cell: Le
}),
u.display({
id: "modifieddOn",
header: (r) => /* @__PURE__ */ i(E, { ...r, title: "Modified On" }),
size: 220,
minSize: 170,
cell: je
}),
u.display({
id: "modifiedBy",
header: (r) => /* @__PURE__ */ i(E, { ...r, title: "Modified By" }),
size: 250,
enableResizing: !0,
cell: Ge
}),
u.display({
id: "project",
header: (r) => /* @__PURE__ */ i(E, { ...r, title: "Size" }),
size: 300,
cell: We
})
];
}
function nt(n) {
return M(n) ? [$.file] : Q(n) ? [$.dataset] : (console.error(
"Cannot determine selectable types for entity type: " + n.concreteType
), []);
}
function rt(n) {
const o = n ? P(_e(n.concreteType)) : "Collection";
let t = "Item", a = "Current Version";
n && M(n) ? (t = P($.file), a = "Draft") : n && Q(n) && (t = P($.dataset));
const l = n && M(n) ? `Use the left pane to browse projects and folders. Select ${f(
t
)} from the right pane to add to this ${o}. ${J(
f(t)
)} in a ${o} can be added from multiple folders. You can also use Search to find and select ${f(
t
)}.` : `Use the left pane to browse projects. Select ${f(
t
)} from the right pane to add to this ${o}. ${J(
f(t)
)} in a ${o} can be added from multiple projects. You can also use Search to find and select ${f(
t
)}.`;
return {
ADD_ITEMS: `Add ${f(t)}`,
ADD_ITEMS_TO: `Add ${f(t)} to ${o}`,
REMOVE_ITEMS: `Remove ${f(t)}`,
NO_ITEMS_IN_THIS_DATASET: `No ${f(
t
)} in this ${o}`,
SAVE_TO_CONTINUE: `Save the ${o} to continue`,
CREATE_VERSION_TO_FREEZE: `Create a Version of this ${o} to freeze it in its current state`,
ENTITY_SAVED: `${o} Saved`,
SAVE_CHANGES: `Save changes to ${a}`,
ENTITY_FINDER_POPOVER: l,
ENTITY_FINDER_PROMPT: `Find ${f(
t
)} to add to the ${o}.`,
PRECONDITION_FAILED_MESSAGE: `Re-retrieve the ${o} to get the latest changes. Your current changes will be lost.`,
PRECONDITION_FAILED_TITLE: `${o} updated since last fetched`,
PRECONDITION_FAILED_ACTION: `Retrieve ${o}`,
NO_CHANGES_MADE: `You have not made any changes to the ${o}.`
};
}
function X(n, o) {
const t = n.filter(
(d) => !o.find((r) => r.entityId === d.entityId)
), a = [...t], { updatedItems: l, newItems: m } = o.reduce(
(d, r) => {
const h = n.find((e) => e.entityId === r.entityId);
return h ? r.versionNumber !== h.versionNumber ? d.updatedItems.push(r) : t.push(r) : d.newItems.push(r), d;
},
{ updatedItems: [], newItems: [] }
);
return { unchangedItems: t, updatedItems: l, newItems: m, deletedItems: a };
}
function st(n, o) {
const { updatedItems: t, newItems: a, deletedItems: l } = X(
n?.items ?? [],
o?.items
);
let m = "";
return l.length > 0 ? m += `${l.length} Item${l.length === 1 ? "" : "s"} removed` : (m += `${a.length} Item${a.length === 1 ? "" : "s"} added`, t.length > 0 && (m += ` and ${t.length} Item${t.length === 1 ? "" : "s"} updated`)), m;
}
const b = 48, at = (n) => {
const {
datasetToUpdate: o,
clearSelectedIds: t,
allItemsAreSelected: a,
addSelectedId: l
} = n, m = a;
return /* @__PURE__ */ i(
$e,
{
inputProps: { "aria-label": "Select All" },
checked: m,
disabled: o.items.length === 0,
onChange: () => {
m ? t() : l(...o.items.map((d) => d.entityId));
}
}
);
};
function lt(n) {
const { titleCopy: o, buttonCopy: t, onButtonClick: a } = n;
return /* @__PURE__ */ C("div", { className: "NoItemsPlaceholder", children: [
/* @__PURE__ */ i(K, { variant: "headline3", children: o }),
/* @__PURE__ */ i(
ve,
{
variant: "contained",
color: "primary",
onClick: a,
startIcon: /* @__PURE__ */ i(Ve, {}),
sx: { mt: 2 },
children: t
}
)
] });
}
function po(n) {
const { entityId: o, onSave: t, onClose: a, onUnsavedChangesChange: l } = n, [m, d] = S(!1), [r, h] = S(!1), [e, y] = S(), [z, ee] = S(), [N, F] = S(), { data: c, refetch: V } = we(o, void 0, {
staleTime: 1 / 0
}), T = !!(c && e && !q(c, e)), {
ADD_ITEMS: k,
ADD_ITEMS_TO: te,
REMOVE_ITEMS: oe,
NO_ITEMS_IN_THIS_DATASET: ie,
SAVE_TO_CONTINUE: ne,
CREATE_VERSION_TO_FREEZE: re,
ENTITY_SAVED: se,
SAVE_CHANGES: ae,
PRECONDITION_FAILED_TITLE: le,
PRECONDITION_FAILED_MESSAGE: de,
PRECONDITION_FAILED_ACTION: ce,
ENTITY_FINDER_POPOVER: me,
ENTITY_FINDER_PROMPT: pe,
NO_CHANGES_MADE: fe
} = g(() => rt(c), [c]);
w(() => {
c && (c.items == null && (c.items = []), y(c));
}, [c]);
const {
set: I,
add: v,
remove: x,
clear: D
} = ze(), B = !!(e && e.items.length === I.size);
w(() => {
e && c && l && l(T);
}, [
c,
e,
T,
l
]);
const { data: ue } = Me(o), H = ue?.path[1]?.id, { mutate: L, isPending: U } = Fe({
onMutate: () => {
N && (N.close(), F(void 0));
},
onSuccess: () => {
V(), t ? t() : R(re, "success", {
title: se
});
},
onError: (s) => {
s.status === 412 ? R(de, "warning", {
title: le,
primaryButtonConfig: {
text: ce,
onClick: () => {
V();
}
}
}) : R(s.reason, "danger", {
title: "An Error Occurred"
});
}
});
w(() => {
if (z && e && !q(z.items, e.items) && !U) {
const s = st(
z,
e
);
N && N.close();
const p = R(ne, "info", {
title: s,
primaryButtonConfig: {
text: ae,
onClick: () => L(e)
}
});
F({ close: p });
}
ee(e);
}, [e]);
const Y = g(
() => e?.items.map(
(s) => ({
...s,
isSelected: I.has(s.entityId),
setSelected: (p) => p ? v(s.entityId) : x(s.entityId)
})
) ?? [],
[v, e?.items, x, I]
);
function he(s) {
y((p) => {
if (p) {
const _ = s.map((Z) => ({
entityId: Z.targetId,
versionNumber: Z.targetVersionNumber
})), { unchangedItems: O, updatedItems: j, newItems: W } = X(
p.items,
_
);
if (j.length == 0 && W.length == 0)
return p;
const Ne = [...O, ...j, ...W];
return {
...p,
items: Ne
};
} else
return console.warn(
"Cannot add items to the Collection because it is undefined. The Collection may not have been fetched yet."
), p;
}), D();
}
function Ie() {
y((s) => ({
...s,
items: s.items.filter(
(p) => !I.has(p.entityId)
)
})), D();
}
function Ee(s, p) {
y((_) => ({
..._,
items: _.items.map(
(O) => O.entityId === s ? { entityId: s, versionNumber: p } : O
)
}));
}
const Te = g(() => {
if (c)
return nt(c);
}, [c]), Ce = g(() => e ? it({
datasetToUpdate: e,
selectedIds: I,
clearSelectedIds: D,
addSelectedId: v,
allItemsAreSelected: B,
changeVersionOnItem: Ee
}) : [], [
v,
B,
D,
e,
I
]), Se = ke({
data: Y,
columns: Ce,
getCoreRowModel: xe(),
columnResizeMode: "onChange",
// There are no backend sort controls. The server only provides ID/version, and a dataset could be up to 10k items,
// so client side sorting is non-trivial. We may consider fetching all entity headers upon user request.
enableSorting: !1
}), G = Oe(null), ge = He({
count: Y?.length ?? 0,
estimateSize: () => b,
// estimate row height for accurate scrollbar dragging
getScrollElement: () => G.current,
// measure dynamic row height, except in firefox because it measures table border height incorrectly
measureElement: typeof window < "u" && navigator.userAgent.indexOf("Firefox") === -1 ? (s) => s?.getBoundingClientRect().height : void 0,
overscan: 5
}), ye = g(
() => e?.items.map(De),
[e]
);
return /* @__PURE__ */ C("div", { className: "DatasetEditor", children: [
/* @__PURE__ */ i(
Ze,
{
initialSelected: ye,
configuration: {
projectId: H,
selectMultiple: !0,
initialScope: qe.CURRENT_PROJECT,
initialContainer: H ?? null,
selectableTypes: Te,
versionSelection: Je.REQUIRED
},
titleHelpPopoverProps: {
markdownText: me,
helpUrl: "https://help.synapse.org/docs/Datasets.2611281979.html",
placement: "right"
},
promptCopy: pe,
show: m,
title: te,
confirmButtonCopy: "Apply changes",
onConfirm: (s) => {
he(s), d(!1);
},
onCancel: () => d(!1)
}
),
/* @__PURE__ */ i(
Ke,
{
title: "Unsaved Changes",
content: "Any unsaved changes will be lost. Are you sure you want to close the editor?",
confirmButtonText: "Close Editor",
onConfirm: () => {
a && (h(!1), l && l(!1), a());
},
open: r,
onConfirmCallbackArgs: [],
onCancel: () => h(!1)
}
),
/* @__PURE__ */ C("div", { className: "DatasetEditorTopBottomPanel", children: [
/* @__PURE__ */ i(Pe, { show: U }),
/* @__PURE__ */ i("div", { className: "ItemCount", children: e ? /* @__PURE__ */ C(K, { variant: "headline3", children: [
e.items.length === 0 ? "No" : e.items.length.toLocaleString(),
" ",
"File",
e.items.length !== 1 && "s"
] }) : /* @__PURE__ */ i(Ae, { variant: "rectangular", width: 200 }) }),
/* @__PURE__ */ i(
A,
{
variant: "contained",
color: "primary",
disabled: e == null,
onClick: () => d(!0),
children: k
}
),
/* @__PURE__ */ i(
A,
{
disabled: I.size === 0,
variant: "outlined",
color: "primary",
onClick: Ie,
children: oe
}
)
] }),
/* @__PURE__ */ C("div", { className: "DatasetEditorTableContainer", children: [
e && e.items.length === 0 && /* @__PURE__ */ i(
lt,
{
titleCopy: ie,
buttonCopy: k,
onButtonClick: () => d(!0)
}
),
e && e.items.length > 0 && /* @__PURE__ */ i(
Xe,
{
styledTableContainerProps: {
className: "DatasetEditorTable",
ref: G,
height: "350px"
},
table: Se,
rowVirtualizer: ge,
slotProps: {
Tr: {
className: "DatasetEditorRow",
style: {
height: `${b}px`,
maxHeight: `${b}px`
}
}
}
}
),
!e && /* @__PURE__ */ i(
Qe,
{
className: "DatasetItemsEditorSkeleton",
numRows: 8,
numCols: 6,
rowHeight: `${b}px`
}
)
] }),
/* @__PURE__ */ C("div", { className: "DatasetEditorTopBottomPanel", children: [
T && /* @__PURE__ */ i(Re, { severity: "warning", children: "You have unsaved changes" }),
/* @__PURE__ */ i(
A,
{
variant: "outlined",
color: "primary",
onClick: () => {
T ? h(!0) : a && a();
},
children: "Cancel"
}
),
/* @__PURE__ */ i(be, { title: !T && fe, children: /* @__PURE__ */ i("div", { children: /* @__PURE__ */ i(
A,
{
disabled: !e || !T,
variant: "contained",
color: "primary",
onClick: () => L(e),
children: "Save"
}
) }) })
] })
] });
}
export {
po as DatasetItemsEditor,
po as default,
rt as getCopy
};
//# sourceMappingURL=DatasetItemsEditor.js.map