synapse-react-client 4.0.23 → 4.0.25
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/SWC.index.d.ts +1 -0
- package/dist/SWC.index.d.ts.map +1 -1
- package/dist/SWC.index.js +2 -1
- package/dist/SWC.index.js.map +1 -1
- package/dist/components/AccessRequirementList/ManagedACTAccessRequirementRequestFlow/DataAccessRequestAccessorsFilesForm/DataAccessRequestAccessorsFilesForm.js.map +1 -1
- package/dist/components/AclEditor/useUpdateAcl.js.map +1 -1
- package/dist/components/Authentication/TwoFactorBackupCodes.js.map +1 -1
- package/dist/components/Authentication/TwoFactorEnrollmentForm.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts +3 -0
- package/dist/components/Breadcrumbs/Breadcrumbs.d.ts.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +43 -30
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/CannedRejectionDialog/CannedRejectionDialog.js.map +1 -1
- package/dist/components/ChallengeSubmission/SubmissionDirectoryList.js.map +1 -1
- package/dist/components/CopyToClipboardString/CopyToClipboardString.d.ts +7 -1
- package/dist/components/CopyToClipboardString/CopyToClipboardString.d.ts.map +1 -1
- package/dist/components/CopyToClipboardString/CopyToClipboardString.js +22 -16
- package/dist/components/CopyToClipboardString/CopyToClipboardString.js.map +1 -1
- package/dist/components/EntityCitation/EntityCitation.d.ts.map +1 -1
- package/dist/components/EntityCitation/EntityCitation.js +28 -26
- package/dist/components/EntityCitation/EntityCitation.js.map +1 -1
- package/dist/components/EntityFinder/tree/EntityTree.js.map +1 -1
- package/dist/components/EntityFinder/tree/VirtualizedTree.js.map +1 -1
- package/dist/components/EntityIcon.d.ts +1 -0
- package/dist/components/EntityIcon.d.ts.map +1 -1
- package/dist/components/EntityIcon.js +37 -36
- package/dist/components/EntityIcon.js.map +1 -1
- package/dist/components/EntitySidebar/EntitySidebar.css +1 -0
- package/dist/components/EntitySidebar/EntitySidebar.d.ts +7 -0
- package/dist/components/EntitySidebar/EntitySidebar.d.ts.map +1 -0
- package/dist/components/EntitySidebar/EntitySidebar.js +62 -0
- package/dist/components/EntitySidebar/EntitySidebar.js.map +1 -0
- package/dist/components/EntitySidebar/EntitySidebar.module.js +17 -0
- package/dist/components/EntitySidebar/EntitySidebar.module.js.map +1 -0
- package/dist/components/EntitySidebar/EntitySidebar.module.scss +87 -0
- package/dist/components/Evaluation/EvaluationRoundEditorList.js.map +1 -1
- package/dist/components/GenericCard/BioregistryRules.js.map +1 -1
- package/dist/components/IconSvg/IconSvg.d.ts +2 -1
- package/dist/components/IconSvg/IconSvg.d.ts.map +1 -1
- package/dist/components/IconSvg/IconSvg.js +112 -89
- package/dist/components/IconSvg/IconSvg.js.map +1 -1
- package/dist/components/Plot/SynapsePlot.d.ts +4 -0
- package/dist/components/Plot/SynapsePlot.d.ts.map +1 -1
- package/dist/components/Plot/SynapsePlot.js +57 -44
- package/dist/components/Plot/SynapsePlot.js.map +1 -1
- package/dist/components/Plot/UpsetPlot.js.map +1 -1
- package/dist/components/QueryWrapperPlotNav/QueryWrapperSynapsePlot.d.ts.map +1 -1
- package/dist/components/QueryWrapperPlotNav/QueryWrapperSynapsePlot.js +77 -34
- package/dist/components/QueryWrapperPlotNav/QueryWrapperSynapsePlot.js.map +1 -1
- package/dist/components/SchemaDrivenAnnotationEditor/field/AdditionalPropertiesSchemaField.js.map +1 -1
- package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.d.ts.map +1 -1
- package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.js +1 -0
- package/dist/components/SearchQueryWrapperPlotNav/SearchQueryWrapperPlotNav.js.map +1 -1
- package/dist/components/StorybookComponentWrapper.js.map +1 -1
- package/dist/components/SynapseTable/TablePagination.js.map +1 -1
- package/dist/components/SynapseTable/datasets/DatasetItemsEditor.js.map +1 -1
- package/dist/components/TableColumnSchemaEditor/TableColumnSchemaForm.js.map +1 -1
- package/dist/components/TanStackTable/StyledTanStackTable.js.map +1 -1
- package/dist/components/entity/page/action_menu/EntityActionMenu.d.ts +2 -0
- package/dist/components/entity/page/action_menu/EntityActionMenu.d.ts.map +1 -1
- package/dist/components/entity/page/action_menu/EntityActionMenu.js +46 -27
- package/dist/components/entity/page/action_menu/EntityActionMenu.js.map +1 -1
- package/dist/components/entity/page/breadcrumbs/EntityPageBreadcrumbs.d.ts +1 -8
- package/dist/components/entity/page/breadcrumbs/EntityPageBreadcrumbs.d.ts.map +1 -1
- package/dist/components/entity/page/breadcrumbs/EntityPageBreadcrumbs.js +17 -13
- package/dist/components/entity/page/breadcrumbs/EntityPageBreadcrumbs.js.map +1 -1
- package/dist/components/entity/page/title_bar/EntityPageTitleBar.d.ts +1 -0
- package/dist/components/entity/page/title_bar/EntityPageTitleBar.d.ts.map +1 -1
- package/dist/components/entity/page/title_bar/EntityPageTitleBar.js +35 -22
- package/dist/components/entity/page/title_bar/EntityPageTitleBar.js.map +1 -1
- package/dist/components/entity/page/title_bar/EntityTitleBarVersionInfo.d.ts.map +1 -1
- package/dist/components/entity/page/title_bar/EntityTitleBarVersionInfo.js +34 -20
- package/dist/components/entity/page/title_bar/EntityTitleBarVersionInfo.js.map +1 -1
- package/dist/components/entity/page/title_bar/TitleBarProperties.d.ts +0 -1
- package/dist/components/entity/page/title_bar/TitleBarProperties.d.ts.map +1 -1
- package/dist/components/entity/page/title_bar/TitleBarProperties.js +17 -82
- package/dist/components/entity/page/title_bar/TitleBarProperties.js.map +1 -1
- package/dist/components/entity/page/title_bar/useGetEntityTitleBarProperties.d.ts.map +1 -1
- package/dist/components/entity/page/title_bar/useGetEntityTitleBarProperties.js +3 -4
- package/dist/components/entity/page/title_bar/useGetEntityTitleBarProperties.js.map +1 -1
- package/dist/components/favorites/FavoriteButton.d.ts +1 -0
- package/dist/components/favorites/FavoriteButton.d.ts.map +1 -1
- package/dist/components/favorites/FavoriteButton.js +8 -8
- package/dist/components/favorites/FavoriteButton.js.map +1 -1
- package/dist/components/menu/ComplexMenu.d.ts.map +1 -1
- package/dist/components/menu/ComplexMenu.js +5 -3
- package/dist/components/menu/ComplexMenu.js.map +1 -1
- package/dist/components/menu/DropdownMenu.d.ts +1 -0
- package/dist/components/menu/DropdownMenu.d.ts.map +1 -1
- package/dist/components/menu/DropdownMenu.js +24 -24
- package/dist/components/menu/DropdownMenu.js.map +1 -1
- package/dist/components/widgets/facet-nav/FacetNavPanel.d.ts.map +1 -1
- package/dist/components/widgets/facet-nav/FacetNavPanel.js +59 -49
- package/dist/components/widgets/facet-nav/FacetNavPanel.js.map +1 -1
- package/dist/components/widgets/facet-nav/PlotsContainer.d.ts.map +1 -1
- package/dist/components/widgets/facet-nav/PlotsContainer.js +8 -2
- package/dist/components/widgets/facet-nav/PlotsContainer.js.map +1 -1
- package/dist/style/components/facet_nav/_facet-nav-panel.scss +11 -0
- package/dist/style/main.css +1 -1
- package/dist/testutils/ReactQueryMockUtils.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/dist/types/ThemeTypes.d.ts +2 -2
- package/dist/utils/functions/IsEqualTreatUndefinedAsMissing.js.map +1 -1
- package/dist/utils/hooks/useDebouncedEffect.js.map +1 -1
- package/dist/utils/hooks/useDetectSSOCode.js.map +1 -1
- package/dist/utils/hooks/useImmutableTableQuery/useImmutableTableQuery.js.map +1 -1
- package/dist/utils/hooks/useLogin.js.map +1 -1
- package/package.json +3 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CannedRejectionDialog.js","names":[],"sources":["../../../src/components/CannedRejectionDialog/CannedRejectionDialog.tsx"],"sourcesContent":["import { useGetFullTableQueryResults } from '@/synapse-queries'\nimport { BUNDLE_MASK_QUERY_RESULTS } from '@/utils/SynapseConstants'\nimport {\n Checkbox,\n Collapse,\n FormControl,\n FormControlLabel,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { SynapseClientError } from '@sage-bionetworks/synapse-client/util/SynapseClientError'\nimport { QueryResultBundle, Row } from '@sage-bionetworks/synapse-types'\nimport { UseQueryResult } from '@tanstack/react-query'\nimport { Set } from 'immutable'\nimport { Dispatch, ReactNode, SetStateAction, useEffect, useState } from 'react'\nimport ConfirmationDialog from '../ConfirmationDialog'\nimport { ErrorBanner } from '../error/ErrorBanner'\nimport FullWidthAlert from '../FullWidthAlert/FullWidthAlert'\nimport IconSvg from '../IconSvg/IconSvg'\nimport { SynapseSpinner } from '../LoadingScreen/LoadingScreen'\n\nconst CATEGORY_COLUMN_NAME = 'category'\nconst CATEGORY_SECTION_EMAIL_TEXT_COLUMN_NAME = 'category email prompt'\nconst REJECTION_REASON_FORM_TEXT_COLUMN_NAME = 'rejection reason'\nconst REJECTION_REASON_EMAIL_TEXT_COLUMN_NAME = 'email text'\n\nexport type CannedRejectionDialogProps = {\n open: boolean\n /* SynID of the table which contains the email responses which should populate this modal */\n tableId: string\n /* Callback invoked when the rejection is confirmed */\n onConfirm: (message: string) => void\n onClose: () => void\n /** Text injected into the beginning of the email template */\n defaultMessagePrefix?: string\n /** Text appended onto the end of the email template */\n defaultMessageAppend?: string\n /** Text to display above the canned rejection reasons */\n rejectionFormPromptCopy?: string\n /** Error to display, in case one occurred in the callback to `onReject` */\n error: SynapseClientError | null\n /** Optional children, can be used to extend the form as needed on step 2 */\n children?: ReactNode\n}\n\ntype RejectionCategoryProps = {\n category: string\n rows: Row[]\n rejectionReasonFormTextIndex: number\n selectedRowIds: Set<number>\n setSelectedRowIds: Dispatch<SetStateAction<Set<number>>>\n initialIsExpanded?: boolean\n}\n\ntype SelectRejectionReasonsFormProps = {\n tableQuery: UseQueryResult<QueryResultBundle, SynapseClientError>\n selectedRowIds: Set<number>\n setSelectedRowIds: Dispatch<SetStateAction<Set<number>>>\n rejectionFormPromptCopy?: string\n}\n\n/**\n * Renders a rejection reason category and checkboxes for each reason in the category\n */\nfunction RejectionCategory(props: RejectionCategoryProps) {\n const {\n category,\n rows,\n selectedRowIds,\n setSelectedRowIds,\n rejectionReasonFormTextIndex,\n initialIsExpanded = false,\n } = props\n const [isExpanded, setIsExpanded] = useState(initialIsExpanded)\n\n return (\n <>\n <Typography\n variant=\"body1\"\n onClick={() => setIsExpanded(!isExpanded)}\n sx={{ fontWeight: 700, cursor: 'pointer', my: 1 }}\n >\n <IconSvg\n icon={isExpanded ? 'expandMore' : 'chevronRight'}\n sx={{ color: 'grey.700' }}\n wrap={false}\n />\n {category}\n </Typography>\n <Collapse in={isExpanded}>\n <Stack sx={{ ml: 3 }}>\n {(rows ?? []).map(row => (\n <FormControlLabel\n key={row.rowId}\n control={\n <Checkbox\n checked={selectedRowIds.has(row.rowId!)}\n size={'small'}\n onChange={event => {\n if (event.target.checked) {\n setSelectedRowIds(selectedRowIds.add(row.rowId!))\n } else {\n setSelectedRowIds(selectedRowIds.remove(row.rowId!))\n }\n }}\n />\n }\n label={\n <Typography variant={'smallText1'}>\n {row.values[rejectionReasonFormTextIndex]}\n </Typography>\n }\n />\n ))}\n </Stack>\n </Collapse>\n </>\n )\n}\n\n/**\n * Renders a form for selecting individual rejection reasons grouped by category.\n * The rejection reason data comes from the table whose data is fetched in the tableQuery prop.\n */\nfunction SelectRejectionReasonsForm(props: SelectRejectionReasonsFormProps) {\n const {\n tableQuery,\n selectedRowIds,\n setSelectedRowIds,\n rejectionFormPromptCopy,\n } = props\n const { data: tableData, isLoading, error } = tableQuery\n const categoryIndex = tableData?.queryResult?.queryResults.headers.findIndex(\n header => header.name.toLowerCase() === CATEGORY_COLUMN_NAME,\n )\n const rejectionReasonFormTextIndex =\n tableData?.queryResult?.queryResults.headers.findIndex(\n header =>\n header.name.toLowerCase() === REJECTION_REASON_FORM_TEXT_COLUMN_NAME,\n )\n\n const rowsGroupedByCategory =\n tableData &&\n tableData.queryResult &&\n tableData.queryResult.queryResults.rows.reduce(\n (acc: Record<string, Row[]>, row) => {\n const category: string = row.values[categoryIndex!]!\n acc[category] = [...(acc[category] || []), row]\n return acc\n },\n {},\n )\n return (\n <>\n <Typography variant=\"headline3\" gutterBottom>\n Reasons for rejecting\n </Typography>\n {rejectionFormPromptCopy && (\n <Typography variant=\"body1\" gutterBottom>\n {rejectionFormPromptCopy}\n </Typography>\n )}\n {isLoading && (\n <Stack sx={{ my: 2 }}>\n <SynapseSpinner size={30} />\n </Stack>\n )}\n {error && <ErrorBanner error={error} />}\n {rowsGroupedByCategory && (\n <FormControl>\n {Object.keys(rowsGroupedByCategory).map(category => (\n <RejectionCategory\n key={category}\n category={category}\n rows={rowsGroupedByCategory[category]}\n selectedRowIds={selectedRowIds}\n setSelectedRowIds={setSelectedRowIds}\n rejectionReasonFormTextIndex={rejectionReasonFormTextIndex!}\n initialIsExpanded={\n Object.keys(rowsGroupedByCategory).length === 1\n }\n />\n ))}\n </FormControl>\n )}\n <Typography variant=\"headline3\" sx={{ mt: 1 }} gutterBottom>\n We’ll generate a response email message based on your selections.\n </Typography>\n <Typography variant=\"body1\" gutterBottom>\n If your reasons for rejecting are not shown here, that’s okay! You can\n edit the complete text of the message on the next screen before sending\n it.\n </Typography>\n </>\n )\n}\n\ntype DraftRejectionMessageProps = {\n emailText: string\n setEmailText: Dispatch<SetStateAction<string>>\n}\n\n/**\n * Presents a text field form that can be used to directly modify the rejection message.\n */\nfunction DraftRejectionMessage(props: DraftRejectionMessageProps) {\n const { emailText, setEmailText } = props\n return (\n <>\n <Typography variant=\"headline3\" gutterBottom>\n Edit the text of the rejection message\n </Typography>\n <Typography variant=\"body1\" gutterBottom>\n This message will be sent to the requester. You may edit it, or add\n custom text to the message.\n </Typography>\n <TextField\n multiline\n fullWidth\n rows={15}\n value={emailText}\n onChange={event => {\n setEmailText(event.target.value)\n }}\n />\n </>\n )\n}\ntype RejectionMessageObject = {\n [category: string]: {\n sectionText: string\n reasons: string[]\n }\n}\n\n/**\n * Modal component presented to a data access submission reviewer when they decide to reject a request.\n * The modal contains a form for selecting rejection reasons and a text field for editing the rejection message.\n * After crafting a message, the user can reject the submission and send the message to the requester.\n */\nexport function CannedRejectionDialog(props: CannedRejectionDialogProps) {\n const {\n open,\n tableId,\n onClose,\n onConfirm: onReject,\n defaultMessagePrefix = '',\n defaultMessageAppend = '',\n rejectionFormPromptCopy,\n children,\n error,\n } = props\n\n const [step, setStep] = useState<1 | 2>(1)\n // selectedRowIds are the row IDs of the canned responses the user selected:\n const [emailText, setEmailText] = useState('')\n const [selectedRowIds, setSelectedRowIds] = useState(Set<number>())\n\n // Fetch the table data\n const tableQuery = useGetFullTableQueryResults({\n entityId: tableId,\n query: {\n sql: `SELECT * FROM ${tableId}`,\n },\n partMask: BUNDLE_MASK_QUERY_RESULTS,\n concreteType: 'org.sagebionetworks.repo.model.table.QueryBundleRequest',\n })\n\n const { data } = tableQuery\n\n const categoryIndex = data?.queryResult?.queryResults.headers.findIndex(\n header => header.name.toLowerCase() === CATEGORY_COLUMN_NAME,\n )\n const categorySectionEmailTextIndex =\n data?.queryResult?.queryResults.headers.findIndex(\n header =>\n header.name.toLowerCase() === CATEGORY_SECTION_EMAIL_TEXT_COLUMN_NAME,\n )\n\n const reasonEmailTextIndex =\n data?.queryResult?.queryResults.headers.findIndex(\n header =>\n header.name.toLowerCase() === REJECTION_REASON_EMAIL_TEXT_COLUMN_NAME,\n )\n\n // Transform the selected rejection reasons into an object that can be easily transformed into an email\n const defaultEmailMessageObject: RejectionMessageObject | undefined =\n data &&\n data.queryResult &&\n selectedRowIds.reduce((messageObject: RejectionMessageObject, rowId) => {\n const row = data.queryResult!.queryResults.rows.find(\n row => row.rowId === rowId,\n )!\n const category = row.values[categoryIndex!]!\n const sectionText = row.values[categorySectionEmailTextIndex!]!\n const reasonText = row.values[reasonEmailTextIndex!]!\n if (messageObject[category]) {\n messageObject[category].reasons = [\n ...messageObject[category].reasons,\n reasonText,\n ]\n } else {\n messageObject[category] = {\n sectionText: sectionText,\n reasons: [reasonText],\n }\n }\n return messageObject\n }, {})\n\n // Using the rejection message object, create the email template.\n const defaultEmailMessage =\n defaultEmailMessageObject &&\n defaultMessagePrefix +\n Object.keys(defaultEmailMessageObject).reduce((message, key) => {\n message += '\\n'\n const sectionText = defaultEmailMessageObject[key].sectionText\n if (sectionText) {\n message += sectionText + '\\n'\n }\n\n for (const reason of defaultEmailMessageObject[key].reasons) {\n if (reason != null) {\n message += '\\n* ' + reason.replaceAll('\\\\n', '\\n') + '\\n'\n }\n }\n return message\n }, '') +\n defaultMessageAppend\n\n /* If the selected rows change, then reset the email text. */\n useEffect(() => {\n if (defaultEmailMessage) {\n setEmailText(defaultEmailMessage)\n }\n // Specifically fire on update to just selectedRowIds\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedRowIds])\n\n // If fetching/processing the table fails, gracefully fall back to just show the email template\n return (\n <ConfirmationDialog\n open={open}\n onCancel={() => {\n if (step === 1) {\n onClose()\n }\n if (step === 2) {\n setStep(1)\n }\n }}\n maxWidth={'md'}\n fullWidth\n title=\"Reject Request?\"\n content={\n <>\n {step === 1 && (\n <SelectRejectionReasonsForm\n tableQuery={tableQuery}\n selectedRowIds={selectedRowIds}\n setSelectedRowIds={setSelectedRowIds}\n rejectionFormPromptCopy={rejectionFormPromptCopy}\n />\n )}\n {step === 2 && (\n <>\n <DraftRejectionMessage\n emailText={emailText}\n setEmailText={setEmailText}\n />\n {children}\n </>\n )}\n {error && (\n <FullWidthAlert\n variant={'danger'}\n description={error.reason}\n isGlobal={false}\n />\n )}\n </>\n }\n onConfirm={() => {\n if (step === 1) {\n setStep(2)\n }\n if (step === 2) {\n onReject(emailText)\n }\n }}\n confirmButtonProps={{\n children: step === 1 ? 'Generate Email' : 'Reject and Notify Requester',\n }}\n cancelButtonProps={{\n children: step === 1 ? 'Cancel' : 'Back',\n }}\n />\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CannedRejectionDialog.js","names":[],"sources":["../../../src/components/CannedRejectionDialog/CannedRejectionDialog.tsx"],"sourcesContent":["import { useGetFullTableQueryResults } from '@/synapse-queries'\nimport { BUNDLE_MASK_QUERY_RESULTS } from '@/utils/SynapseConstants'\nimport {\n Checkbox,\n Collapse,\n FormControl,\n FormControlLabel,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { SynapseClientError } from '@sage-bionetworks/synapse-client/util/SynapseClientError'\nimport { QueryResultBundle, Row } from '@sage-bionetworks/synapse-types'\nimport { UseQueryResult } from '@tanstack/react-query'\nimport { Set } from 'immutable'\nimport { Dispatch, ReactNode, SetStateAction, useEffect, useState } from 'react'\nimport ConfirmationDialog from '../ConfirmationDialog'\nimport { ErrorBanner } from '../error/ErrorBanner'\nimport FullWidthAlert from '../FullWidthAlert/FullWidthAlert'\nimport IconSvg from '../IconSvg/IconSvg'\nimport { SynapseSpinner } from '../LoadingScreen/LoadingScreen'\n\nconst CATEGORY_COLUMN_NAME = 'category'\nconst CATEGORY_SECTION_EMAIL_TEXT_COLUMN_NAME = 'category email prompt'\nconst REJECTION_REASON_FORM_TEXT_COLUMN_NAME = 'rejection reason'\nconst REJECTION_REASON_EMAIL_TEXT_COLUMN_NAME = 'email text'\n\nexport type CannedRejectionDialogProps = {\n open: boolean\n /* SynID of the table which contains the email responses which should populate this modal */\n tableId: string\n /* Callback invoked when the rejection is confirmed */\n onConfirm: (message: string) => void\n onClose: () => void\n /** Text injected into the beginning of the email template */\n defaultMessagePrefix?: string\n /** Text appended onto the end of the email template */\n defaultMessageAppend?: string\n /** Text to display above the canned rejection reasons */\n rejectionFormPromptCopy?: string\n /** Error to display, in case one occurred in the callback to `onReject` */\n error: SynapseClientError | null\n /** Optional children, can be used to extend the form as needed on step 2 */\n children?: ReactNode\n}\n\ntype RejectionCategoryProps = {\n category: string\n rows: Row[]\n rejectionReasonFormTextIndex: number\n selectedRowIds: Set<number>\n setSelectedRowIds: Dispatch<SetStateAction<Set<number>>>\n initialIsExpanded?: boolean\n}\n\ntype SelectRejectionReasonsFormProps = {\n tableQuery: UseQueryResult<QueryResultBundle, SynapseClientError>\n selectedRowIds: Set<number>\n setSelectedRowIds: Dispatch<SetStateAction<Set<number>>>\n rejectionFormPromptCopy?: string\n}\n\n/**\n * Renders a rejection reason category and checkboxes for each reason in the category\n */\nfunction RejectionCategory(props: RejectionCategoryProps) {\n const {\n category,\n rows,\n selectedRowIds,\n setSelectedRowIds,\n rejectionReasonFormTextIndex,\n initialIsExpanded = false,\n } = props\n const [isExpanded, setIsExpanded] = useState(initialIsExpanded)\n\n return (\n <>\n <Typography\n variant=\"body1\"\n onClick={() => setIsExpanded(!isExpanded)}\n sx={{ fontWeight: 700, cursor: 'pointer', my: 1 }}\n >\n <IconSvg\n icon={isExpanded ? 'expandMore' : 'chevronRight'}\n sx={{ color: 'grey.700' }}\n wrap={false}\n />\n {category}\n </Typography>\n <Collapse in={isExpanded}>\n <Stack sx={{ ml: 3 }}>\n {(rows ?? []).map(row => (\n <FormControlLabel\n key={row.rowId}\n control={\n <Checkbox\n checked={selectedRowIds.has(row.rowId!)}\n size={'small'}\n onChange={event => {\n if (event.target.checked) {\n setSelectedRowIds(selectedRowIds.add(row.rowId!))\n } else {\n setSelectedRowIds(selectedRowIds.remove(row.rowId!))\n }\n }}\n />\n }\n label={\n <Typography variant={'smallText1'}>\n {row.values[rejectionReasonFormTextIndex]}\n </Typography>\n }\n />\n ))}\n </Stack>\n </Collapse>\n </>\n )\n}\n\n/**\n * Renders a form for selecting individual rejection reasons grouped by category.\n * The rejection reason data comes from the table whose data is fetched in the tableQuery prop.\n */\nfunction SelectRejectionReasonsForm(props: SelectRejectionReasonsFormProps) {\n const {\n tableQuery,\n selectedRowIds,\n setSelectedRowIds,\n rejectionFormPromptCopy,\n } = props\n const { data: tableData, isLoading, error } = tableQuery\n const categoryIndex = tableData?.queryResult?.queryResults.headers.findIndex(\n header => header.name.toLowerCase() === CATEGORY_COLUMN_NAME,\n )\n const rejectionReasonFormTextIndex =\n tableData?.queryResult?.queryResults.headers.findIndex(\n header =>\n header.name.toLowerCase() === REJECTION_REASON_FORM_TEXT_COLUMN_NAME,\n )\n\n const rowsGroupedByCategory =\n tableData &&\n tableData.queryResult &&\n tableData.queryResult.queryResults.rows.reduce(\n (acc: Record<string, Row[]>, row) => {\n const category: string = row.values[categoryIndex!]!\n acc[category] = [...(acc[category] || []), row]\n return acc\n },\n {},\n )\n return (\n <>\n <Typography variant=\"headline3\" gutterBottom>\n Reasons for rejecting\n </Typography>\n {rejectionFormPromptCopy && (\n <Typography variant=\"body1\" gutterBottom>\n {rejectionFormPromptCopy}\n </Typography>\n )}\n {isLoading && (\n <Stack sx={{ my: 2 }}>\n <SynapseSpinner size={30} />\n </Stack>\n )}\n {error && <ErrorBanner error={error} />}\n {rowsGroupedByCategory && (\n <FormControl>\n {Object.keys(rowsGroupedByCategory).map(category => (\n <RejectionCategory\n key={category}\n category={category}\n rows={rowsGroupedByCategory[category]}\n selectedRowIds={selectedRowIds}\n setSelectedRowIds={setSelectedRowIds}\n rejectionReasonFormTextIndex={rejectionReasonFormTextIndex!}\n initialIsExpanded={\n Object.keys(rowsGroupedByCategory).length === 1\n }\n />\n ))}\n </FormControl>\n )}\n <Typography variant=\"headline3\" sx={{ mt: 1 }} gutterBottom>\n We’ll generate a response email message based on your selections.\n </Typography>\n <Typography variant=\"body1\" gutterBottom>\n If your reasons for rejecting are not shown here, that’s okay! You can\n edit the complete text of the message on the next screen before sending\n it.\n </Typography>\n </>\n )\n}\n\ntype DraftRejectionMessageProps = {\n emailText: string\n setEmailText: Dispatch<SetStateAction<string>>\n}\n\n/**\n * Presents a text field form that can be used to directly modify the rejection message.\n */\nfunction DraftRejectionMessage(props: DraftRejectionMessageProps) {\n const { emailText, setEmailText } = props\n return (\n <>\n <Typography variant=\"headline3\" gutterBottom>\n Edit the text of the rejection message\n </Typography>\n <Typography variant=\"body1\" gutterBottom>\n This message will be sent to the requester. You may edit it, or add\n custom text to the message.\n </Typography>\n <TextField\n multiline\n fullWidth\n rows={15}\n value={emailText}\n onChange={event => {\n setEmailText(event.target.value)\n }}\n />\n </>\n )\n}\ntype RejectionMessageObject = {\n [category: string]: {\n sectionText: string\n reasons: string[]\n }\n}\n\n/**\n * Modal component presented to a data access submission reviewer when they decide to reject a request.\n * The modal contains a form for selecting rejection reasons and a text field for editing the rejection message.\n * After crafting a message, the user can reject the submission and send the message to the requester.\n */\nexport function CannedRejectionDialog(props: CannedRejectionDialogProps) {\n const {\n open,\n tableId,\n onClose,\n onConfirm: onReject,\n defaultMessagePrefix = '',\n defaultMessageAppend = '',\n rejectionFormPromptCopy,\n children,\n error,\n } = props\n\n const [step, setStep] = useState<1 | 2>(1)\n // selectedRowIds are the row IDs of the canned responses the user selected:\n const [emailText, setEmailText] = useState('')\n const [selectedRowIds, setSelectedRowIds] = useState(Set<number>())\n\n // Fetch the table data\n const tableQuery = useGetFullTableQueryResults({\n entityId: tableId,\n query: {\n sql: `SELECT * FROM ${tableId}`,\n },\n partMask: BUNDLE_MASK_QUERY_RESULTS,\n concreteType: 'org.sagebionetworks.repo.model.table.QueryBundleRequest',\n })\n\n const { data } = tableQuery\n\n const categoryIndex = data?.queryResult?.queryResults.headers.findIndex(\n header => header.name.toLowerCase() === CATEGORY_COLUMN_NAME,\n )\n const categorySectionEmailTextIndex =\n data?.queryResult?.queryResults.headers.findIndex(\n header =>\n header.name.toLowerCase() === CATEGORY_SECTION_EMAIL_TEXT_COLUMN_NAME,\n )\n\n const reasonEmailTextIndex =\n data?.queryResult?.queryResults.headers.findIndex(\n header =>\n header.name.toLowerCase() === REJECTION_REASON_EMAIL_TEXT_COLUMN_NAME,\n )\n\n // Transform the selected rejection reasons into an object that can be easily transformed into an email\n const defaultEmailMessageObject: RejectionMessageObject | undefined =\n data &&\n data.queryResult &&\n selectedRowIds.reduce((messageObject: RejectionMessageObject, rowId) => {\n const row = data.queryResult!.queryResults.rows.find(\n row => row.rowId === rowId,\n )!\n const category = row.values[categoryIndex!]!\n const sectionText = row.values[categorySectionEmailTextIndex!]!\n const reasonText = row.values[reasonEmailTextIndex!]!\n if (messageObject[category]) {\n messageObject[category].reasons = [\n ...messageObject[category].reasons,\n reasonText,\n ]\n } else {\n messageObject[category] = {\n sectionText: sectionText,\n reasons: [reasonText],\n }\n }\n return messageObject\n }, {})\n\n // Using the rejection message object, create the email template.\n const defaultEmailMessage =\n defaultEmailMessageObject &&\n defaultMessagePrefix +\n Object.keys(defaultEmailMessageObject).reduce((message, key) => {\n message += '\\n'\n const sectionText = defaultEmailMessageObject[key].sectionText\n if (sectionText) {\n message += sectionText + '\\n'\n }\n\n for (const reason of defaultEmailMessageObject[key].reasons) {\n if (reason != null) {\n message += '\\n* ' + reason.replaceAll('\\\\n', '\\n') + '\\n'\n }\n }\n return message\n }, '') +\n defaultMessageAppend\n\n /* If the selected rows change, then reset the email text. */\n useEffect(() => {\n if (defaultEmailMessage) {\n setEmailText(defaultEmailMessage)\n }\n // Specifically fire on update to just selectedRowIds\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [selectedRowIds])\n\n // If fetching/processing the table fails, gracefully fall back to just show the email template\n return (\n <ConfirmationDialog\n open={open}\n onCancel={() => {\n if (step === 1) {\n onClose()\n }\n if (step === 2) {\n setStep(1)\n }\n }}\n maxWidth={'md'}\n fullWidth\n title=\"Reject Request?\"\n content={\n <>\n {step === 1 && (\n <SelectRejectionReasonsForm\n tableQuery={tableQuery}\n selectedRowIds={selectedRowIds}\n setSelectedRowIds={setSelectedRowIds}\n rejectionFormPromptCopy={rejectionFormPromptCopy}\n />\n )}\n {step === 2 && (\n <>\n <DraftRejectionMessage\n emailText={emailText}\n setEmailText={setEmailText}\n />\n {children}\n </>\n )}\n {error && (\n <FullWidthAlert\n variant={'danger'}\n description={error.reason}\n isGlobal={false}\n />\n )}\n </>\n }\n onConfirm={() => {\n if (step === 1) {\n setStep(2)\n }\n if (step === 2) {\n onReject(emailText)\n }\n }}\n confirmButtonProps={{\n children: step === 1 ? 'Generate Email' : 'Reject and Notify Requester',\n }}\n cancelButtonProps={{\n children: step === 1 ? 'Cancel' : 'Back',\n }}\n />\n )\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SubmissionDirectoryList.js","names":[],"sources":["../../../src/components/ChallengeSubmission/SubmissionDirectoryList.tsx"],"sourcesContent":["import SynapseClient from '@/synapse-client'\nimport {\n invalidateAllQueriesForEntity,\n useGetEntities,\n useGetEntityChildren,\n} from '@/synapse-queries'\nimport { useSynapseContext } from '@/utils'\nimport { formatDate } from '@/utils/functions/DateFormatter'\nimport {\n BackendDestinationEnum,\n getEndpoint,\n} from '@/utils/functions/getEndpoint'\nimport { NotesTwoTone } from '@mui/icons-material'\nimport { Box, Button, Radio, Typography } from '@mui/material'\nimport { DataGrid, GridCellParams, GridColDef } from '@mui/x-data-grid'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport { SynapseClientError } from '@sage-bionetworks/synapse-client/util/SynapseClientError'\nimport {\n Direction,\n Entity,\n EntityChildrenRequest,\n EntityHeader,\n EntityLookupRequest,\n FILE_ENTITY_CONCRETE_TYPE_VALUE,\n FileEntity,\n FileUploadComplete,\n SortBy,\n UploadCallbackResp,\n} from '@sage-bionetworks/synapse-types'\nimport { useQueryClient } from '@tanstack/react-query'\nimport dayjs from 'dayjs'\nimport { useEffect, useMemo, useState } from 'react'\nimport { Link } from 'react-router'\nimport ConfirmationDialog from '../ConfirmationDialog'\nimport CopyToClipboardIcon from '../CopyToClipboardIcon'\nimport { ErrorBanner } from '../error/ErrorBanner'\nimport FileUpload from '../FileUpload'\nimport IconSvg from '../IconSvg'\nimport { EntityItem } from './ChallengeSubmission'\n\ntype SubmissionDirectoryRow = {\n id: string\n name: string\n modifiedOn: string\n}\n\ntype SubmissionDirectoryListProps = {\n pageSize: number\n challengeProjectId: string\n entityType: typeof EntityType.dockerrepo | typeof EntityType.file\n onItemSelected: (selected: EntityItem) => void\n}\n\ntype FileUploadAttempt = FileUploadComplete & {\n entityId?: string\n}\n\nfunction SubmissionDirectoryList({\n pageSize,\n challengeProjectId,\n entityType,\n onItemSelected,\n}: SubmissionDirectoryListProps) {\n const queryClient = useQueryClient()\n const { accessToken, keyFactory } = useSynapseContext()\n const [page, setPage] = useState<number>(0)\n const [selectedItem, setSelectedItem] = useState<EntityItem | undefined>()\n const [errorMessage, setErrorMessage] = useState<string>()\n const [canSubmit, setCanSubmit] = useState<boolean>()\n const [fetchedHeaders, setFetchedHeaders] = useState<EntityHeader[]>([])\n const [nextPageToken, setNextPageToken] = useState<string | undefined>()\n const [fetchNextPage, setFetchNextPage] = useState<boolean>(false)\n const [confirmOpen, setConfirmOpen] = useState<boolean>(false)\n const [uploadAttempt, setUploadAttempt] = useState<FileUploadAttempt>()\n\n const PER_PAGE = pageSize\n const HEADERS_PER_PAGE = 50\n const PROJECT_URL = `${getEndpoint(\n BackendDestinationEnum.PORTAL_ENDPOINT,\n )}Synapse:${challengeProjectId}`\n\n const request: EntityChildrenRequest = {\n parentId: challengeProjectId,\n nextPageToken: fetchNextPage ? nextPageToken : null,\n includeTypes: [entityType],\n includeTotalChildCount: true,\n sortBy: SortBy.MODIFIED_ON,\n sortDirection: Direction.DESC,\n }\n\n const { data: headerResults, refetch } = useGetEntityChildren(request, {\n enabled: !!challengeProjectId,\n throwOnError: true,\n })\n\n useEffect(() => {\n if (headerResults) {\n const newHeaders = [...fetchedHeaders]\n const headerPage = Math.floor(((page + 1) * PER_PAGE) / HEADERS_PER_PAGE)\n const start = headerPage * HEADERS_PER_PAGE\n newHeaders.splice(start, start + HEADERS_PER_PAGE, ...headerResults.page)\n setFetchedHeaders(newHeaders)\n setFetchNextPage(false)\n setNextPageToken(headerResults.nextPageToken)\n }\n // TODO: Temporary useEffect hook to remove onSuccess QueryOption for @tanstack/react-query v5\n // Refactor this component to remove this effect.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [headerResults])\n\n function getPageHeaders() {\n const pageStart = page * PER_PAGE\n const pageHeaders = fetchedHeaders.slice(pageStart, pageStart + PER_PAGE)\n return pageHeaders\n }\n\n function reset() {\n setErrorMessage(undefined)\n setCanSubmit(undefined)\n setFetchedHeaders([])\n setNextPageToken(undefined)\n setFetchNextPage(false)\n refetch()\n }\n\n useEffect(() => {\n reset()\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [entityType, pageSize])\n\n const queries = useGetEntities(getPageHeaders())\n const entities = useMemo(\n () =>\n queries\n .filter(q => q.status === 'success')\n .map(q => q.data) as EntityItem[],\n [queries],\n )\n const areEntitiesLoading = queries.some(q => q.isLoading)\n\n const entityChangeHandler = async (value: string) => {\n setCanSubmit(false)\n const entity = entities.find(entity => entity?.id === value)\n if (entity) {\n setSelectedItem(entity)\n if (entityType === EntityType.dockerrepo) {\n let commits\n try {\n commits = await SynapseClient.getDockerTag(\n entity.id!,\n accessToken,\n 0,\n 1,\n )\n } catch (e) {\n return setErrorMessage(e.message)\n }\n if (commits.totalNumberOfResults === 0) {\n return setErrorMessage(\n 'No commits have been made to this repository. Please select a repository with at least one commit.',\n )\n }\n }\n setCanSubmit(true)\n setErrorMessage(undefined)\n }\n }\n\n const columns: GridColDef[] = [\n {\n field: 'radiobutton',\n headerName: '',\n width: 25,\n sortable: false,\n filterable: false,\n hideable: false,\n disableColumnMenu: true,\n renderCell: params => {\n return (\n <Radio\n value={params.id}\n checked={params.id === selectedItem?.id}\n onChange={event => {\n entityChangeHandler(event.target.value)\n }}\n />\n )\n },\n },\n {\n field: 'name',\n headerName:\n entityType === EntityType.dockerrepo\n ? 'Docker Repository'\n : 'File Name',\n flex: 1,\n filterable: false,\n hideable: false,\n disableColumnMenu: true,\n renderCell: (params: GridCellParams<Entity, SubmissionDirectoryRow>) => (\n <Link\n to={{\n pathname: `${getEndpoint(\n BackendDestinationEnum.PORTAL_ENDPOINT,\n )}Synapse:${params.row.id}`,\n }}\n target=\"_blank\"\n >\n {params.row.name}\n </Link>\n ),\n },\n {\n field: 'modifiedOn',\n headerName: 'Updated On',\n width: 100,\n filterable: false,\n hideable: false,\n disableColumnMenu: true,\n },\n {\n field: 'id',\n headerName: 'ID',\n width: 200,\n filterable: false,\n hideable: false,\n disableColumnMenu: true,\n },\n ]\n\n const getRows = (entities: EntityItem[]) => {\n const newRows: SubmissionDirectoryRow[] = []\n entities.forEach(entity => {\n newRows.push({\n id: entity.id!,\n name:\n entityType === EntityType.dockerrepo\n ? (entity.repositoryName ?? entity.name)\n : entity.name,\n modifiedOn: formatDate(dayjs(entity.modifiedOn), 'MM/DD/YY'),\n })\n })\n return newRows\n }\n\n const handlePageChange = (newPageNum: number) => {\n const lastIndexNeeded = Math.min(\n headerResults?.totalChildCount ?? 0,\n (newPageNum + 1) * PER_PAGE,\n )\n if (lastIndexNeeded > fetchedHeaders.length) {\n setFetchNextPage(true)\n }\n setPage(newPageNum)\n }\n\n const itemSelectedHandler = () => {\n onItemSelected(selectedItem!)\n }\n\n function createEntity(file: FileUploadAttempt) {\n // Create Entity\n if (!file) return\n const { fileHandleId, fileName } = file\n const newFileEntity: FileEntity = {\n parentId: challengeProjectId,\n name: fileName,\n concreteType: FILE_ENTITY_CONCRETE_TYPE_VALUE,\n dataFileHandleId: fileHandleId,\n }\n SynapseClient.createEntity(newFileEntity, accessToken)\n .then(() => {\n reset()\n })\n .catch((err: SynapseClientError) => {\n setErrorMessage(err.reason)\n })\n }\n\n async function updateEntity() {\n if (!uploadAttempt) return\n const { fileHandleId, entityId } = uploadAttempt\n if (!entityId)\n return setErrorMessage('Error: missing entityId. Please try again.')\n // Get the entity\n let entity: FileEntity\n try {\n entity = await SynapseClient.getEntity(accessToken, entityId)\n } catch (err) {\n return setErrorMessage(`The entity ${entityId} could not be retrieved.`)\n }\n\n const updateRequest: FileEntity = {\n id: entity.id,\n name: entity.name,\n dataFileHandleId: fileHandleId,\n concreteType: FILE_ENTITY_CONCRETE_TYPE_VALUE,\n parentId: entity.parentId,\n etag: entity.etag,\n modifiedOn: entity.modifiedOn,\n }\n\n try {\n const updatedEntity = await SynapseClient.updateEntity(\n updateRequest,\n accessToken,\n true,\n )\n await invalidateAllQueriesForEntity(\n queryClient,\n keyFactory,\n updatedEntity.id!,\n )\n queryClient.setQueryData(\n keyFactory.getEntityQueryKey(updatedEntity.id!),\n updatedEntity,\n )\n reset()\n } catch (err) {\n setErrorMessage(err.reason)\n }\n }\n\n const handleUpload = async (response: UploadCallbackResp) => {\n if (response.success && response.resp) {\n const { fileName } = response.resp\n\n // Lookup entity\n const entityLookupRequest: EntityLookupRequest = {\n entityName: fileName,\n parentId: challengeProjectId,\n }\n try {\n const entityId = await SynapseClient.lookupChildEntity(\n entityLookupRequest,\n accessToken,\n )\n // Entity exists, prompt user to update it\n if (entityId) {\n setUploadAttempt({ ...response.resp, entityId: entityId.id })\n setConfirmOpen(true)\n }\n } catch (err) {\n // An existing entity was not found for this file, create it\n setUploadAttempt(undefined)\n createEntity(response.resp)\n }\n } else if (!response.success && response.error) {\n setErrorMessage(response.error.reason as string)\n } else {\n setErrorMessage('An unknown error occurred. Please try again.')\n }\n }\n\n return (\n <Box>\n <Box\n sx={{\n display: 'flex',\n backgroundColor: '#FBFBFC',\n padding: '10px',\n justifyContent: 'space-between',\n }}\n >\n <Typography\n variant=\"h6\"\n sx={{ fontSize: '18px', lineHeight: '20px', fontWeight: 700 }}\n >\n {entityType === EntityType.file\n ? 'Submit File'\n : 'Submit Docker Image'}\n </Typography>\n <Box sx={{ display: 'flex' }}>\n <Typography\n variant=\"body1\"\n sx={{ fontSize: '14px', color: '#71767F' }}\n >\n Your Project SynID:{' '}\n <Link to={{ pathname: PROJECT_URL }} target=\"_blank\">\n {challengeProjectId}\n </Link>\n </Typography>\n <CopyToClipboardIcon value={PROJECT_URL} sx={{ marginTop: '-4px' }} />\n </Box>\n </Box>\n <Box\n sx={{\n my: 3,\n display: 'flex',\n }}\n >\n <NotesTwoTone\n sx={{\n width: '16px',\n height: '16px',\n verticalAlign: 'text-bottom',\n }}\n />\n <Box\n sx={{\n ml: 1,\n }}\n >\n {entityType === EntityType.file && (\n <>\n Choose a prediction file from your submission directory below and\n click \"Submit Selection\" to pick an evaluation queue. If you need\n to add a new file, click \"Upload File\" first.\n </>\n )}\n {entityType === EntityType.dockerrepo && (\n <>\n Choose a Docker image from your submission directory below and\n click \"Submit Selection\" to pick an image version and evaluation\n queue. If you need to upload a new image to your project (\n <code>{challengeProjectId}</code>), see our{' '}\n <Link\n to=\"https://github.com/Sage-Bionetworks-Challenges/sample-model-templates#build-your-model\"\n target=\"_blank\"\n >\n Docker model submission guide\n </Link>{' '}\n for command-line instructions on how to build and upload.\n </>\n )}\n </Box>\n </Box>\n <Box>\n <DataGrid\n loading={areEntitiesLoading}\n columns={columns}\n rows={getRows(entities)}\n rowCount={headerResults?.totalChildCount ?? 0}\n pagination\n paginationMode=\"server\"\n paginationModel={{ page, pageSize: PER_PAGE }}\n pageSizeOptions={[PER_PAGE]}\n onPaginationModelChange={({ page }) => {\n handlePageChange(page)\n }}\n density=\"compact\"\n autoHeight\n sx={{\n fontSize: '14px',\n border: 'none',\n height: '100%',\n '& .MuiDataGrid-columnHeader': {\n backgroundColor: '#F1F3F5',\n },\n '& .Mui-odd': {\n backgroundColor: '#FBFBFC',\n },\n '.MuiDataGrid-columnHeaderTitleContainer': {\n justifyContent: 'space-between',\n },\n '.radio': {\n display: 'flex',\n alignItems: 'center',\n height: '100%',\n },\n }}\n getRowClassName={params =>\n params.indexRelativeToCurrentPage % 2 === 0 ? 'Mui-even' : 'Mui-odd'\n }\n />\n </Box>\n {errorMessage && <ErrorBanner error={errorMessage}></ErrorBanner>}\n <Box\n sx={{\n display: 'flex',\n justifyContent: 'space-between',\n }}\n >\n {entityType === EntityType.file && (\n <FileUpload\n label=\"Upload File\"\n buttonProps={{\n variant: 'outlined',\n endIcon: <IconSvg icon=\"upload\" />,\n sx: { lineHeight: 1 },\n }}\n onComplete={resp => {\n handleUpload(resp)\n }}\n />\n )}\n <Button\n color=\"primary\"\n variant=\"contained\"\n onClick={itemSelectedHandler}\n disabled={!canSubmit}\n >\n Submit Selection\n </Button>\n </Box>\n <ConfirmationDialog\n open={confirmOpen}\n title=\"File exists\"\n content={\n <Typography variant=\"body1\" sx={{ fontSize: '16px' }}>\n A file named "{uploadAttempt?.fileName}" (\n {uploadAttempt?.entityId}) already exists in this location. Do you\n want to update the existing file and create a new version?\n </Typography>\n }\n onCancel={() => setConfirmOpen(false)}\n onConfirm={() => {\n updateEntity()\n setConfirmOpen(false)\n }}\n />\n </Box>\n )\n}\n\nexport default SubmissionDirectoryList\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"SubmissionDirectoryList.js","names":[],"sources":["../../../src/components/ChallengeSubmission/SubmissionDirectoryList.tsx"],"sourcesContent":["import SynapseClient from '@/synapse-client'\nimport {\n invalidateAllQueriesForEntity,\n useGetEntities,\n useGetEntityChildren,\n} from '@/synapse-queries'\nimport { useSynapseContext } from '@/utils'\nimport { formatDate } from '@/utils/functions/DateFormatter'\nimport {\n BackendDestinationEnum,\n getEndpoint,\n} from '@/utils/functions/getEndpoint'\nimport { NotesTwoTone } from '@mui/icons-material'\nimport { Box, Button, Radio, Typography } from '@mui/material'\nimport { DataGrid, GridCellParams, GridColDef } from '@mui/x-data-grid'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport { SynapseClientError } from '@sage-bionetworks/synapse-client/util/SynapseClientError'\nimport {\n Direction,\n Entity,\n EntityChildrenRequest,\n EntityHeader,\n EntityLookupRequest,\n FILE_ENTITY_CONCRETE_TYPE_VALUE,\n FileEntity,\n FileUploadComplete,\n SortBy,\n UploadCallbackResp,\n} from '@sage-bionetworks/synapse-types'\nimport { useQueryClient } from '@tanstack/react-query'\nimport dayjs from 'dayjs'\nimport { useEffect, useMemo, useState } from 'react'\nimport { Link } from 'react-router'\nimport ConfirmationDialog from '../ConfirmationDialog'\nimport CopyToClipboardIcon from '../CopyToClipboardIcon'\nimport { ErrorBanner } from '../error/ErrorBanner'\nimport FileUpload from '../FileUpload'\nimport IconSvg from '../IconSvg'\nimport { EntityItem } from './ChallengeSubmission'\n\ntype SubmissionDirectoryRow = {\n id: string\n name: string\n modifiedOn: string\n}\n\ntype SubmissionDirectoryListProps = {\n pageSize: number\n challengeProjectId: string\n entityType: typeof EntityType.dockerrepo | typeof EntityType.file\n onItemSelected: (selected: EntityItem) => void\n}\n\ntype FileUploadAttempt = FileUploadComplete & {\n entityId?: string\n}\n\nfunction SubmissionDirectoryList({\n pageSize,\n challengeProjectId,\n entityType,\n onItemSelected,\n}: SubmissionDirectoryListProps) {\n const queryClient = useQueryClient()\n const { accessToken, keyFactory } = useSynapseContext()\n const [page, setPage] = useState<number>(0)\n const [selectedItem, setSelectedItem] = useState<EntityItem | undefined>()\n const [errorMessage, setErrorMessage] = useState<string>()\n const [canSubmit, setCanSubmit] = useState<boolean>()\n const [fetchedHeaders, setFetchedHeaders] = useState<EntityHeader[]>([])\n const [nextPageToken, setNextPageToken] = useState<string | undefined>()\n const [fetchNextPage, setFetchNextPage] = useState<boolean>(false)\n const [confirmOpen, setConfirmOpen] = useState<boolean>(false)\n const [uploadAttempt, setUploadAttempt] = useState<FileUploadAttempt>()\n\n const PER_PAGE = pageSize\n const HEADERS_PER_PAGE = 50\n const PROJECT_URL = `${getEndpoint(\n BackendDestinationEnum.PORTAL_ENDPOINT,\n )}Synapse:${challengeProjectId}`\n\n const request: EntityChildrenRequest = {\n parentId: challengeProjectId,\n nextPageToken: fetchNextPage ? nextPageToken : null,\n includeTypes: [entityType],\n includeTotalChildCount: true,\n sortBy: SortBy.MODIFIED_ON,\n sortDirection: Direction.DESC,\n }\n\n const { data: headerResults, refetch } = useGetEntityChildren(request, {\n enabled: !!challengeProjectId,\n throwOnError: true,\n })\n\n useEffect(() => {\n if (headerResults) {\n const newHeaders = [...fetchedHeaders]\n const headerPage = Math.floor(((page + 1) * PER_PAGE) / HEADERS_PER_PAGE)\n const start = headerPage * HEADERS_PER_PAGE\n newHeaders.splice(start, start + HEADERS_PER_PAGE, ...headerResults.page)\n setFetchedHeaders(newHeaders)\n setFetchNextPage(false)\n setNextPageToken(headerResults.nextPageToken)\n }\n // TODO: Temporary useEffect hook to remove onSuccess QueryOption for @tanstack/react-query v5\n // Refactor this component to remove this effect.\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [headerResults])\n\n function getPageHeaders() {\n const pageStart = page * PER_PAGE\n const pageHeaders = fetchedHeaders.slice(pageStart, pageStart + PER_PAGE)\n return pageHeaders\n }\n\n function reset() {\n setErrorMessage(undefined)\n setCanSubmit(undefined)\n setFetchedHeaders([])\n setNextPageToken(undefined)\n setFetchNextPage(false)\n refetch()\n }\n\n useEffect(() => {\n reset()\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [entityType, pageSize])\n\n const queries = useGetEntities(getPageHeaders())\n const entities = useMemo(\n () =>\n queries\n .filter(q => q.status === 'success')\n .map(q => q.data) as EntityItem[],\n [queries],\n )\n const areEntitiesLoading = queries.some(q => q.isLoading)\n\n const entityChangeHandler = async (value: string) => {\n setCanSubmit(false)\n const entity = entities.find(entity => entity?.id === value)\n if (entity) {\n setSelectedItem(entity)\n if (entityType === EntityType.dockerrepo) {\n let commits\n try {\n commits = await SynapseClient.getDockerTag(\n entity.id!,\n accessToken,\n 0,\n 1,\n )\n } catch (e) {\n return setErrorMessage(e.message)\n }\n if (commits.totalNumberOfResults === 0) {\n return setErrorMessage(\n 'No commits have been made to this repository. Please select a repository with at least one commit.',\n )\n }\n }\n setCanSubmit(true)\n setErrorMessage(undefined)\n }\n }\n\n const columns: GridColDef[] = [\n {\n field: 'radiobutton',\n headerName: '',\n width: 25,\n sortable: false,\n filterable: false,\n hideable: false,\n disableColumnMenu: true,\n renderCell: params => {\n return (\n <Radio\n value={params.id}\n checked={params.id === selectedItem?.id}\n onChange={event => {\n entityChangeHandler(event.target.value)\n }}\n />\n )\n },\n },\n {\n field: 'name',\n headerName:\n entityType === EntityType.dockerrepo\n ? 'Docker Repository'\n : 'File Name',\n flex: 1,\n filterable: false,\n hideable: false,\n disableColumnMenu: true,\n renderCell: (params: GridCellParams<Entity, SubmissionDirectoryRow>) => (\n <Link\n to={{\n pathname: `${getEndpoint(\n BackendDestinationEnum.PORTAL_ENDPOINT,\n )}Synapse:${params.row.id}`,\n }}\n target=\"_blank\"\n >\n {params.row.name}\n </Link>\n ),\n },\n {\n field: 'modifiedOn',\n headerName: 'Updated On',\n width: 100,\n filterable: false,\n hideable: false,\n disableColumnMenu: true,\n },\n {\n field: 'id',\n headerName: 'ID',\n width: 200,\n filterable: false,\n hideable: false,\n disableColumnMenu: true,\n },\n ]\n\n const getRows = (entities: EntityItem[]) => {\n const newRows: SubmissionDirectoryRow[] = []\n entities.forEach(entity => {\n newRows.push({\n id: entity.id!,\n name:\n entityType === EntityType.dockerrepo\n ? (entity.repositoryName ?? entity.name)\n : entity.name,\n modifiedOn: formatDate(dayjs(entity.modifiedOn), 'MM/DD/YY'),\n })\n })\n return newRows\n }\n\n const handlePageChange = (newPageNum: number) => {\n const lastIndexNeeded = Math.min(\n headerResults?.totalChildCount ?? 0,\n (newPageNum + 1) * PER_PAGE,\n )\n if (lastIndexNeeded > fetchedHeaders.length) {\n setFetchNextPage(true)\n }\n setPage(newPageNum)\n }\n\n const itemSelectedHandler = () => {\n onItemSelected(selectedItem!)\n }\n\n function createEntity(file: FileUploadAttempt) {\n // Create Entity\n if (!file) return\n const { fileHandleId, fileName } = file\n const newFileEntity: FileEntity = {\n parentId: challengeProjectId,\n name: fileName,\n concreteType: FILE_ENTITY_CONCRETE_TYPE_VALUE,\n dataFileHandleId: fileHandleId,\n }\n SynapseClient.createEntity(newFileEntity, accessToken)\n .then(() => {\n reset()\n })\n .catch((err: SynapseClientError) => {\n setErrorMessage(err.reason)\n })\n }\n\n async function updateEntity() {\n if (!uploadAttempt) return\n const { fileHandleId, entityId } = uploadAttempt\n if (!entityId)\n return setErrorMessage('Error: missing entityId. Please try again.')\n // Get the entity\n let entity: FileEntity\n try {\n entity = await SynapseClient.getEntity(accessToken, entityId)\n } catch (err) {\n return setErrorMessage(`The entity ${entityId} could not be retrieved.`)\n }\n\n const updateRequest: FileEntity = {\n id: entity.id,\n name: entity.name,\n dataFileHandleId: fileHandleId,\n concreteType: FILE_ENTITY_CONCRETE_TYPE_VALUE,\n parentId: entity.parentId,\n etag: entity.etag,\n modifiedOn: entity.modifiedOn,\n }\n\n try {\n const updatedEntity = await SynapseClient.updateEntity(\n updateRequest,\n accessToken,\n true,\n )\n await invalidateAllQueriesForEntity(\n queryClient,\n keyFactory,\n updatedEntity.id!,\n )\n queryClient.setQueryData(\n keyFactory.getEntityQueryKey(updatedEntity.id!),\n updatedEntity,\n )\n reset()\n } catch (err) {\n setErrorMessage(err.reason)\n }\n }\n\n const handleUpload = async (response: UploadCallbackResp) => {\n if (response.success && response.resp) {\n const { fileName } = response.resp\n\n // Lookup entity\n const entityLookupRequest: EntityLookupRequest = {\n entityName: fileName,\n parentId: challengeProjectId,\n }\n try {\n const entityId = await SynapseClient.lookupChildEntity(\n entityLookupRequest,\n accessToken,\n )\n // Entity exists, prompt user to update it\n if (entityId) {\n setUploadAttempt({ ...response.resp, entityId: entityId.id })\n setConfirmOpen(true)\n }\n } catch (err) {\n // An existing entity was not found for this file, create it\n setUploadAttempt(undefined)\n createEntity(response.resp)\n }\n } else if (!response.success && response.error) {\n setErrorMessage(response.error.reason as string)\n } else {\n setErrorMessage('An unknown error occurred. Please try again.')\n }\n }\n\n return (\n <Box>\n <Box\n sx={{\n display: 'flex',\n backgroundColor: '#FBFBFC',\n padding: '10px',\n justifyContent: 'space-between',\n }}\n >\n <Typography\n variant=\"h6\"\n sx={{ fontSize: '18px', lineHeight: '20px', fontWeight: 700 }}\n >\n {entityType === EntityType.file\n ? 'Submit File'\n : 'Submit Docker Image'}\n </Typography>\n <Box sx={{ display: 'flex' }}>\n <Typography\n variant=\"body1\"\n sx={{ fontSize: '14px', color: '#71767F' }}\n >\n Your Project SynID:{' '}\n <Link to={{ pathname: PROJECT_URL }} target=\"_blank\">\n {challengeProjectId}\n </Link>\n </Typography>\n <CopyToClipboardIcon value={PROJECT_URL} sx={{ marginTop: '-4px' }} />\n </Box>\n </Box>\n <Box\n sx={{\n my: 3,\n display: 'flex',\n }}\n >\n <NotesTwoTone\n sx={{\n width: '16px',\n height: '16px',\n verticalAlign: 'text-bottom',\n }}\n />\n <Box\n sx={{\n ml: 1,\n }}\n >\n {entityType === EntityType.file && (\n <>\n Choose a prediction file from your submission directory below and\n click \"Submit Selection\" to pick an evaluation queue. If you need\n to add a new file, click \"Upload File\" first.\n </>\n )}\n {entityType === EntityType.dockerrepo && (\n <>\n Choose a Docker image from your submission directory below and\n click \"Submit Selection\" to pick an image version and evaluation\n queue. If you need to upload a new image to your project (\n <code>{challengeProjectId}</code>), see our{' '}\n <Link\n to=\"https://github.com/Sage-Bionetworks-Challenges/sample-model-templates#build-your-model\"\n target=\"_blank\"\n >\n Docker model submission guide\n </Link>{' '}\n for command-line instructions on how to build and upload.\n </>\n )}\n </Box>\n </Box>\n <Box>\n <DataGrid\n loading={areEntitiesLoading}\n columns={columns}\n rows={getRows(entities)}\n rowCount={headerResults?.totalChildCount ?? 0}\n pagination\n paginationMode=\"server\"\n paginationModel={{ page, pageSize: PER_PAGE }}\n pageSizeOptions={[PER_PAGE]}\n onPaginationModelChange={({ page }) => {\n handlePageChange(page)\n }}\n density=\"compact\"\n autoHeight\n sx={{\n fontSize: '14px',\n border: 'none',\n height: '100%',\n '& .MuiDataGrid-columnHeader': {\n backgroundColor: '#F1F3F5',\n },\n '& .Mui-odd': {\n backgroundColor: '#FBFBFC',\n },\n '.MuiDataGrid-columnHeaderTitleContainer': {\n justifyContent: 'space-between',\n },\n '.radio': {\n display: 'flex',\n alignItems: 'center',\n height: '100%',\n },\n }}\n getRowClassName={params =>\n params.indexRelativeToCurrentPage % 2 === 0 ? 'Mui-even' : 'Mui-odd'\n }\n />\n </Box>\n {errorMessage && <ErrorBanner error={errorMessage}></ErrorBanner>}\n <Box\n sx={{\n display: 'flex',\n justifyContent: 'space-between',\n }}\n >\n {entityType === EntityType.file && (\n <FileUpload\n label=\"Upload File\"\n buttonProps={{\n variant: 'outlined',\n endIcon: <IconSvg icon=\"upload\" />,\n sx: { lineHeight: 1 },\n }}\n onComplete={resp => {\n handleUpload(resp)\n }}\n />\n )}\n <Button\n color=\"primary\"\n variant=\"contained\"\n onClick={itemSelectedHandler}\n disabled={!canSubmit}\n >\n Submit Selection\n </Button>\n </Box>\n <ConfirmationDialog\n open={confirmOpen}\n title=\"File exists\"\n content={\n <Typography variant=\"body1\" sx={{ fontSize: '16px' }}>\n A file named "{uploadAttempt?.fileName}" (\n {uploadAttempt?.entityId}) already exists in this location. Do you\n want to update the existing file and create a new version?\n </Typography>\n }\n onCancel={() => setConfirmOpen(false)}\n onConfirm={() => {\n updateEntity()\n setConfirmOpen(false)\n }}\n />\n </Box>\n )\n}\n\nexport default SubmissionDirectoryList\n"],"mappings":"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"}
|
|
@@ -2,9 +2,15 @@ import { TypographyProps } from '@mui/material';
|
|
|
2
2
|
export type CopyToClipboardStringProps = {
|
|
3
3
|
value: string;
|
|
4
4
|
typographyVariant?: TypographyProps['variant'];
|
|
5
|
+
/**
|
|
6
|
+
* When provided, the value is rendered as an external link to this URL in
|
|
7
|
+
* addition to the copy icon. Defaults to rendering the value as plain text.
|
|
8
|
+
*/
|
|
9
|
+
href?: string;
|
|
10
|
+
useRoundedIcon?: boolean;
|
|
5
11
|
};
|
|
6
12
|
/**
|
|
7
|
-
* Displays a string and a "Copy to Clipboard" icon, that, when clicked, will copy the contents of the string to the clipboard.
|
|
13
|
+
* Displays a string or link and a "Copy to Clipboard" icon, that, when clicked, will copy the contents of the string to the clipboard.
|
|
8
14
|
*/
|
|
9
15
|
export declare function CopyToClipboardString(props: CopyToClipboardStringProps): import("react/jsx-runtime").JSX.Element;
|
|
10
16
|
export default CopyToClipboardString;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyToClipboardString.d.ts","sourceRoot":"","sources":["../../../src/components/CopyToClipboardString/CopyToClipboardString.tsx"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"CopyToClipboardString.d.ts","sourceRoot":"","sources":["../../../src/components/CopyToClipboardString/CopyToClipboardString.tsx"],"names":[],"mappings":"AAAA,OAAO,EAKL,eAAe,EAChB,MAAM,eAAe,CAAA;AAKtB,MAAM,MAAM,0BAA0B,GAAG;IACvC,KAAK,EAAE,MAAM,CAAA;IACb,iBAAiB,CAAC,EAAE,eAAe,CAAC,SAAS,CAAC,CAAA;IAC9C;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,OAAO,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,0BAA0B,2CAuDtE;AAED,eAAe,qBAAqB,CAAA"}
|
|
@@ -1,35 +1,41 @@
|
|
|
1
1
|
import { copyStringToClipboard as e } from "../../utils/functions/StringUtils.js";
|
|
2
2
|
import t from "../IconSvg/IconSvg.js";
|
|
3
3
|
import { useState as n } from "react";
|
|
4
|
-
import {
|
|
5
|
-
import { jsx as
|
|
4
|
+
import { Link as r, Stack as i, Tooltip as a, Typography as o } from "@mui/material";
|
|
5
|
+
import { jsx as s, jsxs as c } from "react/jsx-runtime";
|
|
6
6
|
//#region src/components/CopyToClipboardString/CopyToClipboardString.tsx
|
|
7
|
-
function
|
|
8
|
-
let { value:
|
|
9
|
-
return /* @__PURE__ */
|
|
7
|
+
function l(l) {
|
|
8
|
+
let { value: u, typographyVariant: d = "smallText1", href: f, useRoundedIcon: p = !1 } = l, [m, h] = n("Copy to clipboard");
|
|
9
|
+
return /* @__PURE__ */ c(i, {
|
|
10
10
|
direction: "row",
|
|
11
11
|
spacing: .5,
|
|
12
12
|
sx: {
|
|
13
13
|
alignItems: "center",
|
|
14
14
|
display: "inline-flex"
|
|
15
15
|
},
|
|
16
|
-
children: [/* @__PURE__ */
|
|
16
|
+
children: [f ? /* @__PURE__ */ s(r, {
|
|
17
|
+
href: f,
|
|
18
|
+
rel: "noopener noreferrer",
|
|
19
|
+
target: "_blank",
|
|
20
|
+
variant: d,
|
|
21
|
+
children: u
|
|
22
|
+
}) : /* @__PURE__ */ s(o, {
|
|
17
23
|
component: "span",
|
|
18
|
-
variant:
|
|
19
|
-
children:
|
|
20
|
-
}), /* @__PURE__ */
|
|
21
|
-
title:
|
|
24
|
+
variant: d,
|
|
25
|
+
children: u
|
|
26
|
+
}), /* @__PURE__ */ s(a, {
|
|
27
|
+
title: m,
|
|
22
28
|
placement: "top",
|
|
23
|
-
children: /* @__PURE__ */
|
|
29
|
+
children: /* @__PURE__ */ s("span", {
|
|
24
30
|
role: "button",
|
|
25
31
|
style: { cursor: "pointer" },
|
|
26
32
|
onClick: (t) => {
|
|
27
|
-
t.preventDefault(), e(
|
|
28
|
-
|
|
33
|
+
t.preventDefault(), e(u).then(() => {
|
|
34
|
+
h("Copied to clipboard");
|
|
29
35
|
});
|
|
30
36
|
},
|
|
31
|
-
children: /* @__PURE__ */
|
|
32
|
-
icon: "contentCopy",
|
|
37
|
+
children: /* @__PURE__ */ s(t, {
|
|
38
|
+
icon: p ? "contentCopyRounded" : "contentCopy",
|
|
33
39
|
wrap: !1,
|
|
34
40
|
sx: { width: "16px" }
|
|
35
41
|
})
|
|
@@ -38,6 +44,6 @@ function c(c) {
|
|
|
38
44
|
});
|
|
39
45
|
}
|
|
40
46
|
//#endregion
|
|
41
|
-
export {
|
|
47
|
+
export { l as CopyToClipboardString, l as default };
|
|
42
48
|
|
|
43
49
|
//# sourceMappingURL=CopyToClipboardString.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CopyToClipboardString.js","names":[],"sources":["../../../src/components/CopyToClipboardString/CopyToClipboardString.tsx"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"CopyToClipboardString.js","names":[],"sources":["../../../src/components/CopyToClipboardString/CopyToClipboardString.tsx"],"sourcesContent":["import {\n Link,\n Stack,\n Tooltip,\n Typography,\n TypographyProps,\n} from '@mui/material'\nimport { SyntheticEvent, useState } from 'react'\nimport IconSvg from '../IconSvg/IconSvg'\nimport { copyStringToClipboard } from '@/utils/functions/StringUtils'\n\nexport type CopyToClipboardStringProps = {\n value: string\n typographyVariant?: TypographyProps['variant']\n /**\n * When provided, the value is rendered as an external link to this URL in\n * addition to the copy icon. Defaults to rendering the value as plain text.\n */\n href?: string\n useRoundedIcon?: boolean\n}\n\n/**\n * Displays a string or link and a \"Copy to Clipboard\" icon, that, when clicked, will copy the contents of the string to the clipboard.\n */\nexport function CopyToClipboardString(props: CopyToClipboardStringProps) {\n const {\n value,\n typographyVariant = 'smallText1',\n href,\n useRoundedIcon = false,\n } = props\n const [tooltipText, setTooltipText] = useState('Copy to clipboard')\n\n const copyToClipboard = (event: SyntheticEvent) => {\n event.preventDefault()\n\n copyStringToClipboard(value).then(() => {\n setTooltipText('Copied to clipboard')\n })\n }\n\n return (\n <Stack\n direction={'row'}\n spacing={0.5}\n sx={{\n alignItems: 'center',\n display: 'inline-flex',\n }}\n >\n {href ? (\n <Link\n href={href}\n rel={'noopener noreferrer'}\n target={'_blank'}\n variant={typographyVariant}\n >\n {value}\n </Link>\n ) : (\n <Typography component={'span'} variant={typographyVariant}>\n {value}\n </Typography>\n )}\n <Tooltip title={tooltipText} placement={'top'}>\n <span\n role=\"button\"\n style={{ cursor: 'pointer' }}\n onClick={copyToClipboard}\n >\n <IconSvg\n icon={useRoundedIcon ? 'contentCopyRounded' : 'contentCopy'}\n wrap={false}\n sx={{ width: '16px' }}\n />\n </span>\n </Tooltip>\n </Stack>\n )\n}\n\nexport default CopyToClipboardString\n"],"mappings":";;;;;;AAyBA,SAAgB,EAAsB,GAAmC;CACvE,IAAM,EACJ,UACA,uBAAoB,cACpB,SACA,oBAAiB,OACf,GACE,CAAC,GAAa,KAAkB,EAAS,mBAAmB;CAUlE,OACE,kBAAC,GAAD;EACE,WAAW;EACX,SAAS;EACT,IAAI;GACF,YAAY;GACZ,SAAS;EACX;YANF,CAQG,IACC,kBAAC,GAAD;GACQ;GACN,KAAK;GACL,QAAQ;GACR,SAAS;aAER;EACG,CAAA,IAEN,kBAAC,GAAD;GAAY,WAAW;GAAQ,SAAS;aACrC;EACS,CAAA,GAEd,kBAAC,GAAD;GAAS,OAAO;GAAa,WAAW;aACtC,kBAAC,QAAD;IACE,MAAK;IACL,OAAO,EAAE,QAAQ,UAAU;IAC3B,UAnCiB,MAA0B;KAGjD,AAFA,EAAM,eAAe,GAErB,EAAsB,CAAK,EAAE,WAAW;MACtC,EAAe,qBAAqB;KACtC,CAAC;IACH;cA+BQ,kBAAC,GAAD;KACE,MAAM,IAAiB,uBAAuB;KAC9C,MAAM;KACN,IAAI,EAAE,OAAO,OAAO;IACrB,CAAA;GACG,CAAA;EACC,CAAA,CACJ;;AAEX"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityCitation.d.ts","sourceRoot":"","sources":["../../../src/components/EntityCitation/EntityCitation.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"EntityCitation.d.ts","sourceRoot":"","sources":["../../../src/components/EntityCitation/EntityCitation.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAazB,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB,CAAA;AAED,QAAA,MAAM,cAAc,GAAI,yCAIrB,mBAAmB,KAAG,KAAK,CAAC,SA2J9B,CAAA;AAED,eAAe,cAAc,CAAA"}
|
|
@@ -10,26 +10,26 @@ import { Button as c, Skeleton as l } from "@mui/material";
|
|
|
10
10
|
import { Fragment as u, jsx as d, jsxs as f } from "react/jsx-runtime";
|
|
11
11
|
import { DoiObjectType as p } from "@sage-bionetworks/synapse-client";
|
|
12
12
|
//#region src/components/EntityCitation/EntityCitation.tsx
|
|
13
|
-
var m = ({ projectId:
|
|
14
|
-
let { data:
|
|
15
|
-
id:
|
|
13
|
+
var m = "10px 12px", h = ({ projectId: h, entityId: g, versionNumber: _ }) => {
|
|
14
|
+
let { data: v, isLoading: y } = e(g, _), [b, x] = s(null), [S, C] = s(void 0), [w, T] = s(null), [E, D] = s(void 0), O = o(null), k = b ? "cite-as-popover" : void 0, A = a(g, _, h, v), { data: j, isLoading: M } = t({
|
|
15
|
+
id: h,
|
|
16
16
|
version: void 0,
|
|
17
17
|
type: p.ENTITY
|
|
18
|
-
}, { enabled: !!
|
|
19
|
-
function
|
|
20
|
-
return
|
|
18
|
+
}, { enabled: !!h }), N = j && A;
|
|
19
|
+
function P() {
|
|
20
|
+
return N ? "Cite as..." : A && !j ? "Cite page" : !A && j ? "Cite project" : "";
|
|
21
21
|
}
|
|
22
|
-
let
|
|
23
|
-
return !
|
|
22
|
+
let F = P();
|
|
23
|
+
return !j && !A ? null : y || M ? /* @__PURE__ */ d(l, { sx: {
|
|
24
24
|
width: {
|
|
25
25
|
xs: "100%",
|
|
26
26
|
sm: "140px"
|
|
27
27
|
},
|
|
28
28
|
height: "45px"
|
|
29
|
-
} }) : /* @__PURE__ */ f(u, { children: [
|
|
30
|
-
disabled:
|
|
29
|
+
} }) : /* @__PURE__ */ f(u, { children: [N ? /* @__PURE__ */ d(i, {
|
|
30
|
+
disabled: y && M,
|
|
31
31
|
options: ["Cite this Project", "Cite only this page"],
|
|
32
|
-
anchorRef:
|
|
32
|
+
anchorRef: O,
|
|
33
33
|
sx: {
|
|
34
34
|
width: {
|
|
35
35
|
xs: "100%",
|
|
@@ -37,7 +37,8 @@ var m = ({ projectId: m, entityId: h, versionNumber: g }) => {
|
|
|
37
37
|
},
|
|
38
38
|
"& .MuiButtonGroup-firstButton": {
|
|
39
39
|
borderTopLeftRadius: "6px",
|
|
40
|
-
borderBottomLeftRadius: "6px"
|
|
40
|
+
borderBottomLeftRadius: "6px",
|
|
41
|
+
padding: m
|
|
41
42
|
},
|
|
42
43
|
"& .MuiButtonGroup-lastButton": {
|
|
43
44
|
borderTopRightRadius: "6px",
|
|
@@ -48,42 +49,43 @@ var m = ({ projectId: m, entityId: h, versionNumber: g }) => {
|
|
|
48
49
|
buttonText: "Cite as...",
|
|
49
50
|
variant: "outlined",
|
|
50
51
|
buttonGroupAriaLabel: "Citation options",
|
|
51
|
-
selectedIndex:
|
|
52
|
+
selectedIndex: E,
|
|
52
53
|
setSelectedIndex: (e) => {
|
|
53
|
-
|
|
54
|
-
let t = e === 0 ?
|
|
55
|
-
t && (
|
|
54
|
+
D(e);
|
|
55
|
+
let t = e === 0 ? j?.doiUri : A?.doiUri;
|
|
56
|
+
t && (C(t), T(O.current));
|
|
56
57
|
}
|
|
57
58
|
}) : /* @__PURE__ */ d(c, {
|
|
58
59
|
onClick: (e) => {
|
|
59
|
-
|
|
60
|
+
A && !j ? (C(A.doiUri), T(e.currentTarget)) : !A && j ? (C(j.doiUri), T(e.currentTarget)) : x(e.currentTarget);
|
|
60
61
|
},
|
|
61
|
-
disabled:
|
|
62
|
+
disabled: y && M,
|
|
62
63
|
sx: {
|
|
63
64
|
width: {
|
|
64
65
|
xs: "100%",
|
|
65
66
|
sm: "initial"
|
|
66
67
|
},
|
|
67
68
|
borderRadius: "6px",
|
|
68
|
-
fontWeight: 540
|
|
69
|
+
fontWeight: 540,
|
|
70
|
+
padding: m
|
|
69
71
|
},
|
|
70
72
|
variant: "outlined",
|
|
71
73
|
startIcon: /* @__PURE__ */ d(n, {
|
|
72
74
|
width: 18,
|
|
73
75
|
height: 18
|
|
74
76
|
}),
|
|
75
|
-
children:
|
|
77
|
+
children: F
|
|
76
78
|
}), /* @__PURE__ */ d(r, {
|
|
77
|
-
doi:
|
|
78
|
-
anchorEl:
|
|
79
|
-
open: !!
|
|
80
|
-
id:
|
|
79
|
+
doi: S,
|
|
80
|
+
anchorEl: w,
|
|
81
|
+
open: !!w && !!S,
|
|
82
|
+
id: k,
|
|
81
83
|
onClose: () => {
|
|
82
|
-
|
|
84
|
+
T(null), C(void 0);
|
|
83
85
|
}
|
|
84
86
|
})] });
|
|
85
87
|
};
|
|
86
88
|
//#endregion
|
|
87
|
-
export {
|
|
89
|
+
export { h as default };
|
|
88
90
|
|
|
89
91
|
//# sourceMappingURL=EntityCitation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityCitation.js","names":[],"sources":["../../../src/components/EntityCitation/EntityCitation.tsx"],"sourcesContent":["import React from 'react'\nimport { ReactComponent as DoubleQuotes } from '@/assets/icons/DoubleQuotes.svg'\nimport { Button, Skeleton } from '@mui/material'\nimport { useGetDOIAssociation } from '@/synapse-queries/doi/useDOI'\nimport { useGetEntityBundle } from '@/synapse-queries/entity'\nimport { DoiObjectType } from '@sage-bionetworks/synapse-client'\nimport CitationPopoverContent from '../CitationPopover/CitationPopoverContent'\nimport { useRef, useState } from 'react'\nimport { useGetEntityDoiAssociation } from './EntityCitationUtils'\nimport DropdownSelect from '../DropdownSelect'\n\nexport type EntityCitationProps = {\n projectId: string\n entityId: string\n versionNumber?: number\n}\n\nconst EntityCitation = ({\n projectId,\n entityId,\n versionNumber,\n}: EntityCitationProps): React.ReactNode => {\n const { data: bundle, isLoading: isEntityLoading } = useGetEntityBundle(\n entityId,\n versionNumber,\n )\n const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null)\n const [selectedDoi, setSelectedDoi] = useState<string | undefined>(undefined)\n const [citationAnchorEl, setCitationAnchorEl] = useState<HTMLElement | null>(\n null,\n )\n const [selectedIndex, setSelectedIndex] = useState<number | undefined>(\n undefined,\n )\n const citationButtonRef = useRef<HTMLDivElement>(null)\n\n const open = Boolean(anchorEl)\n const id = open ? 'cite-as-popover' : undefined\n\n const entityDoiAssociation = useGetEntityDoiAssociation(\n entityId,\n versionNumber,\n projectId,\n bundle,\n )\n\n // Get DOI association for the project\n // Projects are not versioned\n const { data: projectDoiAssociation, isLoading: isProjectLoading } =\n useGetDOIAssociation(\n {\n id: projectId,\n version: undefined,\n type: DoiObjectType.ENTITY,\n },\n {\n enabled: !!projectId,\n },\n )\n\n // If both project and entity have DOIs, we show the dropdown variant\n const isProjectAndEntityDoi = projectDoiAssociation && entityDoiAssociation\n\n function getButtonText() {\n if (isProjectAndEntityDoi) {\n return 'Cite as...'\n } else if (entityDoiAssociation && !projectDoiAssociation) {\n return 'Cite page'\n } else if (!entityDoiAssociation && projectDoiAssociation) {\n return 'Cite project'\n }\n return ''\n }\n\n const buttonText = getButtonText()\n\n const handleButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n if (entityDoiAssociation && !projectDoiAssociation) {\n setSelectedDoi(entityDoiAssociation.doiUri)\n setCitationAnchorEl(event.currentTarget)\n } else if (!entityDoiAssociation && projectDoiAssociation) {\n setSelectedDoi(projectDoiAssociation.doiUri)\n setCitationAnchorEl(event.currentTarget)\n } else {\n // If both project and entity doi exist, show dropdown\n setAnchorEl(event.currentTarget)\n }\n }\n\n const popover = (\n <CitationPopoverContent\n doi={selectedDoi}\n anchorEl={citationAnchorEl}\n open={!!citationAnchorEl && !!selectedDoi}\n id={id}\n onClose={() => {\n setCitationAnchorEl(null)\n setSelectedDoi(undefined)\n }}\n />\n )\n\n const options: string[] = ['Cite this Project', 'Cite only this page']\n\n if (!projectDoiAssociation && !entityDoiAssociation) {\n return null\n }\n\n if (isEntityLoading || isProjectLoading) {\n return (\n <Skeleton\n sx={{\n width: { xs: '100%', sm: '140px' },\n height: '45px',\n }}\n />\n )\n }\n\n return (\n <>\n {isProjectAndEntityDoi ? (\n <DropdownSelect\n disabled={isEntityLoading && isProjectLoading}\n options={options}\n anchorRef={citationButtonRef}\n sx={{\n width: { xs: '100%', sm: 'initial' },\n '& .MuiButtonGroup-firstButton': {\n borderTopLeftRadius: '6px',\n borderBottomLeftRadius: '6px',\n },\n '& .MuiButtonGroup-lastButton': {\n borderTopRightRadius: '6px',\n borderBottomRightRadius: '6px',\n },\n '& .MuiButton-root': {\n fontWeight: 540,\n },\n }}\n buttonText=\"Cite as...\"\n variant=\"outlined\"\n buttonGroupAriaLabel=\"Citation options\"\n selectedIndex={selectedIndex}\n setSelectedIndex={(index: number) => {\n setSelectedIndex(index)\n const selected =\n index === 0\n ? projectDoiAssociation?.doiUri\n : entityDoiAssociation?.doiUri\n if (selected) {\n setSelectedDoi(selected)\n setCitationAnchorEl(citationButtonRef.current)\n }\n }}\n />\n ) : (\n <Button\n onClick={handleButtonClick}\n disabled={isEntityLoading && isProjectLoading}\n sx={{\n width: { xs: '100%', sm: 'initial' },\n borderRadius: '6px',\n fontWeight: 540,\n }}\n variant=\"outlined\"\n startIcon={<DoubleQuotes width={18} height={18} />}\n >\n {buttonText}\n </Button>\n )}\n {popover}\n </>\n )\n}\n\nexport default EntityCitation\n"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"EntityCitation.js","names":[],"sources":["../../../src/components/EntityCitation/EntityCitation.tsx"],"sourcesContent":["import React from 'react'\nimport { ReactComponent as DoubleQuotes } from '@/assets/icons/DoubleQuotes.svg'\nimport { Button, Skeleton } from '@mui/material'\nimport { useGetDOIAssociation } from '@/synapse-queries/doi/useDOI'\nimport { useGetEntityBundle } from '@/synapse-queries/entity'\nimport { DoiObjectType } from '@sage-bionetworks/synapse-client'\nimport CitationPopoverContent from '../CitationPopover/CitationPopoverContent'\nimport { useRef, useState } from 'react'\nimport { useGetEntityDoiAssociation } from './EntityCitationUtils'\nimport DropdownSelect from '../DropdownSelect'\n\nconst CITATION_BUTTON_PADDING = '10px 12px'\n\nexport type EntityCitationProps = {\n projectId: string\n entityId: string\n versionNumber?: number\n}\n\nconst EntityCitation = ({\n projectId,\n entityId,\n versionNumber,\n}: EntityCitationProps): React.ReactNode => {\n const { data: bundle, isLoading: isEntityLoading } = useGetEntityBundle(\n entityId,\n versionNumber,\n )\n const [anchorEl, setAnchorEl] = useState<HTMLButtonElement | null>(null)\n const [selectedDoi, setSelectedDoi] = useState<string | undefined>(undefined)\n const [citationAnchorEl, setCitationAnchorEl] = useState<HTMLElement | null>(\n null,\n )\n const [selectedIndex, setSelectedIndex] = useState<number | undefined>(\n undefined,\n )\n const citationButtonRef = useRef<HTMLDivElement>(null)\n\n const open = Boolean(anchorEl)\n const id = open ? 'cite-as-popover' : undefined\n\n const entityDoiAssociation = useGetEntityDoiAssociation(\n entityId,\n versionNumber,\n projectId,\n bundle,\n )\n\n // Get DOI association for the project\n // Projects are not versioned\n const { data: projectDoiAssociation, isLoading: isProjectLoading } =\n useGetDOIAssociation(\n {\n id: projectId,\n version: undefined,\n type: DoiObjectType.ENTITY,\n },\n {\n enabled: !!projectId,\n },\n )\n\n // If both project and entity have DOIs, we show the dropdown variant\n const isProjectAndEntityDoi = projectDoiAssociation && entityDoiAssociation\n\n function getButtonText() {\n if (isProjectAndEntityDoi) {\n return 'Cite as...'\n } else if (entityDoiAssociation && !projectDoiAssociation) {\n return 'Cite page'\n } else if (!entityDoiAssociation && projectDoiAssociation) {\n return 'Cite project'\n }\n return ''\n }\n\n const buttonText = getButtonText()\n\n const handleButtonClick = (event: React.MouseEvent<HTMLButtonElement>) => {\n if (entityDoiAssociation && !projectDoiAssociation) {\n setSelectedDoi(entityDoiAssociation.doiUri)\n setCitationAnchorEl(event.currentTarget)\n } else if (!entityDoiAssociation && projectDoiAssociation) {\n setSelectedDoi(projectDoiAssociation.doiUri)\n setCitationAnchorEl(event.currentTarget)\n } else {\n // If both project and entity doi exist, show dropdown\n setAnchorEl(event.currentTarget)\n }\n }\n\n const popover = (\n <CitationPopoverContent\n doi={selectedDoi}\n anchorEl={citationAnchorEl}\n open={!!citationAnchorEl && !!selectedDoi}\n id={id}\n onClose={() => {\n setCitationAnchorEl(null)\n setSelectedDoi(undefined)\n }}\n />\n )\n\n const options: string[] = ['Cite this Project', 'Cite only this page']\n\n if (!projectDoiAssociation && !entityDoiAssociation) {\n return null\n }\n\n if (isEntityLoading || isProjectLoading) {\n return (\n <Skeleton\n sx={{\n width: { xs: '100%', sm: '140px' },\n height: '45px',\n }}\n />\n )\n }\n\n return (\n <>\n {isProjectAndEntityDoi ? (\n <DropdownSelect\n disabled={isEntityLoading && isProjectLoading}\n options={options}\n anchorRef={citationButtonRef}\n sx={{\n width: { xs: '100%', sm: 'initial' },\n '& .MuiButtonGroup-firstButton': {\n borderTopLeftRadius: '6px',\n borderBottomLeftRadius: '6px',\n padding: CITATION_BUTTON_PADDING,\n },\n '& .MuiButtonGroup-lastButton': {\n borderTopRightRadius: '6px',\n borderBottomRightRadius: '6px',\n },\n '& .MuiButton-root': {\n fontWeight: 540,\n },\n }}\n buttonText=\"Cite as...\"\n variant=\"outlined\"\n buttonGroupAriaLabel=\"Citation options\"\n selectedIndex={selectedIndex}\n setSelectedIndex={(index: number) => {\n setSelectedIndex(index)\n const selected =\n index === 0\n ? projectDoiAssociation?.doiUri\n : entityDoiAssociation?.doiUri\n if (selected) {\n setSelectedDoi(selected)\n setCitationAnchorEl(citationButtonRef.current)\n }\n }}\n />\n ) : (\n <Button\n onClick={handleButtonClick}\n disabled={isEntityLoading && isProjectLoading}\n sx={{\n width: { xs: '100%', sm: 'initial' },\n borderRadius: '6px',\n fontWeight: 540,\n padding: CITATION_BUTTON_PADDING,\n }}\n variant=\"outlined\"\n startIcon={<DoubleQuotes width={18} height={18} />}\n >\n {buttonText}\n </Button>\n )}\n {popover}\n </>\n )\n}\n\nexport default EntityCitation\n"],"mappings":";;;;;;;;;;;;AAWA,IAAM,IAA0B,aAQ1B,KAAkB,EACtB,cACA,aACA,uBAC0C;CAC1C,IAAM,EAAE,MAAM,GAAQ,WAAW,MAAoB,EACnD,GACA,CACF,GACM,CAAC,GAAU,KAAe,EAAmC,IAAI,GACjE,CAAC,GAAa,KAAkB,EAA6B,KAAA,CAAS,GACtE,CAAC,GAAkB,KAAuB,EAC9C,IACF,GACM,CAAC,GAAe,KAAoB,EACxC,KAAA,CACF,GACM,IAAoB,EAAuB,IAAI,GAG/C,IADe,IACH,oBAAoB,KAAA,GAEhC,IAAuB,EAC3B,GACA,GACA,GACA,CACF,GAIM,EAAE,MAAM,GAAuB,WAAW,MAC9C,EACE;EACE,IAAI;EACJ,SAAS,KAAA;EACT,MAAM,EAAc;CACtB,GACA,EACE,SAAS,CAAC,CAAC,EACb,CACF,GAGI,IAAwB,KAAyB;CAEvD,SAAS,IAAgB;EAQvB,OAPI,IACK,eACE,KAAwB,CAAC,IAC3B,cACE,CAAC,KAAwB,IAC3B,iBAEF;CACT;CAEA,IAAM,IAAa,EAAc;CA6CjC,OAfI,CAAC,KAAyB,CAAC,IACtB,OAGL,KAAmB,IAEnB,kBAAC,GAAD,EACE,IAAI;EACF,OAAO;GAAE,IAAI;GAAQ,IAAI;EAAQ;EACjC,QAAQ;CACV,EACD,CAAA,IAKH,kBAAA,GAAA,EAAA,UAAA,CACG,IACC,kBAAC,GAAD;EACE,UAAU,KAAmB;EACpB,UAtBU,qBAAqB,qBAsB/B;EACT,WAAW;EACX,IAAI;GACF,OAAO;IAAE,IAAI;IAAQ,IAAI;GAAU;GACnC,iCAAiC;IAC/B,qBAAqB;IACrB,wBAAwB;IACxB,SAAS;GACX;GACA,gCAAgC;IAC9B,sBAAsB;IACtB,yBAAyB;GAC3B;GACA,qBAAqB,EACnB,YAAY,IACd;EACF;EACA,YAAW;EACX,SAAQ;EACR,sBAAqB;EACN;EACf,mBAAmB,MAAkB;GACnC,EAAiB,CAAK;GACtB,IAAM,IACJ,MAAU,IACN,GAAuB,SACvB,GAAsB;GAC5B,AAAI,MACF,EAAe,CAAQ,GACvB,EAAoB,EAAkB,OAAO;EAEjD;CACD,CAAA,IAED,kBAAC,GAAD;EACE,UAnFmB,MAA+C;GACxE,AAAI,KAAwB,CAAC,KAC3B,EAAe,EAAqB,MAAM,GAC1C,EAAoB,EAAM,aAAa,KAC9B,CAAC,KAAwB,KAClC,EAAe,EAAsB,MAAM,GAC3C,EAAoB,EAAM,aAAa,KAGvC,EAAY,EAAM,aAAa;EAEnC;EAyEQ,UAAU,KAAmB;EAC7B,IAAI;GACF,OAAO;IAAE,IAAI;IAAQ,IAAI;GAAU;GACnC,cAAc;GACd,YAAY;GACZ,SAAS;EACX;EACA,SAAQ;EACR,WAAW,kBAAC,GAAD;GAAc,OAAO;GAAI,QAAQ;EAAK,CAAA;YAEhD;CACK,CAAA,GAET,gBAnFH,EAAC,GAAD;EACE,KAAK;EACL,UAAU;EACV,MAAM,CAAC,CAAC,KAAoB,CAAC,CAAC;EAC1B;EACJ,eAAe;GAEb,AADA,EAAoB,IAAI,GACxB,EAAe,KAAA,CAAS;EAC1B;CACD,CA0EE,CACD,EAAA,CAAA;AAEN"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EntityTree.js","names":[],"sources":["../../../../src/components/EntityFinder/tree/EntityTree.tsx"],"sourcesContent":["import {\n useGetEntityHeader,\n useGetEntityPath,\n useGetFavorites,\n useGetProjectsInfinite,\n} from '@/synapse-queries'\nimport useGetEntityBundle from '@/synapse-queries/entity/useEntityBundle'\nimport { convertToEntityType } from '@/utils/functions/EntityTypeUtils'\nimport { SYNAPSE_ENTITY_ID_REGEX } from '@/utils/functions/RegularExpressions'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport {\n ALL_ENTITY_BUNDLE_FIELDS,\n EntityPath,\n Reference,\n} from '@sage-bionetworks/synapse-types'\nimport {\n Dispatch,\n SetStateAction,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport DropdownSelect from '../../DropdownSelect/DropdownSelect'\nimport { SynapseSpinner } from '../../LoadingScreen/LoadingScreen'\nimport { displayToast } from '../../ToastMessage/ToastMessage'\nimport { BreadcrumbItem } from '../Breadcrumbs'\nimport { toEntityHeader } from '../details/configurations/ProjectListDetails'\nimport {\n EntityDetailsListDataConfiguration,\n EntityDetailsListDataConfigurationType,\n} from '../details/EntityDetailsList'\nimport { EntityFinderHeader } from '../EntityFinderHeader'\nimport { EntitySelectionMapType } from '../useEntitySelection'\nimport {\n EntityTreeNodeType,\n RootNodeConfiguration,\n VirtualizedTree,\n} from './VirtualizedTree'\n\nconst isEntityIdInPath = (entityId: string, path: EntityPath): boolean => {\n for (const eh of path.path) {\n if (entityId === eh.id) {\n return true\n }\n }\n return false\n}\n\nexport enum FinderScope {\n CURRENT_PROJECT = 'Current Project',\n ALL_PROJECTS = 'All Projects',\n CREATED_BY_ME = 'Projects Created By Me',\n FAVORITES = 'My Favorites',\n}\n\nfunction getScopeOptionDefaultContainer(\n scope: FinderScope,\n): EntityTreeContainer {\n switch (scope) {\n case FinderScope.FAVORITES:\n return 'root'\n case FinderScope.CURRENT_PROJECT:\n case FinderScope.ALL_PROJECTS:\n case FinderScope.CREATED_BY_ME:\n return null\n }\n}\n\nfunction getScopeOptionNodeName(scope: FinderScope): string {\n switch (scope) {\n case FinderScope.CURRENT_PROJECT:\n case FinderScope.ALL_PROJECTS:\n case FinderScope.CREATED_BY_ME:\n return 'Projects'\n case FinderScope.FAVORITES:\n return 'Favorites'\n }\n}\n\n/**\n * Indicates which container is selected when the tree is used to control another component. If 'root', then the top-level entities\n * should be shown in the other component. If a synID, then that entity's children should be shown in the other component. If null,\n * then a selection has not been made.\n */\nexport type EntityTreeContainer = string | 'root' | null\n\n// if the first item is selected (matching the dropdown), then output a configuration. otherwise, output a synId\nexport type EntityTreeProps = {\n initialScope?: FinderScope\n /** To show the current project, projectId must be defined */\n projectId?: string\n initialContainer: EntityTreeContainer\n currentContainer: EntityTreeContainer\n setCurrentContainer: Dispatch<SetStateAction<EntityTreeContainer>>\n showDropdown: boolean\n selectedEntities: EntitySelectionMapType\n visibleTypes?: EntityType[]\n toggleSelection?: (entity: Reference) => void\n setDetailsViewConfiguration?: (\n configuration: EntityDetailsListDataConfiguration,\n ) => void\n setBreadcrumbItems?: (items: BreadcrumbItem[]) => void\n /** Determines whether to show the root node corresponding to the selected scope */\n showScopeAsRootNode?: boolean\n treeNodeType: EntityTreeNodeType\n /** The entity types that may be selected. */\n selectableTypes: EntityType[]\n hideScopeSelector?: boolean\n}\n\n/**\n * The TreeView displays a user's entities hierarchically, allowing a user to quickly dive into an entity tree.\n *\n * The tree view currently can only be used to drive a DetailsView using the `setDetailsViewConfiguration` property.\n */\nexport function EntityTree(props: EntityTreeProps) {\n const {\n initialScope = FinderScope.CURRENT_PROJECT,\n projectId,\n initialContainer = null,\n currentContainer,\n setCurrentContainer,\n visibleTypes = [EntityType.project, EntityType.folder],\n toggleSelection,\n selectedEntities,\n setDetailsViewConfiguration,\n setBreadcrumbItems,\n showScopeAsRootNode = true,\n treeNodeType,\n selectableTypes,\n hideScopeSelector = false,\n } = props\n\n const DEFAULT_CONFIGURATION: EntityDetailsListDataConfiguration = {\n type: EntityDetailsListDataConfigurationType.PROMPT,\n }\n\n const [scope, setScope] = useState(initialScope)\n\n useEffect(() => {\n if (setDetailsViewConfiguration) {\n setDetailsViewConfiguration(DEFAULT_CONFIGURATION)\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const setSelectedId = useCallback(\n (entityId: string) => {\n if (toggleSelection) {\n toggleSelection({ targetId: entityId })\n }\n setCurrentContainer(entityId)\n },\n [setCurrentContainer, toggleSelection],\n )\n\n // For these scopes, use the `useGetProjectsInfinite` hook\n const useProjectData =\n scope === FinderScope.ALL_PROJECTS || scope === FinderScope.CREATED_BY_ME\n\n const {\n data: projectData,\n fetchNextPage: fetchNextPageProjects,\n hasNextPage: hasNextPageProjects,\n isLoading: isLoadingProjects,\n } = useGetProjectsInfinite(\n scope === FinderScope.CREATED_BY_ME\n ? { filter: 'CREATED', sort: 'PROJECT_NAME', sortDirection: 'ASC' }\n : { sort: 'PROJECT_NAME', sortDirection: 'ASC' },\n {\n enabled: useProjectData,\n // Don't refetch the projects. Updating the entity headers will drop all the children that VirtualizedTree has fetched\n refetchInterval: Infinity,\n },\n )\n\n const { data: currentContainerBundle, isSuccess: isSuccessBundle } =\n useGetEntityBundle(currentContainer!, undefined, ALL_ENTITY_BUNDLE_FIELDS, {\n enabled: !!currentContainer && currentContainer !== 'root',\n throwOnError: true,\n })\n\n const { data: favorites, isLoading: isLoadingFavorites } = useGetFavorites(\n 'NAME',\n 'ASC',\n {\n select: data =>\n data.filter(eh => visibleTypes.includes(convertToEntityType(eh.type))),\n // Don't refetch the projects. Updating the entity headers will drop all the children that VirtualizedTree has fetched\n refetchInterval: Infinity,\n throwOnError: true,\n },\n )\n\n const { data: initialContainerPath } = useGetEntityPath(initialContainer!, {\n enabled: !!(\n projectId &&\n initialContainer &&\n initialContainer.match(SYNAPSE_ENTITY_ID_REGEX)\n ),\n refetchInterval: Infinity,\n throwOnError: true,\n })\n\n const { data: projectHeader, isLoading: isLoadingProjectHeader } =\n useGetEntityHeader(projectId, undefined, {\n enabled: !!(projectId ?? initialContainerPath?.path[1]?.id),\n refetchInterval: Infinity,\n })\n\n useEffect(() => {\n if (\n FinderScope.CURRENT_PROJECT === scope &&\n projectId &&\n !isLoadingProjectHeader &&\n !projectHeader\n ) {\n // The header wasn't returned, so the user doesn't have access to the current project\n // Let's change the scope to something else\n displayToast(\n `You don't have access to the current project (${projectId}).`,\n 'warning',\n )\n setScope(FinderScope.CREATED_BY_ME)\n }\n }, [isLoadingProjectHeader, projectHeader, projectId, scope])\n\n // Populates the first level of entities in the tree view\n const {\n topLevelEntities,\n isLoading,\n }: {\n topLevelEntities: Pick<EntityFinderHeader, 'name' | 'id' | 'type'>[]\n isLoading: boolean\n } = useMemo(() => {\n let topLevelEntities: Pick<EntityFinderHeader, 'name' | 'id' | 'type'>[] =\n []\n let isLoading: boolean = false\n switch (scope) {\n case FinderScope.ALL_PROJECTS:\n case FinderScope.CREATED_BY_ME:\n if (projectData) {\n topLevelEntities = projectData.pages\n .flatMap(page => page.results)\n .map(toEntityHeader)\n }\n isLoading = isLoadingProjects\n break\n case FinderScope.FAVORITES: {\n topLevelEntities = favorites ?? []\n isLoading = isLoadingFavorites\n break\n }\n case FinderScope.CURRENT_PROJECT:\n if (projectHeader) {\n // use projectHeader as topLevelEntities\n topLevelEntities = [projectHeader]\n isLoading = isLoadingProjectHeader\n }\n break\n default:\n throw new Error('No scope selected')\n }\n return { topLevelEntities, isLoading }\n }, [\n favorites,\n isLoadingFavorites,\n isLoadingProjectHeader,\n isLoadingProjects,\n projectData,\n projectHeader,\n scope,\n ])\n\n // Creates the configuration for the details view and invokes the callback\n useEffect(() => {\n if (setDetailsViewConfiguration || setBreadcrumbItems) {\n let detailsViewConfig: EntityDetailsListDataConfiguration\n let breadcrumbItems: BreadcrumbItem[] = []\n if (currentContainer === null) {\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.PROMPT,\n }\n breadcrumbItems = []\n } else if (currentContainer === 'root') {\n switch (scope) {\n case FinderScope.ALL_PROJECTS:\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.USER_PROJECTS,\n }\n break\n case FinderScope.CREATED_BY_ME:\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.USER_PROJECTS,\n getProjectParams: {\n filter: 'CREATED',\n },\n }\n break\n case FinderScope.CURRENT_PROJECT:\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.HEADER_LIST,\n headerList: topLevelEntities,\n }\n break\n case FinderScope.FAVORITES:\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.USER_FAVORITES,\n }\n break\n }\n breadcrumbItems = [\n {\n name: scope,\n isCurrent: true,\n action: () => {\n setCurrentContainer('root')\n },\n },\n ]\n } else {\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.PARENT_CONTAINER,\n parentContainerId: currentContainer,\n }\n if (currentContainerBundle) {\n breadcrumbItems = [\n {\n name: scope,\n isCurrent: false,\n action: () => {\n setCurrentContainer('root')\n },\n },\n ...currentContainerBundle.path.path\n .slice(1) // Remove the root entity, syn4489\n .map(entity => {\n return {\n name: entity.name ?? entity.id,\n isCurrent: entity.id === currentContainer,\n action: () => {\n setCurrentContainer(entity.id)\n },\n }\n }),\n ]\n }\n }\n if (setDetailsViewConfiguration) {\n setDetailsViewConfiguration(detailsViewConfig)\n }\n if (setBreadcrumbItems) {\n setBreadcrumbItems(breadcrumbItems)\n }\n }\n }, [\n scope,\n currentContainer,\n topLevelEntities,\n setDetailsViewConfiguration,\n setBreadcrumbItems,\n currentContainerBundle,\n isSuccessBundle,\n setCurrentContainer,\n ])\n\n const rootNodeConfiguration: RootNodeConfiguration = useMemo(\n () => ({\n show: showScopeAsRootNode,\n nodeText: getScopeOptionNodeName(scope),\n children: topLevelEntities,\n fetchNextPage: async () => {\n await fetchNextPageProjects()\n },\n hasNextPage: useProjectData && hasNextPageProjects && !isLoadingProjects,\n }),\n [\n showScopeAsRootNode,\n scope,\n topLevelEntities,\n useProjectData,\n hasNextPageProjects,\n isLoadingProjects,\n fetchNextPageProjects,\n ],\n )\n\n const shouldAutoExpand = useCallback(\n (entityId: string) => {\n if (entityId === 'root') {\n return true\n } else {\n return !!(\n scope === FinderScope.CURRENT_PROJECT &&\n initialContainerPath &&\n isEntityIdInPath(entityId, initialContainerPath)\n )\n }\n },\n [scope, initialContainerPath],\n )\n\n const filteredOptions = Object.values(FinderScope).filter(\n scopeOption =>\n !(scopeOption === FinderScope.CURRENT_PROJECT && projectId == null),\n )\n\n const selectedIndex = filteredOptions.indexOf(scope)\n\n return (\n <div\n className={`TreeView ${\n treeNodeType === EntityTreeNodeType.SINGLE_PANE\n ? 'SelectTree'\n : 'BrowseTree'\n }`}\n >\n {!hideScopeSelector && (\n <div className=\"Header\" onClick={e => e.stopPropagation()}>\n <DropdownSelect\n variant={'outlined'}\n options={filteredOptions}\n selectedIndex={selectedIndex}\n setSelectedIndex={index => {\n const selectedScope = filteredOptions[index]\n if (scope !== selectedScope) {\n setScope(selectedScope)\n setCurrentContainer(\n getScopeOptionDefaultContainer(selectedScope),\n )\n }\n }}\n size=\"small\"\n fullWidth\n />\n </div>\n )}\n {isLoading ? (\n <div className=\"Placeholder\">\n <SynapseSpinner size={30} />\n </div>\n ) : (\n <div className=\"Tree\" role=\"tree\">\n <VirtualizedTree\n selected={selectedEntities}\n visibleTypes={visibleTypes}\n autoExpand={shouldAutoExpand}\n rootNodeConfiguration={rootNodeConfiguration}\n treeNodeType={treeNodeType}\n selectableTypes={selectableTypes}\n currentContainer={currentContainer}\n setSelectedId={setSelectedId}\n />\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EntityTree.js","names":[],"sources":["../../../../src/components/EntityFinder/tree/EntityTree.tsx"],"sourcesContent":["import {\n useGetEntityHeader,\n useGetEntityPath,\n useGetFavorites,\n useGetProjectsInfinite,\n} from '@/synapse-queries'\nimport useGetEntityBundle from '@/synapse-queries/entity/useEntityBundle'\nimport { convertToEntityType } from '@/utils/functions/EntityTypeUtils'\nimport { SYNAPSE_ENTITY_ID_REGEX } from '@/utils/functions/RegularExpressions'\nimport { EntityType } from '@sage-bionetworks/synapse-client'\nimport {\n ALL_ENTITY_BUNDLE_FIELDS,\n EntityPath,\n Reference,\n} from '@sage-bionetworks/synapse-types'\nimport {\n Dispatch,\n SetStateAction,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from 'react'\nimport DropdownSelect from '../../DropdownSelect/DropdownSelect'\nimport { SynapseSpinner } from '../../LoadingScreen/LoadingScreen'\nimport { displayToast } from '../../ToastMessage/ToastMessage'\nimport { BreadcrumbItem } from '../Breadcrumbs'\nimport { toEntityHeader } from '../details/configurations/ProjectListDetails'\nimport {\n EntityDetailsListDataConfiguration,\n EntityDetailsListDataConfigurationType,\n} from '../details/EntityDetailsList'\nimport { EntityFinderHeader } from '../EntityFinderHeader'\nimport { EntitySelectionMapType } from '../useEntitySelection'\nimport {\n EntityTreeNodeType,\n RootNodeConfiguration,\n VirtualizedTree,\n} from './VirtualizedTree'\n\nconst isEntityIdInPath = (entityId: string, path: EntityPath): boolean => {\n for (const eh of path.path) {\n if (entityId === eh.id) {\n return true\n }\n }\n return false\n}\n\nexport enum FinderScope {\n CURRENT_PROJECT = 'Current Project',\n ALL_PROJECTS = 'All Projects',\n CREATED_BY_ME = 'Projects Created By Me',\n FAVORITES = 'My Favorites',\n}\n\nfunction getScopeOptionDefaultContainer(\n scope: FinderScope,\n): EntityTreeContainer {\n switch (scope) {\n case FinderScope.FAVORITES:\n return 'root'\n case FinderScope.CURRENT_PROJECT:\n case FinderScope.ALL_PROJECTS:\n case FinderScope.CREATED_BY_ME:\n return null\n }\n}\n\nfunction getScopeOptionNodeName(scope: FinderScope): string {\n switch (scope) {\n case FinderScope.CURRENT_PROJECT:\n case FinderScope.ALL_PROJECTS:\n case FinderScope.CREATED_BY_ME:\n return 'Projects'\n case FinderScope.FAVORITES:\n return 'Favorites'\n }\n}\n\n/**\n * Indicates which container is selected when the tree is used to control another component. If 'root', then the top-level entities\n * should be shown in the other component. If a synID, then that entity's children should be shown in the other component. If null,\n * then a selection has not been made.\n */\nexport type EntityTreeContainer = string | 'root' | null\n\n// if the first item is selected (matching the dropdown), then output a configuration. otherwise, output a synId\nexport type EntityTreeProps = {\n initialScope?: FinderScope\n /** To show the current project, projectId must be defined */\n projectId?: string\n initialContainer: EntityTreeContainer\n currentContainer: EntityTreeContainer\n setCurrentContainer: Dispatch<SetStateAction<EntityTreeContainer>>\n showDropdown: boolean\n selectedEntities: EntitySelectionMapType\n visibleTypes?: EntityType[]\n toggleSelection?: (entity: Reference) => void\n setDetailsViewConfiguration?: (\n configuration: EntityDetailsListDataConfiguration,\n ) => void\n setBreadcrumbItems?: (items: BreadcrumbItem[]) => void\n /** Determines whether to show the root node corresponding to the selected scope */\n showScopeAsRootNode?: boolean\n treeNodeType: EntityTreeNodeType\n /** The entity types that may be selected. */\n selectableTypes: EntityType[]\n hideScopeSelector?: boolean\n}\n\n/**\n * The TreeView displays a user's entities hierarchically, allowing a user to quickly dive into an entity tree.\n *\n * The tree view currently can only be used to drive a DetailsView using the `setDetailsViewConfiguration` property.\n */\nexport function EntityTree(props: EntityTreeProps) {\n const {\n initialScope = FinderScope.CURRENT_PROJECT,\n projectId,\n initialContainer = null,\n currentContainer,\n setCurrentContainer,\n visibleTypes = [EntityType.project, EntityType.folder],\n toggleSelection,\n selectedEntities,\n setDetailsViewConfiguration,\n setBreadcrumbItems,\n showScopeAsRootNode = true,\n treeNodeType,\n selectableTypes,\n hideScopeSelector = false,\n } = props\n\n const DEFAULT_CONFIGURATION: EntityDetailsListDataConfiguration = {\n type: EntityDetailsListDataConfigurationType.PROMPT,\n }\n\n const [scope, setScope] = useState(initialScope)\n\n useEffect(() => {\n if (setDetailsViewConfiguration) {\n setDetailsViewConfiguration(DEFAULT_CONFIGURATION)\n }\n // oxlint-disable-next-line react-hooks/exhaustive-deps\n }, [])\n\n const setSelectedId = useCallback(\n (entityId: string) => {\n if (toggleSelection) {\n toggleSelection({ targetId: entityId })\n }\n setCurrentContainer(entityId)\n },\n [setCurrentContainer, toggleSelection],\n )\n\n // For these scopes, use the `useGetProjectsInfinite` hook\n const useProjectData =\n scope === FinderScope.ALL_PROJECTS || scope === FinderScope.CREATED_BY_ME\n\n const {\n data: projectData,\n fetchNextPage: fetchNextPageProjects,\n hasNextPage: hasNextPageProjects,\n isLoading: isLoadingProjects,\n } = useGetProjectsInfinite(\n scope === FinderScope.CREATED_BY_ME\n ? { filter: 'CREATED', sort: 'PROJECT_NAME', sortDirection: 'ASC' }\n : { sort: 'PROJECT_NAME', sortDirection: 'ASC' },\n {\n enabled: useProjectData,\n // Don't refetch the projects. Updating the entity headers will drop all the children that VirtualizedTree has fetched\n refetchInterval: Infinity,\n },\n )\n\n const { data: currentContainerBundle, isSuccess: isSuccessBundle } =\n useGetEntityBundle(currentContainer!, undefined, ALL_ENTITY_BUNDLE_FIELDS, {\n enabled: !!currentContainer && currentContainer !== 'root',\n throwOnError: true,\n })\n\n const { data: favorites, isLoading: isLoadingFavorites } = useGetFavorites(\n 'NAME',\n 'ASC',\n {\n select: data =>\n data.filter(eh => visibleTypes.includes(convertToEntityType(eh.type))),\n // Don't refetch the projects. Updating the entity headers will drop all the children that VirtualizedTree has fetched\n refetchInterval: Infinity,\n throwOnError: true,\n },\n )\n\n const { data: initialContainerPath } = useGetEntityPath(initialContainer!, {\n enabled: !!(\n projectId &&\n initialContainer &&\n initialContainer.match(SYNAPSE_ENTITY_ID_REGEX)\n ),\n refetchInterval: Infinity,\n throwOnError: true,\n })\n\n const { data: projectHeader, isLoading: isLoadingProjectHeader } =\n useGetEntityHeader(projectId, undefined, {\n enabled: !!(projectId ?? initialContainerPath?.path[1]?.id),\n refetchInterval: Infinity,\n })\n\n useEffect(() => {\n if (\n FinderScope.CURRENT_PROJECT === scope &&\n projectId &&\n !isLoadingProjectHeader &&\n !projectHeader\n ) {\n // The header wasn't returned, so the user doesn't have access to the current project\n // Let's change the scope to something else\n displayToast(\n `You don't have access to the current project (${projectId}).`,\n 'warning',\n )\n setScope(FinderScope.CREATED_BY_ME)\n }\n }, [isLoadingProjectHeader, projectHeader, projectId, scope])\n\n // Populates the first level of entities in the tree view\n const {\n topLevelEntities,\n isLoading,\n }: {\n topLevelEntities: Pick<EntityFinderHeader, 'name' | 'id' | 'type'>[]\n isLoading: boolean\n } = useMemo(() => {\n let topLevelEntities: Pick<EntityFinderHeader, 'name' | 'id' | 'type'>[] =\n []\n let isLoading: boolean = false\n switch (scope) {\n case FinderScope.ALL_PROJECTS:\n case FinderScope.CREATED_BY_ME:\n if (projectData) {\n topLevelEntities = projectData.pages\n .flatMap(page => page.results)\n .map(toEntityHeader)\n }\n isLoading = isLoadingProjects\n break\n case FinderScope.FAVORITES: {\n topLevelEntities = favorites ?? []\n isLoading = isLoadingFavorites\n break\n }\n case FinderScope.CURRENT_PROJECT:\n if (projectHeader) {\n // use projectHeader as topLevelEntities\n topLevelEntities = [projectHeader]\n isLoading = isLoadingProjectHeader\n }\n break\n default:\n throw new Error('No scope selected')\n }\n return { topLevelEntities, isLoading }\n }, [\n favorites,\n isLoadingFavorites,\n isLoadingProjectHeader,\n isLoadingProjects,\n projectData,\n projectHeader,\n scope,\n ])\n\n // Creates the configuration for the details view and invokes the callback\n useEffect(() => {\n if (setDetailsViewConfiguration || setBreadcrumbItems) {\n let detailsViewConfig: EntityDetailsListDataConfiguration\n let breadcrumbItems: BreadcrumbItem[] = []\n if (currentContainer === null) {\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.PROMPT,\n }\n breadcrumbItems = []\n } else if (currentContainer === 'root') {\n switch (scope) {\n case FinderScope.ALL_PROJECTS:\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.USER_PROJECTS,\n }\n break\n case FinderScope.CREATED_BY_ME:\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.USER_PROJECTS,\n getProjectParams: {\n filter: 'CREATED',\n },\n }\n break\n case FinderScope.CURRENT_PROJECT:\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.HEADER_LIST,\n headerList: topLevelEntities,\n }\n break\n case FinderScope.FAVORITES:\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.USER_FAVORITES,\n }\n break\n }\n breadcrumbItems = [\n {\n name: scope,\n isCurrent: true,\n action: () => {\n setCurrentContainer('root')\n },\n },\n ]\n } else {\n detailsViewConfig = {\n type: EntityDetailsListDataConfigurationType.PARENT_CONTAINER,\n parentContainerId: currentContainer,\n }\n if (currentContainerBundle) {\n breadcrumbItems = [\n {\n name: scope,\n isCurrent: false,\n action: () => {\n setCurrentContainer('root')\n },\n },\n ...currentContainerBundle.path.path\n .slice(1) // Remove the root entity, syn4489\n .map(entity => {\n return {\n name: entity.name ?? entity.id,\n isCurrent: entity.id === currentContainer,\n action: () => {\n setCurrentContainer(entity.id)\n },\n }\n }),\n ]\n }\n }\n if (setDetailsViewConfiguration) {\n setDetailsViewConfiguration(detailsViewConfig)\n }\n if (setBreadcrumbItems) {\n setBreadcrumbItems(breadcrumbItems)\n }\n }\n }, [\n scope,\n currentContainer,\n topLevelEntities,\n setDetailsViewConfiguration,\n setBreadcrumbItems,\n currentContainerBundle,\n isSuccessBundle,\n setCurrentContainer,\n ])\n\n const rootNodeConfiguration: RootNodeConfiguration = useMemo(\n () => ({\n show: showScopeAsRootNode,\n nodeText: getScopeOptionNodeName(scope),\n children: topLevelEntities,\n fetchNextPage: async () => {\n await fetchNextPageProjects()\n },\n hasNextPage: useProjectData && hasNextPageProjects && !isLoadingProjects,\n }),\n [\n showScopeAsRootNode,\n scope,\n topLevelEntities,\n useProjectData,\n hasNextPageProjects,\n isLoadingProjects,\n fetchNextPageProjects,\n ],\n )\n\n const shouldAutoExpand = useCallback(\n (entityId: string) => {\n if (entityId === 'root') {\n return true\n } else {\n return !!(\n scope === FinderScope.CURRENT_PROJECT &&\n initialContainerPath &&\n isEntityIdInPath(entityId, initialContainerPath)\n )\n }\n },\n [scope, initialContainerPath],\n )\n\n const filteredOptions = Object.values(FinderScope).filter(\n scopeOption =>\n !(scopeOption === FinderScope.CURRENT_PROJECT && projectId == null),\n )\n\n const selectedIndex = filteredOptions.indexOf(scope)\n\n return (\n <div\n className={`TreeView ${\n treeNodeType === EntityTreeNodeType.SINGLE_PANE\n ? 'SelectTree'\n : 'BrowseTree'\n }`}\n >\n {!hideScopeSelector && (\n <div className=\"Header\" onClick={e => e.stopPropagation()}>\n <DropdownSelect\n variant={'outlined'}\n options={filteredOptions}\n selectedIndex={selectedIndex}\n setSelectedIndex={index => {\n const selectedScope = filteredOptions[index]\n if (scope !== selectedScope) {\n setScope(selectedScope)\n setCurrentContainer(\n getScopeOptionDefaultContainer(selectedScope),\n )\n }\n }}\n size=\"small\"\n fullWidth\n />\n </div>\n )}\n {isLoading ? (\n <div className=\"Placeholder\">\n <SynapseSpinner size={30} />\n </div>\n ) : (\n <div className=\"Tree\" role=\"tree\">\n <VirtualizedTree\n selected={selectedEntities}\n visibleTypes={visibleTypes}\n autoExpand={shouldAutoExpand}\n rootNodeConfiguration={rootNodeConfiguration}\n treeNodeType={treeNodeType}\n selectableTypes={selectableTypes}\n currentContainer={currentContainer}\n setSelectedId={setSelectedId}\n />\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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"}
|