UNPKG

synapse-react-client

Version:

[![Build Status](https://travis-ci.com/Sage-Bionetworks/Synapse-React-Client.svg?branch=main)](https://travis-ci.com/Sage-Bionetworks/Synapse-React-Client) [![npm version](https://badge.fury.io/js/synapse-react-client.svg)](https://badge.fury.io/js/synaps

282 lines 19.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.DetailsView = void 0; var tslib_1 = require("tslib"); var react_base_table_1 = (0, tslib_1.__importStar)(require("@sage-bionetworks/react-base-table")); var react_1 = (0, tslib_1.__importStar)(require("react")); var react_query_1 = require("react-query"); var react_tooltip_1 = (0, tslib_1.__importDefault)(require("react-tooltip")); var EntityTypeUtils_1 = require("../../../../utils/functions/EntityTypeUtils"); var SynapseClient_1 = require("../../../../utils/SynapseClient"); var SynapseContext_1 = require("../../../../utils/SynapseContext"); var synapseTypes_1 = require("../../../../utils/synapseTypes"); var EntityBadgeIcons_1 = require("../../../EntityBadgeIcons"); var MarkdownPopover_1 = require("../../../MarkdownPopover"); var LoadingScreen_1 = require("../../../LoadingScreen"); var Checkbox_1 = require("../../../widgets/Checkbox"); var EntityFinder_1 = require("../../EntityFinder"); var DetailsViewTableRenderers_1 = require("./DetailsViewTableRenderers"); var icons_1 = require("@material-ui/icons"); var TooltipUtils_1 = require("../../../../utils/functions/TooltipUtils"); var MIN_TABLE_WIDTH = 1200; var ROW_HEIGHT = 46; /** * Displays a list of entities in a table. * * If the list of entities is paginated, the `hasNextPage` prop can be set to indicate that there is more data to load. * When the view is ready to load more data, the `fetchNextPage` callback will be invoked. The view is designed to handle * an "infinite scroll" pattern, so entities should not be removed from the list when loading the next page. * * @param param0 */ var DetailsView = function (_a) { var entities = _a.entities, isLoading = _a.isLoading, hasNextPage = _a.hasNextPage, fetchNextPage = _a.fetchNextPage, isFetchingNextPage = _a.isFetchingNextPage, showVersionSelection = _a.showVersionSelection, mustSelectVersionNumber = _a.mustSelectVersionNumber, selectColumnType = _a.selectColumnType, selected = _a.selected, visibleTypes = _a.visibleTypes, selectableTypes = _a.selectableTypes, toggleSelection = _a.toggleSelection, sort = _a.sort, setSort = _a.setSort, noResultsPlaceholder = _a.noResultsPlaceholder, enableSelectAll = _a.enableSelectAll, _b = _a.selectAllIsChecked, selectAllIsChecked = _b === void 0 ? false : _b; var queryClient = (0, react_query_1.useQueryClient)(); var accessToken = (0, SynapseContext_1.useSynapseContext)().accessToken; var showSelectColumn = selectColumnType !== 'none'; var _c = (0, react_1.useState)(false), shouldSelectAll = _c[0], setShouldSelectAll = _c[1]; var _d = (0, react_1.useState)(false), showLoadingScreen = _d[0], setShowLoadingScreen = _d[1]; var determineRowAppearance = function (entity) { if (!visibleTypes.includes((0, EntityTypeUtils_1.getEntityTypeFromHeader)(entity))) { return 'hidden'; } else if (!selectableTypes.includes((0, EntityTypeUtils_1.getEntityTypeFromHeader)(entity))) { return 'disabled'; } else if (selected.has(entity.id)) { return 'selected'; } else { return 'default'; } }; (0, react_1.useEffect)(function () { function handleSelectAll() { return (0, tslib_1.__awaiter)(this, void 0, void 0, function () { var _a; var _this = this; return (0, tslib_1.__generator)(this, function (_b) { switch (_b.label) { case 0: if (!shouldSelectAll) return [3 /*break*/, 5]; if (!(hasNextPage && fetchNextPage)) return [3 /*break*/, 1]; // Show the loading screen since we must fetch data (potentially a lot) to finish the task setShowLoadingScreen(true); if (!isFetchingNextPage) { fetchNextPage(); } return [3 /*break*/, 5]; case 1: if (!selectAllIsChecked) return [3 /*break*/, 2]; // All of the items are selected, so we will deselect all toggleSelection(entities .filter(function (e) { // Collect just entities that are selected // An entity may be in the list and unselected because it isn't of a selectable type return selected.has(e.id); }) .map(function (e) { var selectedVersion = selected.get(e.id); return { targetId: e.id, targetVersionNumber: selectedVersion === EntityFinder_1.NO_VERSION_NUMBER ? undefined : selectedVersion, }; })); return [3 /*break*/, 4]; case 2: // Not all of the items are selected, so we will select all _a = toggleSelection; return [4 /*yield*/, Promise.all(entities .filter(function (e) { // must filter just selectable types or else any entities of unselectable types will get selected var type = (0, EntityTypeUtils_1.getEntityTypeFromHeader)(e); // also exclude already-selected entities, since we don't want to toggle those return (!selected.has(e.id) && selectableTypes.includes(type) && visibleTypes.includes(type)); }) .map(function (e) { return (0, tslib_1.__awaiter)(_this, void 0, void 0, function () { var latestVersion, versions; var _a; return (0, tslib_1.__generator)(this, function (_b) { switch (_b.label) { case 0: if (!(mustSelectVersionNumber && (0, EntityTypeUtils_1.isVersionableEntityType)((0, EntityTypeUtils_1.getEntityTypeFromHeader)(e)))) return [3 /*break*/, 2]; // If `mustSelectVersionNumber` is true, then we need to supply a version with the entity. // We may already have the version from the header: if (Object.prototype.hasOwnProperty.call(e, 'versionNumber')) { latestVersion = e.versionNumber; } if (!!latestVersion) return [3 /*break*/, 2]; // Failsafe if we didn't get the version in the header. This is rare/unlikely, since the only cases we're sure we don't get versions are: // - ProjectHeaders (which are versionless) // - Search Results (for which we don't support Select All) // For large lists, there's a good chance for this to trigger throttling. // Show the loading screen since we must fetch data (potentially a lot) to finish the task setShowLoadingScreen(true); return [4 /*yield*/, queryClient.fetchQuery(['entity', e.id, 'versions', { offset: 0, limit: 1 }], function () { return (0, SynapseClient_1.getEntityVersions)(e.id, accessToken, 0, 1); }) // we pick the first version in the list because it is the most recent ]; case 1: versions = _b.sent(); // we pick the first version in the list because it is the most recent latestVersion = (_a = versions.results[0]) === null || _a === void 0 ? void 0 : _a.versionNumber; _b.label = 2; case 2: return [2 /*return*/, { targetId: e.id, targetVersionNumber: latestVersion, }]; } }); }); }))]; case 3: // Not all of the items are selected, so we will select all _a.apply(void 0, [_b.sent()]); _b.label = 4; case 4: setShouldSelectAll(false); setShowLoadingScreen(false); _b.label = 5; case 5: return [2 /*return*/]; } }); }); } handleSelectAll(); }, [ accessToken, entities, fetchNextPage, hasNextPage, mustSelectVersionNumber, queryClient, isFetchingNextPage, selectAllIsChecked, selectableTypes, selected, shouldSelectAll, toggleSelection, visibleTypes, ]); var tableData = entities.reduce(function (entities, entity) { var appearance = determineRowAppearance(entity); if (appearance !== 'hidden') { // only include entities that should not be hidden var entityType = (0, EntityTypeUtils_1.getEntityTypeFromHeader)(entity); entities.push((0, tslib_1.__assign)((0, tslib_1.__assign)({}, entity), { entityId: entity.id, versionNumber: 'versionNumber' in entity ? entity.versionNumber : undefined, entityType: entityType, isSelected: appearance === 'selected', isDisabled: appearance === 'disabled', isVersionableEntity: (0, EntityTypeUtils_1.isVersionableEntityType)(entityType), currentSelectedVersion: selected.get(entity.id) })); } return entities; }, []); var SelectAllCheckboxRenderer = (0, react_1.useMemo)(function () { // Enabled if there's at least one visble & selectable entity, OR there's a page we haven't fetched var isEnabled = hasNextPage || entities.filter(function (e) { return selectableTypes.includes((0, EntityTypeUtils_1.getEntityTypeFromHeader)(e)) && visibleTypes.includes((0, EntityTypeUtils_1.getEntityTypeFromHeader)(e)); }).length > 0; return (enableSelectAll && (react_1.default.createElement("div", { "data-testid": "Select All", style: isEnabled ? { cursor: 'pointer' } : { cursor: 'not-allowed' }, onClick: function () { if (isEnabled) { setShouldSelectAll(true); } } }, react_1.default.createElement(Checkbox_1.Checkbox, { label: "", className: "SRC-pointer-events-none", checked: selectAllIsChecked, disabled: !isEnabled, onChange: function () { // no-op } })))); }, [ enableSelectAll, entities, hasNextPage, selectAllIsChecked, selectableTypes, visibleTypes, ]); var sortState = {}; if (sort) { sortState[sort.sortBy] = sort.sortDirection.toLowerCase(); } return (react_1.default.createElement("div", { className: "EntityFinderDetailsView bootstrap-4-backport" }, react_1.default.createElement(LoadingScreen_1.BlockingLoader, { show: showLoadingScreen }), react_1.default.createElement(react_base_table_1.AutoResizer, { className: "DetailsViewAutosizer", onResize: TooltipUtils_1.rebuildTooltip }, function (_a) { var height = _a.height, width = _a.width; return (react_1.default.createElement(react_base_table_1.default, { classPrefix: "DetailsViewTable", data: tableData, height: height, width: width > MIN_TABLE_WIDTH ? width : MIN_TABLE_WIDTH, rowHeight: ROW_HEIGHT, overscanRowCount: 5, // Apply classes to the rows for styling rowClassName: function (_a) { var rowIndex = _a.rowIndex; var className = 'EntityFinderDetailsViewRow'; if (rowIndex % 2 === 0) { // Apply a class based on index so we can get alternating colors // We don't use CSSs nth-child because the rows are virtualized, so an even child might change to odd on-the-fly className += ' isEven'; } return className; }, // Apply aria roles to the rows for a11y/styling rowProps: function (_a) { var rowData = _a.rowData; return { 'aria-selected': rowData.isSelected, 'aria-disabled': rowData.isDisabled, }; }, headerCellProps: { role: 'columnheader', }, // Sorting: sortState: sortState, components: { SortIndicator: DetailsViewTableRenderers_1.CustomSortIndicator }, onColumnSort: function (_a) { var key = _a.key, order = _a.order; if (sort && setSort) { setSort(key, order === 'asc' ? synapseTypes_1.Direction.ASC : synapseTypes_1.Direction.DESC); } }, onRowsRendered: TooltipUtils_1.rebuildTooltip, onScroll: TooltipUtils_1.rebuildTooltip, rowEventHandlers: { onClick: function (_a) { var rowData = _a.rowData; var id = rowData.id, isDisabled = rowData.isDisabled, isVersionableEntity = rowData.isVersionableEntity; var currentSelectedVersion = rowData.currentSelectedVersion; if (!isDisabled) { if (isVersionableEntity && mustSelectVersionNumber && currentSelectedVersion == null && Object.prototype.hasOwnProperty.call(rowData, 'versionNumber')) { currentSelectedVersion = rowData .versionNumber; // Note that here we aren't handling the case where the header doesn't have a version, e.g. a search result // That case is actually handled by the VersionRenderer, which has an effect that will toggle the selection after data is fetched. } toggleSelection({ targetId: id, targetVersionNumber: currentSelectedVersion === EntityFinder_1.NO_VERSION_NUMBER ? undefined : currentSelectedVersion, }); } }, }, onEndReached: function () { if (hasNextPage && fetchNextPage && !isFetchingNextPage) { fetchNextPage(); } }, emptyRenderer: isLoading ? DetailsViewTableRenderers_1.LoadingRenderer : function () { return (react_1.default.createElement(DetailsViewTableRenderers_1.EmptyRenderer, { noResultsPlaceholder: noResultsPlaceholder })); } }, showSelectColumn && (react_1.default.createElement(react_base_table_1.Column, { key: "isSelected", title: "", minWidth: 50, maxWidth: 50, width: 50, dataKey: "isSelected", headerRenderer: SelectAllCheckboxRenderer, cellRenderer: DetailsViewTableRenderers_1.DetailsViewCheckboxRenderer })), react_1.default.createElement(react_base_table_1.Column, { key: "type", title: "", minWidth: 45, maxWidth: 45, width: 45, dataKey: "entityType", cellRenderer: DetailsViewTableRenderers_1.TypeIconRenderer }), react_1.default.createElement(react_base_table_1.Column, { key: synapseTypes_1.SortBy.NAME, title: "Name", width: 800, dataKey: "name", sortable: sort != null, resizable: true }), react_1.default.createElement(react_base_table_1.Column, { key: "badge", title: "", width: 75, maxWidth: 75, minWidth: 75, cellRenderer: DetailsViewTableRenderers_1.BadgeIconsRenderer }), react_1.default.createElement(react_base_table_1.Column, { key: "id", width: 120, dataKey: "id", title: "ID", minWidth: 120 }), showVersionSelection && (react_1.default.createElement(react_base_table_1.Column, { key: "version", minWidth: 150, width: 500, title: "Version", cellRenderer: function (props) { return (react_1.default.createElement(DetailsViewTableRenderers_1.DetailsViewVersionRenderer, (0, tslib_1.__assign)({ mustSelectVersionNumber: mustSelectVersionNumber, toggleSelection: toggleSelection }, props))); }, headerRenderer: function () { return (react_1.default.createElement(react_1.default.Fragment, null, "Version", react_1.default.createElement(MarkdownPopover_1.MarkdownPopover, { contentProps: { markdown: 'Allows you to choose which version of this item you would like to perform this action on. If you would like the selected reference to update as new versions are created, choose “Always Latest Version”', }, showCloseButton: false, placement: "right", strategy: "fixed", style: { maxWidth: '350px' } }, react_1.default.createElement(icons_1.HelpOutline, { className: "HelpButton", style: { cursor: 'pointer' } })))); } })), react_1.default.createElement(react_base_table_1.Column, { key: synapseTypes_1.SortBy.CREATED_ON, sortable: sort != null, title: "Created On", width: 200, minWidth: 150, cellRenderer: DetailsViewTableRenderers_1.CreatedOnRenderer }), react_1.default.createElement(react_base_table_1.Column, { key: synapseTypes_1.SortBy.MODIFIED_ON, title: "Modified On", width: 200, minWidth: 150, sortable: sort != null, cellRenderer: DetailsViewTableRenderers_1.ModifiedOnRenderer }), react_1.default.createElement(react_base_table_1.Column, { key: "modifiedBy", title: "Modified By", width: 500, resizable: true, cellRenderer: DetailsViewTableRenderers_1.ModifiedByRenderer }))); }), react_1.default.createElement(react_tooltip_1.default, { id: EntityBadgeIcons_1.ENTITY_BADGE_ICONS_TOOLTIP_ID, className: "EntityBadgeTooltip", delayShow: 100, place: 'right', effect: 'solid' }))); }; exports.DetailsView = DetailsView; //# sourceMappingURL=DetailsView.js.map