synapse-react-client 4.0.11 → 4.0.12

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.
Files changed (135) hide show
  1. package/dist/SWC.index.d.ts +1 -1
  2. package/dist/SWC.index.js +1 -1
  3. package/dist/components/AdaptiveTwoColumnLayout/AdaptiveTwoColumnLayout.d.ts +44 -0
  4. package/dist/components/AdaptiveTwoColumnLayout/AdaptiveTwoColumnLayout.d.ts.map +1 -0
  5. package/dist/components/AdaptiveTwoColumnLayout/AdaptiveTwoColumnLayout.js +46 -0
  6. package/dist/components/AdaptiveTwoColumnLayout/AdaptiveTwoColumnLayout.js.map +1 -0
  7. package/dist/components/DownloadCart/DownloadCartPage.d.ts.map +1 -1
  8. package/dist/components/DownloadCart/DownloadCartPage.js +183 -185
  9. package/dist/components/DownloadCart/DownloadCartPage.js.map +1 -1
  10. package/dist/components/DownloadCart/DownloadIneligibleForPackagingFilesFromListButton.d.ts.map +1 -1
  11. package/dist/components/DownloadCart/DownloadIneligibleForPackagingFilesFromListButton.js +1 -1
  12. package/dist/components/DownloadCart/DownloadIneligibleForPackagingFilesFromListButton.js.map +1 -1
  13. package/dist/components/EntityAclEditor/EntityAclEditor.js +8 -8
  14. package/dist/components/EntityAclEditor/EntityAclEditor.js.map +1 -1
  15. package/dist/components/EntityAclEditor/InheritanceMessage.d.ts.map +1 -1
  16. package/dist/components/EntityAclEditor/InheritanceMessage.js +1 -2
  17. package/dist/components/EntityAclEditor/InheritanceMessage.js.map +1 -1
  18. package/dist/components/FullTextSearch/FullTextSearch.d.ts.map +1 -1
  19. package/dist/components/FullTextSearch/FullTextSearch.js +86 -47
  20. package/dist/components/FullTextSearch/FullTextSearch.js.map +1 -1
  21. package/dist/components/GenericCard/TableRowGenericCard.d.ts.map +1 -1
  22. package/dist/components/GenericCard/TableRowGenericCard.js +157 -157
  23. package/dist/components/GenericCard/TableRowGenericCard.js.map +1 -1
  24. package/dist/components/HeaderCard.d.ts.map +1 -1
  25. package/dist/components/HeaderCard.js +131 -130
  26. package/dist/components/HeaderCard.js.map +1 -1
  27. package/dist/components/MailchimpSubscribeSection/MailchimpSubscribeSection.d.ts +4 -1
  28. package/dist/components/MailchimpSubscribeSection/MailchimpSubscribeSection.d.ts.map +1 -1
  29. package/dist/components/MailchimpSubscribeSection/MailchimpSubscribeSection.js +3 -2
  30. package/dist/components/MailchimpSubscribeSection/MailchimpSubscribeSection.js.map +1 -1
  31. package/dist/components/QueryContext/QueryContext.d.ts +6 -0
  32. package/dist/components/QueryContext/QueryContext.d.ts.map +1 -1
  33. package/dist/components/QueryContext/QueryContext.js.map +1 -1
  34. package/dist/components/QueryVisualizationWrapper/QueryVisualizationWrapper.d.ts.map +1 -1
  35. package/dist/components/QueryVisualizationWrapper/QueryVisualizationWrapper.js +12 -11
  36. package/dist/components/QueryVisualizationWrapper/QueryVisualizationWrapper.js.map +1 -1
  37. package/dist/components/QueryWrapperPlotNav/QueryWrapperPlotNav.d.ts +5 -0
  38. package/dist/components/QueryWrapperPlotNav/QueryWrapperPlotNav.d.ts.map +1 -1
  39. package/dist/components/QueryWrapperPlotNav/QueryWrapperPlotNav.js +32 -29
  40. package/dist/components/QueryWrapperPlotNav/QueryWrapperPlotNav.js.map +1 -1
  41. package/dist/components/QueryWrapperPlotNav/UseRowSet.d.ts.map +1 -1
  42. package/dist/components/QueryWrapperPlotNav/UseRowSet.js +4 -1
  43. package/dist/components/QueryWrapperPlotNav/UseRowSet.js.map +1 -1
  44. package/dist/components/SearchQueryWrapper/SearchQueryUseQueryOptions.d.ts +59 -0
  45. package/dist/components/SearchQueryWrapper/SearchQueryUseQueryOptions.d.ts.map +1 -0
  46. package/dist/components/SearchQueryWrapper/SearchQueryUseQueryOptions.js +174 -0
  47. package/dist/components/SearchQueryWrapper/SearchQueryUseQueryOptions.js.map +1 -0
  48. package/dist/components/SearchQueryWrapper/SearchQueryWrapper.d.ts +40 -0
  49. package/dist/components/SearchQueryWrapper/SearchQueryWrapper.d.ts.map +1 -0
  50. package/dist/components/SearchQueryWrapper/SearchQueryWrapper.js +100 -0
  51. package/dist/components/SearchQueryWrapper/SearchQueryWrapper.js.map +1 -0
  52. package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.d.ts +56 -0
  53. package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.d.ts.map +1 -0
  54. package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.js +73 -0
  55. package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.js.map +1 -0
  56. package/dist/components/ShareThisPage/ShareThisPage.js +1 -1
  57. package/dist/components/ShareThisPage/ShareThisPage.js.map +1 -1
  58. package/dist/components/SynapseChat/GridAgentChat.d.ts.map +1 -1
  59. package/dist/components/SynapseChat/GridAgentChat.js +20 -14
  60. package/dist/components/SynapseChat/GridAgentChat.js.map +1 -1
  61. package/dist/components/SynapseChat/SynapseChat.d.ts +3 -1
  62. package/dist/components/SynapseChat/SynapseChat.d.ts.map +1 -1
  63. package/dist/components/SynapseChat/SynapseChat.js +51 -36
  64. package/dist/components/SynapseChat/SynapseChat.js.map +1 -1
  65. package/dist/components/SynapseHomepageV2/HotdropsAndNews.css +1 -1
  66. package/dist/components/SynapseHomepageV2/HotdropsAndNews.module.js +8 -7
  67. package/dist/components/SynapseHomepageV2/HotdropsAndNews.module.js.map +1 -1
  68. package/dist/components/SynapseHomepageV2/HotdropsAndNews.module.scss +16 -0
  69. package/dist/components/SynapseHomepageV2/WordPressNews.d.ts +3 -1
  70. package/dist/components/SynapseHomepageV2/WordPressNews.d.ts.map +1 -1
  71. package/dist/components/SynapseHomepageV2/WordPressNews.js +9 -8
  72. package/dist/components/SynapseHomepageV2/WordPressNews.js.map +1 -1
  73. package/dist/components/SynapseHomepageV2/WordPressNewsItem.d.ts +2 -1
  74. package/dist/components/SynapseHomepageV2/WordPressNewsItem.d.ts.map +1 -1
  75. package/dist/components/SynapseHomepageV2/WordPressNewsItem.js +2 -2
  76. package/dist/components/SynapseHomepageV2/WordPressNewsItem.js.map +1 -1
  77. package/dist/components/SynapseTable/SearchV2.d.ts +7 -0
  78. package/dist/components/SynapseTable/SearchV2.d.ts.map +1 -1
  79. package/dist/components/SynapseTable/SearchV2.js.map +1 -1
  80. package/dist/components/SynapseTable/SynapseTableRenderers.d.ts.map +1 -1
  81. package/dist/components/SynapseTable/SynapseTableRenderers.js +1 -1
  82. package/dist/components/SynapseTable/SynapseTableRenderers.js.map +1 -1
  83. package/dist/components/SynapseTable/usePrefetchTableData.d.ts.map +1 -1
  84. package/dist/components/SynapseTable/usePrefetchTableData.js +6 -5
  85. package/dist/components/SynapseTable/usePrefetchTableData.js.map +1 -1
  86. package/dist/components/ToastMessage/ToastMessage.d.ts +1 -1
  87. package/dist/components/ToastMessage/ToastMessage.d.ts.map +1 -1
  88. package/dist/components/ToastMessage/ToastMessage.js +2 -2
  89. package/dist/components/ToastMessage/ToastMessage.js.map +1 -1
  90. package/dist/components/error/ErrorPage.d.ts +0 -4
  91. package/dist/components/error/ErrorPage.d.ts.map +1 -1
  92. package/dist/components/error/ErrorPage.js +25 -30
  93. package/dist/components/error/ErrorPage.js.map +1 -1
  94. package/dist/components/index.d.ts +4 -0
  95. package/dist/components/index.d.ts.map +1 -1
  96. package/dist/components/index.js +41 -39
  97. package/dist/components/widgets/query-filter/FacetFilterControls.d.ts.map +1 -1
  98. package/dist/components/widgets/query-filter/FacetFilterControls.js +2 -2
  99. package/dist/components/widgets/query-filter/FacetFilterControls.js.map +1 -1
  100. package/dist/index.js +52 -50
  101. package/dist/index.js.map +1 -1
  102. package/dist/mocks/entity/index.d.ts.map +1 -1
  103. package/dist/mocks/entity/index.js +18 -16
  104. package/dist/mocks/entity/index.js.map +1 -1
  105. package/dist/mocks/mockSearchQueryData.d.ts +14 -0
  106. package/dist/mocks/mockSearchQueryData.d.ts.map +1 -0
  107. package/dist/mocks/mockSearchQueryData.js +564 -0
  108. package/dist/mocks/mockSearchQueryData.js.map +1 -0
  109. package/dist/mocks/msw/handlers/searchQueryHandlers.d.ts +22 -0
  110. package/dist/mocks/msw/handlers/searchQueryHandlers.d.ts.map +1 -0
  111. package/dist/mocks/msw/handlers/searchQueryHandlers.js +63 -0
  112. package/dist/mocks/msw/handlers/searchQueryHandlers.js.map +1 -0
  113. package/dist/mocks/msw/handlers.d.ts.map +1 -1
  114. package/dist/mocks/msw/handlers.js +9 -7
  115. package/dist/mocks/msw/handlers.js.map +1 -1
  116. package/dist/style/components/_cards.scss +15 -0
  117. package/dist/style/main.css +1 -1
  118. package/dist/synapse-client/SynapseClient.d.ts +7 -1
  119. package/dist/synapse-client/SynapseClient.d.ts.map +1 -1
  120. package/dist/synapse-client/SynapseClient.js +422 -417
  121. package/dist/synapse-client/SynapseClient.js.map +1 -1
  122. package/dist/synapse-client/index.js +3 -3
  123. package/dist/synapse-queries/KeyFactory.d.ts +2 -0
  124. package/dist/synapse-queries/KeyFactory.d.ts.map +1 -1
  125. package/dist/synapse-queries/KeyFactory.js +3 -0
  126. package/dist/synapse-queries/KeyFactory.js.map +1 -1
  127. package/dist/tsconfig.build.tsbuildinfo +1 -1
  128. package/dist/utils/APIConstants.d.ts +2 -0
  129. package/dist/utils/APIConstants.d.ts.map +1 -1
  130. package/dist/utils/APIConstants.js +2 -2
  131. package/dist/utils/APIConstants.js.map +1 -1
  132. package/dist/utils/functions/calculateFriendlyFileSize.d.ts.map +1 -1
  133. package/dist/utils/functions/calculateFriendlyFileSize.js +3 -3
  134. package/dist/utils/functions/calculateFriendlyFileSize.js.map +1 -1
  135. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"DownloadCartPage.js","names":[],"sources":["../../../src/components/DownloadCart/DownloadCartPage.tsx"],"sourcesContent":["import styles from './DownloadCartPage.module.scss'\nimport SynapseClient from '@/synapse-client'\nimport { useGetDownloadListStatistics } from '@/synapse-queries/download/useDownloadList'\nimport { useGetFeatureFlag } from '@/synapse-queries/featureflags/useGetFeatureFlag'\nimport { useSynapseContext } from '@/utils/context/SynapseContext'\nimport { DeleteTwoTone } from '@mui/icons-material'\nimport { Button, Tooltip, Typography } from '@mui/material'\nimport {\n AvailableFilter,\n FeatureFlagEnum,\n FilesStatisticsResponse,\n} from '@sage-bionetworks/synapse-types'\nimport { useEffect, useRef, useState } from 'react'\nimport { ErrorBanner } from '../error/ErrorBanner'\nimport FullWidthAlert from '../FullWidthAlert/FullWidthAlert'\nimport IconSvg from '../IconSvg/IconSvg'\nimport { ProgrammaticInstructionsModal } from '../ProgrammaticInstructionsModal/ProgrammaticInstructionsModal'\nimport AvailableForDownloadTable from './AvailableForDownloadTable'\nimport { CreatePackageV2 } from './CreatePackageV2'\nimport { PYTHON_CLIENT_IMPORT_AND_LOGIN } from './DirectProgrammaticDownload'\nimport { calculateFriendlyFileSize } from '@/utils/functions/calculateFriendlyFileSize'\nimport { DownloadIneligibleForPackagingFilesFromListButton } from './DownloadIneligibleForPackagingFilesFromListButton'\nimport {\n DownloadListActionsRequired,\n DownloadListActionsRequiredProps,\n} from './DownloadListActionsRequired'\nimport ComponentCollapse from '../ComponentCollapse'\n\nconst pythonDownloadCode = `${PYTHON_CLIENT_IMPORT_AND_LOGIN}\ndl_list_file_entities = syn.get_download_list()`\n\nconst cliDownloadCode = `synapse get-download-list`\n\nconst filterTabs: { label: string; filter: AvailableFilter }[] = [\n { label: 'All Files', filter: undefined },\n { label: 'Files included in ZIP', filter: 'eligibleForPackaging' },\n { label: 'Files not included in ZIP', filter: 'ineligibleForPackaging' },\n]\n\n/**\n * Show the Download Cart page.\n */\nexport function DownloadCartPage(props: DownloadListActionsRequiredProps) {\n const { accessToken } = useSynapseContext()\n const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0)\n const [selectedFilterTabIndex, setSelectedFilterTabIndex] =\n useState<number>(0)\n const selectedFilter = filterTabs[selectedFilterTabIndex].filter\n const [isShowingCreatePackageUI, setIsShowingCreatePackageUI] =\n useState<boolean>(false)\n const createPackageRef = useRef<HTMLDivElement>(null)\n // scroll to the CreatePackageV2 component when it is shown so that the user is aware of the next steps after clicking the button to create a package\n useEffect(() => {\n if (isShowingCreatePackageUI) {\n createPackageRef.current?.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n })\n }\n }, [isShowingCreatePackageUI])\n const [isShowingModal, setIsShowingModal] = useState<boolean>(false)\n const [isShowingDownloadSuccessAlert, setIsShowingDownloadSuccessAlert] =\n useState(false)\n const [error, setError] = useState<Error>()\n const isDownloadAllEnabled = useGetFeatureFlag(\n FeatureFlagEnum.DOWNLOAD_CART_INELIGIBLE_FILE_DOWNLOADS,\n )\n const {\n data,\n isLoading,\n isError,\n error: newError,\n refetch,\n } = useGetDownloadListStatistics()\n useEffect(() => {\n if (isError && newError) {\n setError(newError)\n }\n }, [isError, newError])\n\n const getFilterCount = (\n data: FilesStatisticsResponse,\n filter: AvailableFilter,\n ) => {\n if (filter === undefined) return data.numberOfFilesAvailableForDownload\n if (filter === 'eligibleForPackaging')\n return data.numberOfFilesAvailableForDownloadAndEligibleForPackaging\n return (\n data.numberOfFilesAvailableForDownload -\n data.numberOfFilesAvailableForDownloadAndEligibleForPackaging\n )\n }\n // SWC-5874: When arriving at the download cart when there are no ARs, the user should start in the Download list\n useEffect(() => {\n if (data && data.numberOfFilesRequiringAction == 0) {\n setSelectedTabIndex(1)\n }\n // also hide the Create Package UI if there are no files available for download\n if (\n data &&\n data.numberOfFilesAvailableForDownloadAndEligibleForPackaging === 0\n ) {\n setIsShowingCreatePackageUI(false)\n }\n }, [data])\n\n if (error) {\n return <ErrorBanner error={error} />\n }\n const clearDownloadList = async () => {\n await SynapseClient.clearDownloadListV2(accessToken)\n refetch()\n }\n return (\n <div className={styles.DownloadCartPage}>\n <div>\n <div className={`${styles.pageHeader}`}>\n <div className=\"container\">\n <div className={styles.grid}>\n <h3 className=\"pageHeaderTitle\">Your Download List</h3>\n <Tooltip\n title=\"Immediately removes all items from your download list\"\n enterNextDelay={300}\n placement=\"right\"\n >\n <Button\n onClick={() => {\n clearDownloadList()\n }}\n variant=\"text\"\n color=\"primary\"\n startIcon={<DeleteTwoTone />}\n >\n Clear Your Download List\n </Button>\n </Tooltip>\n </div>\n </div>\n </div>\n </div>\n <div className={styles.tabsContainer}>\n <div className=\"container\">\n <ul className={styles.navTabs}>\n <li\n className={`${styles.navItem}${\n selectedTabIndex == 0 ? ` ${styles.active}` : ''\n }`}\n aria-selected={selectedTabIndex == 0}\n >\n <button onClick={() => setSelectedTabIndex(0)}>\n Access Actions Required\n {!isError && !isLoading && data && (\n <span className={styles.fileCount}>\n {data.totalNumberOfFiles -\n data.numberOfFilesAvailableForDownload}\n </span>\n )}\n </button>\n </li>\n <li\n className={`${styles.navItem}${\n selectedTabIndex == 1 ? ` ${styles.active}` : ''\n }`}\n aria-selected={selectedTabIndex == 1}\n >\n <button onClick={() => setSelectedTabIndex(1)}>\n Download List\n {!isError && !isLoading && data && (\n <span className={styles.fileCount}>\n {data.numberOfFilesAvailableForDownload}\n </span>\n )}\n </button>\n </li>\n </ul>\n </div>\n </div>\n\n <div\n className={\n /*\n The DownloadListActionsRequired component stores actions in local state to track which actions are completed.\n\n Mount the actions required component and hide it with style to ensure actions stored in component state are persisted as the user switches tabs\n */\n !(selectedTabIndex == 0 && !isError && !isLoading && data)\n ? styles.hidden\n : undefined\n }\n >\n {data?.numberOfFilesRequiringAction &&\n data.numberOfFilesRequiringAction > 0 && (\n // Unmount instead of hiding the DownloadListActionsRequired component when there are 0 actions required\n // The DownloadListActionsRequired cannot discern between a fulfilled action and an action that is no longer required because the\n // file was removed from the download cart.\n // In the typical case where the download cart is cleared, unmounting the component ensures that the actions are cleared out.\n <div>\n <div className=\"container\">\n <DownloadListActionsRequired {...props} />\n </div>\n </div>\n )}\n {data?.numberOfFilesRequiringAction === 0 && (\n <div className={styles.placeholder}>\n <div>No actions are currently required.</div>\n </div>\n )}\n </div>\n\n {selectedTabIndex == 1 && !isError && !isLoading && data && (\n <div>\n {data.numberOfFilesAvailableForDownload > 0 && (\n <div className={`container ${styles.DownloadListTabContent}`}>\n <h3 className={`${styles.sectionTitle} pageHeaderTitle`}>\n Bulk Download Options\n {data.sumOfFileSizesAvailableForDownload != null && (\n <Tooltip\n placement=\"right\"\n title={\n <>\n Total file size\n <br />\n Does not include files hosted outside Synapse\n </>\n }\n slotProps={{ tooltip: { sx: { maxWidth: 400 } } }}\n >\n <span className={styles.totalFileSize}>\n {calculateFriendlyFileSize(\n data.sumOfFileSizesAvailableForDownload,\n )}{' '}\n total*\n </span>\n </Tooltip>\n )}\n </h3>\n <div>\n <div className={styles.subSectionOverview}>\n <div className={styles.subSection}>\n <div className={styles.subSectionHeader}>\n <div className={styles.headline}>\n <Typography variant={'headline3'}>\n <IconSvg icon=\"packagableFile\" /> Web Download (.ZIP)\n </Typography>\n </div>\n <div className={styles.subSectionActions}>\n {data.numberOfFilesAvailableForDownloadAndEligibleForPackaging >\n 0 && (\n <Button\n variant=\"contained\"\n onClick={() => {\n setIsShowingCreatePackageUI(true)\n }}\n startIcon={<IconSvg icon=\"download\" wrap={false} />}\n >\n Download Packages\n </Button>\n )}\n {data.numberOfFilesAvailableForDownloadAndEligibleForPackaging ==\n 0 && (\n <Tooltip\n title=\"You cannot create a .zip package because there are no eligible files.\"\n enterNextDelay={300}\n placement=\"top\"\n >\n <span>\n <Button\n variant=\"contained\"\n disabled\n startIcon={\n <IconSvg icon=\"download\" wrap={false} />\n }\n >\n Download Packages\n </Button>\n </span>\n </Tooltip>\n )}\n </div>\n </div>\n <ComponentCollapse\n component={\n <Typography variant={'body1'} component={'div'}>\n Only some files may be eligible for packaging as ZIP.\n Use the Individual Downloads tab, below, to download\n non-packageable files.&nbsp;\n <a\n href=\"https://help.synapse.org/docs/Downloading-Data-From-the-Synapse-UI.2004254837.html\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n More Information.\n </a>\n </Typography>\n }\n collapseBoxSx={{\n backgroundColor: 'transparent',\n p: 0,\n mt: '15px',\n }}\n componentContainerSx={{\n backgroundColor: 'transparent',\n p: 0,\n }}\n iconSx={{ width: '25px', height: '25px' }}\n >\n <Typography\n variant={'body1'}\n component={'div'}\n sx={{ marginBottom: '15px' }}\n >\n <ul>\n <li>Files are bundled into .ZIPs up to 1 GB each.</li>\n <li>\n Larger selections are split into multiple packages.\n </li>\n <li>\n Includes only files stored in Synapse native\n storage.\n </li>\n <li>\n Each package contains a CSV manifest with file\n annotations and metadata.\n </li>\n </ul>\n </Typography>\n </ComponentCollapse>\n </div>\n <div className={styles.subSection}>\n <div className={styles.subSectionHeader}>\n <div className={styles.headline}>\n <Typography variant={'headline3'}>\n <IconSvg icon=\"code\" /> Programmatic Download\n </Typography>\n </div>\n <div className={styles.subSectionActions}>\n <Button\n variant=\"contained\"\n onClick={() => {\n setIsShowingModal(true)\n }}\n startIcon={<IconSvg icon=\"download\" wrap={false} />}\n >\n Get R / Python / CLI Code\n </Button>\n </div>\n </div>\n <ComponentCollapse\n component={\n <Typography variant={'body1'} component={'div'}>\n Use our Python, R, or command line clients to download\n files quickly.&nbsp;\n <a\n href=\"https://help.synapse.org/docs/Downloading-Data-Programmatically.2003796248.html\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n More Information.\n </a>\n </Typography>\n }\n collapseBoxSx={{\n backgroundColor: 'transparent',\n p: 0,\n mt: '15px',\n }}\n componentContainerSx={{\n backgroundColor: 'transparent',\n p: 0,\n }}\n iconSx={{ width: '25px', height: '25px' }}\n >\n <Typography\n variant={'body1'}\n component={'div'}\n sx={{ marginBottom: '15px' }}\n >\n <ul>\n <li>Requires a client (R, Python, or CLI).</li>\n <li>\n No limits on individual file size or total download\n size.\n </li>\n <li>\n Includes files stored both on and off Synapse native\n storage.\n </li>\n <li>\n Downloads include a CSV manifest with file\n annotations and metadata.\n </li>\n </ul>\n </Typography>\n </ComponentCollapse>\n </div>\n {isDownloadAllEnabled &&\n data.numberOfFilesAvailableForDownloadAndEligibleForPackaging <\n data.numberOfFilesAvailableForDownload && (\n <div className={styles.subSection}>\n <div className={styles.subSectionHeader}>\n <div className={styles.headline}>\n <Typography variant={'headline3'}>\n <IconSvg icon=\"multiFile\" /> Multi-file Download\n </Typography>\n </div>\n <div className={styles.subSectionActions}>\n <div className={styles.ineligibleDownloadContainer}>\n <DownloadIneligibleForPackagingFilesFromListButton />\n </div>\n </div>\n </div>\n <ComponentCollapse\n component={\n <Typography variant={'body1'} component={'div'}>\n Files which <strong>aren't</strong> included in a\n ZIP package may be downloaded as a multi-file\n download.&nbsp;\n </Typography>\n }\n collapseBoxSx={{\n backgroundColor: 'transparent',\n p: 0,\n mt: '15px',\n }}\n componentContainerSx={{\n backgroundColor: 'transparent',\n p: 0,\n }}\n iconSx={{ width: '25px', height: '25px' }}\n >\n <Typography\n variant={'body1'}\n component={'div'}\n sx={{ marginBottom: '15px' }}\n >\n Clicking “Start Multi-file Download” initiates a\n download of all files in your Download List that\n can’t be bundled into a ZIP—such as large files or\n external links—in one step. Files are saved one by\n one with clear progress updates, automatic handling\n of duplicate names, and retry support if something\n fails. You can cancel anytime.\n <br />\n <br />\n Note: in some browsers, files download individually\n to your default Downloads folder instead of a\n selected location.\n </Typography>\n </ComponentCollapse>\n </div>\n )}\n </div>\n </div>\n\n <div>\n {isShowingCreatePackageUI && (\n <div ref={createPackageRef}>\n <CreatePackageV2\n onPackageCreation={() => {\n setIsShowingDownloadSuccessAlert(true)\n }}\n />\n </div>\n )}\n <h3 className={`${styles.sectionTitle} pageHeaderTitle`}>\n Download Individual Files\n </h3>\n <div className={styles.tabsContainer}>\n <ul className={styles.navTabs}>\n <li className={styles.navLabel}>View:</li>\n {filterTabs.map((tab, index) => (\n <li\n key={tab.label}\n className={`${styles.navItem}${\n selectedFilterTabIndex === index\n ? ` ${styles.active}`\n : ''\n }`}\n aria-selected={selectedFilterTabIndex === index}\n >\n <button\n onClick={() => setSelectedFilterTabIndex(index)}\n >\n {tab.label}\n {!isError && !isLoading && data && (\n <span className={styles.fileCount}>\n {getFilterCount(data, tab.filter)}\n </span>\n )}\n </button>\n </li>\n ))}\n </ul>\n </div>\n\n <div className={styles.availableForDownloadTableContainer}>\n <AvailableForDownloadTable\n key={selectedFilter ?? 'all'}\n filter={selectedFilter}\n />\n </div>\n </div>\n </div>\n )}\n {data.numberOfFilesAvailableForDownload === 0 && (\n <div className={styles.placeholder}>\n <Typography variant=\"body1Italic\">\n Your Download List is currently empty.\n </Typography>\n </div>\n )}\n </div>\n )}\n <FullWidthAlert\n show={isShowingDownloadSuccessAlert}\n variant=\"success\"\n title=\"Package Created\"\n description=\"A package has been created with eligible files. The items contained in this .zip file have been removed from your list. If your package is over 1GB, you will need to create multiple packages.\"\n autoCloseAfterDelayInSeconds={10}\n onClose={() => {\n setIsShowingDownloadSuccessAlert(false)\n }}\n />\n {isShowingModal && (\n <ProgrammaticInstructionsModal\n show={true}\n onClose={() => setIsShowingModal(false)}\n title=\"Download Programmatically\"\n pythonCode={pythonDownloadCode}\n cliCode={cliDownloadCode}\n // rCode={rDownloadCode}\n />\n )}\n </div>\n )\n}\n\nexport default DownloadCartPage\n"],"mappings":"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"}
