UNPKG

synapse-react-client

Version:

[![npm version](https://badge.fury.io/js/synapse-react-client.svg)](https://badge.fury.io/js/synapse-react-client) [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square)](https://github.com/prettier/prettie

563 lines (562 loc) 17.4 kB
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