synapse-react-client 4.0.23 → 4.0.25
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/SWC.index.d.ts +1 -0
- package/dist/SWC.index.d.ts.map +1 -1
- package/dist/SWC.index.js +2 -1
- package/dist/SWC.index.js.map +1 -1
- package/dist/components/AccessRequirementList/ManagedACTAccessRequirementRequestFlow/DataAccessRequestAccessorsFilesForm/DataAccessRequestAccessorsFilesForm.js.map +1 -1
- package/dist/components/AclEditor/useUpdateAcl.js.map +1 -1
- package/dist/components/Authentication/TwoFactorBackupCodes.js.map +1 -1
- package/dist/components/Authentication/TwoFactorEnrollmentForm.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +3 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +43 -30
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/CannedRejectionDialog/CannedRejectionDialog.js.map +1 -1
- package/dist/components/ChallengeSubmission/SubmissionDirectoryList.js.map +1 -1
- package/dist/components/CopyToClipboardString/CopyToClipboardString.d.ts +7 -1
- package/dist/components/CopyToClipboardString/CopyToClipboardString.d.ts.map +1 -1
- package/dist/components/CopyToClipboardString/CopyToClipboardString.js +22 -16
- package/dist/components/CopyToClipboardString/CopyToClipboardString.js.map +1 -1
- package/dist/components/EntityCitation/EntityCitation.d.ts.map +1 -1
- package/dist/components/EntityCitation/EntityCitation.js +28 -26
- package/dist/components/EntityCitation/EntityCitation.js.map +1 -1
- package/dist/components/EntityFinder/tree/EntityTree.js.map +1 -1
- package/dist/components/EntityFinder/tree/VirtualizedTree.js.map +1 -1
- package/dist/components/EntityIcon.d.ts +1 -0
- package/dist/components/EntityIcon.d.ts.map +1 -1
- package/dist/components/EntityIcon.js +37 -36
- package/dist/components/EntityIcon.js.map +1 -1
- package/dist/components/EntitySidebar/EntitySidebar.css +1 -0
- package/dist/components/EntitySidebar/EntitySidebar.d.ts +7 -0
- package/dist/components/EntitySidebar/EntitySidebar.d.ts.map +1 -0
- package/dist/components/EntitySidebar/EntitySidebar.js +62 -0
- package/dist/components/EntitySidebar/EntitySidebar.js.map +1 -0
- package/dist/components/EntitySidebar/EntitySidebar.module.js +17 -0
- package/dist/components/EntitySidebar/EntitySidebar.module.js.map +1 -0
- package/dist/components/EntitySidebar/EntitySidebar.module.scss +87 -0
- package/dist/components/Evaluation/EvaluationRoundEditorList.js.map +1 -1
- package/dist/components/GenericCard/BioregistryRules.js.map +1 -1
- package/dist/components/IconSvg/IconSvg.d.ts +2 -1
- package/dist/components/IconSvg/IconSvg.d.ts.map +1 -1
- package/dist/components/IconSvg/IconSvg.js +112 -89
- package/dist/components/IconSvg/IconSvg.js.map +1 -1
- package/dist/components/Plot/SynapsePlot.d.ts +4 -0
- package/dist/components/Plot/SynapsePlot.d.ts.map +1 -1
- package/dist/components/Plot/SynapsePlot.js +57 -44
- package/dist/components/Plot/SynapsePlot.js.map +1 -1
- package/dist/components/Plot/UpsetPlot.js.map +1 -1
- package/dist/components/QueryWrapperPlotNav/QueryWrapperSynapsePlot.d.ts.map +1 -1
- package/dist/components/QueryWrapperPlotNav/QueryWrapperSynapsePlot.js +77 -34
- package/dist/components/QueryWrapperPlotNav/QueryWrapperSynapsePlot.js.map +1 -1
- package/dist/components/SchemaDrivenAnnotationEditor/field/AdditionalPropertiesSchemaField.js.map +1 -1
- package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.d.ts.map +1 -1
- package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.js +1 -0
- package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.js.map +1 -1
- package/dist/components/StorybookComponentWrapper.js.map +1 -1
- package/dist/components/SynapseTable/TablePagination.js.map +1 -1
- package/dist/components/SynapseTable/datasets/DatasetItemsEditor.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/TableColumnSchemaForm.js.map +1 -1
- package/dist/components/TanStackTable/StyledTanStackTable.js.map +1 -1
- package/dist/components/entity/page/action_menu/EntityActionMenu.d.ts +2 -0
- package/dist/components/entity/page/action_menu/EntityActionMenu.d.ts.map +1 -1
- package/dist/components/entity/page/action_menu/EntityActionMenu.js +46 -27
- package/dist/components/entity/page/action_menu/EntityActionMenu.js.map +1 -1
- package/dist/components/entity/page/breadcrumbs/EntityPageBreadcrumbs.d.ts +1 -8
- package/dist/components/entity/page/breadcrumbs/EntityPageBreadcrumbs.d.ts.map +1 -1
- package/dist/components/entity/page/breadcrumbs/EntityPageBreadcrumbs.js +17 -13
- package/dist/components/entity/page/breadcrumbs/EntityPageBreadcrumbs.js.map +1 -1
- package/dist/components/entity/page/title_bar/EntityPageTitleBar.d.ts +1 -0
- package/dist/components/entity/page/title_bar/EntityPageTitleBar.d.ts.map +1 -1
- package/dist/components/entity/page/title_bar/EntityPageTitleBar.js +35 -22
- package/dist/components/entity/page/title_bar/EntityPageTitleBar.js.map +1 -1
- package/dist/components/entity/page/title_bar/EntityTitleBarVersionInfo.d.ts.map +1 -1
- package/dist/components/entity/page/title_bar/EntityTitleBarVersionInfo.js +34 -20
- package/dist/components/entity/page/title_bar/EntityTitleBarVersionInfo.js.map +1 -1
- package/dist/components/entity/page/title_bar/TitleBarProperties.d.ts +0 -1
- package/dist/components/entity/page/title_bar/TitleBarProperties.d.ts.map +1 -1
- package/dist/components/entity/page/title_bar/TitleBarProperties.js +17 -82
- package/dist/components/entity/page/title_bar/TitleBarProperties.js.map +1 -1
- package/dist/components/entity/page/title_bar/useGetEntityTitleBarProperties.d.ts.map +1 -1
- package/dist/components/entity/page/title_bar/useGetEntityTitleBarProperties.js +3 -4
- package/dist/components/entity/page/title_bar/useGetEntityTitleBarProperties.js.map +1 -1
- package/dist/components/favorites/FavoriteButton.d.ts +1 -0
- package/dist/components/favorites/FavoriteButton.d.ts.map +1 -1
- package/dist/components/favorites/FavoriteButton.js +8 -8
- package/dist/components/favorites/FavoriteButton.js.map +1 -1
- package/dist/components/menu/ComplexMenu.d.ts.map +1 -1
- package/dist/components/menu/ComplexMenu.js +5 -3
- package/dist/components/menu/ComplexMenu.js.map +1 -1
- package/dist/components/menu/DropdownMenu.d.ts +1 -0
- package/dist/components/menu/DropdownMenu.d.ts.map +1 -1
- package/dist/components/menu/DropdownMenu.js +24 -24
- package/dist/components/menu/DropdownMenu.js.map +1 -1
- package/dist/components/widgets/facet-nav/FacetNavPanel.d.ts.map +1 -1
- package/dist/components/widgets/facet-nav/FacetNavPanel.js +59 -49
- package/dist/components/widgets/facet-nav/FacetNavPanel.js.map +1 -1
- package/dist/components/widgets/facet-nav/PlotsContainer.d.ts.map +1 -1
- package/dist/components/widgets/facet-nav/PlotsContainer.js +8 -2
- package/dist/components/widgets/facet-nav/PlotsContainer.js.map +1 -1
- package/dist/style/components/facet_nav/_facet-nav-panel.scss +11 -0
- package/dist/style/main.css +1 -1
- package/dist/testutils/ReactQueryMockUtils.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/ThemeTypes.d.ts +2 -2
- package/dist/utils/functions/IsEqualTreatUndefinedAsMissing.js.map +1 -1
- package/dist/utils/hooks/useDebouncedEffect.js.map +1 -1
- package/dist/utils/hooks/useDetectSSOCode.js.map +1 -1
- package/dist/utils/hooks/useImmutableTableQuery/useImmutableTableQuery.js.map +1 -1
- package/dist/utils/hooks/useLogin.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TablePagination.js","names":[],"sources":["../../../src/components/SynapseTable/TablePagination.tsx"],"sourcesContent":["import {\n MenuItem,\n Pagination,\n PaginationItem,\n PaginationRenderItemParams,\n Select,\n SelectChangeEvent,\n Typography,\n} from '@mui/material'\nimport {\n ChangeEvent,\n ComponentProps,\n forwardRef,\n Ref,\n useCallback,\n} from 'react'\nimport React from 'react'\nimport { useQueryContext } from '../QueryContext'\nimport { useSuspenseGetQueryMetadata } from '../QueryWrapper/useGetQueryMetadata'\n\nimport { usePrefetchTableRows } from './usePrefetchTableData'\n\nconst PAGE_SIZE_OPTIONS = [10, 25, 100, 500]\n\nexport const TablePagination = (): React.ReactNode => {\n const { goToPage, pageSize, setPageSize, currentPage, currentQueryRequest } =\n useQueryContext()\n\n const prefetchPage = usePrefetchTableRows()\n\n const {\n data: { queryCount, maxRowsPerPage },\n } = useSuspenseGetQueryMetadata()\n\n const currentLimit = currentQueryRequest.query.limit\n const resolvedPageSize = currentLimit ?? pageSize\n\n // Filter by backend limit maxRowsPerPage\n let pageSizeOptions = maxRowsPerPage\n ? PAGE_SIZE_OPTIONS.filter(option => option < maxRowsPerPage)\n : [...PAGE_SIZE_OPTIONS]\n\n if (queryCount) {\n // first option that is >= queryCount (show all rows)\n const firstSufficientOptionIndex = pageSizeOptions.findIndex(\n option => option >= queryCount,\n )\n // If found, slice to only include options up to and including that one\n if (firstSufficientOptionIndex !== -1) {\n pageSizeOptions = pageSizeOptions.slice(0, firstSufficientOptionIndex + 1)\n }\n }\n\n // If resolvedPageSize isn't in the options, use the smallest available option\n const displayedPageSize = pageSizeOptions.includes(resolvedPageSize)\n ? resolvedPageSize\n : pageSizeOptions[0]\n\n const handlePage = (_event: ChangeEvent<unknown>, value: number) => {\n goToPage(value)\n }\n\n const handlePageSize = (event: SelectChangeEvent<number>) => {\n const value = event.target.value\n\n setPageSize(value)\n goToPage(1)\n }\n\n // A custom `renderItem` implementation for the MUI Pagination component that prefetches a page's data when the page number button is hovered over\n const renderPaginationItem = useCallback(\n (params: PaginationRenderItemParams) => {\n //
|
|
1
|
+
{"version":3,"file":"TablePagination.js","names":[],"sources":["../../../src/components/SynapseTable/TablePagination.tsx"],"sourcesContent":["import {\n MenuItem,\n Pagination,\n PaginationItem,\n PaginationRenderItemParams,\n Select,\n SelectChangeEvent,\n Typography,\n} from '@mui/material'\nimport {\n ChangeEvent,\n ComponentProps,\n forwardRef,\n Ref,\n useCallback,\n} from 'react'\nimport React from 'react'\nimport { useQueryContext } from '../QueryContext'\nimport { useSuspenseGetQueryMetadata } from '../QueryWrapper/useGetQueryMetadata'\n\nimport { usePrefetchTableRows } from './usePrefetchTableData'\n\nconst PAGE_SIZE_OPTIONS = [10, 25, 100, 500]\n\nexport const TablePagination = (): React.ReactNode => {\n const { goToPage, pageSize, setPageSize, currentPage, currentQueryRequest } =\n useQueryContext()\n\n const prefetchPage = usePrefetchTableRows()\n\n const {\n data: { queryCount, maxRowsPerPage },\n } = useSuspenseGetQueryMetadata()\n\n const currentLimit = currentQueryRequest.query.limit\n const resolvedPageSize = currentLimit ?? pageSize\n\n // Filter by backend limit maxRowsPerPage\n let pageSizeOptions = maxRowsPerPage\n ? PAGE_SIZE_OPTIONS.filter(option => option < maxRowsPerPage)\n : [...PAGE_SIZE_OPTIONS]\n\n if (queryCount) {\n // first option that is >= queryCount (show all rows)\n const firstSufficientOptionIndex = pageSizeOptions.findIndex(\n option => option >= queryCount,\n )\n // If found, slice to only include options up to and including that one\n if (firstSufficientOptionIndex !== -1) {\n pageSizeOptions = pageSizeOptions.slice(0, firstSufficientOptionIndex + 1)\n }\n }\n\n // If resolvedPageSize isn't in the options, use the smallest available option\n const displayedPageSize = pageSizeOptions.includes(resolvedPageSize)\n ? resolvedPageSize\n : pageSizeOptions[0]\n\n const handlePage = (_event: ChangeEvent<unknown>, value: number) => {\n goToPage(value)\n }\n\n const handlePageSize = (event: SelectChangeEvent<number>) => {\n const value = event.target.value\n\n setPageSize(value)\n goToPage(1)\n }\n\n // A custom `renderItem` implementation for the MUI Pagination component that prefetches a page's data when the page number button is hovered over\n const renderPaginationItem = useCallback(\n (params: PaginationRenderItemParams) => {\n // oxlint-disable-next-line react/no-unstable-nested-components -- this declaration is within a useCallback itself, so it is stable (enough)\n const ButtonWithPagePrefetchOnHover = forwardRef(\n function PaginationItemButton(\n props: ComponentProps<'button'>,\n ref: Ref<HTMLButtonElement>,\n ) {\n return (\n <button\n ref={ref}\n {...props}\n onMouseOver={() => {\n if (params.page) {\n void prefetchPage(params.page)\n }\n }}\n />\n )\n },\n )\n\n return (\n <PaginationItem {...params} component={ButtonWithPagePrefetchOnHover} />\n )\n },\n [prefetchPage],\n )\n\n // PORTALS-2259: Special presentation case. If we're on the first page,\n // and the total query count is equal to 1 (and is the page size is not 1), then hide the pagination UI.\n // Also hide pagination if the query count is unavailable.\n if (\n (currentPage == 1 && queryCount == 1 && pageSize != 1) ||\n queryCount == undefined ||\n (maxRowsPerPage && maxRowsPerPage < 5)\n ) {\n return null\n }\n\n return (\n <div>\n <Pagination\n page={currentPage}\n count={Math.ceil(queryCount / displayedPageSize)}\n color=\"secondary\"\n onChange={handlePage}\n shape={'rounded'}\n sx={{\n display: 'inline-block',\n float: 'left',\n '.MuiPaginationItem-root': { fontSize: '14px' },\n }}\n renderItem={renderPaginationItem}\n />\n <Typography variant=\"body1\" style={{ display: 'inline-block' }}>\n {`${queryCount?.toLocaleString()} total rows /`}\n </Typography>\n <Select\n name=\"page size\"\n value={displayedPageSize}\n size=\"small\"\n onChange={handlePageSize}\n sx={{ ml: 0.5 }}\n >\n {pageSizeOptions.map(pageSize => {\n return (\n <MenuItem key={pageSize} value={pageSize}>\n {pageSize} per page\n </MenuItem>\n )\n })}\n </Select>\n </div>\n )\n}\n"],"mappings":";;;;;;;;AAsBA,IAAM,IAAoB;CAAC;CAAI;CAAI;CAAK;AAAG,GAE9B,UAAyC;CACpD,IAAM,EAAE,aAAU,aAAU,gBAAa,gBAAa,2BACpD,EAAgB,GAEZ,IAAe,EAAqB,GAEpC,EACJ,MAAM,EAAE,eAAY,wBAClB,EAA4B,GAG1B,IADe,EAAoB,MAAM,SACN,GAGrC,IAAkB,IAClB,EAAkB,QAAO,MAAU,IAAS,CAAc,IAC1D,CAAC,GAAG,CAAiB;CAEzB,IAAI,GAAY;EAEd,IAAM,IAA6B,EAAgB,WACjD,MAAU,KAAU,CACtB;EAEA,AAAI,MAA+B,OACjC,IAAkB,EAAgB,MAAM,GAAG,IAA6B,CAAC;CAE7E;CAGA,IAAM,IAAoB,EAAgB,SAAS,CAAgB,IAC/D,IACA,EAAgB,IAEd,KAAc,GAA8B,MAAkB;EAClE,EAAS,CAAK;CAChB,GAEM,KAAkB,MAAqC;EAC3D,IAAM,IAAQ,EAAM,OAAO;EAG3B,AADA,EAAY,CAAK,GACjB,EAAS,CAAC;CACZ,GAGM,IAAuB,GAC1B,MAAuC;EAEtC,IAAM,IAAgC,EACpC,SACE,GACA,GACA;GACA,OACE,kBAAC,UAAD;IACO;IACL,GAAI;IACJ,mBAAmB;KACjB,AAAI,EAAO,QACT,EAAkB,EAAO,IAAI;IAEjC;GACD,CAAA;EAEL,CACF;EAEA,OACE,kBAAC,GAAD;GAAgB,GAAI;GAAQ,WAAW;EAAgC,CAAA;CAE3E,GACA,CAAC,CAAY,CACf;CAaA,OAPG,KAAe,KAAK,KAAc,KAAK,KAAY,KACpD,KAAc,QACb,KAAkB,IAAiB,IAE7B,OAIP,kBAAC,OAAD,EAAA,UAAA;EACE,kBAAC,GAAD;GACE,MAAM;GACN,OAAO,KAAK,KAAK,IAAa,CAAiB;GAC/C,OAAM;GACN,UAAU;GACV,OAAO;GACP,IAAI;IACF,SAAS;IACT,OAAO;IACP,2BAA2B,EAAE,UAAU,OAAO;GAChD;GACA,YAAY;EACb,CAAA;EACD,kBAAC,GAAD;GAAY,SAAQ;GAAQ,OAAO,EAAE,SAAS,eAAe;aAC1D,GAAG,GAAY,eAAe,EAAE;EACvB,CAAA;EACZ,kBAAC,GAAD;GACE,MAAK;GACL,OAAO;GACP,MAAK;GACL,UAAU;GACV,IAAI,EAAE,IAAI,GAAI;aAEb,EAAgB,KAAI,MAEjB,kBAAC,GAAD;IAAyB,OAAO;cAAhC,CACG,GAAS,WACF;MAFK,CAEL,CAEb;EACK,CAAA;CACL,EAAA,CAAA;AAET"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DatasetItemsEditor.js","names":[],"sources":["../../../../src/components/SynapseTable/datasets/DatasetItemsEditor.tsx"],"sourcesContent":["import React from 'react'\nimport WideButton from '@/components/styled/WideButton'\nimport { datasetItemToReference } from '@/components/SynapseTable/datasets/DatasetEditorUtils'\nimport {\n useGetEntity,\n useGetEntityPath,\n useUpdateEntity,\n} from '@/synapse-queries'\nimport { RequiredProperties } from '@/utils'\nimport {\n convertToEntityType,\n entityTypeToFriendlyName,\n isDataset,\n isDatasetCollection,\n} from '@/utils/functions/EntityTypeUtils'\nimport { useSet } from '@/utils/hooks'\nimport AddCircleTwoToneIcon from '@mui/icons-material/AddCircleTwoTone'\nimport {\n Alert,\n Button,\n Checkbox,\n Skeleton,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport {\n EntityRef,\n EntityRefCollectionView,\n Reference,\n} from '@sage-bionetworks/synapse-types'\nimport {\n createColumnHelper,\n getCoreRowModel,\n Table,\n useReactTable,\n} from '@tanstack/react-table'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport { isEqual, upperFirst } from 'lodash-es'\nimport pluralize from 'pluralize'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { CreatedOnCell } from '../../EntityFinder/details/view/table/CreatedOnCell'\nimport { EntityBadgeIconsCell } from '../../EntityFinder/details/view/table/EntityBadgeIconsCell'\nimport { EntityNameCell } from '../../EntityFinder/details/view/table/EntityNameCell'\nimport { ModifiedByCell } from '../../EntityFinder/details/view/table/ModifiedByCell'\nimport { ModifiedOnCell } from '../../EntityFinder/details/view/table/ModifiedOnCell'\nimport { ParentProjectCell } from '../../EntityFinder/details/view/table/ParentProjectCell'\nimport { EntityFinderModal } from '../../EntityFinder/EntityFinderModal'\nimport { FinderScope } from '../../EntityFinder/tree/EntityTree'\nimport { VersionSelectionType } from '../../EntityFinder/VersionSelectionType'\nimport { BlockingLoader } from '../../LoadingScreen/LoadingScreen'\nimport { SkeletonTable } from '../../Skeleton'\nimport WarningDialog from '../../SynapseForm/WarningDialog'\nimport ColumnHeader from '../../TanStackTable/ColumnHeader'\nimport StyledVirtualTanStackTable from '../../TanStackTable/StyledVirtualTanStackTable'\nimport { displayToast } from '../../ToastMessage'\nimport { DatasetEditorCheckboxCell } from './DatasetEditorCheckboxCell'\nimport { DatasetEditorVersionCell } from './DatasetEditorVersionCell'\nimport { EntityFetchErrorCell } from './EntityFetchErrorCell'\n\nexport type DatasetItemsEditorTableData = EntityRef & {\n isSelected: boolean\n setSelected: (value: boolean) => void\n}\n\nenum DatasetItemsEditorColumn {\n ERROR_STATE = 'errorState',\n SELECTED = 'isSelected',\n NAME = 'name',\n ENTITY_ID = 'entityId',\n BADGES = 'badges',\n VERSION = 'version',\n CREATED_ON = 'createdOn',\n MODIFIED_ON = 'modifieddOn',\n MODIFIED_BY = 'modifiedBy',\n PROJECT = 'project',\n}\n\nconst columnHelper = createColumnHelper<DatasetItemsEditorTableData>()\nfunction getColumns(\n opts: SelectAllCheckboxRendererProps & {\n changeVersionOnItem: (entityId: string, newVersion: number) => void\n },\n) {\n const {\n datasetToUpdate,\n selectedIds,\n clearSelectedIds,\n addSelectedId,\n allItemsAreSelected,\n changeVersionOnItem,\n } = opts\n return [\n columnHelper.display({\n id: DatasetItemsEditorColumn.ERROR_STATE,\n minSize: 35,\n maxSize: 35,\n size: 35,\n enableResizing: false,\n header: () => null,\n cell: EntityFetchErrorCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.SELECTED,\n minSize: 40,\n maxSize: 40,\n size: 40,\n enableResizing: false,\n header: () => (\n <SelectAllCheckboxRenderer\n datasetToUpdate={datasetToUpdate}\n selectedIds={selectedIds}\n clearSelectedIds={clearSelectedIds}\n addSelectedId={addSelectedId}\n allItemsAreSelected={allItemsAreSelected}\n />\n ),\n cell: DatasetEditorCheckboxCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.NAME,\n minSize: 50,\n size: 300,\n header: props => <ColumnHeader {...props} title={'Name'} />,\n cell: EntityNameCell,\n }),\n columnHelper.accessor(DatasetItemsEditorColumn.ENTITY_ID, {\n minSize: 50,\n size: 130,\n header: props => <ColumnHeader {...props} title={'ID'} />,\n enableColumnFilter: false,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.BADGES,\n minSize: 80,\n size: 80,\n enableResizing: true,\n cell: EntityBadgeIconsCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.VERSION,\n minSize: 150,\n size: 150,\n header: props => <ColumnHeader {...props} title={'Version'} />,\n cell: props => (\n <DatasetEditorVersionCell\n {...props}\n toggleSelection={datasetItem => {\n changeVersionOnItem(datasetItem.entityId, datasetItem.versionNumber)\n }}\n />\n ),\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.CREATED_ON,\n header: props => <ColumnHeader {...props} title={'Created On'} />,\n size: 220,\n minSize: 170,\n cell: CreatedOnCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.MODIFIED_ON,\n header: props => <ColumnHeader {...props} title={'Modified On'} />,\n size: 220,\n minSize: 170,\n cell: ModifiedOnCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.MODIFIED_BY,\n header: props => <ColumnHeader {...props} title={'Modified By'} />,\n size: 250,\n enableResizing: true,\n cell: ModifiedByCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.PROJECT,\n header: props => <ColumnHeader {...props} title={'Size'} />,\n size: 300,\n cell: ParentProjectCell,\n }),\n ]\n}\n\nfunction getSelectableTypes(entity: EntityRefCollectionView) {\n if (isDataset(entity)) {\n return [EntityType.file]\n } else if (isDatasetCollection(entity)) {\n return [EntityType.dataset]\n } else {\n console.error(\n 'Cannot determine selectable types for entity type: ' +\n entity.concreteType,\n )\n return []\n }\n}\n\nexport function getCopy(entity?: EntityRefCollectionView) {\n const displayName = entity\n ? entityTypeToFriendlyName(convertToEntityType(entity.concreteType))\n : 'Collection'\n let itemName = 'Item'\n let currentVersionName = 'Current Version'\n if (entity && isDataset(entity)) {\n itemName = entityTypeToFriendlyName(EntityType.file)\n currentVersionName = 'Draft'\n } else if (entity && isDatasetCollection(entity)) {\n itemName = entityTypeToFriendlyName(EntityType.dataset)\n }\n\n const entityFinderPopover =\n entity && isDataset(entity)\n ? `Use the left pane to browse projects and folders. Select ${pluralize(\n itemName,\n )} from the right pane to add to this ${displayName}. ${upperFirst(\n pluralize(itemName),\n )} in a ${displayName} can be added from multiple folders. You can also use Search to find and select ${pluralize(\n itemName,\n )}.`\n : `Use the left pane to browse projects. Select ${pluralize(\n itemName,\n )} from the right pane to add to this ${displayName}. ${upperFirst(\n pluralize(itemName),\n )} in a ${displayName} can be added from multiple projects. You can also use Search to find and select ${pluralize(\n itemName,\n )}.`\n\n return {\n ADD_ITEMS: `Add ${pluralize(itemName)}`,\n ADD_ITEMS_TO: `Add ${pluralize(itemName)} to ${displayName}`,\n REMOVE_ITEMS: `Remove ${pluralize(itemName)}`,\n NO_ITEMS_IN_THIS_DATASET: `No ${pluralize(\n itemName,\n )} in this ${displayName}`,\n SAVE_TO_CONTINUE: `Save the ${displayName} to continue`,\n CREATE_VERSION_TO_FREEZE: `Create a Version of this ${displayName} to freeze it in its current state`,\n ENTITY_SAVED: `${displayName} Saved`,\n SAVE_CHANGES: `Save changes to ${currentVersionName}`,\n ENTITY_FINDER_POPOVER: entityFinderPopover,\n ENTITY_FINDER_PROMPT: `Find ${pluralize(\n itemName,\n )} to add to the ${displayName}.`,\n PRECONDITION_FAILED_MESSAGE: `Re-retrieve the ${displayName} to get the latest changes. Your current changes will be lost.`,\n PRECONDITION_FAILED_TITLE: `${displayName} updated since last fetched`,\n PRECONDITION_FAILED_ACTION: `Retrieve ${displayName}`,\n NO_CHANGES_MADE: `You have not made any changes to the ${displayName}.`,\n }\n}\n\nfunction getDataSetDifference(\n oldDataSet: EntityRef[],\n changedItems: EntityRef[],\n) {\n const unchangedItems = oldDataSet.filter(\n oldItem =>\n !changedItems.find(newItem => newItem.entityId === oldItem.entityId),\n )\n const deletedItems = [...unchangedItems]\n const { updatedItems, newItems } = changedItems.reduce(\n (results, result) => {\n const oldItem = oldDataSet.find(old => old.entityId === result.entityId)\n if (oldItem) {\n if (result.versionNumber !== oldItem.versionNumber) {\n results['updatedItems'].push(result)\n } else {\n unchangedItems.push(result)\n }\n } else {\n results['newItems'].push(result)\n }\n return results\n },\n { updatedItems: [], newItems: [] } as {\n updatedItems: EntityRef[]\n newItems: EntityRef[]\n },\n )\n\n return { unchangedItems, updatedItems, newItems, deletedItems }\n}\n\nfunction getDatasetChangedToastMessageTitle(\n previousDatasetToUpdate?: RequiredProperties<\n EntityRefCollectionView,\n 'items'\n >,\n datasetToUpdate?: RequiredProperties<EntityRefCollectionView, 'items'>,\n) {\n const { updatedItems, newItems, deletedItems } = getDataSetDifference(\n previousDatasetToUpdate?.items ?? [],\n datasetToUpdate?.items!,\n )\n let toastTitle = ''\n\n // \"X items(s) deleted\"\n if (deletedItems.length > 0) {\n toastTitle += `${deletedItems.length} Item${\n deletedItems.length === 1 ? '' : 's'\n } removed`\n } else {\n // \"Y item(s) added\"\n toastTitle += `${newItems.length} Item${\n newItems.length === 1 ? '' : 's'\n } added`\n\n // \"and Z item(s) updated\", only shown if there are updated items\n if (updatedItems.length > 0) {\n toastTitle += ` and ${updatedItems.length} Item${\n updatedItems.length === 1 ? '' : 's'\n } updated`\n }\n }\n return toastTitle\n}\n\nexport type DatasetItemsEditorProps = {\n /* The synId of the EntityRefCollectionView to modify */\n entityId: string\n /** Callback invoked when the editor changes state to contain un/saved changes. */\n onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void\n onSave?: () => void\n onClose?: () => void\n}\n\nconst ROW_HEIGHT = 48\n\ntype SelectAllCheckboxRendererProps = {\n datasetToUpdate: RequiredProperties<EntityRefCollectionView, 'items'>\n selectedIds: Omit<Set<string>, 'add' | 'delete' | 'clear'>\n addSelectedId: (...items: string[]) => void\n clearSelectedIds: () => void\n allItemsAreSelected: boolean\n}\n\nconst SelectAllCheckboxRenderer = (\n props: SelectAllCheckboxRendererProps,\n): React.ReactNode => {\n const {\n datasetToUpdate,\n clearSelectedIds,\n allItemsAreSelected,\n addSelectedId,\n } = props\n const isChecked = allItemsAreSelected\n\n return (\n <Checkbox\n inputProps={{ 'aria-label': 'Select All' }}\n checked={isChecked}\n disabled={datasetToUpdate.items.length === 0}\n onChange={() => {\n if (isChecked) {\n clearSelectedIds()\n } else {\n addSelectedId(...datasetToUpdate.items.map(item => item.entityId))\n }\n }}\n />\n )\n}\n\nfunction NoItemsPlaceholder(props: {\n titleCopy: string\n buttonCopy: string\n onButtonClick: () => void\n}) {\n const { titleCopy, buttonCopy, onButtonClick } = props\n return (\n <div className=\"NoItemsPlaceholder\">\n <Typography variant={'headline3'}>{titleCopy}</Typography>\n <WideButton\n variant=\"contained\"\n color=\"primary\"\n onClick={onButtonClick}\n startIcon={<AddCircleTwoToneIcon />}\n sx={{ mt: 2 }}\n >\n {buttonCopy}\n </WideButton>\n </div>\n )\n}\n\nexport function DatasetItemsEditor(props: DatasetItemsEditorProps) {\n const { entityId, onSave, onClose, onUnsavedChangesChange } = props\n const [showEntityFinder, setShowEntityFinder] = useState<boolean>(false)\n const [showWarningDialog, setShowWarningDialog] = useState<boolean>(false)\n\n // The current 'client state' of dataset that the user is currently editing. Updates to this state are not saved until the user clicks 'Save'.\n const [datasetToUpdate, setDatasetToUpdate] =\n useState<RequiredProperties<EntityRefCollectionView, 'items'>>()\n\n // The previous value of the datasetToUpdate variable. This is used to calculate a summary of bulk actions since the last update to the unsaved dataset state.\n const [previousDatasetToUpdate, setPreviousDatasetToUpdate] =\n useState<RequiredProperties<EntityRefCollectionView, 'items'>>()\n\n // Stores the `close` function that will be used to hide the last toast that prompted the user to save\n const [onCloseLastSavePromptToast, setOnCloseLastSavePromptToast] = useState<\n { close: () => void } | undefined\n >()\n\n const { data: datasetOnServer, refetch: refetchDataset } = useGetEntity<\n RequiredProperties<EntityRefCollectionView, 'items'>\n >(entityId, undefined, {\n staleTime: Infinity,\n })\n\n const hasChangedSinceLastSave = Boolean(\n datasetOnServer &&\n datasetToUpdate &&\n !isEqual(datasetOnServer, datasetToUpdate),\n )\n\n const {\n ADD_ITEMS,\n ADD_ITEMS_TO,\n REMOVE_ITEMS,\n NO_ITEMS_IN_THIS_DATASET,\n SAVE_TO_CONTINUE,\n CREATE_VERSION_TO_FREEZE,\n ENTITY_SAVED,\n SAVE_CHANGES,\n PRECONDITION_FAILED_TITLE,\n PRECONDITION_FAILED_MESSAGE,\n PRECONDITION_FAILED_ACTION,\n ENTITY_FINDER_POPOVER,\n ENTITY_FINDER_PROMPT,\n NO_CHANGES_MADE,\n } = useMemo(() => getCopy(datasetOnServer), [datasetOnServer])\n\n useEffect(() => {\n if (datasetOnServer) {\n // SWC-5876: Dataset Items may be undefined. This has the same inherent meaning as the empty list, so we'll just change it to save us some null checks.\n if (datasetOnServer.items == null) {\n datasetOnServer.items = []\n }\n setDatasetToUpdate(datasetOnServer)\n }\n }, [datasetOnServer])\n\n const {\n set: selectedIds,\n add: addSelectedId,\n remove: removeSelectedId,\n clear: clearSelectedIds,\n } = useSet<string>()\n const allItemsAreSelected = Boolean(\n datasetToUpdate && datasetToUpdate.items.length === selectedIds.size,\n )\n\n useEffect(() => {\n if (datasetToUpdate && datasetOnServer && onUnsavedChangesChange) {\n onUnsavedChangesChange(hasChangedSinceLastSave)\n }\n }, [\n datasetOnServer,\n datasetToUpdate,\n hasChangedSinceLastSave,\n onUnsavedChangesChange,\n ])\n\n // We get the project ID to show the \"Current Project\" context in the Entity Finder.\n const { data: path } = useGetEntityPath(entityId)\n const projectId = path?.path[1]?.id\n\n const { mutate, isPending: updateIsPending } =\n useUpdateEntity<EntityRefCollectionView>({\n onMutate: () => {\n // Close existing toast that prompted the user to save\n if (onCloseLastSavePromptToast) {\n onCloseLastSavePromptToast.close()\n setOnCloseLastSavePromptToast(undefined)\n }\n },\n onSuccess: () => {\n // The query should automatically be invalidated, but just in case\n void refetchDataset()\n\n if (onSave) {\n onSave()\n } else {\n // If onSave isn't specified, push a generic toast message.\n displayToast(CREATE_VERSION_TO_FREEZE, 'success', {\n title: ENTITY_SAVED,\n })\n }\n },\n onError: error => {\n if (error.status === 412) {\n displayToast(PRECONDITION_FAILED_MESSAGE, 'warning', {\n title: PRECONDITION_FAILED_TITLE,\n primaryButtonConfig: {\n text: PRECONDITION_FAILED_ACTION,\n onClick: () => {\n void refetchDataset()\n },\n },\n })\n } else {\n displayToast(error.reason, 'danger', {\n title: 'An Error Occurred',\n })\n }\n },\n })\n\n useEffect(() => {\n // When changes are made (but not saved) to the datasetToUpdate, show a notification that summarizes the change and prompts the user to save\n if (\n previousDatasetToUpdate &&\n datasetToUpdate &&\n !isEqual(previousDatasetToUpdate.items, datasetToUpdate.items) &&\n !updateIsPending\n ) {\n const toastMessageTitle = getDatasetChangedToastMessageTitle(\n previousDatasetToUpdate,\n datasetToUpdate,\n )\n // Replace existing toast that prompts the user to save before showing the new one\n if (onCloseLastSavePromptToast) {\n onCloseLastSavePromptToast.close()\n }\n const onCloseToast = displayToast(SAVE_TO_CONTINUE, 'info', {\n title: toastMessageTitle,\n primaryButtonConfig: {\n text: SAVE_CHANGES,\n onClick: () => mutate(datasetToUpdate),\n },\n })\n setOnCloseLastSavePromptToast({ close: onCloseToast })\n }\n setPreviousDatasetToUpdate(datasetToUpdate)\n // Only run when datasetToUpdate changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [datasetToUpdate])\n\n const tableData = useMemo(\n () =>\n datasetToUpdate?.items.map(\n (item: EntityRef): DatasetItemsEditorTableData => {\n return {\n ...item,\n isSelected: selectedIds.has(item.entityId),\n setSelected: (value: boolean) => {\n return value\n ? addSelectedId(item.entityId)\n : removeSelectedId(item.entityId)\n },\n }\n },\n ) ?? [],\n [addSelectedId, datasetToUpdate?.items, removeSelectedId, selectedIds],\n )\n\n function addItemsToDataset(itemsToAdd: Reference[]) {\n setDatasetToUpdate(datasetToUpdate => {\n if (datasetToUpdate) {\n const refToDatasetItem = itemsToAdd.map(item => ({\n entityId: item.targetId,\n versionNumber: item.targetVersionNumber!,\n }))\n const { unchangedItems, updatedItems, newItems } = getDataSetDifference(\n datasetToUpdate.items,\n refToDatasetItem,\n )\n\n if (updatedItems.length == 0 && newItems.length == 0) {\n // No items were added or updated, so don't update the state variable.\n return datasetToUpdate\n }\n\n const items = [...unchangedItems, ...updatedItems, ...newItems]\n\n return {\n ...datasetToUpdate,\n items: items,\n }\n } else {\n console.warn(\n 'Cannot add items to the Collection because it is undefined. The Collection may not have been fetched yet.',\n )\n return datasetToUpdate\n }\n })\n clearSelectedIds()\n }\n\n function removeSelectedItemsFromDataset() {\n setDatasetToUpdate(dataset => ({\n ...dataset!,\n items: dataset!.items.filter(\n datasetItem => !selectedIds.has(datasetItem.entityId),\n ),\n }))\n clearSelectedIds()\n }\n\n function changeVersionOnItem(entityId: string, newVersion: number) {\n setDatasetToUpdate(dataset => ({\n ...dataset!,\n items: dataset!.items.map(datasetItem =>\n datasetItem.entityId === entityId\n ? { entityId: entityId, versionNumber: newVersion }\n : datasetItem,\n ),\n }))\n }\n\n const selectableTypes: EntityType[] | undefined = useMemo(() => {\n if (datasetOnServer) {\n return getSelectableTypes(datasetOnServer)\n } else {\n return undefined\n }\n }, [datasetOnServer])\n\n const columns = useMemo(() => {\n if (!datasetToUpdate) {\n return []\n }\n return getColumns({\n datasetToUpdate,\n selectedIds,\n clearSelectedIds,\n addSelectedId,\n allItemsAreSelected,\n changeVersionOnItem,\n })\n }, [\n addSelectedId,\n allItemsAreSelected,\n clearSelectedIds,\n datasetToUpdate,\n selectedIds,\n ])\n\n const table: Table<DatasetItemsEditorTableData> =\n useReactTable<DatasetItemsEditorTableData>({\n data: tableData,\n columns,\n getCoreRowModel: getCoreRowModel(),\n columnResizeMode: 'onChange',\n // There are no backend sort controls. The server only provides ID/version, and a dataset could be up to 10k items,\n // so client side sorting is non-trivial. We may consider fetching all entity headers upon user request.\n enableSorting: false,\n })\n\n const tableContainerRef = useRef<HTMLDivElement>(null)\n\n const rowVirtualizer = useVirtualizer({\n count: tableData?.length ?? 0,\n estimateSize: () => ROW_HEIGHT, // estimate row height for accurate scrollbar dragging\n getScrollElement: () => tableContainerRef.current,\n // measure dynamic row height, except in firefox because it measures table border height incorrectly\n measureElement:\n typeof window !== 'undefined' &&\n navigator.userAgent.indexOf('Firefox') === -1\n ? element => element?.getBoundingClientRect().height\n : undefined,\n overscan: 5,\n })\n\n const datasetItemsAsEntityFinderRef = useMemo(\n () => datasetToUpdate?.items.map(datasetItemToReference),\n [datasetToUpdate],\n )\n\n return (\n <div className=\"DatasetEditor\">\n <EntityFinderModal\n initialSelected={datasetItemsAsEntityFinderRef}\n configuration={{\n projectId: projectId,\n selectMultiple: true,\n initialScope: FinderScope.CURRENT_PROJECT,\n initialContainer: projectId ?? null,\n selectableTypes: selectableTypes,\n versionSelection: VersionSelectionType.REQUIRED,\n }}\n titleHelpPopoverProps={{\n markdownText: ENTITY_FINDER_POPOVER,\n helpUrl: 'https://help.synapse.org/docs/Datasets.2611281979.html',\n placement: 'right',\n }}\n promptCopy={ENTITY_FINDER_PROMPT}\n show={showEntityFinder}\n title={ADD_ITEMS_TO}\n confirmButtonCopy={'Apply changes'}\n onConfirm={items => {\n addItemsToDataset(items)\n setShowEntityFinder(false)\n }}\n onCancel={() => setShowEntityFinder(false)}\n />\n <WarningDialog\n title=\"Unsaved Changes\"\n content=\"Any unsaved changes will be lost. Are you sure you want to close the editor?\"\n confirmButtonText=\"Close Editor\"\n onConfirm={() => {\n if (onClose) {\n setShowWarningDialog(false)\n if (onUnsavedChangesChange) {\n onUnsavedChangesChange(false)\n }\n onClose()\n }\n }}\n open={showWarningDialog}\n onConfirmCallbackArgs={[]}\n onCancel={() => setShowWarningDialog(false)}\n />\n\n <div className=\"DatasetEditorTopBottomPanel\">\n <BlockingLoader show={updateIsPending} />\n <div className=\"ItemCount\">\n {datasetToUpdate ? (\n <Typography variant=\"headline3\">\n {datasetToUpdate.items.length === 0\n ? 'No'\n : datasetToUpdate.items.length.toLocaleString()}{' '}\n File\n {datasetToUpdate.items.length !== 1 && 's'}\n </Typography>\n ) : (\n <Skeleton variant=\"rectangular\" width={200} />\n )}\n </div>\n\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={datasetToUpdate == null}\n onClick={() => setShowEntityFinder(true)}\n >\n {ADD_ITEMS}\n </Button>\n <Button\n disabled={selectedIds.size === 0}\n variant=\"outlined\"\n color=\"primary\"\n onClick={removeSelectedItemsFromDataset}\n >\n {REMOVE_ITEMS}\n </Button>\n </div>\n <div className=\"DatasetEditorTableContainer\">\n {datasetToUpdate && datasetToUpdate.items.length === 0 && (\n <NoItemsPlaceholder\n titleCopy={NO_ITEMS_IN_THIS_DATASET}\n buttonCopy={ADD_ITEMS}\n onButtonClick={() => setShowEntityFinder(true)}\n />\n )}\n {datasetToUpdate && datasetToUpdate.items.length > 0 && (\n <StyledVirtualTanStackTable<DatasetItemsEditorTableData>\n styledTableContainerProps={{\n className: 'DatasetEditorTable',\n ref: tableContainerRef,\n height: '350px',\n }}\n table={table}\n rowVirtualizer={rowVirtualizer}\n slotProps={{\n Tr: {\n className: `DatasetEditorRow`,\n style: {\n height: `${ROW_HEIGHT}px`,\n maxHeight: `${ROW_HEIGHT}px`,\n },\n },\n }}\n />\n )}\n {!datasetToUpdate && (\n <SkeletonTable\n className=\"DatasetItemsEditorSkeleton\"\n numRows={8}\n numCols={6}\n rowHeight={`${ROW_HEIGHT}px`}\n />\n )}\n </div>\n <div className=\"DatasetEditorTopBottomPanel\">\n {hasChangedSinceLastSave && (\n <Alert severity=\"warning\">You have unsaved changes</Alert>\n )}\n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => {\n if (hasChangedSinceLastSave) {\n setShowWarningDialog(true)\n } else if (onClose) {\n onClose()\n }\n }}\n >\n Cancel\n </Button>\n\n <Tooltip title={!hasChangedSinceLastSave && NO_CHANGES_MADE}>\n <div>\n <Button\n disabled={!datasetToUpdate || !hasChangedSinceLastSave}\n variant=\"contained\"\n color=\"primary\"\n onClick={() => mutate(datasetToUpdate!)}\n >\n Save\n </Button>\n </div>\n </Tooltip>\n </div>\n </div>\n )\n}\n\nexport default DatasetItemsEditor\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DatasetItemsEditor.js","names":[],"sources":["../../../../src/components/SynapseTable/datasets/DatasetItemsEditor.tsx"],"sourcesContent":["import React from 'react'\nimport WideButton from '@/components/styled/WideButton'\nimport { datasetItemToReference } from '@/components/SynapseTable/datasets/DatasetEditorUtils'\nimport {\n useGetEntity,\n useGetEntityPath,\n useUpdateEntity,\n} from '@/synapse-queries'\nimport { RequiredProperties } from '@/utils'\nimport {\n convertToEntityType,\n entityTypeToFriendlyName,\n isDataset,\n isDatasetCollection,\n} from '@/utils/functions/EntityTypeUtils'\nimport { useSet } from '@/utils/hooks'\nimport AddCircleTwoToneIcon from '@mui/icons-material/AddCircleTwoTone'\nimport {\n Alert,\n Button,\n Checkbox,\n Skeleton,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport {\n EntityRef,\n EntityRefCollectionView,\n Reference,\n} from '@sage-bionetworks/synapse-types'\nimport {\n createColumnHelper,\n getCoreRowModel,\n Table,\n useReactTable,\n} from '@tanstack/react-table'\nimport { useVirtualizer } from '@tanstack/react-virtual'\nimport { isEqual, upperFirst } from 'lodash-es'\nimport pluralize from 'pluralize'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport { CreatedOnCell } from '../../EntityFinder/details/view/table/CreatedOnCell'\nimport { EntityBadgeIconsCell } from '../../EntityFinder/details/view/table/EntityBadgeIconsCell'\nimport { EntityNameCell } from '../../EntityFinder/details/view/table/EntityNameCell'\nimport { ModifiedByCell } from '../../EntityFinder/details/view/table/ModifiedByCell'\nimport { ModifiedOnCell } from '../../EntityFinder/details/view/table/ModifiedOnCell'\nimport { ParentProjectCell } from '../../EntityFinder/details/view/table/ParentProjectCell'\nimport { EntityFinderModal } from '../../EntityFinder/EntityFinderModal'\nimport { FinderScope } from '../../EntityFinder/tree/EntityTree'\nimport { VersionSelectionType } from '../../EntityFinder/VersionSelectionType'\nimport { BlockingLoader } from '../../LoadingScreen/LoadingScreen'\nimport { SkeletonTable } from '../../Skeleton'\nimport WarningDialog from '../../SynapseForm/WarningDialog'\nimport ColumnHeader from '../../TanStackTable/ColumnHeader'\nimport StyledVirtualTanStackTable from '../../TanStackTable/StyledVirtualTanStackTable'\nimport { displayToast } from '../../ToastMessage'\nimport { DatasetEditorCheckboxCell } from './DatasetEditorCheckboxCell'\nimport { DatasetEditorVersionCell } from './DatasetEditorVersionCell'\nimport { EntityFetchErrorCell } from './EntityFetchErrorCell'\n\nexport type DatasetItemsEditorTableData = EntityRef & {\n isSelected: boolean\n setSelected: (value: boolean) => void\n}\n\nenum DatasetItemsEditorColumn {\n ERROR_STATE = 'errorState',\n SELECTED = 'isSelected',\n NAME = 'name',\n ENTITY_ID = 'entityId',\n BADGES = 'badges',\n VERSION = 'version',\n CREATED_ON = 'createdOn',\n MODIFIED_ON = 'modifieddOn',\n MODIFIED_BY = 'modifiedBy',\n PROJECT = 'project',\n}\n\nconst columnHelper = createColumnHelper<DatasetItemsEditorTableData>()\nfunction getColumns(\n opts: SelectAllCheckboxRendererProps & {\n changeVersionOnItem: (entityId: string, newVersion: number) => void\n },\n) {\n const {\n datasetToUpdate,\n selectedIds,\n clearSelectedIds,\n addSelectedId,\n allItemsAreSelected,\n changeVersionOnItem,\n } = opts\n return [\n columnHelper.display({\n id: DatasetItemsEditorColumn.ERROR_STATE,\n minSize: 35,\n maxSize: 35,\n size: 35,\n enableResizing: false,\n header: () => null,\n cell: EntityFetchErrorCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.SELECTED,\n minSize: 40,\n maxSize: 40,\n size: 40,\n enableResizing: false,\n header: () => (\n <SelectAllCheckboxRenderer\n datasetToUpdate={datasetToUpdate}\n selectedIds={selectedIds}\n clearSelectedIds={clearSelectedIds}\n addSelectedId={addSelectedId}\n allItemsAreSelected={allItemsAreSelected}\n />\n ),\n cell: DatasetEditorCheckboxCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.NAME,\n minSize: 50,\n size: 300,\n header: props => <ColumnHeader {...props} title={'Name'} />,\n cell: EntityNameCell,\n }),\n columnHelper.accessor(DatasetItemsEditorColumn.ENTITY_ID, {\n minSize: 50,\n size: 130,\n header: props => <ColumnHeader {...props} title={'ID'} />,\n enableColumnFilter: false,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.BADGES,\n minSize: 80,\n size: 80,\n enableResizing: true,\n cell: EntityBadgeIconsCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.VERSION,\n minSize: 150,\n size: 150,\n header: props => <ColumnHeader {...props} title={'Version'} />,\n cell: props => (\n <DatasetEditorVersionCell\n {...props}\n toggleSelection={datasetItem => {\n changeVersionOnItem(datasetItem.entityId, datasetItem.versionNumber)\n }}\n />\n ),\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.CREATED_ON,\n header: props => <ColumnHeader {...props} title={'Created On'} />,\n size: 220,\n minSize: 170,\n cell: CreatedOnCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.MODIFIED_ON,\n header: props => <ColumnHeader {...props} title={'Modified On'} />,\n size: 220,\n minSize: 170,\n cell: ModifiedOnCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.MODIFIED_BY,\n header: props => <ColumnHeader {...props} title={'Modified By'} />,\n size: 250,\n enableResizing: true,\n cell: ModifiedByCell,\n }),\n columnHelper.display({\n id: DatasetItemsEditorColumn.PROJECT,\n header: props => <ColumnHeader {...props} title={'Size'} />,\n size: 300,\n cell: ParentProjectCell,\n }),\n ]\n}\n\nfunction getSelectableTypes(entity: EntityRefCollectionView) {\n if (isDataset(entity)) {\n return [EntityType.file]\n } else if (isDatasetCollection(entity)) {\n return [EntityType.dataset]\n } else {\n console.error(\n 'Cannot determine selectable types for entity type: ' +\n entity.concreteType,\n )\n return []\n }\n}\n\nexport function getCopy(entity?: EntityRefCollectionView) {\n const displayName = entity\n ? entityTypeToFriendlyName(convertToEntityType(entity.concreteType))\n : 'Collection'\n let itemName = 'Item'\n let currentVersionName = 'Current Version'\n if (entity && isDataset(entity)) {\n itemName = entityTypeToFriendlyName(EntityType.file)\n currentVersionName = 'Draft'\n } else if (entity && isDatasetCollection(entity)) {\n itemName = entityTypeToFriendlyName(EntityType.dataset)\n }\n\n const entityFinderPopover =\n entity && isDataset(entity)\n ? `Use the left pane to browse projects and folders. Select ${pluralize(\n itemName,\n )} from the right pane to add to this ${displayName}. ${upperFirst(\n pluralize(itemName),\n )} in a ${displayName} can be added from multiple folders. You can also use Search to find and select ${pluralize(\n itemName,\n )}.`\n : `Use the left pane to browse projects. Select ${pluralize(\n itemName,\n )} from the right pane to add to this ${displayName}. ${upperFirst(\n pluralize(itemName),\n )} in a ${displayName} can be added from multiple projects. You can also use Search to find and select ${pluralize(\n itemName,\n )}.`\n\n return {\n ADD_ITEMS: `Add ${pluralize(itemName)}`,\n ADD_ITEMS_TO: `Add ${pluralize(itemName)} to ${displayName}`,\n REMOVE_ITEMS: `Remove ${pluralize(itemName)}`,\n NO_ITEMS_IN_THIS_DATASET: `No ${pluralize(\n itemName,\n )} in this ${displayName}`,\n SAVE_TO_CONTINUE: `Save the ${displayName} to continue`,\n CREATE_VERSION_TO_FREEZE: `Create a Version of this ${displayName} to freeze it in its current state`,\n ENTITY_SAVED: `${displayName} Saved`,\n SAVE_CHANGES: `Save changes to ${currentVersionName}`,\n ENTITY_FINDER_POPOVER: entityFinderPopover,\n ENTITY_FINDER_PROMPT: `Find ${pluralize(\n itemName,\n )} to add to the ${displayName}.`,\n PRECONDITION_FAILED_MESSAGE: `Re-retrieve the ${displayName} to get the latest changes. Your current changes will be lost.`,\n PRECONDITION_FAILED_TITLE: `${displayName} updated since last fetched`,\n PRECONDITION_FAILED_ACTION: `Retrieve ${displayName}`,\n NO_CHANGES_MADE: `You have not made any changes to the ${displayName}.`,\n }\n}\n\nfunction getDataSetDifference(\n oldDataSet: EntityRef[],\n changedItems: EntityRef[],\n) {\n const unchangedItems = oldDataSet.filter(\n oldItem =>\n !changedItems.find(newItem => newItem.entityId === oldItem.entityId),\n )\n const deletedItems = [...unchangedItems]\n const { updatedItems, newItems } = changedItems.reduce(\n (results, result) => {\n const oldItem = oldDataSet.find(old => old.entityId === result.entityId)\n if (oldItem) {\n if (result.versionNumber !== oldItem.versionNumber) {\n results['updatedItems'].push(result)\n } else {\n unchangedItems.push(result)\n }\n } else {\n results['newItems'].push(result)\n }\n return results\n },\n { updatedItems: [], newItems: [] } as {\n updatedItems: EntityRef[]\n newItems: EntityRef[]\n },\n )\n\n return { unchangedItems, updatedItems, newItems, deletedItems }\n}\n\nfunction getDatasetChangedToastMessageTitle(\n previousDatasetToUpdate?: RequiredProperties<\n EntityRefCollectionView,\n 'items'\n >,\n datasetToUpdate?: RequiredProperties<EntityRefCollectionView, 'items'>,\n) {\n const { updatedItems, newItems, deletedItems } = getDataSetDifference(\n previousDatasetToUpdate?.items ?? [],\n datasetToUpdate?.items!,\n )\n let toastTitle = ''\n\n // \"X items(s) deleted\"\n if (deletedItems.length > 0) {\n toastTitle += `${deletedItems.length} Item${\n deletedItems.length === 1 ? '' : 's'\n } removed`\n } else {\n // \"Y item(s) added\"\n toastTitle += `${newItems.length} Item${\n newItems.length === 1 ? '' : 's'\n } added`\n\n // \"and Z item(s) updated\", only shown if there are updated items\n if (updatedItems.length > 0) {\n toastTitle += ` and ${updatedItems.length} Item${\n updatedItems.length === 1 ? '' : 's'\n } updated`\n }\n }\n return toastTitle\n}\n\nexport type DatasetItemsEditorProps = {\n /* The synId of the EntityRefCollectionView to modify */\n entityId: string\n /** Callback invoked when the editor changes state to contain un/saved changes. */\n onUnsavedChangesChange?: (hasUnsavedChanges: boolean) => void\n onSave?: () => void\n onClose?: () => void\n}\n\nconst ROW_HEIGHT = 48\n\ntype SelectAllCheckboxRendererProps = {\n datasetToUpdate: RequiredProperties<EntityRefCollectionView, 'items'>\n selectedIds: Omit<Set<string>, 'add' | 'delete' | 'clear'>\n addSelectedId: (...items: string[]) => void\n clearSelectedIds: () => void\n allItemsAreSelected: boolean\n}\n\nconst SelectAllCheckboxRenderer = (\n props: SelectAllCheckboxRendererProps,\n): React.ReactNode => {\n const {\n datasetToUpdate,\n clearSelectedIds,\n allItemsAreSelected,\n addSelectedId,\n } = props\n const isChecked = allItemsAreSelected\n\n return (\n <Checkbox\n inputProps={{ 'aria-label': 'Select All' }}\n checked={isChecked}\n disabled={datasetToUpdate.items.length === 0}\n onChange={() => {\n if (isChecked) {\n clearSelectedIds()\n } else {\n addSelectedId(...datasetToUpdate.items.map(item => item.entityId))\n }\n }}\n />\n )\n}\n\nfunction NoItemsPlaceholder(props: {\n titleCopy: string\n buttonCopy: string\n onButtonClick: () => void\n}) {\n const { titleCopy, buttonCopy, onButtonClick } = props\n return (\n <div className=\"NoItemsPlaceholder\">\n <Typography variant={'headline3'}>{titleCopy}</Typography>\n <WideButton\n variant=\"contained\"\n color=\"primary\"\n onClick={onButtonClick}\n startIcon={<AddCircleTwoToneIcon />}\n sx={{ mt: 2 }}\n >\n {buttonCopy}\n </WideButton>\n </div>\n )\n}\n\nexport function DatasetItemsEditor(props: DatasetItemsEditorProps) {\n const { entityId, onSave, onClose, onUnsavedChangesChange } = props\n const [showEntityFinder, setShowEntityFinder] = useState<boolean>(false)\n const [showWarningDialog, setShowWarningDialog] = useState<boolean>(false)\n\n // The current 'client state' of dataset that the user is currently editing. Updates to this state are not saved until the user clicks 'Save'.\n const [datasetToUpdate, setDatasetToUpdate] =\n useState<RequiredProperties<EntityRefCollectionView, 'items'>>()\n\n // The previous value of the datasetToUpdate variable. This is used to calculate a summary of bulk actions since the last update to the unsaved dataset state.\n const [previousDatasetToUpdate, setPreviousDatasetToUpdate] =\n useState<RequiredProperties<EntityRefCollectionView, 'items'>>()\n\n // Stores the `close` function that will be used to hide the last toast that prompted the user to save\n const [onCloseLastSavePromptToast, setOnCloseLastSavePromptToast] = useState<\n { close: () => void } | undefined\n >()\n\n const { data: datasetOnServer, refetch: refetchDataset } = useGetEntity<\n RequiredProperties<EntityRefCollectionView, 'items'>\n >(entityId, undefined, {\n staleTime: Infinity,\n })\n\n const hasChangedSinceLastSave = Boolean(\n datasetOnServer &&\n datasetToUpdate &&\n !isEqual(datasetOnServer, datasetToUpdate),\n )\n\n const {\n ADD_ITEMS,\n ADD_ITEMS_TO,\n REMOVE_ITEMS,\n NO_ITEMS_IN_THIS_DATASET,\n SAVE_TO_CONTINUE,\n CREATE_VERSION_TO_FREEZE,\n ENTITY_SAVED,\n SAVE_CHANGES,\n PRECONDITION_FAILED_TITLE,\n PRECONDITION_FAILED_MESSAGE,\n PRECONDITION_FAILED_ACTION,\n ENTITY_FINDER_POPOVER,\n ENTITY_FINDER_PROMPT,\n NO_CHANGES_MADE,\n } = useMemo(() => getCopy(datasetOnServer), [datasetOnServer])\n\n useEffect(() => {\n if (datasetOnServer) {\n // SWC-5876: Dataset Items may be undefined. This has the same inherent meaning as the empty list, so we'll just change it to save us some null checks.\n if (datasetOnServer.items == null) {\n datasetOnServer.items = []\n }\n setDatasetToUpdate(datasetOnServer)\n }\n }, [datasetOnServer])\n\n const {\n set: selectedIds,\n add: addSelectedId,\n remove: removeSelectedId,\n clear: clearSelectedIds,\n } = useSet<string>()\n const allItemsAreSelected = Boolean(\n datasetToUpdate && datasetToUpdate.items.length === selectedIds.size,\n )\n\n useEffect(() => {\n if (datasetToUpdate && datasetOnServer && onUnsavedChangesChange) {\n onUnsavedChangesChange(hasChangedSinceLastSave)\n }\n }, [\n datasetOnServer,\n datasetToUpdate,\n hasChangedSinceLastSave,\n onUnsavedChangesChange,\n ])\n\n // We get the project ID to show the \"Current Project\" context in the Entity Finder.\n const { data: path } = useGetEntityPath(entityId)\n const projectId = path?.path[1]?.id\n\n const { mutate, isPending: updateIsPending } =\n useUpdateEntity<EntityRefCollectionView>({\n onMutate: () => {\n // Close existing toast that prompted the user to save\n if (onCloseLastSavePromptToast) {\n onCloseLastSavePromptToast.close()\n setOnCloseLastSavePromptToast(undefined)\n }\n },\n onSuccess: () => {\n // The query should automatically be invalidated, but just in case\n void refetchDataset()\n\n if (onSave) {\n onSave()\n } else {\n // If onSave isn't specified, push a generic toast message.\n displayToast(CREATE_VERSION_TO_FREEZE, 'success', {\n title: ENTITY_SAVED,\n })\n }\n },\n onError: error => {\n if (error.status === 412) {\n displayToast(PRECONDITION_FAILED_MESSAGE, 'warning', {\n title: PRECONDITION_FAILED_TITLE,\n primaryButtonConfig: {\n text: PRECONDITION_FAILED_ACTION,\n onClick: () => {\n void refetchDataset()\n },\n },\n })\n } else {\n displayToast(error.reason, 'danger', {\n title: 'An Error Occurred',\n })\n }\n },\n })\n\n useEffect(() => {\n // When changes are made (but not saved) to the datasetToUpdate, show a notification that summarizes the change and prompts the user to save\n if (\n previousDatasetToUpdate &&\n datasetToUpdate &&\n !isEqual(previousDatasetToUpdate.items, datasetToUpdate.items) &&\n !updateIsPending\n ) {\n const toastMessageTitle = getDatasetChangedToastMessageTitle(\n previousDatasetToUpdate,\n datasetToUpdate,\n )\n // Replace existing toast that prompts the user to save before showing the new one\n if (onCloseLastSavePromptToast) {\n onCloseLastSavePromptToast.close()\n }\n const onCloseToast = displayToast(SAVE_TO_CONTINUE, 'info', {\n title: toastMessageTitle,\n primaryButtonConfig: {\n text: SAVE_CHANGES,\n onClick: () => mutate(datasetToUpdate),\n },\n })\n setOnCloseLastSavePromptToast({ close: onCloseToast })\n }\n setPreviousDatasetToUpdate(datasetToUpdate)\n // Only run when datasetToUpdate changes\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [datasetToUpdate])\n\n const tableData = useMemo(\n () =>\n datasetToUpdate?.items.map(\n (item: EntityRef): DatasetItemsEditorTableData => {\n return {\n ...item,\n isSelected: selectedIds.has(item.entityId),\n setSelected: (value: boolean) => {\n return value\n ? addSelectedId(item.entityId)\n : removeSelectedId(item.entityId)\n },\n }\n },\n ) ?? [],\n [addSelectedId, datasetToUpdate?.items, removeSelectedId, selectedIds],\n )\n\n function addItemsToDataset(itemsToAdd: Reference[]) {\n setDatasetToUpdate(datasetToUpdate => {\n if (datasetToUpdate) {\n const refToDatasetItem = itemsToAdd.map(item => ({\n entityId: item.targetId,\n versionNumber: item.targetVersionNumber!,\n }))\n const { unchangedItems, updatedItems, newItems } = getDataSetDifference(\n datasetToUpdate.items,\n refToDatasetItem,\n )\n\n if (updatedItems.length == 0 && newItems.length == 0) {\n // No items were added or updated, so don't update the state variable.\n return datasetToUpdate\n }\n\n const items = [...unchangedItems, ...updatedItems, ...newItems]\n\n return {\n ...datasetToUpdate,\n items: items,\n }\n } else {\n console.warn(\n 'Cannot add items to the Collection because it is undefined. The Collection may not have been fetched yet.',\n )\n return datasetToUpdate\n }\n })\n clearSelectedIds()\n }\n\n function removeSelectedItemsFromDataset() {\n setDatasetToUpdate(dataset => ({\n ...dataset!,\n items: dataset!.items.filter(\n datasetItem => !selectedIds.has(datasetItem.entityId),\n ),\n }))\n clearSelectedIds()\n }\n\n function changeVersionOnItem(entityId: string, newVersion: number) {\n setDatasetToUpdate(dataset => ({\n ...dataset!,\n items: dataset!.items.map(datasetItem =>\n datasetItem.entityId === entityId\n ? { entityId: entityId, versionNumber: newVersion }\n : datasetItem,\n ),\n }))\n }\n\n const selectableTypes: EntityType[] | undefined = useMemo(() => {\n if (datasetOnServer) {\n return getSelectableTypes(datasetOnServer)\n } else {\n return undefined\n }\n }, [datasetOnServer])\n\n const columns = useMemo(() => {\n if (!datasetToUpdate) {\n return []\n }\n return getColumns({\n datasetToUpdate,\n selectedIds,\n clearSelectedIds,\n addSelectedId,\n allItemsAreSelected,\n changeVersionOnItem,\n })\n }, [\n addSelectedId,\n allItemsAreSelected,\n clearSelectedIds,\n datasetToUpdate,\n selectedIds,\n ])\n\n const table: Table<DatasetItemsEditorTableData> =\n useReactTable<DatasetItemsEditorTableData>({\n data: tableData,\n columns,\n getCoreRowModel: getCoreRowModel(),\n columnResizeMode: 'onChange',\n // There are no backend sort controls. The server only provides ID/version, and a dataset could be up to 10k items,\n // so client side sorting is non-trivial. We may consider fetching all entity headers upon user request.\n enableSorting: false,\n })\n\n const tableContainerRef = useRef<HTMLDivElement>(null)\n\n const rowVirtualizer = useVirtualizer({\n count: tableData?.length ?? 0,\n estimateSize: () => ROW_HEIGHT, // estimate row height for accurate scrollbar dragging\n getScrollElement: () => tableContainerRef.current,\n // measure dynamic row height, except in firefox because it measures table border height incorrectly\n measureElement:\n typeof window !== 'undefined' &&\n navigator.userAgent.indexOf('Firefox') === -1\n ? element => element?.getBoundingClientRect().height\n : undefined,\n overscan: 5,\n })\n\n const datasetItemsAsEntityFinderRef = useMemo(\n () => datasetToUpdate?.items.map(datasetItemToReference),\n [datasetToUpdate],\n )\n\n return (\n <div className=\"DatasetEditor\">\n <EntityFinderModal\n initialSelected={datasetItemsAsEntityFinderRef}\n configuration={{\n projectId: projectId,\n selectMultiple: true,\n initialScope: FinderScope.CURRENT_PROJECT,\n initialContainer: projectId ?? null,\n selectableTypes: selectableTypes,\n versionSelection: VersionSelectionType.REQUIRED,\n }}\n titleHelpPopoverProps={{\n markdownText: ENTITY_FINDER_POPOVER,\n helpUrl: 'https://help.synapse.org/docs/Datasets.2611281979.html',\n placement: 'right',\n }}\n promptCopy={ENTITY_FINDER_PROMPT}\n show={showEntityFinder}\n title={ADD_ITEMS_TO}\n confirmButtonCopy={'Apply changes'}\n onConfirm={items => {\n addItemsToDataset(items)\n setShowEntityFinder(false)\n }}\n onCancel={() => setShowEntityFinder(false)}\n />\n <WarningDialog\n title=\"Unsaved Changes\"\n content=\"Any unsaved changes will be lost. Are you sure you want to close the editor?\"\n confirmButtonText=\"Close Editor\"\n onConfirm={() => {\n if (onClose) {\n setShowWarningDialog(false)\n if (onUnsavedChangesChange) {\n onUnsavedChangesChange(false)\n }\n onClose()\n }\n }}\n open={showWarningDialog}\n onConfirmCallbackArgs={[]}\n onCancel={() => setShowWarningDialog(false)}\n />\n\n <div className=\"DatasetEditorTopBottomPanel\">\n <BlockingLoader show={updateIsPending} />\n <div className=\"ItemCount\">\n {datasetToUpdate ? (\n <Typography variant=\"headline3\">\n {datasetToUpdate.items.length === 0\n ? 'No'\n : datasetToUpdate.items.length.toLocaleString()}{' '}\n File\n {datasetToUpdate.items.length !== 1 && 's'}\n </Typography>\n ) : (\n <Skeleton variant=\"rectangular\" width={200} />\n )}\n </div>\n\n <Button\n variant=\"contained\"\n color=\"primary\"\n disabled={datasetToUpdate == null}\n onClick={() => setShowEntityFinder(true)}\n >\n {ADD_ITEMS}\n </Button>\n <Button\n disabled={selectedIds.size === 0}\n variant=\"outlined\"\n color=\"primary\"\n onClick={removeSelectedItemsFromDataset}\n >\n {REMOVE_ITEMS}\n </Button>\n </div>\n <div className=\"DatasetEditorTableContainer\">\n {datasetToUpdate && datasetToUpdate.items.length === 0 && (\n <NoItemsPlaceholder\n titleCopy={NO_ITEMS_IN_THIS_DATASET}\n buttonCopy={ADD_ITEMS}\n onButtonClick={() => setShowEntityFinder(true)}\n />\n )}\n {datasetToUpdate && datasetToUpdate.items.length > 0 && (\n <StyledVirtualTanStackTable<DatasetItemsEditorTableData>\n styledTableContainerProps={{\n className: 'DatasetEditorTable',\n ref: tableContainerRef,\n height: '350px',\n }}\n table={table}\n rowVirtualizer={rowVirtualizer}\n slotProps={{\n Tr: {\n className: `DatasetEditorRow`,\n style: {\n height: `${ROW_HEIGHT}px`,\n maxHeight: `${ROW_HEIGHT}px`,\n },\n },\n }}\n />\n )}\n {!datasetToUpdate && (\n <SkeletonTable\n className=\"DatasetItemsEditorSkeleton\"\n numRows={8}\n numCols={6}\n rowHeight={`${ROW_HEIGHT}px`}\n />\n )}\n </div>\n <div className=\"DatasetEditorTopBottomPanel\">\n {hasChangedSinceLastSave && (\n <Alert severity=\"warning\">You have unsaved changes</Alert>\n )}\n <Button\n variant=\"outlined\"\n color=\"primary\"\n onClick={() => {\n if (hasChangedSinceLastSave) {\n setShowWarningDialog(true)\n } else if (onClose) {\n onClose()\n }\n }}\n >\n Cancel\n </Button>\n\n <Tooltip title={!hasChangedSinceLastSave && NO_CHANGES_MADE}>\n <div>\n <Button\n disabled={!datasetToUpdate || !hasChangedSinceLastSave}\n variant=\"contained\"\n color=\"primary\"\n onClick={() => mutate(datasetToUpdate!)}\n >\n Save\n </Button>\n </div>\n </Tooltip>\n </div>\n </div>\n )\n}\n\nexport default DatasetItemsEditor\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"TableColumnSchemaForm.js","names":[],"sources":["../../../src/components/TableColumnSchemaEditor/TableColumnSchemaForm.tsx"],"sourcesContent":["import AddToList from '@/assets/icons/AddToList'\nimport {\n useGetAnnotationColumnModels,\n useGetDefaultColumnModels,\n} from '@/synapse-queries/table/useColumnModel'\nimport {\n convertToConcreteEntityType,\n entityTypeToFriendlyName,\n} from '@/utils/functions/EntityTypeUtils'\nimport { AddCircleTwoTone } from '@mui/icons-material'\nimport { Box, Button, styled } from '@mui/material'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport {\n ColumnModel,\n ColumnTypeEnum,\n VIEW_CONCRETE_TYPE_VALUES,\n ViewScope,\n} from '@sage-bionetworks/synapse-types'\nimport { atom, Provider, useAtomValue, useSetAtom } from 'jotai'\nimport { selectAtom, useAtomCallback } from 'jotai/utils'\nimport { groupBy, isEqual, noop, omit, times } from 'lodash-es'\nimport pluralize from 'pluralize'\nimport {\n ForwardedRef,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useState,\n} from 'react'\nimport { SetOptional } from 'type-fest'\nimport { ZodError, ZodIssue } from 'zod'\nimport { SynapseSpinner } from '../LoadingScreen/LoadingScreen'\nimport { displayToast } from '../ToastMessage'\nimport ColumnModelForm from './ColumnModelForm'\nimport ImportTableColumnsButton from './ImportTableColumnsButton'\nimport {\n findMatchingColumnModel,\n getAllowedColumnTypes,\n transformColumnModelsToFormData,\n} from './TableColumnSchemaEditorUtils'\nimport { TableColumnSchemaFormActions } from './TableColumnSchemaFormActions'\nimport { tableColumnSchemaFormDataAtom } from './TableColumnSchemaFormReducer'\nimport { validateColumnModelFormData } from './Validators/ColumnModelValidator'\n\nconst COLUMN_SCHEMA_FORM_GRID_TEMPLATE_COLUMNS =\n '18px 18px 1.75fr 1.75fr 0.75fr 1fr 1.25fr 1.25fr 1fr 1.25fr'\nconst GRID_CONTAINER_Y_MARGIN_PX = 6\nexport const HIERARCHY_VERTICAL_LINE_COMPONENT = (\n <Box\n sx={{\n width: '1px',\n height: `calc(100% + 2 * ${GRID_CONTAINER_Y_MARGIN_PX}px)`,\n backgroundColor: 'grey.400',\n marginLeft: '8px',\n }}\n />\n)\nexport const HIERARCHY_END_COMPONENT = (\n <Box\n sx={theme => ({\n width: '50%',\n height: '60%',\n borderLeft: `1px solid ${theme.palette.grey[400]}`,\n borderBottom: `1px solid ${theme.palette.grey[400]}`,\n marginLeft: '8px',\n marginBottom: 'auto',\n marginRight: '0',\n borderBottomLeftRadius: '5px',\n })}\n />\n)\n\nexport type SubmitHandle = {\n // Allow the parent component to trigger a submit of the form, so this may be embedded in an arbitrary modal.\n submit: () => void\n // Imperative handle to get the data out of the form for SWC compatibility\n getEditedColumnModels: () => SetOptional<ColumnModel, 'id'>[]\n // Used to check if all form data is valid. Returns the current validity state\n validate: () => boolean\n}\n\ntype TableColumnSchemaFormProps = {\n /* The type of the Table, which determines various schema restrictions and form functionality */\n entityType: EntityType\n /* If this is an entity view, the ViewScope can be used to determine the default column models and fetch annotation column models */\n viewScope?: ViewScope\n initialData?: SetOptional<ColumnModel, 'id'>[]\n onSubmit?: (newColumnModels: SetOptional<ColumnModel, 'id'>[]) => void\n isSubmitting?: boolean\n /** The original/current column models that are applied to the table, if they exist. */\n originalColumnModels?: ColumnModel[]\n}\n\nconst ColumnHeader = styled(Box, {\n label: 'ColumnHeader',\n})({\n fontWeight: 700,\n})\n\nexport const ADD_ALL_ANNOTATIONS_BUTTON_TEXT = 'Add All Annotations'\n\nfunction TableColumnSchemaFormInternal(\n props: TableColumnSchemaFormProps,\n ref: ForwardedRef<SubmitHandle>,\n) {\n const {\n initialData,\n entityType,\n viewScope,\n onSubmit = noop,\n isSubmitting = false,\n originalColumnModels,\n } = props\n\n const numColumnModels = useAtomValue(\n useMemo(() => atom(get => get(tableColumnSchemaFormDataAtom).length), []),\n )\n\n const dispatch = useSetAtom(tableColumnSchemaFormDataAtom)\n const [validationErrors, setValidationErrors] = useState<ZodError | null>(\n null,\n )\n\n // useAtomCallback will let us imperatively read the form data, instead of tracking it in state and triggering a full re-render of the form when any data changes\n const readFormData = useAtomCallback(\n useCallback(get => {\n return get(tableColumnSchemaFormDataAtom)\n }, []),\n )\n const concreteTableType = convertToConcreteEntityType(entityType)\n const isView = VIEW_CONCRETE_TYPE_VALUES.includes(\n concreteTableType as (typeof VIEW_CONCRETE_TYPE_VALUES)[number],\n )\n const hasAnnotationColumnModels = isView,\n hasDefaultColumnModels = isView\n\n const { data: defaultColumnModels, isLoading: isLoadingDefaultColumns } =\n useGetDefaultColumnModels(\n viewScope?.viewEntityType!,\n viewScope?.viewTypeMask,\n {\n enabled: hasDefaultColumnModels,\n staleTime: Infinity, // The default column models will never change\n },\n )\n\n const annotationColumnModelsQuery = useGetAnnotationColumnModels(\n {\n viewScope: viewScope!,\n includeDerivedAnnotations: true,\n concreteType:\n 'org.sagebionetworks.repo.model.table.ViewColumnModelRequest',\n },\n {\n enabled: hasAnnotationColumnModels,\n },\n )\n\n const {\n data: annotationColumnModels,\n isLoading: isLoadingAnnotationColumns,\n } = annotationColumnModelsQuery\n\n /**\n * Set the initialData in the form state atom on mount, if it exists and we have no data.\n */\n useEffect(() => {\n if (initialData && !isLoadingDefaultColumns) {\n dispatch({\n type: 'setValue',\n value: transformColumnModelsToFormData(\n initialData,\n defaultColumnModels ?? [],\n ),\n })\n }\n // Don't re-run if initial data changes\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isLoadingDefaultColumns])\n\n const validateInternal = useCallback(() => {\n const result = validateColumnModelFormData(readFormData())\n if (result.success) {\n setValidationErrors(null)\n } else {\n setValidationErrors(result.error)\n }\n return result\n }, [readFormData])\n\n useImperativeHandle(ref, () => {\n return {\n submit() {\n const result = validateInternal()\n if (result.success) {\n onSubmit(result.data)\n }\n },\n getEditedColumnModels() {\n const result = validateInternal()\n if (!result.success) {\n throw new Error('Column models were not valid')\n }\n return result.data\n },\n validate() {\n const result = validateInternal()\n return result.success\n },\n }\n }, [onSubmit, validateInternal])\n\n // Generic function to add a set of columns to the schema (e.g. default columns, annotation columns)\n const addColumnSet = useCallback(\n (newColumns: SetOptional<ColumnModel, 'id'>[]) => {\n const currentFormData = readFormData()\n let columnsToAdd = newColumns.filter(\n (cm: SetOptional<ColumnModel, 'id'>) => {\n // Don't add columns that cannot be added (for example, Views cannot have JSON columns)\n if (\n !getAllowedColumnTypes(isView, false).includes(\n cm.columnType as ColumnTypeEnum,\n )\n ) {\n return false\n }\n // Only add columns that are not already present\n // Use the name column to match because the ID may vary, e.g. if the facetType was changed\n return !currentFormData.find(fd => fd.name === cm.name)\n },\n )\n // Remove the ID column so TableColumnSchemaUtils.createTableUpdateTransactionRequest recognizes these as new columns\n // createTableUpdateTransactionRequest uses the existing column ID to track column updates in the table, so any new columns should have no ID\n // The user can also modify these columns before submitting them, so the ID may not be accurate when we end up submitting them\n columnsToAdd = columnsToAdd.map(cm => omit(cm, ['id']))\n if (columnsToAdd.length > 0) {\n dispatch({\n type: 'setValue',\n value: [\n ...currentFormData,\n ...transformColumnModelsToFormData(\n columnsToAdd,\n defaultColumnModels,\n ),\n ],\n })\n displayToast(\n `${columnsToAdd.length} ${pluralize(\n 'column',\n columnsToAdd.length,\n )} added to schema.`,\n 'success',\n )\n } else {\n displayToast(\n 'No columns added. All columns to add are present in the column schema.',\n 'info',\n )\n }\n },\n [defaultColumnModels, dispatch, isView, readFormData],\n )\n\n const addDefaultColumns = useCallback(() => {\n if (defaultColumnModels) {\n addColumnSet(defaultColumnModels)\n }\n }, [defaultColumnModels, addColumnSet])\n\n const addAnnotationColumns = useCallback(() => {\n if (annotationColumnModels) {\n addColumnSet(annotationColumnModels)\n }\n }, [annotationColumnModels, addColumnSet])\n\n // Put the errors into a map of columnModelIndex -> errors\n const errorsByColumnModel = useMemo(() => {\n if (validationErrors) {\n return groupBy(validationErrors.issues, e => e.path[0])\n }\n return {}\n }, [validationErrors])\n\n return (\n <Box\n component={'form'}\n sx={{\n py: 2.5,\n }}\n >\n <TableColumnSchemaFormActions\n disabled={isSubmitting}\n annotationColumnModelsQuery={annotationColumnModelsQuery}\n />\n <Box\n sx={{\n display: 'grid',\n gap: '8px',\n gridTemplateColumns: COLUMN_SCHEMA_FORM_GRID_TEMPLATE_COLUMNS,\n py: 2.5,\n }}\n >\n <ColumnHeader>{/* Checkbox */}</ColumnHeader>\n <ColumnHeader sx={{ gridColumn: '2 / span 2' }}>\n Column Name\n </ColumnHeader>\n <ColumnHeader>Column Type</ColumnHeader>\n <ColumnHeader>Size</ColumnHeader>\n <ColumnHeader>Max List Length</ColumnHeader>\n <ColumnHeader>Default Value</ColumnHeader>\n <ColumnHeader>Restrict Values</ColumnHeader>\n <ColumnHeader>Facet</ColumnHeader>\n <ColumnHeader>Sort Facet Values</ColumnHeader>\n <Box\n sx={{\n gridColumn: '1 / span 11',\n backgroundColor: 'grey.300',\n height: '2px',\n }}\n />\n {times(numColumnModels, index => {\n return (\n <TableColumnSchemaFormRow\n entityType={entityType}\n columnModelIndex={index}\n disabled={isSubmitting}\n key={index}\n columnModelValidationErrors={errorsByColumnModel[index]}\n annotationColumnModels={annotationColumnModels}\n originalColumnModels={originalColumnModels}\n />\n )\n })}\n </Box>\n <Box\n sx={{\n display: 'flex',\n gap: 1,\n }}\n >\n <Button\n variant={'outlined'}\n onClick={() => {\n dispatch({ type: 'appendColumn' })\n }}\n startIcon={<AddCircleTwoTone />}\n disabled={isSubmitting}\n >\n Add Column\n </Button>\n\n {hasDefaultColumnModels && (\n <Button\n variant={'outlined'}\n startIcon={\n isLoadingDefaultColumns ? (\n <SynapseSpinner />\n ) : (\n <AddCircleTwoTone />\n )\n }\n onClick={() => {\n addDefaultColumns()\n }}\n disabled={isLoadingDefaultColumns || isSubmitting}\n >\n Add Default {entityTypeToFriendlyName(entityType)} Columns\n </Button>\n )}\n\n {hasAnnotationColumnModels && (\n <Button\n variant={'outlined'}\n startIcon={\n isLoadingAnnotationColumns ? (\n <SynapseSpinner />\n ) : (\n <AddCircleTwoTone />\n )\n }\n disabled={isLoadingAnnotationColumns || isSubmitting}\n onClick={() => {\n addAnnotationColumns()\n }}\n >\n {ADD_ALL_ANNOTATIONS_BUTTON_TEXT}\n </Button>\n )}\n <ImportTableColumnsButton\n onAddColumns={cms => {\n addColumnSet(cms)\n }}\n disabled={isSubmitting}\n />\n </Box>\n </Box>\n )\n}\n\ntype TableColumnSchemaFormRowProps = {\n entityType: EntityType\n columnModelIndex: number\n disabled: boolean\n columnModelValidationErrors: ZodIssue[] | null\n annotationColumnModels?: ColumnModel[]\n originalColumnModels?: ColumnModel[]\n}\n\nfunction TableColumnSchemaFormRow(props: TableColumnSchemaFormRowProps) {\n const {\n columnModelIndex,\n entityType,\n disabled,\n columnModelValidationErrors = null,\n annotationColumnModels,\n originalColumnModels,\n } = props\n const dispatch = useSetAtom(tableColumnSchemaFormDataAtom)\n const columnModel = useAtomValue(\n useMemo(\n () =>\n selectAtom(\n tableColumnSchemaFormDataAtom,\n formData => formData[columnModelIndex],\n isEqual,\n ),\n [columnModelIndex],\n ),\n )\n\n // Find the original column model that matches the current column model by name and type, so we can tell the user what fields have changed\n const originalColumnModel = originalColumnModels\n ? findMatchingColumnModel(originalColumnModels, columnModel)\n : undefined\n\n // Find the closest match between passed column model and current column model with name and type\n const defaultAnnotationModel = annotationColumnModels\n ? findMatchingColumnModel(annotationColumnModels, columnModel)\n : undefined\n\n // Organize the JSON Subcolumn errors into a map of subcolumn index to errors\n const errorsForSubcolumns = useMemo(() => {\n if (columnModelValidationErrors) {\n const errorsForAllSubcolumns = columnModelValidationErrors.filter(e => {\n return e.path[0] === columnModelIndex && e.path[1] == 'jsonSubColumns'\n })\n return groupBy(errorsForAllSubcolumns, e => e.path[2])\n }\n return {}\n }, [columnModelIndex, columnModelValidationErrors])\n\n if (!columnModel) {\n return <></>\n }\n const isDefaultColumn = columnModel.isOriginallyDefaultColumn\n\n return (\n <>\n <ColumnModelForm\n entityType={entityType}\n columnModelIndex={columnModelIndex}\n isDefaultColumn={isDefaultColumn}\n disabled={disabled}\n validationErrors={columnModelValidationErrors}\n defaultAnnotationModel={defaultAnnotationModel}\n originalColumnModel={originalColumnModel}\n />\n {columnModel.columnType === ColumnTypeEnum.JSON &&\n columnModel.jsonSubColumns &&\n columnModel.jsonSubColumns.map((subColumnFacet, index) => (\n <ColumnModelForm\n key={index}\n entityType={entityType}\n columnModelIndex={columnModelIndex}\n jsonSubColumnIndex={index}\n isDefaultColumn={isDefaultColumn}\n disabled={disabled}\n validationErrors={errorsForSubcolumns[index]}\n />\n ))}\n {columnModel.columnType === ColumnTypeEnum.JSON && (\n <>\n <Box\n sx={{\n gridColumn: '1 / span 2',\n }}\n >\n {HIERARCHY_END_COMPONENT}\n </Box>\n <Box\n sx={{\n gridColumn: '3 / span 5',\n }}\n >\n <Button\n startIcon={<AddToList />}\n variant={'text'}\n disabled={disabled}\n onClick={() =>\n dispatch({ type: 'appendJsonSubColumn', columnModelIndex })\n }\n >\n Add sub-column\n </Button>\n </Box>\n </>\n )}\n </>\n )\n}\n\nconst TableColumnSchemaFormInternalWithForwardRef = forwardRef<\n SubmitHandle,\n TableColumnSchemaFormProps\n>(TableColumnSchemaFormInternal)\n\nconst TableColumnSchemaFormWrapped = forwardRef<\n SubmitHandle,\n TableColumnSchemaFormProps\n>(function TableColumnSchemaForm(props: TableColumnSchemaFormProps, ref) {\n // Wrap the form in a Jotai provider so that the internal atoms are scoped to just this component instance\n // Use forwardRef to ensure that the ref can be passed along\n return (\n <Provider>\n <TableColumnSchemaFormInternalWithForwardRef {...props} ref={ref} />\n </Provider>\n )\n})\n\nexport default TableColumnSchemaFormWrapped\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"TableColumnSchemaForm.js","names":[],"sources":["../../../src/components/TableColumnSchemaEditor/TableColumnSchemaForm.tsx"],"sourcesContent":["import AddToList from '@/assets/icons/AddToList'\nimport {\n useGetAnnotationColumnModels,\n useGetDefaultColumnModels,\n} from '@/synapse-queries/table/useColumnModel'\nimport {\n convertToConcreteEntityType,\n entityTypeToFriendlyName,\n} from '@/utils/functions/EntityTypeUtils'\nimport { AddCircleTwoTone } from '@mui/icons-material'\nimport { Box, Button, styled } from '@mui/material'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport {\n ColumnModel,\n ColumnTypeEnum,\n VIEW_CONCRETE_TYPE_VALUES,\n ViewScope,\n} from '@sage-bionetworks/synapse-types'\nimport { atom, Provider, useAtomValue, useSetAtom } from 'jotai'\nimport { selectAtom, useAtomCallback } from 'jotai/utils'\nimport { groupBy, isEqual, noop, omit, times } from 'lodash-es'\nimport pluralize from 'pluralize'\nimport {\n ForwardedRef,\n forwardRef,\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useState,\n} from 'react'\nimport { SetOptional } from 'type-fest'\nimport { ZodError, ZodIssue } from 'zod'\nimport { SynapseSpinner } from '../LoadingScreen/LoadingScreen'\nimport { displayToast } from '../ToastMessage'\nimport ColumnModelForm from './ColumnModelForm'\nimport ImportTableColumnsButton from './ImportTableColumnsButton'\nimport {\n findMatchingColumnModel,\n getAllowedColumnTypes,\n transformColumnModelsToFormData,\n} from './TableColumnSchemaEditorUtils'\nimport { TableColumnSchemaFormActions } from './TableColumnSchemaFormActions'\nimport { tableColumnSchemaFormDataAtom } from './TableColumnSchemaFormReducer'\nimport { validateColumnModelFormData } from './Validators/ColumnModelValidator'\n\nconst COLUMN_SCHEMA_FORM_GRID_TEMPLATE_COLUMNS =\n '18px 18px 1.75fr 1.75fr 0.75fr 1fr 1.25fr 1.25fr 1fr 1.25fr'\nconst GRID_CONTAINER_Y_MARGIN_PX = 6\nexport const HIERARCHY_VERTICAL_LINE_COMPONENT = (\n <Box\n sx={{\n width: '1px',\n height: `calc(100% + 2 * ${GRID_CONTAINER_Y_MARGIN_PX}px)`,\n backgroundColor: 'grey.400',\n marginLeft: '8px',\n }}\n />\n)\nexport const HIERARCHY_END_COMPONENT = (\n <Box\n sx={theme => ({\n width: '50%',\n height: '60%',\n borderLeft: `1px solid ${theme.palette.grey[400]}`,\n borderBottom: `1px solid ${theme.palette.grey[400]}`,\n marginLeft: '8px',\n marginBottom: 'auto',\n marginRight: '0',\n borderBottomLeftRadius: '5px',\n })}\n />\n)\n\nexport type SubmitHandle = {\n // Allow the parent component to trigger a submit of the form, so this may be embedded in an arbitrary modal.\n submit: () => void\n // Imperative handle to get the data out of the form for SWC compatibility\n getEditedColumnModels: () => SetOptional<ColumnModel, 'id'>[]\n // Used to check if all form data is valid. Returns the current validity state\n validate: () => boolean\n}\n\ntype TableColumnSchemaFormProps = {\n /* The type of the Table, which determines various schema restrictions and form functionality */\n entityType: EntityType\n /* If this is an entity view, the ViewScope can be used to determine the default column models and fetch annotation column models */\n viewScope?: ViewScope\n initialData?: SetOptional<ColumnModel, 'id'>[]\n onSubmit?: (newColumnModels: SetOptional<ColumnModel, 'id'>[]) => void\n isSubmitting?: boolean\n /** The original/current column models that are applied to the table, if they exist. */\n originalColumnModels?: ColumnModel[]\n}\n\nconst ColumnHeader = styled(Box, {\n label: 'ColumnHeader',\n})({\n fontWeight: 700,\n})\n\nexport const ADD_ALL_ANNOTATIONS_BUTTON_TEXT = 'Add All Annotations'\n\nfunction TableColumnSchemaFormInternal(\n props: TableColumnSchemaFormProps,\n ref: ForwardedRef<SubmitHandle>,\n) {\n const {\n initialData,\n entityType,\n viewScope,\n onSubmit = noop,\n isSubmitting = false,\n originalColumnModels,\n } = props\n\n const numColumnModels = useAtomValue(\n useMemo(() => atom(get => get(tableColumnSchemaFormDataAtom).length), []),\n )\n\n const dispatch = useSetAtom(tableColumnSchemaFormDataAtom)\n const [validationErrors, setValidationErrors] = useState<ZodError | null>(\n null,\n )\n\n // useAtomCallback will let us imperatively read the form data, instead of tracking it in state and triggering a full re-render of the form when any data changes\n const readFormData = useAtomCallback(\n useCallback(get => {\n return get(tableColumnSchemaFormDataAtom)\n }, []),\n )\n const concreteTableType = convertToConcreteEntityType(entityType)\n const isView = VIEW_CONCRETE_TYPE_VALUES.includes(\n concreteTableType as (typeof VIEW_CONCRETE_TYPE_VALUES)[number],\n )\n const hasAnnotationColumnModels = isView,\n hasDefaultColumnModels = isView\n\n const { data: defaultColumnModels, isLoading: isLoadingDefaultColumns } =\n useGetDefaultColumnModels(\n viewScope?.viewEntityType!,\n viewScope?.viewTypeMask,\n {\n enabled: hasDefaultColumnModels,\n staleTime: Infinity, // The default column models will never change\n },\n )\n\n const annotationColumnModelsQuery = useGetAnnotationColumnModels(\n {\n viewScope: viewScope!,\n includeDerivedAnnotations: true,\n concreteType:\n 'org.sagebionetworks.repo.model.table.ViewColumnModelRequest',\n },\n {\n enabled: hasAnnotationColumnModels,\n },\n )\n\n const {\n data: annotationColumnModels,\n isLoading: isLoadingAnnotationColumns,\n } = annotationColumnModelsQuery\n\n /**\n * Set the initialData in the form state atom on mount, if it exists and we have no data.\n */\n useEffect(() => {\n if (initialData && !isLoadingDefaultColumns) {\n dispatch({\n type: 'setValue',\n value: transformColumnModelsToFormData(\n initialData,\n defaultColumnModels ?? [],\n ),\n })\n }\n // Don't re-run if initial data changes\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [isLoadingDefaultColumns])\n\n const validateInternal = useCallback(() => {\n const result = validateColumnModelFormData(readFormData())\n if (result.success) {\n setValidationErrors(null)\n } else {\n setValidationErrors(result.error)\n }\n return result\n }, [readFormData])\n\n useImperativeHandle(ref, () => {\n return {\n submit() {\n const result = validateInternal()\n if (result.success) {\n onSubmit(result.data)\n }\n },\n getEditedColumnModels() {\n const result = validateInternal()\n if (!result.success) {\n throw new Error('Column models were not valid')\n }\n return result.data\n },\n validate() {\n const result = validateInternal()\n return result.success\n },\n }\n }, [onSubmit, validateInternal])\n\n // Generic function to add a set of columns to the schema (e.g. default columns, annotation columns)\n const addColumnSet = useCallback(\n (newColumns: SetOptional<ColumnModel, 'id'>[]) => {\n const currentFormData = readFormData()\n let columnsToAdd = newColumns.filter(\n (cm: SetOptional<ColumnModel, 'id'>) => {\n // Don't add columns that cannot be added (for example, Views cannot have JSON columns)\n if (\n !getAllowedColumnTypes(isView, false).includes(\n cm.columnType as ColumnTypeEnum,\n )\n ) {\n return false\n }\n // Only add columns that are not already present\n // Use the name column to match because the ID may vary, e.g. if the facetType was changed\n return !currentFormData.find(fd => fd.name === cm.name)\n },\n )\n // Remove the ID column so TableColumnSchemaUtils.createTableUpdateTransactionRequest recognizes these as new columns\n // createTableUpdateTransactionRequest uses the existing column ID to track column updates in the table, so any new columns should have no ID\n // The user can also modify these columns before submitting them, so the ID may not be accurate when we end up submitting them\n columnsToAdd = columnsToAdd.map(cm => omit(cm, ['id']))\n if (columnsToAdd.length > 0) {\n dispatch({\n type: 'setValue',\n value: [\n ...currentFormData,\n ...transformColumnModelsToFormData(\n columnsToAdd,\n defaultColumnModels,\n ),\n ],\n })\n displayToast(\n `${columnsToAdd.length} ${pluralize(\n 'column',\n columnsToAdd.length,\n )} added to schema.`,\n 'success',\n )\n } else {\n displayToast(\n 'No columns added. All columns to add are present in the column schema.',\n 'info',\n )\n }\n },\n [defaultColumnModels, dispatch, isView, readFormData],\n )\n\n const addDefaultColumns = useCallback(() => {\n if (defaultColumnModels) {\n addColumnSet(defaultColumnModels)\n }\n }, [defaultColumnModels, addColumnSet])\n\n const addAnnotationColumns = useCallback(() => {\n if (annotationColumnModels) {\n addColumnSet(annotationColumnModels)\n }\n }, [annotationColumnModels, addColumnSet])\n\n // Put the errors into a map of columnModelIndex -> errors\n const errorsByColumnModel = useMemo(() => {\n if (validationErrors) {\n return groupBy(validationErrors.issues, e => e.path[0])\n }\n return {}\n }, [validationErrors])\n\n return (\n <Box\n component={'form'}\n sx={{\n py: 2.5,\n }}\n >\n <TableColumnSchemaFormActions\n disabled={isSubmitting}\n annotationColumnModelsQuery={annotationColumnModelsQuery}\n />\n <Box\n sx={{\n display: 'grid',\n gap: '8px',\n gridTemplateColumns: COLUMN_SCHEMA_FORM_GRID_TEMPLATE_COLUMNS,\n py: 2.5,\n }}\n >\n <ColumnHeader>{/* Checkbox */}</ColumnHeader>\n <ColumnHeader sx={{ gridColumn: '2 / span 2' }}>\n Column Name\n </ColumnHeader>\n <ColumnHeader>Column Type</ColumnHeader>\n <ColumnHeader>Size</ColumnHeader>\n <ColumnHeader>Max List Length</ColumnHeader>\n <ColumnHeader>Default Value</ColumnHeader>\n <ColumnHeader>Restrict Values</ColumnHeader>\n <ColumnHeader>Facet</ColumnHeader>\n <ColumnHeader>Sort Facet Values</ColumnHeader>\n <Box\n sx={{\n gridColumn: '1 / span 11',\n backgroundColor: 'grey.300',\n height: '2px',\n }}\n />\n {times(numColumnModels, index => {\n return (\n <TableColumnSchemaFormRow\n entityType={entityType}\n columnModelIndex={index}\n disabled={isSubmitting}\n key={index}\n columnModelValidationErrors={errorsByColumnModel[index]}\n annotationColumnModels={annotationColumnModels}\n originalColumnModels={originalColumnModels}\n />\n )\n })}\n </Box>\n <Box\n sx={{\n display: 'flex',\n gap: 1,\n }}\n >\n <Button\n variant={'outlined'}\n onClick={() => {\n dispatch({ type: 'appendColumn' })\n }}\n startIcon={<AddCircleTwoTone />}\n disabled={isSubmitting}\n >\n Add Column\n </Button>\n\n {hasDefaultColumnModels && (\n <Button\n variant={'outlined'}\n startIcon={\n isLoadingDefaultColumns ? (\n <SynapseSpinner />\n ) : (\n <AddCircleTwoTone />\n )\n }\n onClick={() => {\n addDefaultColumns()\n }}\n disabled={isLoadingDefaultColumns || isSubmitting}\n >\n Add Default {entityTypeToFriendlyName(entityType)} Columns\n </Button>\n )}\n\n {hasAnnotationColumnModels && (\n <Button\n variant={'outlined'}\n startIcon={\n isLoadingAnnotationColumns ? (\n <SynapseSpinner />\n ) : (\n <AddCircleTwoTone />\n )\n }\n disabled={isLoadingAnnotationColumns || isSubmitting}\n onClick={() => {\n addAnnotationColumns()\n }}\n >\n {ADD_ALL_ANNOTATIONS_BUTTON_TEXT}\n </Button>\n )}\n <ImportTableColumnsButton\n onAddColumns={cms => {\n addColumnSet(cms)\n }}\n disabled={isSubmitting}\n />\n </Box>\n </Box>\n )\n}\n\ntype TableColumnSchemaFormRowProps = {\n entityType: EntityType\n columnModelIndex: number\n disabled: boolean\n columnModelValidationErrors: ZodIssue[] | null\n annotationColumnModels?: ColumnModel[]\n originalColumnModels?: ColumnModel[]\n}\n\nfunction TableColumnSchemaFormRow(props: TableColumnSchemaFormRowProps) {\n const {\n columnModelIndex,\n entityType,\n disabled,\n columnModelValidationErrors = null,\n annotationColumnModels,\n originalColumnModels,\n } = props\n const dispatch = useSetAtom(tableColumnSchemaFormDataAtom)\n const columnModel = useAtomValue(\n useMemo(\n () =>\n selectAtom(\n tableColumnSchemaFormDataAtom,\n formData => formData[columnModelIndex],\n isEqual,\n ),\n [columnModelIndex],\n ),\n )\n\n // Find the original column model that matches the current column model by name and type, so we can tell the user what fields have changed\n const originalColumnModel = originalColumnModels\n ? findMatchingColumnModel(originalColumnModels, columnModel)\n : undefined\n\n // Find the closest match between passed column model and current column model with name and type\n const defaultAnnotationModel = annotationColumnModels\n ? findMatchingColumnModel(annotationColumnModels, columnModel)\n : undefined\n\n // Organize the JSON Subcolumn errors into a map of subcolumn index to errors\n const errorsForSubcolumns = useMemo(() => {\n if (columnModelValidationErrors) {\n const errorsForAllSubcolumns = columnModelValidationErrors.filter(e => {\n return e.path[0] === columnModelIndex && e.path[1] == 'jsonSubColumns'\n })\n return groupBy(errorsForAllSubcolumns, e => e.path[2])\n }\n return {}\n }, [columnModelIndex, columnModelValidationErrors])\n\n if (!columnModel) {\n return <></>\n }\n const isDefaultColumn = columnModel.isOriginallyDefaultColumn\n\n return (\n <>\n <ColumnModelForm\n entityType={entityType}\n columnModelIndex={columnModelIndex}\n isDefaultColumn={isDefaultColumn}\n disabled={disabled}\n validationErrors={columnModelValidationErrors}\n defaultAnnotationModel={defaultAnnotationModel}\n originalColumnModel={originalColumnModel}\n />\n {columnModel.columnType === ColumnTypeEnum.JSON &&\n columnModel.jsonSubColumns &&\n columnModel.jsonSubColumns.map((subColumnFacet, index) => (\n <ColumnModelForm\n key={index}\n entityType={entityType}\n columnModelIndex={columnModelIndex}\n jsonSubColumnIndex={index}\n isDefaultColumn={isDefaultColumn}\n disabled={disabled}\n validationErrors={errorsForSubcolumns[index]}\n />\n ))}\n {columnModel.columnType === ColumnTypeEnum.JSON && (\n <>\n <Box\n sx={{\n gridColumn: '1 / span 2',\n }}\n >\n {HIERARCHY_END_COMPONENT}\n </Box>\n <Box\n sx={{\n gridColumn: '3 / span 5',\n }}\n >\n <Button\n startIcon={<AddToList />}\n variant={'text'}\n disabled={disabled}\n onClick={() =>\n dispatch({ type: 'appendJsonSubColumn', columnModelIndex })\n }\n >\n Add sub-column\n </Button>\n </Box>\n </>\n )}\n </>\n )\n}\n\nconst TableColumnSchemaFormInternalWithForwardRef = forwardRef<\n SubmitHandle,\n TableColumnSchemaFormProps\n>(TableColumnSchemaFormInternal)\n\nconst TableColumnSchemaFormWrapped = forwardRef<\n SubmitHandle,\n TableColumnSchemaFormProps\n>(function TableColumnSchemaForm(props: TableColumnSchemaFormProps, ref) {\n // Wrap the form in a Jotai provider so that the internal atoms are scoped to just this component instance\n // Use forwardRef to ensure that the ref can be passed along\n return (\n <Provider>\n <TableColumnSchemaFormInternalWithForwardRef {...props} ref={ref} />\n </Provider>\n )\n})\n\nexport default TableColumnSchemaFormWrapped\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StyledTanStackTable.js","names":[],"sources":["../../../src/components/TanStackTable/StyledTanStackTable.tsx"],"sourcesContent":["import {\n StyledTableContainer,\n StyledTableContainerProps,\n} from '@/components/styled/StyledTableContainer'\nimport {\n ColumnSizingState,\n flexRender,\n Row,\n Table,\n} from '@tanstack/react-table'\nimport { useLayoutEffect, useMemo, useRef, useState } from 'react'\nimport { MemoizedTableBody, TableBody, TableBodyProps } from './TableBody'\nimport {\n getColumnSizeCssVariable,\n getCommonPinningStyles,\n getHeaderSizeCssVariable,\n} from './TanStackTableUtils'\nimport { StyledTanStackTableSlotProps, StyledTanStackTableSlots } from './types'\n\nexport type StyledTanStackTableProps<TData = unknown, TRowType = Row<TData>> = {\n table: Table<TData>\n styledTableContainerProps?: StyledTableContainerProps\n fullWidth?: boolean\n /**\n * When true, uses `table-layout: auto` so the browser sizes columns to fit their content.\n * Column sizes from TanStack act as minimum widths, and columns expand to fit wider content.\n * Resizing still works — dragging a column sets a new minimum width.\n * Defaults to false (fixed layout).\n */\n autoColumnSizing?: boolean\n slots?: StyledTanStackTableSlots<TData, TRowType>\n slotProps?: StyledTanStackTableSlotProps<TData, TRowType>\n} & Pick<TableBodyProps<TData, TRowType>, 'rows' | 'rowTransform'>\n\n/**\n * Component that renders a styled table using @tanstack/react-table. Pass your table instance into the component and\n * it will render the table with the appropriate headers and rows.\n */\nexport default function StyledTanStackTable<\n TData = unknown,\n TRowType = Row<TData>,\n>(props: StyledTanStackTableProps<TData, TRowType>) {\n const {\n table,\n styledTableContainerProps,\n fullWidth = true,\n autoColumnSizing = false,\n slots = {},\n slotProps = {},\n } = props\n const {\n Thead = 'thead',\n Th = 'th',\n Table = 'table',\n ...tableBodySlots\n } = slots\n const {\n Table: tableSlotProps = {},\n Thead: theadSlotProps = {},\n Th: thSlotProps = {},\n ...tableBodySlotProps\n } = slotProps\n\n const tableBodyProps: TableBodyProps<TData, TRowType> = {\n table,\n slots: tableBodySlots,\n slotProps: tableBodySlotProps,\n rows: props.rows!,\n rowTransform: props.rowTransform!,\n }\n\n /**\n * When autoColumnSizing is true we do a two-phase render:\n * 1. Render with table-layout: auto and no width constraints so the browser\n * natively sizes each column to its content.\n * 2. After the first paint, measure each <th> and write the real pixel widths\n * into TanStack's columnSizing state, then switch to table-layout: fixed.\n *\n * This gives content-based initial sizes while still allowing the user to drag\n * a column narrower than its content.\n */\n const tableRef = useRef<HTMLTableElement>(null)\n\n // Track what columns+visibility combination was last measured so we know when\n // a re-measurement is needed (e.g. columns change or visibility is toggled).\n const columnVisibility = table.getState().columnVisibility\n const [lastMeasuredState, setLastMeasuredState] = useState<{\n columns: typeof table.options.columns\n columnVisibility: typeof columnVisibility\n } | null>(null)\n\n // hasMeasured is true when auto-sizing is disabled (nothing to do) OR when the\n // current columns+visibility matches the last measured snapshot.\n const hasMeasured =\n !autoColumnSizing ||\n (lastMeasuredState?.columns === table.options.columns &&\n lastMeasuredState?.columnVisibility === columnVisibility)\n\n // After the browser has laid out the auto-sized table, capture the widths and\n // switch to fixed layout so that the user can resize below the content width.\n // useLayoutEffect runs synchronously after DOM mutations, before the browser\n // paints, which is required for accurate DOM measurements.\n useLayoutEffect(() => {\n if (!autoColumnSizing || hasMeasured || !tableRef.current) return\n\n const ths = tableRef.current.querySelectorAll(\n ':scope > thead > tr:first-child > th',\n )\n const headers = table.getFlatHeaders()\n const newSizes: ColumnSizingState = {}\n headers.forEach((header, i) => {\n const th = ths[i] as HTMLElement | undefined\n if (th) {\n newSizes[header.column.id] = Math.ceil(th.getBoundingClientRect().width)\n }\n })\n if (Object.keys(newSizes).length > 0) {\n table.setColumnSizing(newSizes)\n setLastMeasuredState({\n columns: table.options.columns,\n columnVisibility,\n })\n }\n }, [autoColumnSizing, columnVisibility, hasMeasured, table])\n\n /**\n * Instead of calling `column.getSize()` on every render for every header\n * and especially every data cell (very expensive),\n * we will calculate all column sizes at once at the root table level in a useMemo\n * and pass the column sizes down as CSS variables to the <table> element.\n *\n * See https://tanstack.com/table/v8/docs/guide/column-sizing#advanced-column-resizing-performance\n */\n const columnSizeVars = useMemo(() => {\n const headers = table.getFlatHeaders()\n const colSizes: { [key: string]: number } = {}\n for (let i = 0; i < headers.length; i++) {\n const header = headers[i]\n colSizes[getHeaderSizeCssVariable(header.id)] = header.getSize()\n colSizes[getColumnSizeCssVariable(header.column.id)] =\n header.column.getSize()\n }\n return colSizes\n // Intentionally limit the dependencies to only recompute when the column sizes change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n table.getState().columnSizingInfo,\n table.getState().columnSizing,\n table.getState().columnVisibility, // If a column is added, its width should be recalculated\n ])\n\n // Before measurement: let the browser shrink the table to content width so we\n // can read accurate sizes. After measurement: use the exact measured total.\n let tableWidth: string\n if (fullWidth) {\n tableWidth = '100%'\n } else if (!hasMeasured) {\n tableWidth = 'max-content'\n } else {\n tableWidth = `${table.getTotalSize()}px`\n }\n\n /* When resizing any column we will render this special memoized version of our table body */\n const TableBodyElement = table.getState().columnSizingInfo.isResizingColumn\n ? MemoizedTableBody\n : TableBody\n\n return (\n <StyledTableContainer {...styledTableContainerProps}>\n <Table\n {...tableSlotProps}\n ref={tableRef}\n style={{\n ...columnSizeVars,\n // Pre-measurement: auto layout so the browser can size columns to content.\n // Post-measurement: fixed layout so the user can resize below content width.\n tableLayout: hasMeasured ? 'fixed' : 'auto',\n width: tableWidth,\n ...tableSlotProps['style'],\n }}\n >\n <Thead {...theadSlotProps}>\n {table.getHeaderGroups().map(headerGroup => {\n return (\n <tr key={headerGroup.id}>\n {headerGroup.headers.map(header => (\n <Th\n key={header.id}\n colSpan={header.colSpan}\n {...thSlotProps}\n style={{\n // Pre-measurement: no width hint — let the browser size freely.\n // Post-measurement: exact width from the measured/resized value.\n ...(hasMeasured && {\n width: `calc(var(${getHeaderSizeCssVariable(\n header.id,\n )}) * 1px)`,\n }),\n ...getCommonPinningStyles(header.column),\n ...thSlotProps['style'],\n }}\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n {header.column.getCanResize() && (\n <div\n className={`resizer ${\n header.column.getIsResizing() ? 'isResizing' : ''\n }`}\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n />\n )}\n </Th>\n ))}\n </tr>\n )\n })}\n </Thead>\n <TableBodyElement<TData, TRowType> {...tableBodyProps} />\n </Table>\n </StyledTableContainer>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"StyledTanStackTable.js","names":[],"sources":["../../../src/components/TanStackTable/StyledTanStackTable.tsx"],"sourcesContent":["import {\n StyledTableContainer,\n StyledTableContainerProps,\n} from '@/components/styled/StyledTableContainer'\nimport {\n ColumnSizingState,\n flexRender,\n Row,\n Table,\n} from '@tanstack/react-table'\nimport { useLayoutEffect, useMemo, useRef, useState } from 'react'\nimport { MemoizedTableBody, TableBody, TableBodyProps } from './TableBody'\nimport {\n getColumnSizeCssVariable,\n getCommonPinningStyles,\n getHeaderSizeCssVariable,\n} from './TanStackTableUtils'\nimport { StyledTanStackTableSlotProps, StyledTanStackTableSlots } from './types'\n\nexport type StyledTanStackTableProps<TData = unknown, TRowType = Row<TData>> = {\n table: Table<TData>\n styledTableContainerProps?: StyledTableContainerProps\n fullWidth?: boolean\n /**\n * When true, uses `table-layout: auto` so the browser sizes columns to fit their content.\n * Column sizes from TanStack act as minimum widths, and columns expand to fit wider content.\n * Resizing still works — dragging a column sets a new minimum width.\n * Defaults to false (fixed layout).\n */\n autoColumnSizing?: boolean\n slots?: StyledTanStackTableSlots<TData, TRowType>\n slotProps?: StyledTanStackTableSlotProps<TData, TRowType>\n} & Pick<TableBodyProps<TData, TRowType>, 'rows' | 'rowTransform'>\n\n/**\n * Component that renders a styled table using @tanstack/react-table. Pass your table instance into the component and\n * it will render the table with the appropriate headers and rows.\n */\nexport default function StyledTanStackTable<\n TData = unknown,\n TRowType = Row<TData>,\n>(props: StyledTanStackTableProps<TData, TRowType>) {\n const {\n table,\n styledTableContainerProps,\n fullWidth = true,\n autoColumnSizing = false,\n slots = {},\n slotProps = {},\n } = props\n const {\n Thead = 'thead',\n Th = 'th',\n Table = 'table',\n ...tableBodySlots\n } = slots\n const {\n Table: tableSlotProps = {},\n Thead: theadSlotProps = {},\n Th: thSlotProps = {},\n ...tableBodySlotProps\n } = slotProps\n\n const tableBodyProps: TableBodyProps<TData, TRowType> = {\n table,\n slots: tableBodySlots,\n slotProps: tableBodySlotProps,\n rows: props.rows!,\n rowTransform: props.rowTransform!,\n }\n\n /**\n * When autoColumnSizing is true we do a two-phase render:\n * 1. Render with table-layout: auto and no width constraints so the browser\n * natively sizes each column to its content.\n * 2. After the first paint, measure each <th> and write the real pixel widths\n * into TanStack's columnSizing state, then switch to table-layout: fixed.\n *\n * This gives content-based initial sizes while still allowing the user to drag\n * a column narrower than its content.\n */\n const tableRef = useRef<HTMLTableElement>(null)\n\n // Track what columns+visibility combination was last measured so we know when\n // a re-measurement is needed (e.g. columns change or visibility is toggled).\n const columnVisibility = table.getState().columnVisibility\n const [lastMeasuredState, setLastMeasuredState] = useState<{\n columns: typeof table.options.columns\n columnVisibility: typeof columnVisibility\n } | null>(null)\n\n // hasMeasured is true when auto-sizing is disabled (nothing to do) OR when the\n // current columns+visibility matches the last measured snapshot.\n const hasMeasured =\n !autoColumnSizing ||\n (lastMeasuredState?.columns === table.options.columns &&\n lastMeasuredState?.columnVisibility === columnVisibility)\n\n // After the browser has laid out the auto-sized table, capture the widths and\n // switch to fixed layout so that the user can resize below the content width.\n // useLayoutEffect runs synchronously after DOM mutations, before the browser\n // paints, which is required for accurate DOM measurements.\n useLayoutEffect(() => {\n if (!autoColumnSizing || hasMeasured || !tableRef.current) return\n\n const ths = tableRef.current.querySelectorAll(\n ':scope > thead > tr:first-child > th',\n )\n const headers = table.getFlatHeaders()\n const newSizes: ColumnSizingState = {}\n headers.forEach((header, i) => {\n const th = ths[i] as HTMLElement | undefined\n if (th) {\n newSizes[header.column.id] = Math.ceil(th.getBoundingClientRect().width)\n }\n })\n if (Object.keys(newSizes).length > 0) {\n table.setColumnSizing(newSizes)\n setLastMeasuredState({\n columns: table.options.columns,\n columnVisibility,\n })\n }\n }, [autoColumnSizing, columnVisibility, hasMeasured, table])\n\n /**\n * Instead of calling `column.getSize()` on every render for every header\n * and especially every data cell (very expensive),\n * we will calculate all column sizes at once at the root table level in a useMemo\n * and pass the column sizes down as CSS variables to the <table> element.\n *\n * See https://tanstack.com/table/v8/docs/guide/column-sizing#advanced-column-resizing-performance\n */\n const columnSizeVars = useMemo(() => {\n const headers = table.getFlatHeaders()\n const colSizes: { [key: string]: number } = {}\n for (let i = 0; i < headers.length; i++) {\n const header = headers[i]\n colSizes[getHeaderSizeCssVariable(header.id)] = header.getSize()\n colSizes[getColumnSizeCssVariable(header.column.id)] =\n header.column.getSize()\n }\n return colSizes\n // Intentionally limit the dependencies to only recompute when the column sizes change\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [\n table.getState().columnSizingInfo,\n table.getState().columnSizing,\n table.getState().columnVisibility, // If a column is added, its width should be recalculated\n ])\n\n // Before measurement: let the browser shrink the table to content width so we\n // can read accurate sizes. After measurement: use the exact measured total.\n let tableWidth: string\n if (fullWidth) {\n tableWidth = '100%'\n } else if (!hasMeasured) {\n tableWidth = 'max-content'\n } else {\n tableWidth = `${table.getTotalSize()}px`\n }\n\n /* When resizing any column we will render this special memoized version of our table body */\n const TableBodyElement = table.getState().columnSizingInfo.isResizingColumn\n ? MemoizedTableBody\n : TableBody\n\n return (\n <StyledTableContainer {...styledTableContainerProps}>\n <Table\n {...tableSlotProps}\n ref={tableRef}\n style={{\n ...columnSizeVars,\n // Pre-measurement: auto layout so the browser can size columns to content.\n // Post-measurement: fixed layout so the user can resize below content width.\n tableLayout: hasMeasured ? 'fixed' : 'auto',\n width: tableWidth,\n ...tableSlotProps['style'],\n }}\n >\n <Thead {...theadSlotProps}>\n {table.getHeaderGroups().map(headerGroup => {\n return (\n <tr key={headerGroup.id}>\n {headerGroup.headers.map(header => (\n <Th\n key={header.id}\n colSpan={header.colSpan}\n {...thSlotProps}\n style={{\n // Pre-measurement: no width hint — let the browser size freely.\n // Post-measurement: exact width from the measured/resized value.\n ...(hasMeasured && {\n width: `calc(var(${getHeaderSizeCssVariable(\n header.id,\n )}) * 1px)`,\n }),\n ...getCommonPinningStyles(header.column),\n ...thSlotProps['style'],\n }}\n >\n {header.isPlaceholder\n ? null\n : flexRender(\n header.column.columnDef.header,\n header.getContext(),\n )}\n {header.column.getCanResize() && (\n <div\n className={`resizer ${\n header.column.getIsResizing() ? 'isResizing' : ''\n }`}\n onMouseDown={header.getResizeHandler()}\n onTouchStart={header.getResizeHandler()}\n />\n )}\n </Th>\n ))}\n </tr>\n )\n })}\n </Thead>\n <TableBodyElement<TData, TRowType> {...tableBodyProps} />\n </Table>\n </StyledTableContainer>\n )\n}\n"],"mappings":"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"}
|
|
@@ -34,6 +34,8 @@ export type EntityActionMenuLayout = {
|
|
|
34
34
|
primaryMenuEndIcon: IconName;
|
|
35
35
|
primaryMenuEndIconSx?: SxProps;
|
|
36
36
|
menuButtonSx?: SystemStyleObject<Theme>;
|
|
37
|
+
downloadMenuVariant?: ButtonProps['variant'];
|
|
38
|
+
primaryMenuVariant?: ButtonProps['variant'];
|
|
37
39
|
};
|
|
38
40
|
export type EntityActionMenuProps = {
|
|
39
41
|
actionConfiguration: ActionConfigurationMap;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityActionMenu.d.ts","sourceRoot":"","sources":["../../../../../src/components/entity/page/action_menu/EntityActionMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAEhE,OAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAqB,MAAM,4BAA4B,CAAA;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;
|
|
1
|
+
{"version":3,"file":"EntityActionMenu.d.ts","sourceRoot":"","sources":["../../../../../src/components/entity/page/action_menu/EntityActionMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAO,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAA;AAEhE,OAAgB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAA;AAC5D,OAAO,EAAE,gBAAgB,EAAqB,MAAM,4BAA4B,CAAA;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAClC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAK/C,KAAK,gCAAgC,GAAG,UAAU,GAAG,SAAS,CAAA;AAE9D,KAAK,yCAAyC,GAAG;IAC/C,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,OAAO,EAAE,OAAO,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,UAAU,KAAK,IAAI,CAAA;IACjC,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAA;AAExE,MAAM,MAAM,oBAAoB,GAAG,MAAM,CACvC,gCAAgC,EAChC,yCAAyC,CAC1C,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,OAAO,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;CACjC,CAAA;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,aAAa,EAAE,eAAe,EAAE,CAAA;IAChC,mBAAmB,EAAE,eAAe,EAAE,EAAE,CAAA;IACxC,kBAAkB,EAAE,eAAe,EAAE,EAAE,CAAA;IACvC,eAAe,EAAE,MAAM,CAAA;IACvB,kBAAkB,EAAE,QAAQ,CAAA;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAA;IAC9B,YAAY,CAAC,EAAE,iBAAiB,CAAC,KAAK,CAAC,CAAA;IACvC,mBAAmB,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;IAC5C,kBAAkB,CAAC,EAAE,WAAW,CAAC,SAAS,CAAC,CAAA;CAC5C,CAAA;AAED,MAAM,MAAM,qBAAqB,GAAG;IAElC,mBAAmB,EAAE,sBAAsB,CAAA;IAE3C,iBAAiB,EAAE,oBAAoB,CAAA;IAEvC,MAAM,EAAE,sBAAsB,CAAA;CAC/B,CAAA;AAMD,iBAAS,kBAAkB,CACzB,mBAAmB,EAAE,sBAAsB,EAC3C,MAAM,EAAE,sBAAsB,GAC7B,sBAAsB,CAmBxB;AAED;;;;;GAKG;AACH,iBAAS,4BAA4B,CACnC,KAAK,EAAE,eAAe,EAAE,EACxB,mBAAmB,EAAE,sBAAsB,GAC1C,gBAAgB,EAAE,CAqBpB;AAED;;;;;;GAMG;AACH,iBAAS,sBAAsB,CAC7B,WAAW,EAAE,eAAe,EAAE,EAAE,EAChC,mBAAmB,EAAE,sBAAsB,GAC1C,gBAAgB,EAAE,EAAE,CAYtB;AAED;;GAEG;AACH,MAAM,CAAC,OAAO,UAAU,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,2CAoIpE;AAED,eAAO,MAAM,yBAAyB;;;;CAIrC,CAAA"}
|
|
@@ -3,14 +3,15 @@ import { ComplexMenu as t } from "../../../menu/ComplexMenu.js";
|
|
|
3
3
|
import { Box as n } from "@mui/material";
|
|
4
4
|
import { jsx as r } from "react/jsx-runtime";
|
|
5
5
|
//#region src/components/entity/page/action_menu/EntityActionMenu.tsx
|
|
6
|
-
|
|
6
|
+
var i = "rgba(0, 0, 0, 0.01)";
|
|
7
|
+
function a(e, t) {
|
|
7
8
|
return Object.fromEntries(Object.entries(e).filter(([e, n]) => !n || !n.visible ? !1 : ![
|
|
8
9
|
...t.buttonActions,
|
|
9
10
|
...t.primaryMenuActions.flat(),
|
|
10
11
|
...t.downloadMenuActions.flat()
|
|
11
12
|
].some((t) => t.action === e)));
|
|
12
13
|
}
|
|
13
|
-
function
|
|
14
|
+
function o(e, t) {
|
|
14
15
|
return e.reduce((e, n) => {
|
|
15
16
|
let r = t[n.action];
|
|
16
17
|
return r && r.visible && e.push({
|
|
@@ -25,17 +26,17 @@ function a(e, t) {
|
|
|
25
26
|
}), e;
|
|
26
27
|
}, []);
|
|
27
28
|
}
|
|
28
|
-
function
|
|
29
|
+
function s(e, t) {
|
|
29
30
|
return e.reduce((e, n) => {
|
|
30
|
-
let r =
|
|
31
|
+
let r = o(n, t);
|
|
31
32
|
return r.length > 0 && e.push(r), e;
|
|
32
33
|
}, []);
|
|
33
34
|
}
|
|
34
|
-
function
|
|
35
|
-
let { actionConfiguration:
|
|
36
|
-
Object.entries(
|
|
37
|
-
let
|
|
38
|
-
let n =
|
|
35
|
+
function c(o) {
|
|
36
|
+
let { actionConfiguration: c, menuConfiguration: l, layout: u } = o, d = a(c, u);
|
|
37
|
+
Object.entries(d).length > 0 && (console.warn("Actions are visible but have not been configured in the layout:", Object.entries(d).map((e) => e[0])), u.primaryMenuActions.unshift(Object.entries(d).map((e) => ({ action: e[0] }))));
|
|
38
|
+
let f = u.buttonActions.reduce((e, t) => {
|
|
39
|
+
let n = c[t.action];
|
|
39
40
|
if (n && n.visible) {
|
|
40
41
|
let r = n.onClick;
|
|
41
42
|
r == null && !n.href && (console.warn(`No handler registered for ${t.action}`), r = () => {
|
|
@@ -52,51 +53,69 @@ function s(a) {
|
|
|
52
53
|
});
|
|
53
54
|
}
|
|
54
55
|
return e;
|
|
55
|
-
}, []),
|
|
56
|
+
}, []), p = {
|
|
56
57
|
dropdownButtonText: "Download Options",
|
|
57
58
|
convertSingleItemToButton: !0,
|
|
58
59
|
renderMenuIfNoItems: !1,
|
|
59
|
-
|
|
60
|
+
variant: u.downloadMenuVariant,
|
|
61
|
+
buttonTooltip: l.DOWNLOAD.tooltipText,
|
|
60
62
|
buttonProps: {
|
|
61
|
-
disabled:
|
|
63
|
+
disabled: l.DOWNLOAD.disabled,
|
|
62
64
|
endIcon: /* @__PURE__ */ r(e, {
|
|
63
65
|
icon: "expandMore",
|
|
64
66
|
wrap: !1
|
|
65
67
|
})
|
|
66
68
|
},
|
|
67
|
-
items:
|
|
68
|
-
},
|
|
69
|
-
dropdownButtonText:
|
|
69
|
+
items: s(u.downloadMenuActions, c)
|
|
70
|
+
}, m = {
|
|
71
|
+
dropdownButtonText: u.primaryMenuText,
|
|
70
72
|
convertSingleItemToButton: !0,
|
|
71
73
|
renderMenuIfNoItems: !1,
|
|
74
|
+
variant: u.primaryMenuVariant,
|
|
72
75
|
buttonProps: { endIcon: /* @__PURE__ */ r(e, {
|
|
73
|
-
icon:
|
|
76
|
+
icon: u.primaryMenuEndIcon,
|
|
74
77
|
wrap: !1,
|
|
75
|
-
sx:
|
|
78
|
+
sx: u.primaryMenuEndIconSx
|
|
76
79
|
}) },
|
|
77
|
-
items:
|
|
78
|
-
};
|
|
80
|
+
items: s(u.primaryMenuActions, c)
|
|
81
|
+
}, h = u.menuButtonSx ?? {};
|
|
79
82
|
return /* @__PURE__ */ r(n, {
|
|
80
83
|
sx: {
|
|
81
84
|
"& .MuiButton-root": {
|
|
82
85
|
borderRadius: "6px",
|
|
83
86
|
fontWeight: 540,
|
|
84
|
-
|
|
87
|
+
padding: "10px 12px"
|
|
88
|
+
},
|
|
89
|
+
"& .MuiButton-outlined": {
|
|
90
|
+
...h,
|
|
91
|
+
"&:hover": {
|
|
92
|
+
...h,
|
|
93
|
+
backgroundColor: i
|
|
94
|
+
}
|
|
95
|
+
},
|
|
96
|
+
"& .MuiButton-text": {
|
|
97
|
+
...h,
|
|
98
|
+
backgroundColor: "transparent",
|
|
99
|
+
"&:hover": {
|
|
100
|
+
...h,
|
|
101
|
+
backgroundColor: i,
|
|
102
|
+
textDecoration: "none"
|
|
103
|
+
}
|
|
85
104
|
},
|
|
86
105
|
"& .MuiButton-root .MuiTypography-root": { fontWeight: 540 }
|
|
87
106
|
},
|
|
88
107
|
children: /* @__PURE__ */ r(t, {
|
|
89
|
-
iconButtons:
|
|
90
|
-
dropdownMenus: [
|
|
108
|
+
iconButtons: f,
|
|
109
|
+
dropdownMenus: [m, p]
|
|
91
110
|
})
|
|
92
111
|
});
|
|
93
112
|
}
|
|
94
|
-
var
|
|
95
|
-
getUnmappedActions:
|
|
96
|
-
mapAndFilterItemsInMenuGroup:
|
|
97
|
-
mapAndFilterMenuGroups:
|
|
113
|
+
var l = {
|
|
114
|
+
getUnmappedActions: a,
|
|
115
|
+
mapAndFilterItemsInMenuGroup: o,
|
|
116
|
+
mapAndFilterMenuGroups: s
|
|
98
117
|
};
|
|
99
118
|
//#endregion
|
|
100
|
-
export {
|
|
119
|
+
export { l as EXPORTED_FOR_UNIT_TESTING, c as default };
|
|
101
120
|
|
|
102
121
|
//# sourceMappingURL=EntityActionMenu.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityActionMenu.js","names":[],"sources":["../../../../../src/components/entity/page/action_menu/EntityActionMenu.tsx"],"sourcesContent":["import { Box, ButtonProps, SxProps, Theme } from '@mui/material'\nimport { ComplexMenu, ComplexMenuButtonProps } from '../../../menu/ComplexMenu'\nimport IconSvg, { IconName } from '../../../IconSvg/IconSvg'\nimport { DropdownMenuItem, DropdownMenuProps } from '../../../menu/DropdownMenu'\nimport { MouseEvent } from 'react'\nimport { SystemStyleObject } from '@mui/system'\n\n// Represents the two types of dropdown menus that will be displayed on the entity page\ntype EntityActionMenuDropdownMenuType = 'DOWNLOAD' | 'PRIMARY'\n\ntype EntityActionMenuDropdownMenuConfiguration = {\n visible: boolean\n tooltipText?: string\n disabled?: boolean\n}\n\nexport type ActionConfiguration = {\n visible: boolean\n text: string\n disabled?: boolean\n tooltipText?: string\n onClick?: (e: MouseEvent) => void\n href?: string\n}\n\nexport type ActionConfigurationMap = Record<string, ActionConfiguration>\n\nexport type MenuConfigurationMap = Record<\n EntityActionMenuDropdownMenuType,\n EntityActionMenuDropdownMenuConfiguration\n>\n\nexport type ActionViewProps = {\n action: string\n icon?: IconName\n textSx?: SxProps\n iconSx?: SxProps\n variant?: ButtonProps['variant']\n}\n\nexport type EntityActionMenuLayout = {\n buttonActions: ActionViewProps[]\n downloadMenuActions: ActionViewProps[][]\n primaryMenuActions: ActionViewProps[][]\n primaryMenuText: string\n primaryMenuEndIcon: IconName\n primaryMenuEndIconSx?: SxProps\n menuButtonSx?: SystemStyleObject<Theme>\n}\n\nexport type EntityActionMenuProps = {\n /* Maps a unique action string to a dynamic configuration for that particular action */\n actionConfiguration: ActionConfigurationMap\n /* Maps each dropdown menu to a dynamic configuration for that particular menu */\n menuConfiguration: MenuConfigurationMap\n /* Defines the layout of the actions. Typically depends on just the entity type and area. */\n layout: EntityActionMenuLayout\n}\n\n/*\n * All visible actions should be specified in the layout, but this is not guaranteed at compile time.\n * Find all visible actions in the action configuration that are not in the layout.\n */\nfunction getUnmappedActions(\n actionConfiguration: ActionConfigurationMap,\n layout: EntityActionMenuLayout,\n): ActionConfigurationMap {\n return Object.fromEntries(\n Object.entries(actionConfiguration).filter(([action, config]) => {\n // We don't care about actions that are not configured or should not be visible\n if (!config || !config.visible) {\n return false\n }\n\n const allLayoutActions: ActionViewProps[] = [\n ...layout.buttonActions,\n ...layout.primaryMenuActions.flat(),\n ...layout.downloadMenuActions.flat(),\n ]\n // Action is unmapped if it's visible and the layout doesn't have it\n return !allLayoutActions.some(\n layoutAction => layoutAction.action === action,\n )\n }),\n )\n}\n\n/**\n * Given a group of dropdown menu items and an action configuration,\n * return a list of dropdown menu items with non-visible and non-configured actions removed.\n * @param group\n * @param actionConfiguration\n */\nfunction mapAndFilterItemsInMenuGroup(\n group: ActionViewProps[],\n actionConfiguration: ActionConfigurationMap,\n): DropdownMenuItem[] {\n return group.reduce(\n (itemAcc: DropdownMenuItem[], actionViewProps: ActionViewProps) => {\n const configForAction = actionConfiguration[actionViewProps.action]\n // Only show the item if it's configured + visible\n if (configForAction && configForAction.visible) {\n itemAcc.push({\n text: configForAction.text ?? actionViewProps.action,\n onClick: configForAction.onClick,\n href: configForAction.href,\n tooltipText: configForAction.tooltipText,\n disabled: configForAction.disabled,\n icon: actionViewProps.icon,\n textSx: actionViewProps.textSx,\n iconSx: actionViewProps.iconSx,\n })\n }\n return itemAcc\n },\n [],\n )\n}\n\n/**\n * Given a list of groups of dropdown menu actions, returns a list of dropdown menu item\n * groups where non-visible actions have been removed, and groups with no visible actions\n * have been removed.\n * @param menuActions\n * @param actionConfiguration\n */\nfunction mapAndFilterMenuGroups(\n menuActions: ActionViewProps[][],\n actionConfiguration: ActionConfigurationMap,\n): DropdownMenuItem[][] {\n return menuActions.reduce(\n (groupAcc: DropdownMenuItem[][], group: ActionViewProps[]) => {\n const itemGroup = mapAndFilterItemsInMenuGroup(group, actionConfiguration)\n // Only show the group if it contains visible items\n if (itemGroup.length > 0) {\n groupAcc.push(itemGroup)\n }\n return groupAcc\n },\n [],\n )\n}\n\n/**\n * The EntityActionMenu renders a menu that displays the actions that can be invoked on an Entity page.\n */\nexport default function EntityActionMenu(props: EntityActionMenuProps) {\n const { actionConfiguration, menuConfiguration, layout } = props\n\n /*\n * All actions should be specified in the layout, but this is not guaranteed at compile time.\n * Find all visible actions in the action configuration that are not in the layout.\n */\n const unmappedActions = getUnmappedActions(actionConfiguration, layout)\n // Warn if unmapped actions were found and push them on to the layout.\n if (Object.entries(unmappedActions).length > 0) {\n console.warn(\n 'Actions are visible but have not been configured in the layout:',\n Object.entries(unmappedActions).map(entry => entry[0]),\n )\n // Modify the layout: put the unmapped actions in their own group at the top of the menu\n layout.primaryMenuActions.unshift(\n Object.entries(unmappedActions).map(\n (entry): ActionViewProps => ({\n action: entry[0],\n }),\n ),\n )\n }\n\n // Map button actions to a button configuration and omit non-visible actions\n const iconButtonConfigs: ComplexMenuButtonProps[] =\n layout.buttonActions.reduce(\n (acc: ComplexMenuButtonProps[], buttonViewProps: ActionViewProps) => {\n const configForAction = actionConfiguration[buttonViewProps.action]\n if (configForAction && configForAction.visible) {\n let onClick = configForAction.onClick\n if (onClick == null && !configForAction.href) {\n console.warn(`No handler registered for ${buttonViewProps.action}`)\n onClick = () => {\n console.warn(\n `No handler registered for ${buttonViewProps.action}`,\n )\n }\n }\n acc.push({\n icon: buttonViewProps.icon as IconName,\n onClick: onClick,\n tooltipText: configForAction.text,\n text: configForAction.text,\n variant: buttonViewProps.variant,\n iconSx: buttonViewProps.iconSx,\n disabled: configForAction.disabled,\n href: configForAction.href,\n })\n }\n return acc\n },\n [],\n )\n\n const downloadMenuConfig: DropdownMenuProps = {\n dropdownButtonText: 'Download Options',\n convertSingleItemToButton: true,\n renderMenuIfNoItems: false,\n buttonTooltip: menuConfiguration.DOWNLOAD.tooltipText,\n buttonProps: {\n disabled: menuConfiguration.DOWNLOAD.disabled,\n endIcon: <IconSvg icon={'expandMore'} wrap={false} />,\n },\n items: mapAndFilterMenuGroups(\n layout.downloadMenuActions,\n actionConfiguration,\n ),\n }\n\n const primaryMenuConfig: DropdownMenuProps = {\n dropdownButtonText: layout.primaryMenuText,\n convertSingleItemToButton: true,\n renderMenuIfNoItems: false,\n buttonProps: {\n endIcon: (\n <IconSvg\n icon={layout.primaryMenuEndIcon}\n wrap={false}\n sx={layout.primaryMenuEndIconSx}\n />\n ),\n },\n items: mapAndFilterMenuGroups(\n layout.primaryMenuActions,\n actionConfiguration,\n ),\n }\n\n return (\n <Box\n sx={{\n '& .MuiButton-root': {\n borderRadius: '6px',\n fontWeight: 540,\n // Optional per-layout overrides (e.g. a different border color for\n // entity pages vs the project header).\n ...(layout.menuButtonSx ?? {}),\n },\n '& .MuiButton-root .MuiTypography-root': {\n fontWeight: 540,\n },\n }}\n >\n <ComplexMenu\n iconButtons={iconButtonConfigs}\n dropdownMenus={[primaryMenuConfig, downloadMenuConfig]}\n />\n </Box>\n )\n}\n\nexport const EXPORTED_FOR_UNIT_TESTING = {\n getUnmappedActions,\n mapAndFilterItemsInMenuGroup,\n mapAndFilterMenuGroups,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EntityActionMenu.js","names":[],"sources":["../../../../../src/components/entity/page/action_menu/EntityActionMenu.tsx"],"sourcesContent":["import { Box, ButtonProps, SxProps, Theme } from '@mui/material'\nimport { ComplexMenu, ComplexMenuButtonProps } from '../../../menu/ComplexMenu'\nimport IconSvg, { IconName } from '../../../IconSvg/IconSvg'\nimport { DropdownMenuItem, DropdownMenuProps } from '../../../menu/DropdownMenu'\nimport { MouseEvent } from 'react'\nimport { SystemStyleObject } from '@mui/system'\n\nconst outlinedButtonHoverBackgroundColor = 'rgba(0, 0, 0, 0.01)'\n\n// Represents the two types of dropdown menus that will be displayed on the entity page\ntype EntityActionMenuDropdownMenuType = 'DOWNLOAD' | 'PRIMARY'\n\ntype EntityActionMenuDropdownMenuConfiguration = {\n visible: boolean\n tooltipText?: string\n disabled?: boolean\n}\n\nexport type ActionConfiguration = {\n visible: boolean\n text: string\n disabled?: boolean\n tooltipText?: string\n onClick?: (e: MouseEvent) => void\n href?: string\n}\n\nexport type ActionConfigurationMap = Record<string, ActionConfiguration>\n\nexport type MenuConfigurationMap = Record<\n EntityActionMenuDropdownMenuType,\n EntityActionMenuDropdownMenuConfiguration\n>\n\nexport type ActionViewProps = {\n action: string\n icon?: IconName\n textSx?: SxProps\n iconSx?: SxProps\n variant?: ButtonProps['variant']\n}\n\nexport type EntityActionMenuLayout = {\n buttonActions: ActionViewProps[]\n downloadMenuActions: ActionViewProps[][]\n primaryMenuActions: ActionViewProps[][]\n primaryMenuText: string\n primaryMenuEndIcon: IconName\n primaryMenuEndIconSx?: SxProps\n menuButtonSx?: SystemStyleObject<Theme>\n downloadMenuVariant?: ButtonProps['variant']\n primaryMenuVariant?: ButtonProps['variant']\n}\n\nexport type EntityActionMenuProps = {\n /* Maps a unique action string to a dynamic configuration for that particular action */\n actionConfiguration: ActionConfigurationMap\n /* Maps each dropdown menu to a dynamic configuration for that particular menu */\n menuConfiguration: MenuConfigurationMap\n /* Defines the layout of the actions. Typically depends on just the entity type and area. */\n layout: EntityActionMenuLayout\n}\n\n/*\n * All visible actions should be specified in the layout, but this is not guaranteed at compile time.\n * Find all visible actions in the action configuration that are not in the layout.\n */\nfunction getUnmappedActions(\n actionConfiguration: ActionConfigurationMap,\n layout: EntityActionMenuLayout,\n): ActionConfigurationMap {\n return Object.fromEntries(\n Object.entries(actionConfiguration).filter(([action, config]) => {\n // We don't care about actions that are not configured or should not be visible\n if (!config || !config.visible) {\n return false\n }\n\n const allLayoutActions: ActionViewProps[] = [\n ...layout.buttonActions,\n ...layout.primaryMenuActions.flat(),\n ...layout.downloadMenuActions.flat(),\n ]\n // Action is unmapped if it's visible and the layout doesn't have it\n return !allLayoutActions.some(\n layoutAction => layoutAction.action === action,\n )\n }),\n )\n}\n\n/**\n * Given a group of dropdown menu items and an action configuration,\n * return a list of dropdown menu items with non-visible and non-configured actions removed.\n * @param group\n * @param actionConfiguration\n */\nfunction mapAndFilterItemsInMenuGroup(\n group: ActionViewProps[],\n actionConfiguration: ActionConfigurationMap,\n): DropdownMenuItem[] {\n return group.reduce(\n (itemAcc: DropdownMenuItem[], actionViewProps: ActionViewProps) => {\n const configForAction = actionConfiguration[actionViewProps.action]\n // Only show the item if it's configured + visible\n if (configForAction && configForAction.visible) {\n itemAcc.push({\n text: configForAction.text ?? actionViewProps.action,\n onClick: configForAction.onClick,\n href: configForAction.href,\n tooltipText: configForAction.tooltipText,\n disabled: configForAction.disabled,\n icon: actionViewProps.icon,\n textSx: actionViewProps.textSx,\n iconSx: actionViewProps.iconSx,\n })\n }\n return itemAcc\n },\n [],\n )\n}\n\n/**\n * Given a list of groups of dropdown menu actions, returns a list of dropdown menu item\n * groups where non-visible actions have been removed, and groups with no visible actions\n * have been removed.\n * @param menuActions\n * @param actionConfiguration\n */\nfunction mapAndFilterMenuGroups(\n menuActions: ActionViewProps[][],\n actionConfiguration: ActionConfigurationMap,\n): DropdownMenuItem[][] {\n return menuActions.reduce(\n (groupAcc: DropdownMenuItem[][], group: ActionViewProps[]) => {\n const itemGroup = mapAndFilterItemsInMenuGroup(group, actionConfiguration)\n // Only show the group if it contains visible items\n if (itemGroup.length > 0) {\n groupAcc.push(itemGroup)\n }\n return groupAcc\n },\n [],\n )\n}\n\n/**\n * The EntityActionMenu renders a menu that displays the actions that can be invoked on an Entity page.\n */\nexport default function EntityActionMenu(props: EntityActionMenuProps) {\n const { actionConfiguration, menuConfiguration, layout } = props\n\n /*\n * All actions should be specified in the layout, but this is not guaranteed at compile time.\n * Find all visible actions in the action configuration that are not in the layout.\n */\n const unmappedActions = getUnmappedActions(actionConfiguration, layout)\n // Warn if unmapped actions were found and push them on to the layout.\n if (Object.entries(unmappedActions).length > 0) {\n console.warn(\n 'Actions are visible but have not been configured in the layout:',\n Object.entries(unmappedActions).map(entry => entry[0]),\n )\n // Modify the layout: put the unmapped actions in their own group at the top of the menu\n layout.primaryMenuActions.unshift(\n Object.entries(unmappedActions).map(\n (entry): ActionViewProps => ({\n action: entry[0],\n }),\n ),\n )\n }\n\n // Map button actions to a button configuration and omit non-visible actions\n const iconButtonConfigs: ComplexMenuButtonProps[] =\n layout.buttonActions.reduce(\n (acc: ComplexMenuButtonProps[], buttonViewProps: ActionViewProps) => {\n const configForAction = actionConfiguration[buttonViewProps.action]\n if (configForAction && configForAction.visible) {\n let onClick = configForAction.onClick\n if (onClick == null && !configForAction.href) {\n console.warn(`No handler registered for ${buttonViewProps.action}`)\n onClick = () => {\n console.warn(\n `No handler registered for ${buttonViewProps.action}`,\n )\n }\n }\n acc.push({\n icon: buttonViewProps.icon as IconName,\n onClick: onClick,\n tooltipText: configForAction.text,\n text: configForAction.text,\n variant: buttonViewProps.variant,\n iconSx: buttonViewProps.iconSx,\n disabled: configForAction.disabled,\n href: configForAction.href,\n })\n }\n return acc\n },\n [],\n )\n\n const downloadMenuConfig: DropdownMenuProps = {\n dropdownButtonText: 'Download Options',\n convertSingleItemToButton: true,\n renderMenuIfNoItems: false,\n variant: layout.downloadMenuVariant,\n buttonTooltip: menuConfiguration.DOWNLOAD.tooltipText,\n buttonProps: {\n disabled: menuConfiguration.DOWNLOAD.disabled,\n endIcon: <IconSvg icon={'expandMore'} wrap={false} />,\n },\n items: mapAndFilterMenuGroups(\n layout.downloadMenuActions,\n actionConfiguration,\n ),\n }\n\n const primaryMenuConfig: DropdownMenuProps = {\n dropdownButtonText: layout.primaryMenuText,\n convertSingleItemToButton: true,\n renderMenuIfNoItems: false,\n variant: layout.primaryMenuVariant,\n buttonProps: {\n endIcon: (\n <IconSvg\n icon={layout.primaryMenuEndIcon}\n wrap={false}\n sx={layout.primaryMenuEndIconSx}\n />\n ),\n },\n items: mapAndFilterMenuGroups(\n layout.primaryMenuActions,\n actionConfiguration,\n ),\n }\n\n const menuButtonSx = layout.menuButtonSx ?? {}\n\n return (\n <Box\n sx={{\n '& .MuiButton-root': {\n borderRadius: '6px',\n fontWeight: 540,\n padding: '10px 12px',\n },\n // Optional per-layout overrides (e.g. background/border color for entity\n // menu buttons).\n '& .MuiButton-outlined': {\n ...menuButtonSx,\n\n '&:hover': {\n ...menuButtonSx,\n backgroundColor: outlinedButtonHoverBackgroundColor,\n },\n },\n '& .MuiButton-text': {\n ...menuButtonSx,\n backgroundColor: 'transparent',\n\n '&:hover': {\n ...menuButtonSx,\n backgroundColor: outlinedButtonHoverBackgroundColor,\n textDecoration: 'none',\n },\n },\n '& .MuiButton-root .MuiTypography-root': {\n fontWeight: 540,\n },\n }}\n >\n <ComplexMenu\n iconButtons={iconButtonConfigs}\n dropdownMenus={[primaryMenuConfig, downloadMenuConfig]}\n />\n </Box>\n )\n}\n\nexport const EXPORTED_FOR_UNIT_TESTING = {\n getUnmappedActions,\n mapAndFilterItemsInMenuGroup,\n mapAndFilterMenuGroups,\n}\n"],"mappings":"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"}
|