1
+ {"version":3,"file":"DownloadCartPage.js","names":[],"sources":["../../../src/components/DownloadCart/DownloadCartPage.tsx"],"sourcesContent":["import styles from './DownloadCartPage.module.scss'\nimport SynapseClient from '@/synapse-client'\nimport { useGetDownloadListStatistics } from '@/synapse-queries/download/useDownloadList'\nimport { useSynapseContext } from '@/utils/context/SynapseContext'\nimport { DeleteTwoTone } from '@mui/icons-material'\nimport { Button, Tooltip, Typography } from '@mui/material'\nimport {\n AvailableFilter,\n FilesStatisticsResponse,\n} from '@sage-bionetworks/synapse-types'\nimport { useEffect, useRef, useState } from 'react'\nimport { ErrorBanner } from '../error/ErrorBanner'\nimport FullWidthAlert from '../FullWidthAlert/FullWidthAlert'\nimport IconSvg from '../IconSvg/IconSvg'\nimport { ProgrammaticInstructionsModal } from '../ProgrammaticInstructionsModal/ProgrammaticInstructionsModal'\nimport AvailableForDownloadTable from './AvailableForDownloadTable'\nimport { CreatePackageV2 } from './CreatePackageV2'\nimport { PYTHON_CLIENT_IMPORT_AND_LOGIN } from './DirectProgrammaticDownload'\nimport { calculateFriendlyFileSize } from '@/utils/functions/calculateFriendlyFileSize'\nimport { DownloadIneligibleForPackagingFilesFromListButton } from './DownloadIneligibleForPackagingFilesFromListButton'\nimport {\n DownloadListActionsRequired,\n DownloadListActionsRequiredProps,\n} from './DownloadListActionsRequired'\nimport ComponentCollapse from '../ComponentCollapse'\n\nconst pythonDownloadCode = `${PYTHON_CLIENT_IMPORT_AND_LOGIN}\ndl_list_file_entities = syn.get_download_list()`\n\nconst cliDownloadCode = `synapse get-download-list`\n\nconst filterTabs: { label: string; filter: AvailableFilter }[] = [\n { label: 'All Files', filter: undefined },\n { label: 'Files included in ZIP', filter: 'eligibleForPackaging' },\n { label: 'Files not included in ZIP', filter: 'ineligibleForPackaging' },\n]\n\n/**\n * Show the Download Cart page.\n */\nexport function DownloadCartPage(props: DownloadListActionsRequiredProps) {\n const { accessToken } = useSynapseContext()\n const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0)\n const [selectedFilterTabIndex, setSelectedFilterTabIndex] =\n useState<number>(0)\n const selectedFilter = filterTabs[selectedFilterTabIndex].filter\n const [isShowingCreatePackageUI, setIsShowingCreatePackageUI] =\n useState<boolean>(false)\n const createPackageRef = useRef<HTMLDivElement>(null)\n // scroll to the CreatePackageV2 component when it is shown so that the user is aware of the next steps after clicking the button to create a package\n useEffect(() => {\n if (isShowingCreatePackageUI) {\n createPackageRef.current?.scrollIntoView({\n behavior: 'smooth',\n block: 'center',\n })\n }\n }, [isShowingCreatePackageUI])\n const [isShowingModal, setIsShowingModal] = useState<boolean>(false)\n const [isShowingDownloadSuccessAlert, setIsShowingDownloadSuccessAlert] =\n useState(false)\n const [error, setError] = useState<Error>()\n const {\n data,\n isLoading,\n isError,\n error: newError,\n refetch,\n } = useGetDownloadListStatistics()\n useEffect(() => {\n if (isError && newError) {\n setError(newError)\n }\n }, [isError, newError])\n\n const getFilterCount = (\n data: FilesStatisticsResponse,\n filter: AvailableFilter,\n ) => {\n if (filter === undefined) return data.numberOfFilesAvailableForDownload\n if (filter === 'eligibleForPackaging')\n return data.numberOfFilesAvailableForDownloadAndEligibleForPackaging\n return (\n data.numberOfFilesAvailableForDownload -\n data.numberOfFilesAvailableForDownloadAndEligibleForPackaging\n )\n }\n // SWC-5874: When arriving at the download cart when there are no ARs, the user should start in the Download list\n useEffect(() => {\n if (data && data.numberOfFilesRequiringAction == 0) {\n setSelectedTabIndex(1)\n }\n // also hide the Create Package UI if there are no files available for download\n if (\n data &&\n data.numberOfFilesAvailableForDownloadAndEligibleForPackaging === 0\n ) {\n setIsShowingCreatePackageUI(false)\n }\n }, [data])\n\n if (error) {\n return <ErrorBanner error={error} />\n }\n const clearDownloadList = async () => {\n await SynapseClient.clearDownloadListV2(accessToken)\n refetch()\n }\n return (\n <div className={styles.DownloadCartPage}>\n <div>\n <div className={`${styles.pageHeader}`}>\n <div className=\"container\">\n <div className={styles.grid}>\n <h3 className=\"pageHeaderTitle\">Your Download List</h3>\n <Tooltip\n title=\"Immediately removes all items from your download list\"\n enterNextDelay={300}\n placement=\"right\"\n >\n <Button\n onClick={() => {\n clearDownloadList()\n }}\n variant=\"text\"\n color=\"primary\"\n startIcon={<DeleteTwoTone />}\n >\n Clear Your Download List\n </Button>\n </Tooltip>\n </div>\n </div>\n </div>\n </div>\n <div className={styles.tabsContainer}>\n <div className=\"container\">\n <ul className={styles.navTabs}>\n <li\n className={`${styles.navItem}${\n selectedTabIndex == 0 ? ` ${styles.active}` : ''\n }`}\n aria-selected={selectedTabIndex == 0}\n >\n <button onClick={() => setSelectedTabIndex(0)}>\n Access Actions Required\n {!isError && !isLoading && data && (\n <span className={styles.fileCount}>\n {data.totalNumberOfFiles -\n data.numberOfFilesAvailableForDownload}\n </span>\n )}\n </button>\n </li>\n <li\n className={`${styles.navItem}${\n selectedTabIndex == 1 ? ` ${styles.active}` : ''\n }`}\n aria-selected={selectedTabIndex == 1}\n >\n <button onClick={() => setSelectedTabIndex(1)}>\n Download List\n {!isError && !isLoading && data && (\n <span className={styles.fileCount}>\n {data.numberOfFilesAvailableForDownload}\n </span>\n )}\n </button>\n </li>\n </ul>\n </div>\n </div>\n\n <div\n className={\n /*\n The DownloadListActionsRequired component stores actions in local state to track which actions are completed.\n\n Mount the actions required component and hide it with style to ensure actions stored in component state are persisted as the user switches tabs\n */\n !(selectedTabIndex == 0 && !isError && !isLoading && data)\n ? styles.hidden\n : undefined\n }\n >\n {data?.numberOfFilesRequiringAction &&\n data.numberOfFilesRequiringAction > 0 && (\n // Unmount instead of hiding the DownloadListActionsRequired component when there are 0 actions required\n // The DownloadListActionsRequired cannot discern between a fulfilled action and an action that is no longer required because the\n // file was removed from the download cart.\n // In the typical case where the download cart is cleared, unmounting the component ensures that the actions are cleared out.\n <div>\n <div className=\"container\">\n <DownloadListActionsRequired {...props} />\n </div>\n </div>\n )}\n {data?.numberOfFilesRequiringAction === 0 && (\n <div className={styles.placeholder}>\n <div>No actions are currently required.</div>\n </div>\n )}\n </div>\n\n {selectedTabIndex == 1 && !isError && !isLoading && data && (\n <div>\n {data.numberOfFilesAvailableForDownload > 0 && (\n <div className={`container ${styles.DownloadListTabContent}`}>\n <h3 className={`${styles.sectionTitle} pageHeaderTitle`}>\n Bulk Download Options\n {data.sumOfFileSizesAvailableForDownload != null && (\n <Tooltip\n placement=\"right\"\n title={\n <>\n Total file size\n <br />\n Does not include files hosted outside Synapse\n </>\n }\n slotProps={{ tooltip: { sx: { maxWidth: 400 } } }}\n >\n <span className={styles.totalFileSize}>\n {calculateFriendlyFileSize(\n data.sumOfFileSizesAvailableForDownload,\n )}{' '}\n total*\n </span>\n </Tooltip>\n )}\n </h3>\n <div>\n <div className={styles.subSectionOverview}>\n <div className={styles.subSection}>\n <div className={styles.subSectionHeader}>\n <div className={styles.headline}>\n <Typography variant={'headline3'}>\n <IconSvg icon=\"packagableFile\" /> Web Download (.ZIP)\n </Typography>\n </div>\n <div className={styles.subSectionActions}>\n {data.numberOfFilesAvailableForDownloadAndEligibleForPackaging >\n 0 && (\n <Button\n variant=\"contained\"\n onClick={() => {\n setIsShowingCreatePackageUI(true)\n }}\n startIcon={<IconSvg icon=\"download\" wrap={false} />}\n >\n Download Packages\n </Button>\n )}\n {data.numberOfFilesAvailableForDownloadAndEligibleForPackaging ==\n 0 && (\n <Tooltip\n title=\"You cannot create a .zip package because there are no eligible files.\"\n enterNextDelay={300}\n placement=\"top\"\n >\n <span>\n <Button\n variant=\"contained\"\n disabled\n startIcon={\n <IconSvg icon=\"download\" wrap={false} />\n }\n >\n Download Packages\n </Button>\n </span>\n </Tooltip>\n )}\n </div>\n </div>\n <ComponentCollapse\n component={\n <Typography variant={'body1'} component={'div'}>\n Only some files may be eligible for packaging as ZIP.\n Use the Individual Downloads tab, below, to download\n non-packageable files.&nbsp;\n <a\n href=\"https://help.synapse.org/docs/Downloading-Data-From-the-Synapse-UI.2004254837.html\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n More Information.\n </a>\n </Typography>\n }\n collapseBoxSx={{\n backgroundColor: 'transparent',\n p: 0,\n mt: '15px',\n }}\n componentContainerSx={{\n backgroundColor: 'transparent',\n p: 0,\n }}\n iconSx={{ width: '25px', height: '25px' }}\n >\n <Typography\n variant={'body1'}\n component={'div'}\n sx={{ marginBottom: '15px' }}\n >\n <ul>\n <li>Files are bundled into .ZIPs up to 1 GB each.</li>\n <li>\n Larger selections are split into multiple packages.\n </li>\n <li>\n Includes only files stored in Synapse native\n storage.\n </li>\n <li>\n Each package contains a CSV manifest with file\n annotations and metadata.\n </li>\n </ul>\n </Typography>\n </ComponentCollapse>\n </div>\n <div className={styles.subSection}>\n <div className={styles.subSectionHeader}>\n <div className={styles.headline}>\n <Typography variant={'headline3'}>\n <IconSvg icon=\"code\" /> Programmatic Download\n </Typography>\n </div>\n <div className={styles.subSectionActions}>\n <Button\n variant=\"contained\"\n onClick={() => {\n setIsShowingModal(true)\n }}\n startIcon={<IconSvg icon=\"download\" wrap={false} />}\n >\n Get R / Python / CLI Code\n </Button>\n </div>\n </div>\n <ComponentCollapse\n component={\n <Typography variant={'body1'} component={'div'}>\n Use our Python, R, or command line clients to download\n files quickly.&nbsp;\n <a\n href=\"https://help.synapse.org/docs/Downloading-Data-Programmatically.2003796248.html\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n More Information.\n </a>\n </Typography>\n }\n collapseBoxSx={{\n backgroundColor: 'transparent',\n p: 0,\n mt: '15px',\n }}\n componentContainerSx={{\n backgroundColor: 'transparent',\n p: 0,\n }}\n iconSx={{ width: '25px', height: '25px' }}\n >\n <Typography\n variant={'body1'}\n component={'div'}\n sx={{ marginBottom: '15px' }}\n >\n <ul>\n <li>Requires a client (R, Python, or CLI).</li>\n <li>\n No limits on individual file size or total download\n size.\n </li>\n <li>\n Includes files stored both on and off Synapse native\n storage.\n </li>\n <li>\n Downloads include a CSV manifest with file\n annotations and metadata.\n </li>\n </ul>\n </Typography>\n </ComponentCollapse>\n </div>\n {data.numberOfFilesAvailableForDownloadAndEligibleForPackaging <\n data.numberOfFilesAvailableForDownload && (\n <div className={styles.subSection}>\n <div className={styles.subSectionHeader}>\n <div className={styles.headline}>\n <Typography variant={'headline3'}>\n <IconSvg icon=\"multiFile\" /> Multi-file Download\n </Typography>\n </div>\n <div className={styles.subSectionActions}>\n <div className={styles.ineligibleDownloadContainer}>\n <DownloadIneligibleForPackagingFilesFromListButton />\n </div>\n </div>\n </div>\n <ComponentCollapse\n component={\n <Typography variant={'body1'} component={'div'}>\n Files which <strong>aren't</strong> included in a\n ZIP package may be downloaded as a multi-file\n download.&nbsp;\n </Typography>\n }\n collapseBoxSx={{\n backgroundColor: 'transparent',\n p: 0,\n mt: '15px',\n }}\n componentContainerSx={{\n backgroundColor: 'transparent',\n p: 0,\n }}\n iconSx={{ width: '25px', height: '25px' }}\n >\n <Typography\n variant={'body1'}\n component={'div'}\n sx={{ marginBottom: '15px' }}\n >\n Clicking “Start Multi-file Download” initiates a\n download of all files in your Download List that can’t\n be bundled into a ZIP—such as large files or external\n links—in one step. Files are saved one by one with\n clear progress updates, automatic handling of\n duplicate names, and retry support if something fails.\n You can cancel anytime.\n <br />\n <br />\n Note: in some browsers, files download individually to\n your default Downloads folder instead of a selected\n location.\n </Typography>\n </ComponentCollapse>\n </div>\n )}\n </div>\n </div>\n\n <div>\n {isShowingCreatePackageUI && (\n <div ref={createPackageRef}>\n <CreatePackageV2\n onPackageCreation={() => {\n setIsShowingDownloadSuccessAlert(true)\n }}\n />\n </div>\n )}\n <h3 className={`${styles.sectionTitle} pageHeaderTitle`}>\n Download Individual Files\n </h3>\n <div className={styles.tabsContainer}>\n <ul className={styles.navTabs}>\n <li className={styles.navLabel}>View:</li>\n {filterTabs.map((tab, index) => (\n <li\n key={tab.label}\n className={`${styles.navItem}${\n selectedFilterTabIndex === index\n ? ` ${styles.active}`\n : ''\n }`}\n aria-selected={selectedFilterTabIndex === index}\n >\n <button\n onClick={() => setSelectedFilterTabIndex(index)}\n >\n {tab.label}\n {!isError && !isLoading && data && (\n <span className={styles.fileCount}>\n {getFilterCount(data, tab.filter)}\n </span>\n )}\n </button>\n </li>\n ))}\n </ul>\n </div>\n\n <div className={styles.availableForDownloadTableContainer}>\n <AvailableForDownloadTable\n key={selectedFilter ?? 'all'}\n filter={selectedFilter}\n />\n </div>\n </div>\n </div>\n )}\n {data.numberOfFilesAvailableForDownload === 0 && (\n <div className={styles.placeholder}>\n <Typography variant=\"body1Italic\">\n Your Download List is currently empty.\n </Typography>\n </div>\n )}\n </div>\n )}\n <FullWidthAlert\n show={isShowingDownloadSuccessAlert}\n variant=\"success\"\n title=\"Package Created\"\n description=\"A package has been created with eligible files. The items contained in this .zip file have been removed from your list. If your package is over 1GB, you will need to create multiple packages.\"\n autoCloseAfterDelayInSeconds={10}\n onClose={() => {\n setIsShowingDownloadSuccessAlert(false)\n }}\n />\n {isShowingModal && (\n <ProgrammaticInstructionsModal\n show={true}\n onClose={() => setIsShowingModal(false)}\n title=\"Download Programmatically\"\n pythonCode={pythonDownloadCode}\n cliCode={cliDownloadCode}\n // rCode={rDownloadCode}\n />\n )}\n </div>\n )\n}\n\nexport default DownloadCartPage\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA0BA,IAAM,IAAqB,GAAG,EAA+B;kDAGvD,IAAkB,6BAElB,IAA2D;CAC/D;EAAE,OAAO;EAAa,QAAQ,KAAA;EAAW;CACzC;EAAE,OAAO;EAAyB,QAAQ;EAAwB;CAClE;EAAE,OAAO;EAA6B,QAAQ;EAA0B;CACzE;AAKD,SAAgB,EAAiB,GAAyC;CACxE,IAAM,EAAE,mBAAgB,GAAmB,EACrC,CAAC,GAAkB,KAAuB,EAAiB,EAAE,EAC7D,CAAC,GAAwB,KAC7B,EAAiB,EAAE,EACf,IAAiB,EAAW,GAAwB,QACpD,CAAC,GAA0B,KAC/B,EAAkB,GAAM,EACpB,IAAmB,EAAuB,KAAK;AAErD,SAAgB;AACd,EAAI,KACF,EAAiB,SAAS,eAAe;GACvC,UAAU;GACV,OAAO;GACR,CAAC;IAEH,CAAC,EAAyB,CAAC;CAC9B,IAAM,CAAC,GAAgB,KAAqB,EAAkB,GAAM,EAC9D,CAAC,GAA+B,KACpC,EAAS,GAAM,EACX,CAAC,GAAO,KAAY,GAAiB,EACrC,EACJ,SACA,cACA,YACA,OAAO,GACP,eACE,GAA8B;AAClC,SAAgB;AACd,EAAI,KAAW,KACb,EAAS,EAAS;IAEnB,CAAC,GAAS,EAAS,CAAC;CAEvB,IAAM,KACJ,GACA,MAEI,MAAW,KAAA,IAAkB,EAAK,oCAClC,MAAW,yBACN,EAAK,2DAEZ,EAAK,oCACL,EAAK;AAiBT,KAbA,QAAgB;AAKd,EAJI,KAAQ,EAAK,gCAAgC,KAC/C,EAAoB,EAAE,EAItB,KACA,EAAK,6DAA6D,KAElE,EAA4B,GAAM;IAEnC,CAAC,EAAK,CAAC,EAEN,EACF,QAAO,kBAAC,GAAD,EAAoB,UAAS,CAAA;CAEtC,IAAM,IAAoB,YAAY;AAEpC,EADA,MAAM,EAAc,oBAAoB,EAAY,EACpD,GAAS;;AAEX,QACE,kBAAC,OAAD;EAAK,WAAW,EAAO;YAAvB;GACE,kBAAC,OAAD,EAAA,UACE,kBAAC,OAAD;IAAK,WAAW,GAAG,EAAO;cACxB,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,OAAD;MAAK,WAAW,EAAO;gBAAvB,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAkB;OAAuB,CAAA,EACvD,kBAAC,GAAD;OACE,OAAM;OACN,gBAAgB;OAChB,WAAU;iBAEV,kBAAC,GAAD;QACE,eAAe;AACb,YAAmB;;QAErB,SAAQ;QACR,OAAM;QACN,WAAW,kBAAC,GAAD,EAAiB,CAAA;kBAC7B;QAEQ,CAAA;OACD,CAAA,CACN;;KACF,CAAA;IACF,CAAA,EACF,CAAA;GACN,kBAAC,OAAD;IAAK,WAAW,EAAO;cACrB,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,MAAD;MAAI,WAAW,EAAO;gBAAtB,CACE,kBAAC,MAAD;OACE,WAAW,GAAG,EAAO,UACnB,KAAoB,IAAI,IAAI,EAAO,WAAW;OAEhD,iBAAe,KAAoB;iBAEnC,kBAAC,UAAD;QAAQ,eAAe,EAAoB,EAAE;kBAA7C,CAA+C,2BAE5C,CAAC,KAAW,CAAC,KAAa,KACzB,kBAAC,QAAD;SAAM,WAAW,EAAO;mBACrB,EAAK,qBACJ,EAAK;SACF,CAAA,CAEF;;OACN,CAAA,EACL,kBAAC,MAAD;OACE,WAAW,GAAG,EAAO,UACnB,KAAoB,IAAI,IAAI,EAAO,WAAW;OAEhD,iBAAe,KAAoB;iBAEnC,kBAAC,UAAD;QAAQ,eAAe,EAAoB,EAAE;kBAA7C,CAA+C,iBAE5C,CAAC,KAAW,CAAC,KAAa,KACzB,kBAAC,QAAD;SAAM,WAAW,EAAO;mBACrB,EAAK;SACD,CAAA,CAEF;;OACN,CAAA,CACF;;KACD,CAAA;IACF,CAAA;GAEN,kBAAC,OAAD;IACE,WAMI,KAAoB,KAAK,CAAC,KAAW,CAAC,KAAa,IAEjD,KAAA,IADA,EAAO;cARf,CAYG,GAAM,gCACL,EAAK,+BAA+B,KAKlC,kBAAC,OAAD,EAAA,UACE,kBAAC,OAAD;KAAK,WAAU;eACb,kBAAC,GAAD,EAA6B,GAAI,GAAS,CAAA;KACtC,CAAA,EACF,CAAA,EAET,GAAM,iCAAiC,KACtC,kBAAC,OAAD;KAAK,WAAW,EAAO;eACrB,kBAAC,OAAD,EAAA,UAAK,sCAAwC,CAAA;KACzC,CAAA,CAEJ;;GAEL,KAAoB,KAAK,CAAC,KAAW,CAAC,KAAa,KAClD,kBAAC,OAAD,EAAA,UAAA,CACG,EAAK,oCAAoC,KACxC,kBAAC,OAAD;IAAK,WAAW,aAAa,EAAO;cAApC;KACE,kBAAC,MAAD;MAAI,WAAW,GAAG,EAAO,aAAa;gBAAtC,CAAyD,yBAEtD,EAAK,sCAAsC,QAC1C,kBAAC,GAAD;OACE,WAAU;OACV,OACE,kBAAA,GAAA,EAAA,UAAA;QAAE;QAEA,kBAAC,MAAD,EAAM,CAAA;;QAEL,EAAA,CAAA;OAEL,WAAW,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,KAAK,EAAE,EAAE;iBAEjD,kBAAC,QAAD;QAAM,WAAW,EAAO;kBAAxB;SACG,EACC,EAAK,mCACN;SAAE;SAAI;SAEF;;OACC,CAAA,CAET;;KACL,kBAAC,OAAD,EAAA,UACE,kBAAC,OAAD;MAAK,WAAW,EAAO;gBAAvB;OACE,kBAAC,OAAD;QAAK,WAAW,EAAO;kBAAvB,CACE,kBAAC,OAAD;SAAK,WAAW,EAAO;mBAAvB,CACE,kBAAC,OAAD;UAAK,WAAW,EAAO;oBACrB,kBAAC,GAAD;WAAY,SAAS;qBAArB,CACE,kBAAC,GAAD,EAAS,MAAK,kBAAmB,CAAA,EAAA,uBACtB;;UACT,CAAA,EACN,kBAAC,OAAD;UAAK,WAAW,EAAO;oBAAvB,CACG,EAAK,2DACJ,KACA,kBAAC,GAAD;WACE,SAAQ;WACR,eAAe;AACb,cAA4B,GAAK;;WAEnC,WAAW,kBAAC,GAAD;YAAS,MAAK;YAAW,MAAM;YAAS,CAAA;qBACpD;WAEQ,CAAA,EAEV,EAAK,4DACJ,KACA,kBAAC,GAAD;WACE,OAAM;WACN,gBAAgB;WAChB,WAAU;qBAEV,kBAAC,QAAD,EAAA,UACE,kBAAC,GAAD;YACE,SAAQ;YACR,UAAA;YACA,WACE,kBAAC,GAAD;aAAS,MAAK;aAAW,MAAM;aAAS,CAAA;sBAE3C;YAEQ,CAAA,EACJ,CAAA;WACC,CAAA,CAER;YACF;YACN,kBAAC,GAAD;SACE,WACE,kBAAC,GAAD;UAAY,SAAS;UAAS,WAAW;oBAAzC,CAAgD,yIAI9C,kBAAC,KAAD;WACE,MAAK;WACL,QAAO;WACP,KAAI;qBACL;WAEG,CAAA,CACO;;SAEf,eAAe;UACb,iBAAiB;UACjB,GAAG;UACH,IAAI;UACL;SACD,sBAAsB;UACpB,iBAAiB;UACjB,GAAG;UACJ;SACD,QAAQ;UAAE,OAAO;UAAQ,QAAQ;UAAQ;mBAEzC,kBAAC,GAAD;UACE,SAAS;UACT,WAAW;UACX,IAAI,EAAE,cAAc,QAAQ;oBAE5B,kBAAC,MAAD,EAAA,UAAA;WACE,kBAAC,MAAD,EAAA,UAAI,iDAAkD,CAAA;WACtD,kBAAC,MAAD,EAAA,UAAI,uDAEC,CAAA;WACL,kBAAC,MAAD,EAAA,UAAI,yDAGC,CAAA;WACL,kBAAC,MAAD,EAAA,UAAI,4EAGC,CAAA;WACF,EAAA,CAAA;UACM,CAAA;SACK,CAAA,CAChB;;OACN,kBAAC,OAAD;QAAK,WAAW,EAAO;kBAAvB,CACE,kBAAC,OAAD;SAAK,WAAW,EAAO;mBAAvB,CACE,kBAAC,OAAD;UAAK,WAAW,EAAO;oBACrB,kBAAC,GAAD;WAAY,SAAS;qBAArB,CACE,kBAAC,GAAD,EAAS,MAAK,QAAS,CAAA,EAAA,yBACZ;;UACT,CAAA,EACN,kBAAC,OAAD;UAAK,WAAW,EAAO;oBACrB,kBAAC,GAAD;WACE,SAAQ;WACR,eAAe;AACb,cAAkB,GAAK;;WAEzB,WAAW,kBAAC,GAAD;YAAS,MAAK;YAAW,MAAM;YAAS,CAAA;qBACpD;WAEQ,CAAA;UACL,CAAA,CACF;YACN,kBAAC,GAAD;SACE,WACE,kBAAC,GAAD;UAAY,SAAS;UAAS,WAAW;oBAAzC,CAAgD,6EAG9C,kBAAC,KAAD;WACE,MAAK;WACL,QAAO;WACP,KAAI;qBACL;WAEG,CAAA,CACO;;SAEf,eAAe;UACb,iBAAiB;UACjB,GAAG;UACH,IAAI;UACL;SACD,sBAAsB;UACpB,iBAAiB;UACjB,GAAG;UACJ;SACD,QAAQ;UAAE,OAAO;UAAQ,QAAQ;UAAQ;mBAEzC,kBAAC,GAAD;UACE,SAAS;UACT,WAAW;UACX,IAAI,EAAE,cAAc,QAAQ;oBAE5B,kBAAC,MAAD,EAAA,UAAA;WACE,kBAAC,MAAD,EAAA,UAAI,0CAA2C,CAAA;WAC/C,kBAAC,MAAD,EAAA,UAAI,6DAGC,CAAA;WACL,kBAAC,MAAD,EAAA,UAAI,iEAGC,CAAA;WACL,kBAAC,MAAD,EAAA,UAAI,wEAGC,CAAA;WACF,EAAA,CAAA;UACM,CAAA;SACK,CAAA,CAChB;;OACL,EAAK,2DACJ,EAAK,qCACL,kBAAC,OAAD;QAAK,WAAW,EAAO;kBAAvB,CACE,kBAAC,OAAD;SAAK,WAAW,EAAO;mBAAvB,CACE,kBAAC,OAAD;UAAK,WAAW,EAAO;oBACrB,kBAAC,GAAD;WAAY,SAAS;qBAArB,CACE,kBAAC,GAAD,EAAS,MAAK,aAAc,CAAA,EAAA,uBACjB;;UACT,CAAA,EACN,kBAAC,OAAD;UAAK,WAAW,EAAO;oBACrB,kBAAC,OAAD;WAAK,WAAW,EAAO;qBACrB,kBAAC,GAAD,EAAqD,CAAA;WACjD,CAAA;UACF,CAAA,CACF;YACN,kBAAC,GAAD;SACE,WACE,kBAAC,GAAD;UAAY,SAAS;UAAS,WAAW;oBAAzC;WAAgD;WAClC,kBAAC,UAAD,EAAA,UAAQ,UAAe,CAAA;;WAGxB;;SAEf,eAAe;UACb,iBAAiB;UACjB,GAAG;UACH,IAAI;UACL;SACD,sBAAsB;UACpB,iBAAiB;UACjB,GAAG;UACJ;SACD,QAAQ;UAAE,OAAO;UAAQ,QAAQ;UAAQ;mBAEzC,kBAAC,GAAD;UACE,SAAS;UACT,WAAW;UACX,IAAI,EAAE,cAAc,QAAQ;oBAH9B;WAIC;WAQC,kBAAC,MAAD,EAAM,CAAA;WACN,kBAAC,MAAD,EAAM,CAAA;;WAIK;;SACK,CAAA,CAChB;;OAEJ;SACF,CAAA;KAEN,kBAAC,OAAD,EAAA,UAAA;MACG,KACC,kBAAC,OAAD;OAAK,KAAK;iBACR,kBAAC,GAAD,EACE,yBAAyB;AACvB,UAAiC,GAAK;UAExC,CAAA;OACE,CAAA;MAER,kBAAC,MAAD;OAAI,WAAW,GAAG,EAAO,aAAa;iBAAmB;OAEpD,CAAA;MACL,kBAAC,OAAD;OAAK,WAAW,EAAO;iBACrB,kBAAC,MAAD;QAAI,WAAW,EAAO;kBAAtB,CACE,kBAAC,MAAD;SAAI,WAAW,EAAO;mBAAU;SAAU,CAAA,EACzC,EAAW,KAAK,GAAK,MACpB,kBAAC,MAAD;SAEE,WAAW,GAAG,EAAO,UACnB,MAA2B,IACvB,IAAI,EAAO,WACX;SAEN,iBAAe,MAA2B;mBAE1C,kBAAC,UAAD;UACE,eAAe,EAA0B,EAAM;oBADjD,CAGG,EAAI,OACJ,CAAC,KAAW,CAAC,KAAa,KACzB,kBAAC,QAAD;WAAM,WAAW,EAAO;qBACrB,EAAe,GAAM,EAAI,OAAO;WAC5B,CAAA,CAEF;;SACN,EAlBE,EAAI,MAkBN,CACL,CACC;;OACD,CAAA;MAEN,kBAAC,OAAD;OAAK,WAAW,EAAO;iBACrB,kBAAC,GAAD,EAEE,QAAQ,GACR,EAFK,KAAkB,MAEvB;OACE,CAAA;MACF,EAAA,CAAA;KACF;OAEP,EAAK,sCAAsC,KAC1C,kBAAC,OAAD;IAAK,WAAW,EAAO;cACrB,kBAAC,GAAD;KAAY,SAAQ;eAAc;KAErB,CAAA;IACT,CAAA,CAEJ,EAAA,CAAA;GAER,kBAAC,GAAD;IACE,MAAM;IACN,SAAQ;IACR,OAAM;IACN,aAAY;IACZ,8BAA8B;IAC9B,eAAe;AACb,OAAiC,GAAM;;IAEzC,CAAA;GACD,KACC,kBAAC,GAAD;IACE,MAAM;IACN,eAAe,EAAkB,GAAM;IACvC,OAAM;IACN,YAAY;IACZ,SAAS;IAET,CAAA;GAEA"}
@@ -1 +1 @@
1
- {"version":3,"file":"DownloadIneligibleForPackagingFilesFromListButton.d.ts","sourceRoot":"","sources":["../../../src/components/DownloadCart/DownloadIneligibleForPackagingFilesFromListButton.tsx"],"names":[],"mappings":"AAoEA,MAAM,MAAM,sDAAsD,GAAG;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,CAAA;CAC5C,CAAA;AAED;;;;GAIG;AACH,wBAAgB,iDAAiD,CAC/D,KAAK,EAAE,sDAAsD,kDAw0B9D;AAED,eAAe,iDAAiD,CAAA"}
1
+ {"version":3,"file":"DownloadIneligibleForPackagingFilesFromListButton.d.ts","sourceRoot":"","sources":["../../../src/components/DownloadCart/DownloadIneligibleForPackagingFilesFromListButton.tsx"],"names":[],"mappings":"AAoEA,MAAM,MAAM,sDAAsD,GAAG;IACnE,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,OAAO,CAAC,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,CAAA;CAC5C,CAAA;AAED;;;;GAIG;AACH,wBAAgB,iDAAiD,CAC/D,KAAK,EAAE,sDAAsD,kDAu0B9D;AAED,eAAe,iDAAiD,CAAA"}
@@ -345,7 +345,7 @@ function A(E) {
345
345
  className: f.progressStats,
346
346
  children: [/* @__PURE__ */ C(x, {
347
347
  className: f.progressStatsText,
348
- children: V.totalBytes > 0 ? `${Math.round(V.bytesDownloaded / V.totalBytes * 100)}% ( ${t(V.bytesDownloaded, 0)} / ${t(V.totalBytes)} )` : ""
348
+ children: V.totalBytes > 0 ? `${Math.round(V.bytesDownloaded / V.totalBytes * 100)}% ( ${t(V.bytesDownloaded)} / ${t(V.totalBytes)} )` : ""
349
349
  }), /* @__PURE__ */ C(x, {
350
350
  className: f.progressStatsText,
351
351
  children: V.estimatedSecondsRemaining === null ? "" : `About ${T.duration({ seconds: V.estimatedSecondsRemaining }).humanize()} remaining`
@@ -1 +1 @@
1
- {"version":3,"file":"DownloadIneligibleForPackagingFilesFromListButton.js","names":[],"sources":["../../../src/components/DownloadCart/DownloadIneligibleForPackagingFilesFromListButton.tsx"],"sourcesContent":["import { useSynapseContext } from '@/utils'\nimport { implementsExternalFileHandleInterface } from '@/utils/types/IsType'\nimport { calculateFriendlyFileSize } from '@/utils/functions/calculateFriendlyFileSize'\nimport {\n Box,\n Button,\n IconButton,\n LinearProgress,\n Paper,\n Typography,\n} from '@mui/material'\nimport { Close, Download } from '@mui/icons-material'\nimport CloudWarning from '@/assets/icons/CloudWarning'\nimport { SynapseClientError } from '@sage-bionetworks/synapse-client/util/SynapseClientError'\nimport dayjs from 'dayjs'\nimport duration from 'dayjs/plugin/duration'\nimport relativeTime from 'dayjs/plugin/relativeTime'\nimport {\n BatchFileRequest,\n BatchFileResult,\n ExternalFileHandle,\n FileEntity,\n FileHandleAssociation,\n FileHandleAssociateType,\n} from '@sage-bionetworks/synapse-types'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { displayToast } from '../ToastMessage/ToastMessage'\nimport {\n useGetAvailableFilesToDownloadInfinite,\n useGetDownloadListStatistics,\n useRemoveFilesFromDownloadList,\n} from '@/synapse-queries/download/useDownloadList'\nimport { getFiles } from '@/synapse-client/SynapseClient'\nimport { useGetEntityQueryOptions } from '@/synapse-queries/entity/useEntity'\n\nimport { deduplicateFileName, getFileName } from './fileNameUtils'\nimport styles from './DownloadIneligibleForPackagingFilesFromListButton.module.scss'\n\n// showDirectoryPicker is not yet available on the Window interface.\ninterface FileSystemAccessWindow extends Window {\n showDirectoryPicker(options?: {\n mode?: 'read' | 'readwrite'\n startIn?:\n | 'desktop'\n | 'documents'\n | 'downloads'\n | 'music'\n | 'pictures'\n | 'videos'\n }): Promise<FileSystemDirectoryHandle>\n}\n\n/**\n * Type guard to check if File System Access API is available\n */\nfunction supportsFileSystemAccess(\n window: Window,\n): window is FileSystemAccessWindow {\n return 'showDirectoryPicker' in window\n}\n\nconst FILE_BATCH_SIZE = 50 // Maximum batch size for fetching presigned URLs\nconst ETA_DELAY_MS = 3000 // Wait this long before showing ETA\n\ndayjs.extend(duration)\ndayjs.extend(relativeTime)\n\nexport type DownloadIneligibleForPackagingFilesFromListButtonProps = {\n buttonText?: string\n variant?: 'text' | 'outlined' | 'contained'\n}\n\n/**\n * Button component that downloads files from the user's download list that are ineligible for packaging.\n * Uses the infinite query hook to fetch all pages of download list items,\n * batches requests to get presigned URLs, and triggers browser downloads.\n */\nexport function DownloadIneligibleForPackagingFilesFromListButton(\n props: DownloadIneligibleForPackagingFilesFromListButtonProps,\n) {\n const { buttonText = 'Start Multi-file Download', variant = 'contained' } =\n props\n const { accessToken, isAuthenticated } = useSynapseContext()\n const queryClient = useQueryClient()\n const getEntityQueryOptions = useGetEntityQueryOptions<FileEntity>()\n const [isDownloading, setIsDownloading] = useState(false)\n const isDownloadingRef = useRef(false)\n const [showExternalFileWarning, setShowExternalFileWarning] = useState(false)\n const isCancelledRef = useRef(false)\n const abortControllerRef = useRef<AbortController | null>(null)\n const [downloadProgress, setDownloadProgress] = useState<{\n currentFile: string\n fileIndex: number\n totalFiles: number\n bytesDownloaded: number\n totalBytes: number\n estimatedSecondsRemaining: number | null\n } | null>(null)\n const fileDownloadStartRef = useRef<number | null>(null)\n\n // Accessibility: tracks the last announced file index and progress bucket so\n // the aria-live region only updates on meaningful changes (not every chunk).\n const [announceText, setAnnounceText] = useState('')\n const lastAnnouncedFileIndexRef = useRef<number>(-1)\n const lastAnnouncedPercentBucketRef = useRef<number>(-1)\n\n useEffect(() => {\n if (!downloadProgress) {\n lastAnnouncedFileIndexRef.current = -1\n lastAnnouncedPercentBucketRef.current = -1\n setAnnounceText('')\n return\n }\n\n const { fileIndex, totalFiles, currentFile, bytesDownloaded, totalBytes } =\n downloadProgress\n\n // Announce when a new file starts downloading\n if (fileIndex !== lastAnnouncedFileIndexRef.current) {\n lastAnnouncedFileIndexRef.current = fileIndex\n lastAnnouncedPercentBucketRef.current = -1\n setAnnounceText(\n `Downloading file ${fileIndex} of ${totalFiles}: ${currentFile}`,\n )\n return\n }\n\n // Announce at each 25% milestone (25, 50, 75, 100) once per file\n if (totalBytes > 0) {\n const percent = Math.floor((bytesDownloaded / totalBytes) * 100)\n const bucket = Math.floor(percent / 25) * 25\n if (bucket > 0 && bucket !== lastAnnouncedPercentBucketRef.current) {\n lastAnnouncedPercentBucketRef.current = bucket\n setAnnounceText(`${bucket}% of ${currentFile} downloaded`)\n }\n }\n }, [downloadProgress])\n\n const { data, status, hasNextPage, fetchNextPage, error } =\n useGetAvailableFilesToDownloadInfinite()\n\n const { mutateAsync: removeFilesFromDownloadList } =\n useRemoveFilesFromDownloadList()\n\n const { refetch: refetchDownloadListStatistics } =\n useGetDownloadListStatistics()\n\n /**\n * Downloads all files from the download list.\n */\n const downloadAllFiles = useCallback(\n async (downloadListItems: typeof data) => {\n // Prevent re-entry if already downloading\n if (isDownloadingRef.current) {\n return\n }\n\n if (!downloadListItems || !isAuthenticated) {\n setIsDownloading(false)\n return\n }\n\n try {\n isDownloadingRef.current = true\n isCancelledRef.current = false\n setShowExternalFileWarning(false)\n const allItems = downloadListItems.pages.flatMap(page => page.page)\n\n // Filter to only include files that are NOT eligible for packaging\n const nonPackageableFiles = allItems.filter(\n item => !item.isEligibleForPackaging,\n )\n\n if (nonPackageableFiles.length === 0) {\n displayToast('No non-packageable files available to download', 'info')\n setIsDownloading(false)\n isDownloadingRef.current = false\n return\n }\n\n // Determine if File System Access API is available\n const useFileSystemAccess = supportsFileSystemAccess(window)\n\n // Prompt user to select a directory for downloads BEFORE async operations\n // (must be called during user gesture to avoid SecurityError)\n let directoryHandle: FileSystemDirectoryHandle | undefined\n\n if (useFileSystemAccess) {\n // TypeScript narrowing: we know window has showDirectoryPicker at this point\n const fileSystemWindow = window as unknown as FileSystemAccessWindow\n\n try {\n directoryHandle = await fileSystemWindow.showDirectoryPicker({\n mode: 'readwrite',\n })\n } catch (error) {\n // User cancelled directory picker\n console.error('Failed to select directory:', error)\n displayToast(\n 'Directory selection is required to directly download files from the website.',\n 'warning',\n )\n setIsDownloading(false)\n isDownloadingRef.current = false\n return\n }\n } else {\n // Show info message for browsers without File System Access API\n displayToast(\n 'Your browser will download files individually to your default downloads folder. Each file may trigger a separate download prompt.',\n 'info',\n )\n }\n\n // Initialize progress tracking\n fileDownloadStartRef.current = null\n setDownloadProgress({\n currentFile: '',\n fileIndex: 0,\n totalFiles: nonPackageableFiles.length,\n bytesDownloaded: 0,\n totalBytes: 0,\n estimatedSecondsRemaining: null,\n })\n\n // Fetch entities to get dataFileHandleId for each file\n // Use React Query's ensureQueryData to leverage cache and built-in retry logic (handles 429s with exponential backoff)\n // Map key combines entity ID and version to handle multiple versions of the same entity\n const entityIdToFileHandleId = new Map<string, string>()\n\n // Fetch all entities in parallel - React Query handles concurrency and retries\n const entityPromises = nonPackageableFiles.map(async item => {\n if (isCancelledRef.current) {\n return\n }\n\n try {\n const entity = await queryClient.ensureQueryData(\n getEntityQueryOptions(\n item.fileEntityId,\n item.versionNumber?.toString(),\n ),\n )\n // Use composite key: entityId-version (or entityId if no version)\n const mapKey = `${entity.id!}-${item.versionNumber ?? 'latest'}`\n entityIdToFileHandleId.set(mapKey, entity.dataFileHandleId)\n } catch (error) {\n console.error(`Failed to fetch entity ${item.fileEntityId}:`, error)\n // Continue even if one entity fails\n }\n })\n\n await Promise.all(entityPromises)\n\n // Convert download list items to file handle associations using the fetched dataFileHandleIds\n const fileHandleAssociations: FileHandleAssociation[] =\n nonPackageableFiles\n .filter(item => {\n const mapKey = `${item.fileEntityId}-${\n item.versionNumber ?? 'latest'\n }`\n return entityIdToFileHandleId.has(mapKey)\n })\n .map(item => {\n const mapKey = `${item.fileEntityId}-${\n item.versionNumber ?? 'latest'\n }`\n return {\n fileHandleId: entityIdToFileHandleId.get(mapKey)!,\n associateObjectId: item.fileEntityId,\n associateObjectType: FileHandleAssociateType.FileEntity,\n }\n })\n\n if (fileHandleAssociations.length === 0) {\n displayToast(\n 'Failed to fetch file information for download',\n 'danger',\n )\n setIsDownloading(false)\n isDownloadingRef.current = false\n return\n }\n\n // Split into batches to avoid overwhelming the API\n const batches: FileHandleAssociation[][] = []\n for (\n let i = 0;\n i < fileHandleAssociations.length;\n i += FILE_BATCH_SIZE\n ) {\n batches.push(fileHandleAssociations.slice(i, i + FILE_BATCH_SIZE))\n }\n\n let downloadedCount = 0\n let failedCount = 0\n const successfullyDownloadedItems: typeof nonPackageableFiles = []\n\n // Track used filenames to prevent collisions\n const usedFilenames = new Map<string, number>()\n\n /**\n * Helper function to download file using traditional anchor tag method\n * @returns boolean indicating if the download was successful\n */\n const downloadFileTraditional = async (\n downloadUrl: string,\n fileName: string,\n ): Promise<boolean> => {\n try {\n const response = await fetch(downloadUrl)\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`)\n }\n\n const blob = await response.blob()\n const blobUrl = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = blobUrl\n anchor.download = fileName\n anchor.style.display = 'none'\n document.body.appendChild(anchor)\n anchor.click()\n document.body.removeChild(anchor)\n\n // Clean up blob URL after a delay to ensure download initiation\n setTimeout(() => URL.revokeObjectURL(blobUrl), 1000)\n\n return true\n } catch (error) {\n console.error(`Failed to download file ${fileName}:`, error)\n // Fallback to window.open method\n try {\n window.open(downloadUrl, '_blank', 'noopener,noreferrer')\n return true\n } catch (fallbackError) {\n console.error(`Fallback window.open also failed:`, fallbackError)\n return false\n }\n }\n }\n\n /**\n * Helper function to process a single file download\n * @returns boolean indicating if download is successful\n */\n const processFileDownload = async (\n fileResult: BatchFileResult['requestedFiles'][0],\n originalItem: (typeof nonPackageableFiles)[0] | undefined,\n ): Promise<boolean> => {\n try {\n let downloadUrl: string | undefined\n\n // Check if this is an external file handle\n if (\n fileResult.fileHandle &&\n implementsExternalFileHandleInterface(fileResult.fileHandle)\n ) {\n // For external file handles, use the externalURL\n downloadUrl = (fileResult.fileHandle as ExternalFileHandle)\n .externalURL\n setShowExternalFileWarning(true)\n } else if (fileResult.preSignedURL) {\n // For other file types, use the presigned URL\n downloadUrl = fileResult.preSignedURL\n }\n\n if (!downloadUrl) {\n return false\n }\n\n // Get filename from file result or create a default name\n const baseFileName = getFileName(fileResult, originalItem)\n\n // Handle filename collisions by adding a counter suffix\n const fileName = deduplicateFileName(baseFileName, usedFilenames)\n\n // Update progress with current file info\n const currentFileIndex = downloadedCount + 1\n fileDownloadStartRef.current = null\n setDownloadProgress(prev =>\n prev\n ? {\n ...prev,\n currentFile: fileName,\n fileIndex: currentFileIndex,\n bytesDownloaded: 0,\n totalBytes: 0,\n estimatedSecondsRemaining: null,\n }\n : null,\n )\n\n // Use traditional download method for unsupported browsers\n if (!useFileSystemAccess) {\n const success = await downloadFileTraditional(\n downloadUrl,\n fileName,\n )\n if (success) {\n downloadedCount = currentFileIndex\n if (originalItem) {\n successfullyDownloadedItems.push(originalItem)\n }\n }\n return success\n }\n\n // Use File System Access API for supported browsers\n if (!directoryHandle) {\n return false\n }\n\n try {\n // Fetch the file\n abortControllerRef.current = new AbortController()\n const response = await fetch(downloadUrl, {\n signal: abortControllerRef.current.signal,\n })\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`)\n }\n\n // Get total file size from headers\n const contentLength = response.headers.get('content-length')\n const totalBytes = contentLength ? parseInt(contentLength, 10) : 0\n\n // Update progress with file size\n setDownloadProgress(prev =>\n prev\n ? {\n ...prev,\n bytesDownloaded: 0,\n totalBytes,\n }\n : null,\n )\n\n // Create file handle in selected directory\n const fileHandle = await directoryHandle.getFileHandle(fileName, {\n create: true,\n })\n const writableStream = await fileHandle.createWritable()\n\n // Stream the response body to the file with progress tracking\n if (response.body) {\n const reader = response.body.getReader()\n let bytesReceived = 0\n let streamAborted = false\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n bytesReceived += value.length\n await writableStream.write(value)\n\n // Check for cancellation on every chunk\n if (isCancelledRef.current) {\n streamAborted = true\n reader.cancel()\n await writableStream.abort()\n break\n }\n\n if (fileDownloadStartRef.current === null) {\n fileDownloadStartRef.current = Date.now()\n }\n const elapsedMs = Date.now() - fileDownloadStartRef.current\n let estimatedSecondsRemaining: number | null = null\n if (\n elapsedMs > ETA_DELAY_MS &&\n bytesReceived > 0 &&\n totalBytes > 0\n ) {\n const rate = bytesReceived / elapsedMs\n estimatedSecondsRemaining = Math.max(\n 0,\n (totalBytes - bytesReceived) / rate / 1000,\n )\n }\n\n // Update progress\n setDownloadProgress(prev =>\n prev\n ? {\n ...prev,\n bytesDownloaded: bytesReceived,\n ...(elapsedMs > ETA_DELAY_MS && {\n estimatedSecondsRemaining,\n }),\n }\n : null,\n )\n }\n } catch (streamError) {\n if (!streamAborted) {\n await writableStream.abort()\n streamAborted = true\n }\n throw streamError\n } finally {\n if (!streamAborted) {\n await writableStream.close()\n }\n }\n\n // If cancelled mid-stream, return false without falling through to success path\n if (isCancelledRef.current) {\n return false\n }\n } else {\n // Fallback if streaming not supported\n try {\n const blob = await response.blob()\n await writableStream.write(blob)\n } finally {\n await writableStream.close()\n }\n }\n\n downloadedCount = currentFileIndex\n\n // Track successfully downloaded item for batch removal\n if (originalItem) {\n successfullyDownloadedItems.push(originalItem)\n }\n return true\n } catch (fetchError) {\n // If the fetch was aborted due to cancellation, do not fall back\n if (\n isCancelledRef.current ||\n (fetchError instanceof DOMException &&\n fetchError.name === 'AbortError')\n ) {\n return false\n }\n console.error(\n `Failed to fetch/write file ${fileResult.fileHandleId}:`,\n fetchError,\n )\n // Fallback to window.open if File System Access API fails\n try {\n downloadFileTraditional(downloadUrl, fileName)\n downloadedCount = currentFileIndex\n if (originalItem) {\n successfullyDownloadedItems.push(originalItem)\n }\n return true\n } catch (fallbackError) {\n console.error(\n `Fallback downloadFileTraditional also failed for ${fileResult.fileHandleId}:`,\n fallbackError,\n )\n return false\n }\n }\n } catch (error) {\n console.error(\n `Failed to download file ${fileResult.fileHandleId}:`,\n error,\n )\n return false\n }\n }\n\n /**\n * Fallback function to fetch files individually when batch fails\n */\n const fetchIndividually = async (\n batch: FileHandleAssociation[],\n ): Promise<void> => {\n console.info(\n `Attempting to fetch ${batch.length} files individually after batch failure`,\n )\n\n for (const fileHandleAssociation of batch) {\n // Check if user cancelled the operation\n if (isCancelledRef.current) {\n console.info('Individual file fetch cancelled by user')\n break\n }\n\n const singleBatch = [fileHandleAssociation]\n try {\n const batchRequest: BatchFileRequest = {\n requestedFiles: singleBatch,\n includeFileHandles: true,\n includePreSignedURLs: true,\n includePreviewPreSignedURLs: false,\n }\n\n const batchResult = await getFiles(batchRequest, accessToken)\n\n if (batchResult.requestedFiles.length > 0) {\n const fileResult = batchResult.requestedFiles[0]\n const originalItem = nonPackageableFiles.find(\n item =>\n item.fileEntityId ===\n fileHandleAssociation.associateObjectId,\n )\n\n const success = await processFileDownload(\n fileResult,\n originalItem,\n )\n if (!success) {\n failedCount++\n }\n } else {\n failedCount++\n }\n } catch (error) {\n console.error(\n `Failed to fetch individual file ${fileHandleAssociation.fileHandleId}:`,\n error,\n )\n failedCount++\n }\n }\n }\n\n // Process each batch\n for (const batch of batches) {\n // Check if user cancelled the operation\n if (isCancelledRef.current) {\n console.info('Download cancelled by user')\n break\n }\n\n // Try to fetch the entire batch (getFiles has built-in retry logic)\n let batchResult: BatchFileResult | null = null\n try {\n const batchRequest: BatchFileRequest = {\n requestedFiles: batch,\n includeFileHandles: true,\n includePreSignedURLs: true,\n includePreviewPreSignedURLs: false,\n }\n batchResult = await getFiles(batchRequest, accessToken)\n } catch (error) {\n console.error('Failed to fetch batch:', error)\n }\n\n if (batchResult) {\n // Batch fetch succeeded, process all files\n for (let i = 0; i < batchResult.requestedFiles.length; i++) {\n // Check if user cancelled the operation\n if (isCancelledRef.current) {\n console.info(\n 'Download cancelled by user during batch processing',\n )\n break\n }\n\n const fileResult = batchResult.requestedFiles[i]\n const originalItem = nonPackageableFiles.find(\n item => item.fileEntityId === batch[i].associateObjectId,\n )\n\n const success = await processFileDownload(\n fileResult,\n originalItem,\n )\n if (!success) {\n failedCount++\n }\n }\n } else {\n // Batch fetch failed after retries, try fetching files individually\n await fetchIndividually(batch)\n }\n }\n\n // Remove all successfully downloaded files from the download list in one batch\n if (successfullyDownloadedItems.length > 0) {\n await removeFilesFromDownloadList({\n batchToRemove: successfullyDownloadedItems.map(\n (item: (typeof nonPackageableFiles)[0]) => ({\n fileEntityId: item.fileEntityId,\n versionNumber: item.versionNumber,\n }),\n ),\n })\n // Refetch statistics after removing files\n await refetchDownloadListStatistics()\n // Scroll to top of page to show updated statistics\n window.scrollTo({ top: 0, behavior: 'smooth' })\n }\n\n // Show completion message\n if (isCancelledRef.current) {\n displayToast(\n `Download cancelled. ${downloadedCount} file${\n downloadedCount !== 1 ? 's were' : ' was'\n } downloaded before cancellation.`,\n 'info',\n )\n } else if (downloadedCount > 0) {\n displayToast(\n `Download started for ${downloadedCount} file${\n downloadedCount !== 1 ? 's' : ''\n }${\n failedCount > 0\n ? `. ${failedCount} file${failedCount !== 1 ? 's' : ''} failed.`\n : '.'\n }`,\n failedCount > 0 ? 'warning' : 'success',\n )\n } else {\n displayToast('No files could be downloaded', 'warning')\n }\n } catch (error) {\n console.error('Error downloading files:', error)\n const message =\n (error as SynapseClientError | undefined)?.reason ??\n (error instanceof Error ? error.message : undefined) ??\n 'An error occurred while downloading files'\n displayToast(message, 'danger')\n } finally {\n setDownloadProgress(null)\n setIsDownloading(false)\n isDownloadingRef.current = false\n }\n },\n [\n accessToken,\n isAuthenticated,\n queryClient,\n getEntityQueryOptions,\n removeFilesFromDownloadList,\n refetchDownloadListStatistics,\n ],\n )\n\n const handleClick = useCallback(async () => {\n if (isDownloading) {\n return\n }\n setIsDownloading(true)\n\n // Fetch all remaining pages imperatively, then download\n try {\n let stillHasNextPage = hasNextPage\n let latestData = data\n while (stillHasNextPage && fetchNextPage) {\n const result = await fetchNextPage()\n stillHasNextPage = result.hasNextPage\n if (result.data) {\n latestData = result.data\n }\n }\n\n // All pages loaded, now download\n if (status === 'success' && latestData) {\n void downloadAllFiles(latestData)\n } else {\n // Failed to load data\n setIsDownloading(false)\n isDownloadingRef.current = false\n displayToast(\n 'Failed to load download list. Please try again.',\n 'danger',\n )\n }\n } catch (error) {\n console.error('Error downloading files:', error)\n setIsDownloading(false)\n isDownloadingRef.current = false\n const message =\n (error as SynapseClientError | undefined)?.reason ??\n (error instanceof Error ? error.message : undefined) ??\n 'An error occurred while preparing files for download'\n displayToast(message, 'danger')\n }\n }, [\n isDownloading,\n hasNextPage,\n fetchNextPage,\n status,\n data,\n downloadAllFiles,\n ])\n\n const handleCancel = useCallback(() => {\n isCancelledRef.current = true\n abortControllerRef.current?.abort()\n setDownloadProgress(null)\n setIsDownloading(false)\n isDownloadingRef.current = false\n }, [])\n\n if (error) {\n return null\n }\n\n return (\n <>\n {/* Visually-hidden live region — announces file start and progress milestones to screen readers */}\n <span\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className={styles.visuallyHidden}\n >\n {announceText}\n </span>\n {!!downloadProgress && showExternalFileWarning && (\n <Paper\n elevation={8}\n className={styles.externalWarningPanel}\n sx={{ zIndex: theme => theme.zIndex.modal }}\n role=\"region\"\n aria-label=\"External file warning\"\n >\n <CloudWarning className={styles.warningIcon} />\n <Box className={styles.warningTextContainer}>\n <Typography className={styles.warningTitleText}>\n Some of your files are hosted externally.\n </Typography>\n <Typography className={styles.progressFileNameText}>\n Downloads may be slower than expected\n </Typography>\n </Box>\n <IconButton\n onClick={() => setShowExternalFileWarning(false)}\n size=\"small\"\n className={styles.warningCloseButton}\n aria-label=\"Dismiss external file warning\"\n >\n <Close />\n </IconButton>\n </Paper>\n )}\n {!!downloadProgress && (\n <Paper\n elevation={8}\n className={styles.progressPanel}\n sx={{ zIndex: theme => theme.zIndex.modal }}\n role=\"region\"\n aria-label=\"Download progress\"\n >\n <Typography className={styles.progressTitleText}>\n Downloading file {downloadProgress.fileIndex} of{' '}\n {downloadProgress.totalFiles}\n </Typography>\n <Typography className={styles.progressFileNameText}>\n {downloadProgress.currentFile}\n </Typography>\n <Box className={styles.progressFooter}>\n <Box className={styles.progressBarContainer}>\n <Box className={styles.progressStats}>\n <Typography className={styles.progressStatsText}>\n {downloadProgress.totalBytes > 0\n ? `${Math.round(\n (downloadProgress.bytesDownloaded /\n downloadProgress.totalBytes) *\n 100,\n )}% ( ${calculateFriendlyFileSize(\n downloadProgress.bytesDownloaded,\n 0,\n )} / ${calculateFriendlyFileSize(\n downloadProgress.totalBytes,\n )} )`\n : ''}\n </Typography>\n <Typography className={styles.progressStatsText}>\n {downloadProgress.estimatedSecondsRemaining !== null\n ? `About ${dayjs\n .duration({\n seconds: downloadProgress.estimatedSecondsRemaining,\n })\n .humanize()} remaining`\n : ''}\n </Typography>\n </Box>\n <LinearProgress\n variant={\n downloadProgress.totalBytes > 0\n ? 'determinate'\n : 'indeterminate'\n }\n value={\n downloadProgress.totalBytes > 0\n ? (downloadProgress.bytesDownloaded /\n downloadProgress.totalBytes) *\n 100\n : undefined\n }\n aria-label=\"Download progress\"\n className={styles.progressBar}\n />\n </Box>\n <Box className={styles.cancelButtonContainer}>\n <Button\n variant=\"outlined\"\n onClick={handleCancel}\n className={styles.cancelButton}\n >\n Cancel\n </Button>\n </Box>\n </Box>\n </Paper>\n )}\n <Button\n variant={variant}\n onClick={() => {\n void handleClick()\n }}\n loading={isDownloading}\n startIcon={<Download />}\n >\n {buttonText}\n </Button>\n </>\n )\n}\n\nexport default DownloadIneligibleForPackagingFilesFromListButton\n"],"mappings":"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"}
1
+ {"version":3,"file":"DownloadIneligibleForPackagingFilesFromListButton.js","names":[],"sources":["../../../src/components/DownloadCart/DownloadIneligibleForPackagingFilesFromListButton.tsx"],"sourcesContent":["import { useSynapseContext } from '@/utils'\nimport { implementsExternalFileHandleInterface } from '@/utils/types/IsType'\nimport { calculateFriendlyFileSize } from '@/utils/functions/calculateFriendlyFileSize'\nimport {\n Box,\n Button,\n IconButton,\n LinearProgress,\n Paper,\n Typography,\n} from '@mui/material'\nimport { Close, Download } from '@mui/icons-material'\nimport CloudWarning from '@/assets/icons/CloudWarning'\nimport { SynapseClientError } from '@sage-bionetworks/synapse-client/util/SynapseClientError'\nimport dayjs from 'dayjs'\nimport duration from 'dayjs/plugin/duration'\nimport relativeTime from 'dayjs/plugin/relativeTime'\nimport {\n BatchFileRequest,\n BatchFileResult,\n ExternalFileHandle,\n FileEntity,\n FileHandleAssociation,\n FileHandleAssociateType,\n} from '@sage-bionetworks/synapse-types'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport { useQueryClient } from '@tanstack/react-query'\nimport { displayToast } from '../ToastMessage/ToastMessage'\nimport {\n useGetAvailableFilesToDownloadInfinite,\n useGetDownloadListStatistics,\n useRemoveFilesFromDownloadList,\n} from '@/synapse-queries/download/useDownloadList'\nimport { getFiles } from '@/synapse-client/SynapseClient'\nimport { useGetEntityQueryOptions } from '@/synapse-queries/entity/useEntity'\n\nimport { deduplicateFileName, getFileName } from './fileNameUtils'\nimport styles from './DownloadIneligibleForPackagingFilesFromListButton.module.scss'\n\n// showDirectoryPicker is not yet available on the Window interface.\ninterface FileSystemAccessWindow extends Window {\n showDirectoryPicker(options?: {\n mode?: 'read' | 'readwrite'\n startIn?:\n | 'desktop'\n | 'documents'\n | 'downloads'\n | 'music'\n | 'pictures'\n | 'videos'\n }): Promise<FileSystemDirectoryHandle>\n}\n\n/**\n * Type guard to check if File System Access API is available\n */\nfunction supportsFileSystemAccess(\n window: Window,\n): window is FileSystemAccessWindow {\n return 'showDirectoryPicker' in window\n}\n\nconst FILE_BATCH_SIZE = 50 // Maximum batch size for fetching presigned URLs\nconst ETA_DELAY_MS = 3000 // Wait this long before showing ETA\n\ndayjs.extend(duration)\ndayjs.extend(relativeTime)\n\nexport type DownloadIneligibleForPackagingFilesFromListButtonProps = {\n buttonText?: string\n variant?: 'text' | 'outlined' | 'contained'\n}\n\n/**\n * Button component that downloads files from the user's download list that are ineligible for packaging.\n * Uses the infinite query hook to fetch all pages of download list items,\n * batches requests to get presigned URLs, and triggers browser downloads.\n */\nexport function DownloadIneligibleForPackagingFilesFromListButton(\n props: DownloadIneligibleForPackagingFilesFromListButtonProps,\n) {\n const { buttonText = 'Start Multi-file Download', variant = 'contained' } =\n props\n const { accessToken, isAuthenticated } = useSynapseContext()\n const queryClient = useQueryClient()\n const getEntityQueryOptions = useGetEntityQueryOptions<FileEntity>()\n const [isDownloading, setIsDownloading] = useState(false)\n const isDownloadingRef = useRef(false)\n const [showExternalFileWarning, setShowExternalFileWarning] = useState(false)\n const isCancelledRef = useRef(false)\n const abortControllerRef = useRef<AbortController | null>(null)\n const [downloadProgress, setDownloadProgress] = useState<{\n currentFile: string\n fileIndex: number\n totalFiles: number\n bytesDownloaded: number\n totalBytes: number\n estimatedSecondsRemaining: number | null\n } | null>(null)\n const fileDownloadStartRef = useRef<number | null>(null)\n\n // Accessibility: tracks the last announced file index and progress bucket so\n // the aria-live region only updates on meaningful changes (not every chunk).\n const [announceText, setAnnounceText] = useState('')\n const lastAnnouncedFileIndexRef = useRef<number>(-1)\n const lastAnnouncedPercentBucketRef = useRef<number>(-1)\n\n useEffect(() => {\n if (!downloadProgress) {\n lastAnnouncedFileIndexRef.current = -1\n lastAnnouncedPercentBucketRef.current = -1\n setAnnounceText('')\n return\n }\n\n const { fileIndex, totalFiles, currentFile, bytesDownloaded, totalBytes } =\n downloadProgress\n\n // Announce when a new file starts downloading\n if (fileIndex !== lastAnnouncedFileIndexRef.current) {\n lastAnnouncedFileIndexRef.current = fileIndex\n lastAnnouncedPercentBucketRef.current = -1\n setAnnounceText(\n `Downloading file ${fileIndex} of ${totalFiles}: ${currentFile}`,\n )\n return\n }\n\n // Announce at each 25% milestone (25, 50, 75, 100) once per file\n if (totalBytes > 0) {\n const percent = Math.floor((bytesDownloaded / totalBytes) * 100)\n const bucket = Math.floor(percent / 25) * 25\n if (bucket > 0 && bucket !== lastAnnouncedPercentBucketRef.current) {\n lastAnnouncedPercentBucketRef.current = bucket\n setAnnounceText(`${bucket}% of ${currentFile} downloaded`)\n }\n }\n }, [downloadProgress])\n\n const { data, status, hasNextPage, fetchNextPage, error } =\n useGetAvailableFilesToDownloadInfinite()\n\n const { mutateAsync: removeFilesFromDownloadList } =\n useRemoveFilesFromDownloadList()\n\n const { refetch: refetchDownloadListStatistics } =\n useGetDownloadListStatistics()\n\n /**\n * Downloads all files from the download list.\n */\n const downloadAllFiles = useCallback(\n async (downloadListItems: typeof data) => {\n // Prevent re-entry if already downloading\n if (isDownloadingRef.current) {\n return\n }\n\n if (!downloadListItems || !isAuthenticated) {\n setIsDownloading(false)\n return\n }\n\n try {\n isDownloadingRef.current = true\n isCancelledRef.current = false\n setShowExternalFileWarning(false)\n const allItems = downloadListItems.pages.flatMap(page => page.page)\n\n // Filter to only include files that are NOT eligible for packaging\n const nonPackageableFiles = allItems.filter(\n item => !item.isEligibleForPackaging,\n )\n\n if (nonPackageableFiles.length === 0) {\n displayToast('No non-packageable files available to download', 'info')\n setIsDownloading(false)\n isDownloadingRef.current = false\n return\n }\n\n // Determine if File System Access API is available\n const useFileSystemAccess = supportsFileSystemAccess(window)\n\n // Prompt user to select a directory for downloads BEFORE async operations\n // (must be called during user gesture to avoid SecurityError)\n let directoryHandle: FileSystemDirectoryHandle | undefined\n\n if (useFileSystemAccess) {\n // TypeScript narrowing: we know window has showDirectoryPicker at this point\n const fileSystemWindow = window as unknown as FileSystemAccessWindow\n\n try {\n directoryHandle = await fileSystemWindow.showDirectoryPicker({\n mode: 'readwrite',\n })\n } catch (error) {\n // User cancelled directory picker\n console.error('Failed to select directory:', error)\n displayToast(\n 'Directory selection is required to directly download files from the website.',\n 'warning',\n )\n setIsDownloading(false)\n isDownloadingRef.current = false\n return\n }\n } else {\n // Show info message for browsers without File System Access API\n displayToast(\n 'Your browser will download files individually to your default downloads folder. Each file may trigger a separate download prompt.',\n 'info',\n )\n }\n\n // Initialize progress tracking\n fileDownloadStartRef.current = null\n setDownloadProgress({\n currentFile: '',\n fileIndex: 0,\n totalFiles: nonPackageableFiles.length,\n bytesDownloaded: 0,\n totalBytes: 0,\n estimatedSecondsRemaining: null,\n })\n\n // Fetch entities to get dataFileHandleId for each file\n // Use React Query's ensureQueryData to leverage cache and built-in retry logic (handles 429s with exponential backoff)\n // Map key combines entity ID and version to handle multiple versions of the same entity\n const entityIdToFileHandleId = new Map<string, string>()\n\n // Fetch all entities in parallel - React Query handles concurrency and retries\n const entityPromises = nonPackageableFiles.map(async item => {\n if (isCancelledRef.current) {\n return\n }\n\n try {\n const entity = await queryClient.ensureQueryData(\n getEntityQueryOptions(\n item.fileEntityId,\n item.versionNumber?.toString(),\n ),\n )\n // Use composite key: entityId-version (or entityId if no version)\n const mapKey = `${entity.id!}-${item.versionNumber ?? 'latest'}`\n entityIdToFileHandleId.set(mapKey, entity.dataFileHandleId)\n } catch (error) {\n console.error(`Failed to fetch entity ${item.fileEntityId}:`, error)\n // Continue even if one entity fails\n }\n })\n\n await Promise.all(entityPromises)\n\n // Convert download list items to file handle associations using the fetched dataFileHandleIds\n const fileHandleAssociations: FileHandleAssociation[] =\n nonPackageableFiles\n .filter(item => {\n const mapKey = `${item.fileEntityId}-${\n item.versionNumber ?? 'latest'\n }`\n return entityIdToFileHandleId.has(mapKey)\n })\n .map(item => {\n const mapKey = `${item.fileEntityId}-${\n item.versionNumber ?? 'latest'\n }`\n return {\n fileHandleId: entityIdToFileHandleId.get(mapKey)!,\n associateObjectId: item.fileEntityId,\n associateObjectType: FileHandleAssociateType.FileEntity,\n }\n })\n\n if (fileHandleAssociations.length === 0) {\n displayToast(\n 'Failed to fetch file information for download',\n 'danger',\n )\n setIsDownloading(false)\n isDownloadingRef.current = false\n return\n }\n\n // Split into batches to avoid overwhelming the API\n const batches: FileHandleAssociation[][] = []\n for (\n let i = 0;\n i < fileHandleAssociations.length;\n i += FILE_BATCH_SIZE\n ) {\n batches.push(fileHandleAssociations.slice(i, i + FILE_BATCH_SIZE))\n }\n\n let downloadedCount = 0\n let failedCount = 0\n const successfullyDownloadedItems: typeof nonPackageableFiles = []\n\n // Track used filenames to prevent collisions\n const usedFilenames = new Map<string, number>()\n\n /**\n * Helper function to download file using traditional anchor tag method\n * @returns boolean indicating if the download was successful\n */\n const downloadFileTraditional = async (\n downloadUrl: string,\n fileName: string,\n ): Promise<boolean> => {\n try {\n const response = await fetch(downloadUrl)\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`)\n }\n\n const blob = await response.blob()\n const blobUrl = URL.createObjectURL(blob)\n const anchor = document.createElement('a')\n anchor.href = blobUrl\n anchor.download = fileName\n anchor.style.display = 'none'\n document.body.appendChild(anchor)\n anchor.click()\n document.body.removeChild(anchor)\n\n // Clean up blob URL after a delay to ensure download initiation\n setTimeout(() => URL.revokeObjectURL(blobUrl), 1000)\n\n return true\n } catch (error) {\n console.error(`Failed to download file ${fileName}:`, error)\n // Fallback to window.open method\n try {\n window.open(downloadUrl, '_blank', 'noopener,noreferrer')\n return true\n } catch (fallbackError) {\n console.error(`Fallback window.open also failed:`, fallbackError)\n return false\n }\n }\n }\n\n /**\n * Helper function to process a single file download\n * @returns boolean indicating if download is successful\n */\n const processFileDownload = async (\n fileResult: BatchFileResult['requestedFiles'][0],\n originalItem: (typeof nonPackageableFiles)[0] | undefined,\n ): Promise<boolean> => {\n try {\n let downloadUrl: string | undefined\n\n // Check if this is an external file handle\n if (\n fileResult.fileHandle &&\n implementsExternalFileHandleInterface(fileResult.fileHandle)\n ) {\n // For external file handles, use the externalURL\n downloadUrl = (fileResult.fileHandle as ExternalFileHandle)\n .externalURL\n setShowExternalFileWarning(true)\n } else if (fileResult.preSignedURL) {\n // For other file types, use the presigned URL\n downloadUrl = fileResult.preSignedURL\n }\n\n if (!downloadUrl) {\n return false\n }\n\n // Get filename from file result or create a default name\n const baseFileName = getFileName(fileResult, originalItem)\n\n // Handle filename collisions by adding a counter suffix\n const fileName = deduplicateFileName(baseFileName, usedFilenames)\n\n // Update progress with current file info\n const currentFileIndex = downloadedCount + 1\n fileDownloadStartRef.current = null\n setDownloadProgress(prev =>\n prev\n ? {\n ...prev,\n currentFile: fileName,\n fileIndex: currentFileIndex,\n bytesDownloaded: 0,\n totalBytes: 0,\n estimatedSecondsRemaining: null,\n }\n : null,\n )\n\n // Use traditional download method for unsupported browsers\n if (!useFileSystemAccess) {\n const success = await downloadFileTraditional(\n downloadUrl,\n fileName,\n )\n if (success) {\n downloadedCount = currentFileIndex\n if (originalItem) {\n successfullyDownloadedItems.push(originalItem)\n }\n }\n return success\n }\n\n // Use File System Access API for supported browsers\n if (!directoryHandle) {\n return false\n }\n\n try {\n // Fetch the file\n abortControllerRef.current = new AbortController()\n const response = await fetch(downloadUrl, {\n signal: abortControllerRef.current.signal,\n })\n if (!response.ok) {\n throw new Error(`HTTP error! status: ${response.status}`)\n }\n\n // Get total file size from headers\n const contentLength = response.headers.get('content-length')\n const totalBytes = contentLength ? parseInt(contentLength, 10) : 0\n\n // Update progress with file size\n setDownloadProgress(prev =>\n prev\n ? {\n ...prev,\n bytesDownloaded: 0,\n totalBytes,\n }\n : null,\n )\n\n // Create file handle in selected directory\n const fileHandle = await directoryHandle.getFileHandle(fileName, {\n create: true,\n })\n const writableStream = await fileHandle.createWritable()\n\n // Stream the response body to the file with progress tracking\n if (response.body) {\n const reader = response.body.getReader()\n let bytesReceived = 0\n let streamAborted = false\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n\n bytesReceived += value.length\n await writableStream.write(value)\n\n // Check for cancellation on every chunk\n if (isCancelledRef.current) {\n streamAborted = true\n reader.cancel()\n await writableStream.abort()\n break\n }\n\n if (fileDownloadStartRef.current === null) {\n fileDownloadStartRef.current = Date.now()\n }\n const elapsedMs = Date.now() - fileDownloadStartRef.current\n let estimatedSecondsRemaining: number | null = null\n if (\n elapsedMs > ETA_DELAY_MS &&\n bytesReceived > 0 &&\n totalBytes > 0\n ) {\n const rate = bytesReceived / elapsedMs\n estimatedSecondsRemaining = Math.max(\n 0,\n (totalBytes - bytesReceived) / rate / 1000,\n )\n }\n\n // Update progress\n setDownloadProgress(prev =>\n prev\n ? {\n ...prev,\n bytesDownloaded: bytesReceived,\n ...(elapsedMs > ETA_DELAY_MS && {\n estimatedSecondsRemaining,\n }),\n }\n : null,\n )\n }\n } catch (streamError) {\n if (!streamAborted) {\n await writableStream.abort()\n streamAborted = true\n }\n throw streamError\n } finally {\n if (!streamAborted) {\n await writableStream.close()\n }\n }\n\n // If cancelled mid-stream, return false without falling through to success path\n if (isCancelledRef.current) {\n return false\n }\n } else {\n // Fallback if streaming not supported\n try {\n const blob = await response.blob()\n await writableStream.write(blob)\n } finally {\n await writableStream.close()\n }\n }\n\n downloadedCount = currentFileIndex\n\n // Track successfully downloaded item for batch removal\n if (originalItem) {\n successfullyDownloadedItems.push(originalItem)\n }\n return true\n } catch (fetchError) {\n // If the fetch was aborted due to cancellation, do not fall back\n if (\n isCancelledRef.current ||\n (fetchError instanceof DOMException &&\n fetchError.name === 'AbortError')\n ) {\n return false\n }\n console.error(\n `Failed to fetch/write file ${fileResult.fileHandleId}:`,\n fetchError,\n )\n // Fallback to window.open if File System Access API fails\n try {\n downloadFileTraditional(downloadUrl, fileName)\n downloadedCount = currentFileIndex\n if (originalItem) {\n successfullyDownloadedItems.push(originalItem)\n }\n return true\n } catch (fallbackError) {\n console.error(\n `Fallback downloadFileTraditional also failed for ${fileResult.fileHandleId}:`,\n fallbackError,\n )\n return false\n }\n }\n } catch (error) {\n console.error(\n `Failed to download file ${fileResult.fileHandleId}:`,\n error,\n )\n return false\n }\n }\n\n /**\n * Fallback function to fetch files individually when batch fails\n */\n const fetchIndividually = async (\n batch: FileHandleAssociation[],\n ): Promise<void> => {\n console.info(\n `Attempting to fetch ${batch.length} files individually after batch failure`,\n )\n\n for (const fileHandleAssociation of batch) {\n // Check if user cancelled the operation\n if (isCancelledRef.current) {\n console.info('Individual file fetch cancelled by user')\n break\n }\n\n const singleBatch = [fileHandleAssociation]\n try {\n const batchRequest: BatchFileRequest = {\n requestedFiles: singleBatch,\n includeFileHandles: true,\n includePreSignedURLs: true,\n includePreviewPreSignedURLs: false,\n }\n\n const batchResult = await getFiles(batchRequest, accessToken)\n\n if (batchResult.requestedFiles.length > 0) {\n const fileResult = batchResult.requestedFiles[0]\n const originalItem = nonPackageableFiles.find(\n item =>\n item.fileEntityId ===\n fileHandleAssociation.associateObjectId,\n )\n\n const success = await processFileDownload(\n fileResult,\n originalItem,\n )\n if (!success) {\n failedCount++\n }\n } else {\n failedCount++\n }\n } catch (error) {\n console.error(\n `Failed to fetch individual file ${fileHandleAssociation.fileHandleId}:`,\n error,\n )\n failedCount++\n }\n }\n }\n\n // Process each batch\n for (const batch of batches) {\n // Check if user cancelled the operation\n if (isCancelledRef.current) {\n console.info('Download cancelled by user')\n break\n }\n\n // Try to fetch the entire batch (getFiles has built-in retry logic)\n let batchResult: BatchFileResult | null = null\n try {\n const batchRequest: BatchFileRequest = {\n requestedFiles: batch,\n includeFileHandles: true,\n includePreSignedURLs: true,\n includePreviewPreSignedURLs: false,\n }\n batchResult = await getFiles(batchRequest, accessToken)\n } catch (error) {\n console.error('Failed to fetch batch:', error)\n }\n\n if (batchResult) {\n // Batch fetch succeeded, process all files\n for (let i = 0; i < batchResult.requestedFiles.length; i++) {\n // Check if user cancelled the operation\n if (isCancelledRef.current) {\n console.info(\n 'Download cancelled by user during batch processing',\n )\n break\n }\n\n const fileResult = batchResult.requestedFiles[i]\n const originalItem = nonPackageableFiles.find(\n item => item.fileEntityId === batch[i].associateObjectId,\n )\n\n const success = await processFileDownload(\n fileResult,\n originalItem,\n )\n if (!success) {\n failedCount++\n }\n }\n } else {\n // Batch fetch failed after retries, try fetching files individually\n await fetchIndividually(batch)\n }\n }\n\n // Remove all successfully downloaded files from the download list in one batch\n if (successfullyDownloadedItems.length > 0) {\n await removeFilesFromDownloadList({\n batchToRemove: successfullyDownloadedItems.map(\n (item: (typeof nonPackageableFiles)[0]) => ({\n fileEntityId: item.fileEntityId,\n versionNumber: item.versionNumber,\n }),\n ),\n })\n // Refetch statistics after removing files\n await refetchDownloadListStatistics()\n // Scroll to top of page to show updated statistics\n window.scrollTo({ top: 0, behavior: 'smooth' })\n }\n\n // Show completion message\n if (isCancelledRef.current) {\n displayToast(\n `Download cancelled. ${downloadedCount} file${\n downloadedCount !== 1 ? 's were' : ' was'\n } downloaded before cancellation.`,\n 'info',\n )\n } else if (downloadedCount > 0) {\n displayToast(\n `Download started for ${downloadedCount} file${\n downloadedCount !== 1 ? 's' : ''\n }${\n failedCount > 0\n ? `. ${failedCount} file${failedCount !== 1 ? 's' : ''} failed.`\n : '.'\n }`,\n failedCount > 0 ? 'warning' : 'success',\n )\n } else {\n displayToast('No files could be downloaded', 'warning')\n }\n } catch (error) {\n console.error('Error downloading files:', error)\n const message =\n (error as SynapseClientError | undefined)?.reason ??\n (error instanceof Error ? error.message : undefined) ??\n 'An error occurred while downloading files'\n displayToast(message, 'danger')\n } finally {\n setDownloadProgress(null)\n setIsDownloading(false)\n isDownloadingRef.current = false\n }\n },\n [\n accessToken,\n isAuthenticated,\n queryClient,\n getEntityQueryOptions,\n removeFilesFromDownloadList,\n refetchDownloadListStatistics,\n ],\n )\n\n const handleClick = useCallback(async () => {\n if (isDownloading) {\n return\n }\n setIsDownloading(true)\n\n // Fetch all remaining pages imperatively, then download\n try {\n let stillHasNextPage = hasNextPage\n let latestData = data\n while (stillHasNextPage && fetchNextPage) {\n const result = await fetchNextPage()\n stillHasNextPage = result.hasNextPage\n if (result.data) {\n latestData = result.data\n }\n }\n\n // All pages loaded, now download\n if (status === 'success' && latestData) {\n void downloadAllFiles(latestData)\n } else {\n // Failed to load data\n setIsDownloading(false)\n isDownloadingRef.current = false\n displayToast(\n 'Failed to load download list. Please try again.',\n 'danger',\n )\n }\n } catch (error) {\n console.error('Error downloading files:', error)\n setIsDownloading(false)\n isDownloadingRef.current = false\n const message =\n (error as SynapseClientError | undefined)?.reason ??\n (error instanceof Error ? error.message : undefined) ??\n 'An error occurred while preparing files for download'\n displayToast(message, 'danger')\n }\n }, [\n isDownloading,\n hasNextPage,\n fetchNextPage,\n status,\n data,\n downloadAllFiles,\n ])\n\n const handleCancel = useCallback(() => {\n isCancelledRef.current = true\n abortControllerRef.current?.abort()\n setDownloadProgress(null)\n setIsDownloading(false)\n isDownloadingRef.current = false\n }, [])\n\n if (error) {\n return null\n }\n\n return (\n <>\n {/* Visually-hidden live region — announces file start and progress milestones to screen readers */}\n <span\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n className={styles.visuallyHidden}\n >\n {announceText}\n </span>\n {!!downloadProgress && showExternalFileWarning && (\n <Paper\n elevation={8}\n className={styles.externalWarningPanel}\n sx={{ zIndex: theme => theme.zIndex.modal }}\n role=\"region\"\n aria-label=\"External file warning\"\n >\n <CloudWarning className={styles.warningIcon} />\n <Box className={styles.warningTextContainer}>\n <Typography className={styles.warningTitleText}>\n Some of your files are hosted externally.\n </Typography>\n <Typography className={styles.progressFileNameText}>\n Downloads may be slower than expected\n </Typography>\n </Box>\n <IconButton\n onClick={() => setShowExternalFileWarning(false)}\n size=\"small\"\n className={styles.warningCloseButton}\n aria-label=\"Dismiss external file warning\"\n >\n <Close />\n </IconButton>\n </Paper>\n )}\n {!!downloadProgress && (\n <Paper\n elevation={8}\n className={styles.progressPanel}\n sx={{ zIndex: theme => theme.zIndex.modal }}\n role=\"region\"\n aria-label=\"Download progress\"\n >\n <Typography className={styles.progressTitleText}>\n Downloading file {downloadProgress.fileIndex} of{' '}\n {downloadProgress.totalFiles}\n </Typography>\n <Typography className={styles.progressFileNameText}>\n {downloadProgress.currentFile}\n </Typography>\n <Box className={styles.progressFooter}>\n <Box className={styles.progressBarContainer}>\n <Box className={styles.progressStats}>\n <Typography className={styles.progressStatsText}>\n {downloadProgress.totalBytes > 0\n ? `${Math.round(\n (downloadProgress.bytesDownloaded /\n downloadProgress.totalBytes) *\n 100,\n )}% ( ${calculateFriendlyFileSize(\n downloadProgress.bytesDownloaded,\n )} / ${calculateFriendlyFileSize(\n downloadProgress.totalBytes,\n )} )`\n : ''}\n </Typography>\n <Typography className={styles.progressStatsText}>\n {downloadProgress.estimatedSecondsRemaining !== null\n ? `About ${dayjs\n .duration({\n seconds: downloadProgress.estimatedSecondsRemaining,\n })\n .humanize()} remaining`\n : ''}\n </Typography>\n </Box>\n <LinearProgress\n variant={\n downloadProgress.totalBytes > 0\n ? 'determinate'\n : 'indeterminate'\n }\n value={\n downloadProgress.totalBytes > 0\n ? (downloadProgress.bytesDownloaded /\n downloadProgress.totalBytes) *\n 100\n : undefined\n }\n aria-label=\"Download progress\"\n className={styles.progressBar}\n />\n </Box>\n <Box className={styles.cancelButtonContainer}>\n <Button\n variant=\"outlined\"\n onClick={handleCancel}\n className={styles.cancelButton}\n >\n Cancel\n </Button>\n </Box>\n </Box>\n </Paper>\n )}\n <Button\n variant={variant}\n onClick={() => {\n void handleClick()\n }}\n loading={isDownloading}\n startIcon={<Download />}\n >\n {buttonText}\n </Button>\n </>\n )\n}\n\nexport default DownloadIneligibleForPackagingFilesFromListButton\n"],"mappings":"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"}
@@ -152,6 +152,14 @@ var A = y(function(e, t) {
152
152
  "."
153
153
  ] })] })
154
154
  }),
155
+ !d && !M && j.permissions.canEnableInheritance && /* @__PURE__ */ C(m, {
156
+ isInherited: z,
157
+ setIsInherited: B
158
+ }),
159
+ H && /* @__PURE__ */ C(ce, {
160
+ severity: "error",
161
+ children: H.message
162
+ }),
155
163
  /* @__PURE__ */ C(f, {
156
164
  isInherited: z,
157
165
  canEdit: D(I, z, v, A),
@@ -169,14 +177,6 @@ var A = y(function(e, t) {
169
177
  notifyCheckboxValue: V,
170
178
  onNotifyCheckboxChange: he,
171
179
  showAddRemovePublicButton: !0
172
- }),
173
- !d && !M && j.permissions.canEnableInheritance && /* @__PURE__ */ C(m, {
174
- isInherited: z,
175
- setIsInherited: B
176
- }),
177
- H && /* @__PURE__ */ C(ce, {
178
- severity: "error",
179
- children: H.message
180
180
  })
181
181
  ]
182
182
  });
@@ -1 +1 @@
1
- {"version":3,"file":"EntityAclEditor.js","names":[],"sources":["../../../src/components/EntityAclEditor/EntityAclEditor.tsx"],"sourcesContent":["import {\n useCreateEntityACL,\n useDeleteEntityACL,\n useSuspenseGetCurrentUserProfile,\n useSuspenseGetEntityBenefactorACL,\n useSuspenseGetEntityBundle,\n useUpdateEntityACL,\n} from '@/synapse-queries'\nimport { useGetRealmPrincipals } from '@/synapse-queries/realm/useRealmPrincipals'\nimport { BackendDestinationEnum, getEndpoint } from '@/utils/functions'\nimport {\n consolidateResourceAccessList,\n isEntityPublic,\n resourceAccessListIsEqual,\n} from '@/utils/functions/AccessControlListUtils'\nimport { getPrincipalDisplayName } from '@/utils/functions/getPrincipalDisplayName'\nimport { entityTypeToFriendlyName } from '@/utils/functions/EntityTypeUtils'\nimport {\n getAccessTypeFromPermissionLevel,\n PermissionLevel,\n permissionLevelToLabel,\n} from '@/utils/PermissionLevelToAccessType'\nimport { SYNAPSE_DOCS_SHARING_SETTINGS_PERMISSIONS_CONDITIONS_FOR_USE_URL } from '@/utils/SynapseConstants'\nimport { Alert, Link, Stack } from '@mui/material'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport {\n ALL_ENTITY_BUNDLE_FIELDS,\n ResourceAccess,\n UserProfile,\n} from '@sage-bionetworks/synapse-types'\nimport {\n ForwardedRef,\n forwardRef,\n Suspense,\n useEffect,\n useImperativeHandle,\n useMemo,\n useState,\n} from 'react'\nimport { AclEditor, AclEditorProps } from '../AclEditor/AclEditor'\nimport { AclEditorSkeleton } from '../AclEditor/AclEditorSkeleton'\nimport useUpdateAcl from '../AclEditor/useUpdateAcl'\nimport { SynapseErrorBoundary } from '../error'\nimport FullWidthAlert from '../FullWidthAlert'\nimport { CreateOrDeleteLocalSharingSettingsButton } from './CreateOrDeleteLocalSharingSettingsButton'\nimport { InheritanceMessage } from './InheritanceMessage'\nimport OpenData from './OpenData'\nimport useNotifyNewACLUsers from './useNotifyNewACLUsers'\n\nconst availablePermissionLevels: PermissionLevel[] = [\n 'CAN_VIEW',\n 'CAN_DOWNLOAD',\n 'CAN_EDIT',\n 'CAN_EDIT_DELETE',\n 'CAN_ADMINISTER',\n]\n\nexport type EntityAclEditorProps = {\n /** The ID of the entity on which to view/edit the ACL. Note that the ACL may actually belong to an entity ancestor (benefactor). */\n entityId: string\n /** Invoked when the user can/cannot save the pending changes. */\n onCanSaveChange: (canSaveChanges: boolean) => void\n /** Invoked when changes are successfully made. */\n onUpdateSuccess: () => void\n /** Special case to show specific copy and allow changes to an inherited ACL immediately after a file is uploaded.\n * Note that if this is true, the entityId should be the ID of the folder or project that is the benefactor of the newly uploaded file(s)\n * @defaultValue false */\n isAfterUpload?: boolean\n}\n\nexport type EntityAclEditorHandle = {\n save: () => void\n}\n\nfunction getSubject(entityName: string) {\n return `${entityName} (shared on Synapse)`\n}\n\nfunction getBody(profile: UserProfile, entityId: string) {\n return `${getPrincipalDisplayName(\n {\n userName: profile.userName ?? '',\n firstName: profile.firstName,\n lastName: profile.lastName,\n },\n { showFullName: true, showUsername: false },\n )} has shared an item with you on Synapse:\\n${getEndpoint(\n BackendDestinationEnum.PORTAL_ENDPOINT,\n )}Synapse:${entityId}`\n}\n\nfunction getCanEditResourceAccess(\n canEdit: boolean,\n isInherited: boolean,\n ownProfile: UserProfile,\n publicGroupId: string | undefined,\n): AclEditorProps['canEdit'] {\n if (!canEdit || isInherited) {\n return false\n }\n return (resourceAccess: ResourceAccess): boolean => {\n // Users cannot change their own permissions\n const isSelf = ownProfile.ownerId === String(resourceAccess.principalId)\n // Users cannot change permission level for the public group, only add/remove it.\n // To give the public group DOWNLOAD access, ACT must mark it as anonymous access.\n const isPublicGroup =\n publicGroupId !== undefined &&\n String(resourceAccess.principalId) === publicGroupId\n if (isSelf || isPublicGroup) {\n return false\n }\n return true\n }\n}\n\nfunction getCanDeleteResourceAccess(\n canEdit: boolean,\n isInherited: boolean,\n ownProfile: UserProfile,\n): AclEditorProps['canRemoveEntry'] {\n if (!canEdit || isInherited) {\n return false\n }\n return (resourceAccess: ResourceAccess): boolean => {\n // Users cannot delete their own permissions\n const isSelf = ownProfile.ownerId === String(resourceAccess.principalId)\n if (isSelf) {\n return false\n }\n return canEdit\n }\n}\n\nfunction getDisplayedPermissionLevelOverride(\n isOpenData: boolean,\n publicGroupId: string | undefined,\n): AclEditorProps['displayedPermissionLevelOverride'] {\n return (resourceAccess: ResourceAccess) => {\n if (\n publicGroupId !== undefined &&\n String(resourceAccess.principalId) === publicGroupId\n ) {\n return isOpenData\n ? permissionLevelToLabel['CAN_DOWNLOAD']\n : permissionLevelToLabel['CAN_VIEW']\n }\n return undefined\n }\n}\n\nconst EntityAclEditor = forwardRef(function EntityAclEditor(\n props: EntityAclEditorProps,\n ref: ForwardedRef<EntityAclEditorHandle>,\n) {\n const {\n entityId,\n onCanSaveChange,\n onUpdateSuccess,\n isAfterUpload = false,\n } = props\n\n const { data: ownProfile } = useSuspenseGetCurrentUserProfile()\n const { data: realmPrincipals } = useGetRealmPrincipals()\n const { publicGroup: publicGroupId } = realmPrincipals || {}\n const { data: entityBundle } = useSuspenseGetEntityBundle(\n entityId,\n undefined,\n ALL_ENTITY_BUNDLE_FIELDS,\n { staleTime: Infinity },\n )\n const isProject = EntityType.project == entityBundle.entityType\n\n // The parent's benefactor ACL will be shown if the user removes the ACL on the current entity\n const { data: parentAcl } = useSuspenseGetEntityBenefactorACL(\n entityBundle.entity.parentId!,\n {\n staleTime: Infinity,\n },\n )\n\n const consolidatedOriginalResourceAccess = useMemo(\n () =>\n consolidateResourceAccessList(entityBundle.benefactorAcl.resourceAccess),\n [entityBundle.benefactorAcl.resourceAccess],\n )\n\n const parentResourceAccess = useMemo(\n () => parentAcl?.resourceAccess ?? [],\n [parentAcl],\n )\n const canEdit = entityBundle.permissions.canChangePermissions\n const isOpenData = entityBundle.permissions.isEntityOpenData\n const originalIsInherited = !(entityBundle.benefactorAcl.id == entityId)\n const [updatedIsInherited, setUpdatedIsInherited] =\n useState(originalIsInherited)\n const [notifyNewAdditions, setNotifyNewAdditions] = useState(false)\n const [error, setError] = useState<Error>()\n\n const {\n resourceAccessList: updatedResourceAccessList,\n setResourceAccessList,\n addResourceAccessItem,\n updateResourceAccessItem,\n removeResourceAccessItem,\n resetDirtyState,\n } = useUpdateAcl({\n initialResourceAccessList: consolidatedOriginalResourceAccess,\n })\n\n // If `consolidatedOriginalResourceAccess` changes, reset state\n useEffect(() => {\n if (consolidatedOriginalResourceAccess) {\n resetDirtyState()\n setResourceAccessList([...consolidatedOriginalResourceAccess])\n }\n }, [\n consolidatedOriginalResourceAccess,\n resetDirtyState,\n setResourceAccessList,\n ])\n\n // If `originalIsInherited` changes, reset state\n useEffect(() => {\n resetDirtyState()\n setUpdatedIsInherited(originalIsInherited)\n }, [originalIsInherited, resetDirtyState])\n\n // If the editor toggles the inherited state, reset the resource access list\n useEffect(() => {\n if (originalIsInherited == updatedIsInherited) {\n // The user reverted to the original state (regardless of inherited or not)\n setResourceAccessList(consolidatedOriginalResourceAccess)\n } else if (updatedIsInherited) {\n // The user toggled to inherited, update the resource access list to match the parent\n setResourceAccessList(parentResourceAccess)\n } else {\n // The user toggled to local, no need to update the list (the parent's list is already in the editor)\n }\n resetDirtyState()\n }, [\n originalIsInherited,\n consolidatedOriginalResourceAccess,\n parentResourceAccess,\n resetDirtyState,\n setResourceAccessList,\n updatedIsInherited,\n ])\n\n const isPublic = isEntityPublic(\n updatedResourceAccessList,\n realmPrincipals ?? {},\n )\n\n const {\n sendNotification,\n isLoading: isLoadingSendMessageToNewACLUsers,\n isPending: isPendingSendMessageToNewACLUsers,\n } = useNotifyNewACLUsers({\n subject: getSubject(entityBundle.entity.name || ''),\n body: getBody(ownProfile, entityId),\n initialResourceAccessList: consolidatedOriginalResourceAccess,\n newResourceAccessList: updatedResourceAccessList,\n })\n\n const mutationOptions = {\n onSuccess: () => {\n if (notifyNewAdditions) {\n sendNotification()\n }\n onUpdateSuccess()\n },\n onError: (e: Error) => {\n setError(e)\n },\n }\n\n const { mutate: createAcl, isPending: isPendingCreateAcl } =\n useCreateEntityACL(mutationOptions)\n const { mutate: updateAcl, isPending: isPendingUpdateAcl } =\n useUpdateEntityACL(mutationOptions)\n const { mutate: deleteAcl, isPending: isPendingDeleteAcl } =\n useDeleteEntityACL(mutationOptions)\n\n const isPending =\n isPendingCreateAcl ||\n isPendingUpdateAcl ||\n isPendingDeleteAcl ||\n isPendingSendMessageToNewACLUsers\n\n const hasAclChanged = useMemo(() => {\n return (\n originalIsInherited != updatedIsInherited ||\n !resourceAccessListIsEqual(\n consolidatedOriginalResourceAccess,\n updatedResourceAccessList,\n )\n )\n }, [\n consolidatedOriginalResourceAccess,\n originalIsInherited,\n updatedIsInherited,\n updatedResourceAccessList,\n ])\n\n const canSave =\n hasAclChanged || isLoadingSendMessageToNewACLUsers || isPending\n\n const showInheritedAclUpdateWarning = hasAclChanged && isAfterUpload\n\n useEffect(() => {\n onCanSaveChange(canSave)\n }, [onCanSaveChange, canSave])\n\n useImperativeHandle(\n ref,\n () => {\n return {\n save() {\n if (canSave) {\n // Check if the inheritance changed; if so, the ACL should be created/deleted\n if (originalIsInherited != updatedIsInherited) {\n if (updatedIsInherited) {\n deleteAcl(entityId)\n } else {\n createAcl({\n id: entityId,\n resourceAccess: updatedResourceAccessList,\n })\n }\n } else {\n // Inheritance did not change; update the ACL\n updateAcl({\n // ensure we get all fields from the original ACL, including the etag\n ...entityBundle.accessControlList!,\n resourceAccess: updatedResourceAccessList,\n })\n }\n } else {\n console.error('EntityAclEditor: save() called but canSave is false')\n }\n },\n }\n },\n [\n canSave,\n createAcl,\n deleteAcl,\n entityBundle,\n entityId,\n originalIsInherited,\n updateAcl,\n updatedIsInherited,\n updatedResourceAccessList,\n ],\n )\n\n return (\n <Stack\n sx={{\n gap: 2,\n }}\n >\n <OpenData\n isOpenData={isOpenData}\n isPublic={isPublic}\n currentUserCanUpdateSharingSettings={canEdit}\n />\n <InheritanceMessage\n isProject={isProject}\n isInherited={updatedIsInherited}\n benefactorId={updatedIsInherited ? parentAcl?.id : entityId}\n isAfterUpload={isAfterUpload}\n />\n {showInheritedAclUpdateWarning && (\n <FullWidthAlert\n isGlobal={false}\n variant={'warning'}\n title={`Edits will affect settings of entire ${entityTypeToFriendlyName(\n entityBundle?.entityType,\n ).toLowerCase()}.`}\n description={\n <>\n <p>\n Editing the settings here will impact the sharing settings for\n all files and folders within this{' '}\n {entityTypeToFriendlyName(\n entityBundle?.entityType,\n ).toLowerCase()}\n , not just the ones you&apos;ve recently uploaded.\n </p>\n <p>\n View the instructions above for setting your{' '}\n <Link\n href={\n SYNAPSE_DOCS_SHARING_SETTINGS_PERMISSIONS_CONDITIONS_FOR_USE_URL\n }\n target={'_blank'}\n >\n Local Sharing Settings\n </Link>\n .\n </p>\n </>\n }\n />\n )}\n <AclEditor\n isInherited={updatedIsInherited}\n canEdit={getCanEditResourceAccess(\n canEdit,\n updatedIsInherited,\n ownProfile,\n publicGroupId,\n )}\n canRemoveEntry={getCanDeleteResourceAccess(\n canEdit,\n updatedIsInherited,\n ownProfile,\n )}\n resourceAccessList={updatedResourceAccessList}\n availablePermissionLevels={availablePermissionLevels}\n emptyText={/* This should never happen */ ''}\n displayedPermissionLevelOverride={getDisplayedPermissionLevelOverride(\n isOpenData,\n publicGroupId,\n )}\n onAddPrincipalToAcl={id => {\n if (publicGroupId !== undefined && String(id) === publicGroupId) {\n addResourceAccessItem(\n id,\n getAccessTypeFromPermissionLevel('CAN_VIEW'),\n )\n } else {\n addResourceAccessItem(\n id,\n getAccessTypeFromPermissionLevel('CAN_DOWNLOAD'),\n )\n }\n }}\n updateResourceAccessItem={updateResourceAccessItem}\n removeResourceAccessItem={removeResourceAccessItem}\n showNotifyCheckbox={true}\n notifyCheckboxValue={notifyNewAdditions}\n onNotifyCheckboxChange={setNotifyNewAdditions}\n showAddRemovePublicButton={true}\n />\n {/* Create / delete local sharing settings button */}\n {!isAfterUpload &&\n !isProject &&\n entityBundle.permissions.canEnableInheritance && (\n <CreateOrDeleteLocalSharingSettingsButton\n isInherited={updatedIsInherited}\n setIsInherited={setUpdatedIsInherited}\n />\n )}\n {error && <Alert severity=\"error\">{error.message}</Alert>}\n </Stack>\n )\n})\n\nconst EntityAclEditorWithSuspense = forwardRef(\n function EntityAclEditorWithSuspense(\n props: EntityAclEditorProps,\n ref: ForwardedRef<EntityAclEditorHandle>,\n ) {\n return (\n <SynapseErrorBoundary>\n <Suspense fallback={<AclEditorSkeleton />}>\n <EntityAclEditor {...props} ref={ref} />\n </Suspense>\n </SynapseErrorBoundary>\n )\n },\n)\n\nexport default EntityAclEditorWithSuspense\n"],"mappings":"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"}
1
+ {"version":3,"file":"EntityAclEditor.js","names":[],"sources":["../../../src/components/EntityAclEditor/EntityAclEditor.tsx"],"sourcesContent":["import {\n useCreateEntityACL,\n useDeleteEntityACL,\n useSuspenseGetCurrentUserProfile,\n useSuspenseGetEntityBenefactorACL,\n useSuspenseGetEntityBundle,\n useUpdateEntityACL,\n} from '@/synapse-queries'\nimport { useGetRealmPrincipals } from '@/synapse-queries/realm/useRealmPrincipals'\nimport { BackendDestinationEnum, getEndpoint } from '@/utils/functions'\nimport {\n consolidateResourceAccessList,\n isEntityPublic,\n resourceAccessListIsEqual,\n} from '@/utils/functions/AccessControlListUtils'\nimport { getPrincipalDisplayName } from '@/utils/functions/getPrincipalDisplayName'\nimport { entityTypeToFriendlyName } from '@/utils/functions/EntityTypeUtils'\nimport {\n getAccessTypeFromPermissionLevel,\n PermissionLevel,\n permissionLevelToLabel,\n} from '@/utils/PermissionLevelToAccessType'\nimport { SYNAPSE_DOCS_SHARING_SETTINGS_PERMISSIONS_CONDITIONS_FOR_USE_URL } from '@/utils/SynapseConstants'\nimport { Alert, Link, Stack } from '@mui/material'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport {\n ALL_ENTITY_BUNDLE_FIELDS,\n ResourceAccess,\n UserProfile,\n} from '@sage-bionetworks/synapse-types'\nimport {\n ForwardedRef,\n forwardRef,\n Suspense,\n useEffect,\n useImperativeHandle,\n useMemo,\n useState,\n} from 'react'\nimport { AclEditor, AclEditorProps } from '../AclEditor/AclEditor'\nimport { AclEditorSkeleton } from '../AclEditor/AclEditorSkeleton'\nimport useUpdateAcl from '../AclEditor/useUpdateAcl'\nimport { SynapseErrorBoundary } from '../error'\nimport FullWidthAlert from '../FullWidthAlert'\nimport { CreateOrDeleteLocalSharingSettingsButton } from './CreateOrDeleteLocalSharingSettingsButton'\nimport { InheritanceMessage } from './InheritanceMessage'\nimport OpenData from './OpenData'\nimport useNotifyNewACLUsers from './useNotifyNewACLUsers'\n\nconst availablePermissionLevels: PermissionLevel[] = [\n 'CAN_VIEW',\n 'CAN_DOWNLOAD',\n 'CAN_EDIT',\n 'CAN_EDIT_DELETE',\n 'CAN_ADMINISTER',\n]\n\nexport type EntityAclEditorProps = {\n /** The ID of the entity on which to view/edit the ACL. Note that the ACL may actually belong to an entity ancestor (benefactor). */\n entityId: string\n /** Invoked when the user can/cannot save the pending changes. */\n onCanSaveChange: (canSaveChanges: boolean) => void\n /** Invoked when changes are successfully made. */\n onUpdateSuccess: () => void\n /** Special case to show specific copy and allow changes to an inherited ACL immediately after a file is uploaded.\n * Note that if this is true, the entityId should be the ID of the folder or project that is the benefactor of the newly uploaded file(s)\n * @defaultValue false */\n isAfterUpload?: boolean\n}\n\nexport type EntityAclEditorHandle = {\n save: () => void\n}\n\nfunction getSubject(entityName: string) {\n return `${entityName} (shared on Synapse)`\n}\n\nfunction getBody(profile: UserProfile, entityId: string) {\n return `${getPrincipalDisplayName(\n {\n userName: profile.userName ?? '',\n firstName: profile.firstName,\n lastName: profile.lastName,\n },\n { showFullName: true, showUsername: false },\n )} has shared an item with you on Synapse:\\n${getEndpoint(\n BackendDestinationEnum.PORTAL_ENDPOINT,\n )}Synapse:${entityId}`\n}\n\nfunction getCanEditResourceAccess(\n canEdit: boolean,\n isInherited: boolean,\n ownProfile: UserProfile,\n publicGroupId: string | undefined,\n): AclEditorProps['canEdit'] {\n if (!canEdit || isInherited) {\n return false\n }\n return (resourceAccess: ResourceAccess): boolean => {\n // Users cannot change their own permissions\n const isSelf = ownProfile.ownerId === String(resourceAccess.principalId)\n // Users cannot change permission level for the public group, only add/remove it.\n // To give the public group DOWNLOAD access, ACT must mark it as anonymous access.\n const isPublicGroup =\n publicGroupId !== undefined &&\n String(resourceAccess.principalId) === publicGroupId\n if (isSelf || isPublicGroup) {\n return false\n }\n return true\n }\n}\n\nfunction getCanDeleteResourceAccess(\n canEdit: boolean,\n isInherited: boolean,\n ownProfile: UserProfile,\n): AclEditorProps['canRemoveEntry'] {\n if (!canEdit || isInherited) {\n return false\n }\n return (resourceAccess: ResourceAccess): boolean => {\n // Users cannot delete their own permissions\n const isSelf = ownProfile.ownerId === String(resourceAccess.principalId)\n if (isSelf) {\n return false\n }\n return canEdit\n }\n}\n\nfunction getDisplayedPermissionLevelOverride(\n isOpenData: boolean,\n publicGroupId: string | undefined,\n): AclEditorProps['displayedPermissionLevelOverride'] {\n return (resourceAccess: ResourceAccess) => {\n if (\n publicGroupId !== undefined &&\n String(resourceAccess.principalId) === publicGroupId\n ) {\n return isOpenData\n ? permissionLevelToLabel['CAN_DOWNLOAD']\n : permissionLevelToLabel['CAN_VIEW']\n }\n return undefined\n }\n}\n\nconst EntityAclEditor = forwardRef(function EntityAclEditor(\n props: EntityAclEditorProps,\n ref: ForwardedRef<EntityAclEditorHandle>,\n) {\n const {\n entityId,\n onCanSaveChange,\n onUpdateSuccess,\n isAfterUpload = false,\n } = props\n\n const { data: ownProfile } = useSuspenseGetCurrentUserProfile()\n const { data: realmPrincipals } = useGetRealmPrincipals()\n const { publicGroup: publicGroupId } = realmPrincipals || {}\n const { data: entityBundle } = useSuspenseGetEntityBundle(\n entityId,\n undefined,\n ALL_ENTITY_BUNDLE_FIELDS,\n { staleTime: Infinity },\n )\n const isProject = EntityType.project == entityBundle.entityType\n\n // The parent's benefactor ACL will be shown if the user removes the ACL on the current entity\n const { data: parentAcl } = useSuspenseGetEntityBenefactorACL(\n entityBundle.entity.parentId!,\n {\n staleTime: Infinity,\n },\n )\n\n const consolidatedOriginalResourceAccess = useMemo(\n () =>\n consolidateResourceAccessList(entityBundle.benefactorAcl.resourceAccess),\n [entityBundle.benefactorAcl.resourceAccess],\n )\n\n const parentResourceAccess = useMemo(\n () => parentAcl?.resourceAccess ?? [],\n [parentAcl],\n )\n const canEdit = entityBundle.permissions.canChangePermissions\n const isOpenData = entityBundle.permissions.isEntityOpenData\n const originalIsInherited = !(entityBundle.benefactorAcl.id == entityId)\n const [updatedIsInherited, setUpdatedIsInherited] =\n useState(originalIsInherited)\n const [notifyNewAdditions, setNotifyNewAdditions] = useState(false)\n const [error, setError] = useState<Error>()\n\n const {\n resourceAccessList: updatedResourceAccessList,\n setResourceAccessList,\n addResourceAccessItem,\n updateResourceAccessItem,\n removeResourceAccessItem,\n resetDirtyState,\n } = useUpdateAcl({\n initialResourceAccessList: consolidatedOriginalResourceAccess,\n })\n\n // If `consolidatedOriginalResourceAccess` changes, reset state\n useEffect(() => {\n if (consolidatedOriginalResourceAccess) {\n resetDirtyState()\n setResourceAccessList([...consolidatedOriginalResourceAccess])\n }\n }, [\n consolidatedOriginalResourceAccess,\n resetDirtyState,\n setResourceAccessList,\n ])\n\n // If `originalIsInherited` changes, reset state\n useEffect(() => {\n resetDirtyState()\n setUpdatedIsInherited(originalIsInherited)\n }, [originalIsInherited, resetDirtyState])\n\n // If the editor toggles the inherited state, reset the resource access list\n useEffect(() => {\n if (originalIsInherited == updatedIsInherited) {\n // The user reverted to the original state (regardless of inherited or not)\n setResourceAccessList(consolidatedOriginalResourceAccess)\n } else if (updatedIsInherited) {\n // The user toggled to inherited, update the resource access list to match the parent\n setResourceAccessList(parentResourceAccess)\n } else {\n // The user toggled to local, no need to update the list (the parent's list is already in the editor)\n }\n resetDirtyState()\n }, [\n originalIsInherited,\n consolidatedOriginalResourceAccess,\n parentResourceAccess,\n resetDirtyState,\n setResourceAccessList,\n updatedIsInherited,\n ])\n\n const isPublic = isEntityPublic(\n updatedResourceAccessList,\n realmPrincipals ?? {},\n )\n\n const {\n sendNotification,\n isLoading: isLoadingSendMessageToNewACLUsers,\n isPending: isPendingSendMessageToNewACLUsers,\n } = useNotifyNewACLUsers({\n subject: getSubject(entityBundle.entity.name || ''),\n body: getBody(ownProfile, entityId),\n initialResourceAccessList: consolidatedOriginalResourceAccess,\n newResourceAccessList: updatedResourceAccessList,\n })\n\n const mutationOptions = {\n onSuccess: () => {\n if (notifyNewAdditions) {\n sendNotification()\n }\n onUpdateSuccess()\n },\n onError: (e: Error) => {\n setError(e)\n },\n }\n\n const { mutate: createAcl, isPending: isPendingCreateAcl } =\n useCreateEntityACL(mutationOptions)\n const { mutate: updateAcl, isPending: isPendingUpdateAcl } =\n useUpdateEntityACL(mutationOptions)\n const { mutate: deleteAcl, isPending: isPendingDeleteAcl } =\n useDeleteEntityACL(mutationOptions)\n\n const isPending =\n isPendingCreateAcl ||\n isPendingUpdateAcl ||\n isPendingDeleteAcl ||\n isPendingSendMessageToNewACLUsers\n\n const hasAclChanged = useMemo(() => {\n return (\n originalIsInherited != updatedIsInherited ||\n !resourceAccessListIsEqual(\n consolidatedOriginalResourceAccess,\n updatedResourceAccessList,\n )\n )\n }, [\n consolidatedOriginalResourceAccess,\n originalIsInherited,\n updatedIsInherited,\n updatedResourceAccessList,\n ])\n\n const canSave =\n hasAclChanged || isLoadingSendMessageToNewACLUsers || isPending\n\n const showInheritedAclUpdateWarning = hasAclChanged && isAfterUpload\n\n useEffect(() => {\n onCanSaveChange(canSave)\n }, [onCanSaveChange, canSave])\n\n useImperativeHandle(\n ref,\n () => {\n return {\n save() {\n if (canSave) {\n // Check if the inheritance changed; if so, the ACL should be created/deleted\n if (originalIsInherited != updatedIsInherited) {\n if (updatedIsInherited) {\n deleteAcl(entityId)\n } else {\n createAcl({\n id: entityId,\n resourceAccess: updatedResourceAccessList,\n })\n }\n } else {\n // Inheritance did not change; update the ACL\n updateAcl({\n // ensure we get all fields from the original ACL, including the etag\n ...entityBundle.accessControlList!,\n resourceAccess: updatedResourceAccessList,\n })\n }\n } else {\n console.error('EntityAclEditor: save() called but canSave is false')\n }\n },\n }\n },\n [\n canSave,\n createAcl,\n deleteAcl,\n entityBundle,\n entityId,\n originalIsInherited,\n updateAcl,\n updatedIsInherited,\n updatedResourceAccessList,\n ],\n )\n\n return (\n <Stack\n sx={{\n gap: 2,\n }}\n >\n <OpenData\n isOpenData={isOpenData}\n isPublic={isPublic}\n currentUserCanUpdateSharingSettings={canEdit}\n />\n <InheritanceMessage\n isProject={isProject}\n isInherited={updatedIsInherited}\n benefactorId={updatedIsInherited ? parentAcl?.id : entityId}\n isAfterUpload={isAfterUpload}\n />\n {showInheritedAclUpdateWarning && (\n <FullWidthAlert\n isGlobal={false}\n variant={'warning'}\n title={`Edits will affect settings of entire ${entityTypeToFriendlyName(\n entityBundle?.entityType,\n ).toLowerCase()}.`}\n description={\n <>\n <p>\n Editing the settings here will impact the sharing settings for\n all files and folders within this{' '}\n {entityTypeToFriendlyName(\n entityBundle?.entityType,\n ).toLowerCase()}\n , not just the ones you&apos;ve recently uploaded.\n </p>\n <p>\n View the instructions above for setting your{' '}\n <Link\n href={\n SYNAPSE_DOCS_SHARING_SETTINGS_PERMISSIONS_CONDITIONS_FOR_USE_URL\n }\n target={'_blank'}\n >\n Local Sharing Settings\n </Link>\n .\n </p>\n </>\n }\n />\n )}\n {/* Create / delete local sharing settings button */}\n {!isAfterUpload &&\n !isProject &&\n entityBundle.permissions.canEnableInheritance && (\n <CreateOrDeleteLocalSharingSettingsButton\n isInherited={updatedIsInherited}\n setIsInherited={setUpdatedIsInherited}\n />\n )}\n {error && <Alert severity=\"error\">{error.message}</Alert>}\n <AclEditor\n isInherited={updatedIsInherited}\n canEdit={getCanEditResourceAccess(\n canEdit,\n updatedIsInherited,\n ownProfile,\n publicGroupId,\n )}\n canRemoveEntry={getCanDeleteResourceAccess(\n canEdit,\n updatedIsInherited,\n ownProfile,\n )}\n resourceAccessList={updatedResourceAccessList}\n availablePermissionLevels={availablePermissionLevels}\n emptyText={/* This should never happen */ ''}\n displayedPermissionLevelOverride={getDisplayedPermissionLevelOverride(\n isOpenData,\n publicGroupId,\n )}\n onAddPrincipalToAcl={id => {\n if (publicGroupId !== undefined && String(id) === publicGroupId) {\n addResourceAccessItem(\n id,\n getAccessTypeFromPermissionLevel('CAN_VIEW'),\n )\n } else {\n addResourceAccessItem(\n id,\n getAccessTypeFromPermissionLevel('CAN_DOWNLOAD'),\n )\n }\n }}\n updateResourceAccessItem={updateResourceAccessItem}\n removeResourceAccessItem={removeResourceAccessItem}\n showNotifyCheckbox={true}\n notifyCheckboxValue={notifyNewAdditions}\n onNotifyCheckboxChange={setNotifyNewAdditions}\n showAddRemovePublicButton={true}\n />\n </Stack>\n )\n})\n\nconst EntityAclEditorWithSuspense = forwardRef(\n function EntityAclEditorWithSuspense(\n props: EntityAclEditorProps,\n ref: ForwardedRef<EntityAclEditorHandle>,\n ) {\n return (\n <SynapseErrorBoundary>\n <Suspense fallback={<AclEditorSkeleton />}>\n <EntityAclEditor {...props} ref={ref} />\n </Suspense>\n </SynapseErrorBoundary>\n )\n },\n)\n\nexport default EntityAclEditorWithSuspense\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"InheritanceMessage.d.ts","sourceRoot":"","sources":["../../../src/components/EntityAclEditor/InheritanceMessage.tsx"],"names":[],"mappings":"AASA,KAAK,uBAAuB,GAAG;IAC7B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,2CA4EhE"}
1
+ {"version":3,"file":"InheritanceMessage.d.ts","sourceRoot":"","sources":["../../../src/components/EntityAclEditor/InheritanceMessage.tsx"],"names":[],"mappings":"AASA,KAAK,uBAAuB,GAAG;IAC7B,SAAS,EAAE,OAAO,CAAA;IAClB,WAAW,EAAE,OAAO,CAAA;IACpB,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,uBAAuB,2CA2EhE"}
@@ -47,8 +47,7 @@ function u(u) {
47
47
  "from ",
48
48
  /* @__PURE__ */ c(i, { entity: m }),
49
49
  " "
50
- ] }) : "",
51
- "and cannot be modified here."
50
+ ] }) : ""
52
51
  ]
53
52
  }) : /* @__PURE__ */ l(o, {
54
53
  variant: "body1",
@@ -1 +1 @@
1
- {"version":3,"file":"InheritanceMessage.js","names":[],"sources":["../../../src/components/EntityAclEditor/InheritanceMessage.tsx"],"sourcesContent":["import { useGetEntityHeader } from '@/synapse-queries/entity/useGetEntityHeaders'\nimport {\n convertToEntityType,\n entityTypeToFriendlyName,\n} from '@/utils/functions/EntityTypeUtils'\nimport { SYNAPSE_DOCS_SHARING_SETTINGS_PERMISSIONS_CONDITIONS_FOR_USE_URL } from '@/utils/SynapseConstants'\nimport { Link, Typography } from '@mui/material'\nimport { EntityLink } from '../EntityLink'\n\ntype InheritanceMessageProps = {\n isProject: boolean\n isInherited: boolean\n isAfterUpload?: boolean\n benefactorId?: string\n}\n\nexport function InheritanceMessage(props: InheritanceMessageProps) {\n const { isProject, isInherited, isAfterUpload = false, benefactorId } = props\n const { data: benefactorHeader } = useGetEntityHeader(\n benefactorId,\n undefined,\n {\n enabled: Boolean(benefactorId),\n },\n )\n\n if (isAfterUpload) {\n return (\n <>\n <Typography variant={'body1'}>\n Currently, the sharing settings are inherited from the{' '}\n {benefactorHeader\n ? entityTypeToFriendlyName(\n convertToEntityType(benefactorHeader.type),\n ).toLowerCase()\n : 'container'}{' '}\n named\n {benefactorId ? (\n <>\n {' '}\n <EntityLink entity={benefactorId} />{' '}\n </>\n ) : (\n ''\n )}\n . If you wish to customize settings for a particular file, folder, or\n table, you must establish <strong>Local Sharing Settings</strong> to\n make modifications.\n </Typography>\n <Typography variant={'body1'}>\n After uploading, choose the file you want to customize sharing\n settings for, then navigate to File Tools &gt;{' '}\n <Link\n href={\n SYNAPSE_DOCS_SHARING_SETTINGS_PERMISSIONS_CONDITIONS_FOR_USE_URL\n }\n >\n File Sharing Settings\n </Link>\n .\n </Typography>\n </>\n )\n } else if (isProject) {\n return (\n <Typography variant={'body1'}>\n The sharing settings shown below apply to this project and are inherited\n by all project contents unless local sharing settings have been set.\n </Typography>\n )\n } else if (isInherited) {\n return (\n <Typography variant={'body1'}>\n The sharing settings shown below are currently being inherited{' '}\n {benefactorId ? (\n <>\n from <EntityLink entity={benefactorId} />{' '}\n </>\n ) : (\n ''\n )}\n and cannot be modified here.\n </Typography>\n )\n } else {\n return (\n <Typography variant={'body1'}>\n The local sharing settings shown below are <strong>not</strong> being\n inherited from a parent resource.\n </Typography>\n )\n }\n}\n"],"mappings":";;;;;;;AAgBA,SAAgB,EAAmB,GAAgC;CACjE,IAAM,EAAE,cAAW,gBAAa,mBAAgB,IAAO,oBAAiB,GAClE,EAAE,MAAM,MAAqB,EACjC,GACA,KAAA,GACA,EACE,SAAS,EAAQ,GAClB,CACF;AA6DC,QA3DE,IAEA,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EAAY,SAAS;YAArB;GAA8B;GAC2B;GACtD,IACG,EACE,EAAoB,EAAiB,KAAK,CAC3C,CAAC,aAAa,GACf;GAAa;GAAI;GAEpB,IACC,kBAAA,GAAA,EAAA,UAAA;IACG;IACD,kBAAC,GAAD,EAAY,QAAQ,GAAgB,CAAA;IAAC;IACpC,EAAA,CAAA,GAEH;GACA;GAEwB,kBAAC,UAAD,EAAA,UAAQ,0BAA+B,CAAA;;GAEtD;KACb,kBAAC,GAAD;EAAY,SAAS;YAArB;GAA8B;GAEmB;GAC/C,kBAAC,GAAD;IACE,MACE;cAEH;IAEM,CAAA;;GAEI;IACZ,EAAA,CAAA,GAEI,IAEP,kBAAC,GAAD;EAAY,SAAS;YAAS;EAGjB,CAAA,GAEN,IAEP,kBAAC,GAAD;EAAY,SAAS;YAArB;GAA8B;GACmC;GAC9D,IACC,kBAAA,GAAA,EAAA,UAAA;IAAE;IACK,kBAAC,GAAD,EAAY,QAAQ,GAAgB,CAAA;IAAC;IACzC,EAAA,CAAA,GAEH;GACA;GAES;MAIb,kBAAC,GAAD;EAAY,SAAS;YAArB;GAA8B;GACe,kBAAC,UAAD,EAAA,UAAQ,OAAY,CAAA;;GAEpD"}
1
+ {"version":3,"file":"InheritanceMessage.js","names":[],"sources":["../../../src/components/EntityAclEditor/InheritanceMessage.tsx"],"sourcesContent":["import { useGetEntityHeader } from '@/synapse-queries/entity/useGetEntityHeaders'\nimport {\n convertToEntityType,\n entityTypeToFriendlyName,\n} from '@/utils/functions/EntityTypeUtils'\nimport { SYNAPSE_DOCS_SHARING_SETTINGS_PERMISSIONS_CONDITIONS_FOR_USE_URL } from '@/utils/SynapseConstants'\nimport { Link, Typography } from '@mui/material'\nimport { EntityLink } from '../EntityLink'\n\ntype InheritanceMessageProps = {\n isProject: boolean\n isInherited: boolean\n isAfterUpload?: boolean\n benefactorId?: string\n}\n\nexport function InheritanceMessage(props: InheritanceMessageProps) {\n const { isProject, isInherited, isAfterUpload = false, benefactorId } = props\n const { data: benefactorHeader } = useGetEntityHeader(\n benefactorId,\n undefined,\n {\n enabled: Boolean(benefactorId),\n },\n )\n\n if (isAfterUpload) {\n return (\n <>\n <Typography variant={'body1'}>\n Currently, the sharing settings are inherited from the{' '}\n {benefactorHeader\n ? entityTypeToFriendlyName(\n convertToEntityType(benefactorHeader.type),\n ).toLowerCase()\n : 'container'}{' '}\n named\n {benefactorId ? (\n <>\n {' '}\n <EntityLink entity={benefactorId} />{' '}\n </>\n ) : (\n ''\n )}\n . If you wish to customize settings for a particular file, folder, or\n table, you must establish <strong>Local Sharing Settings</strong> to\n make modifications.\n </Typography>\n <Typography variant={'body1'}>\n After uploading, choose the file you want to customize sharing\n settings for, then navigate to File Tools &gt;{' '}\n <Link\n href={\n SYNAPSE_DOCS_SHARING_SETTINGS_PERMISSIONS_CONDITIONS_FOR_USE_URL\n }\n >\n File Sharing Settings\n </Link>\n .\n </Typography>\n </>\n )\n } else if (isProject) {\n return (\n <Typography variant={'body1'}>\n The sharing settings shown below apply to this project and are inherited\n by all project contents unless local sharing settings have been set.\n </Typography>\n )\n } else if (isInherited) {\n return (\n <Typography variant={'body1'}>\n The sharing settings shown below are currently being inherited{' '}\n {benefactorId ? (\n <>\n from <EntityLink entity={benefactorId} />{' '}\n </>\n ) : (\n ''\n )}\n </Typography>\n )\n } else {\n return (\n <Typography variant={'body1'}>\n The local sharing settings shown below are <strong>not</strong> being\n inherited from a parent resource.\n </Typography>\n )\n }\n}\n"],"mappings":";;;;;;;AAgBA,SAAgB,EAAmB,GAAgC;CACjE,IAAM,EAAE,cAAW,gBAAa,mBAAgB,IAAO,oBAAiB,GAClE,EAAE,MAAM,MAAqB,EACjC,GACA,KAAA,GACA,EACE,SAAS,EAAQ,GAClB,CACF;AA4DC,QA1DE,IAEA,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD;EAAY,SAAS;YAArB;GAA8B;GAC2B;GACtD,IACG,EACE,EAAoB,EAAiB,KAAK,CAC3C,CAAC,aAAa,GACf;GAAa;GAAI;GAEpB,IACC,kBAAA,GAAA,EAAA,UAAA;IACG;IACD,kBAAC,GAAD,EAAY,QAAQ,GAAgB,CAAA;IAAC;IACpC,EAAA,CAAA,GAEH;GACA;GAEwB,kBAAC,UAAD,EAAA,UAAQ,0BAA+B,CAAA;;GAEtD;KACb,kBAAC,GAAD;EAAY,SAAS;YAArB;GAA8B;GAEmB;GAC/C,kBAAC,GAAD;IACE,MACE;cAEH;IAEM,CAAA;;GAEI;IACZ,EAAA,CAAA,GAEI,IAEP,kBAAC,GAAD;EAAY,SAAS;YAAS;EAGjB,CAAA,GAEN,IAEP,kBAAC,GAAD;EAAY,SAAS;YAArB;GAA8B;GACmC;GAC9D,IACC,kBAAA,GAAA,EAAA,UAAA;IAAE;IACK,kBAAC,GAAD,EAAY,QAAQ,GAAgB,CAAA;IAAC;IACzC,EAAA,CAAA,GAEH;GAES;MAIb,kBAAC,GAAD;EAAY,SAAS;YAArB;GAA8B;GACe,kBAAC,UAAD,EAAA,UAAQ,OAAY,CAAA;;GAEpD"}
@@ -1 +1 @@
1
- {"version":3,"file":"FullTextSearch.d.ts","sourceRoot":"","sources":["../../../src/components/FullTextSearch/FullTextSearch.tsx"],"names":[],"mappings":"AACA,OAAO,KAAwD,MAAM,OAAO,CAAA;AAQ5E,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAKpD,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED,wBAAgB,cAAc,CAAC,EAC7B,SAAS,GACV,EAAE,mBAAmB,GAAG,KAAK,CAAC,SAAS,CA2FvC;AAED,eAAe,cAAc,CAAA"}
1
+ {"version":3,"file":"FullTextSearch.d.ts","sourceRoot":"","sources":["../../../src/components/FullTextSearch/FullTextSearch.tsx"],"names":[],"mappings":"AACA,OAAO,KAMN,MAAM,OAAO,CAAA;AASd,OAAO,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAA;AAMpD,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB,CAAA;AAED,wBAAgB,cAAc,CAAC,EAC7B,SAAS,GACV,EAAE,mBAAmB,GAAG,KAAK,CAAC,SAAS,CA2KvC;AAED,eAAe,cAAc,CAAA"}