diff --git a/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx b/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx index 4bae4f6381..bdf8b33034 100755 --- a/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx +++ b/packages/libs/eda/src/lib/map/analysis/MapAnalysis.tsx @@ -86,6 +86,8 @@ import { AnalysisError } from '../../core/components/AnalysisError'; import useSnackbar from '@veupathdb/coreui/lib/components/notifications/useSnackbar'; import SettingsButton from '@veupathdb/coreui/lib/components/containers/DraggablePanel/SettingsButton'; import { getGeoConfig } from '../../core/utils/geoVariables'; +import UserDatasetDetailController from '@veupathdb/user-datasets/lib/Controllers/UserDatasetDetailController'; +import { wdkRecordIdToDiyUserDatasetId } from '@veupathdb/user-datasets/lib/Utils/diyDatasets'; enum MapSideNavItemLabels { Download = 'Download', @@ -235,6 +237,7 @@ function MapAnalysisImpl(props: ImplProps) { const downloadClient = useDownloadClient(); const subsettingClient = useSubsettingClient(); const history = useHistory(); + const { url } = useRouteMatch(); const sharingUrl = new URL( sharingUrlPrefix @@ -703,10 +706,34 @@ function MapAnalysisImpl(props: ImplProps) { }} >
Study Details
- p.value).join('/')} - /> + {studyMetadata.isUserStudy ? ( + // TODO Make both cases below configurable via the root component. + // This will need to be done if we want EDA to stand on its own. + + // Note that we are not inluding the custom detail page. + // As of this writing, details pages only add a link to + // EDA. Since we are in EDA, we don't want to add it here. + + ) : ( + p.value).join('/')} + /> + )} ); }, diff --git a/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx b/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx index d25e336abf..3b5419a4ca 100755 --- a/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx +++ b/packages/libs/eda/src/lib/map/analysis/TimeSliderQuickFilter.tsx @@ -386,7 +386,7 @@ export default function TimeSliderQuickFilter({ > {!minimized && ( <> -
+
-
+
{/* add axis range control */} -
+ +
+
-); - -type HeaderProps = { - color: string; - iconType: string; - date: string; -}; -const UploadHeader = ({ color, iconType, date }: HeaderProps) => ( -
- - - {new Date(date).toLocaleString()} - -
-); - -const OngoingUpload = ( - upload: UserDatasetUpload, - onClickCancel: () => void -) => ( -
- -
- Currently uploading: {upload.datasetName} -
-
- Status:{' '} - {upload.status + - (upload.stepPercent ? ' ... ' + upload.stepPercent + '%' : '')} -
- {upload.isCancellable && ( - - )} -
-); - -const SuccessfulUpload = (upload: UserDatasetUpload, baseUrl: string) => ( -
- - Successfully uploaded:   - - {upload.datasetName} - -
-); - -const InvalidatedUpload = (upload: UserDatasetUpload) => { - return ( -
- -
- {upload.datasetName} was rejected as it is invalid - {upload.errors ? ( - - : - - {upload.errors.map((line, ix) => ( -
{line}
- ))} -
-
- ) : ( - . - )} -
-
- ); -}; - -const FailedUpload = (upload: UserDatasetUpload) => ( -
- -
- {upload.datasetName} could not be uploaded. -
-
- Please try again. If the problem persists, please let us know through our -   - - support form - - . -
-
-); - -const UploadsTable = (props: { - baseUrl: string; - uploads: Array; - cancelCurrentUpload: (id: string) => void; -}) => { - const { baseUrl, uploads, cancelCurrentUpload } = props; - return ( - - - {uploads.map((upload, ix) => ( - - - - ))} - -
- {upload.isOngoing - ? OngoingUpload(upload, () => cancelCurrentUpload(upload.id)) - : upload.isSuccessful - ? SuccessfulUpload(upload, baseUrl) - : upload.isUserError - ? InvalidatedUpload(upload) - : FailedUpload(upload)} -
- ); -}; -const RefreshButton = () => ( - -); -const ErrorMessage = (message: string) => ( -
- {message.split('\n').map((line, ix) => ( -
- {ix === 0 && } - {line} -
- ))} -
-); -const AllUploads = (props: Props) => { - const uploads = useMemo(() => props.uploadList ?? [], [props.uploadList]); - const ongoingUploads = useMemo( - () => uploads.filter((u) => u.isOngoing), - [uploads] - ); - const finishedUploads = useMemo( - () => uploads.filter((u) => !u.isOngoing), - [uploads] - ); - - const projectInfo = useWdkService(async (wdkService) => { - const config = await wdkService.getConfig(); - - return { - id: config.projectId, - name: config.displayName, - }; - }, []); - - const [projectFilter, setProjectFilter] = useProjectFilter(); - - const hasUploadFromAnotherProject = useMemo( - () => - uploads.some((upload) => - upload.projects.some((project) => project !== projectInfo?.id) - ), - [projectInfo, uploads] - ); - - const projectFilterApplied = projectFilter !== false; - - const uploadFilterPredicate = useCallback( - (upload: UserDatasetUpload) => - projectInfo == null || - !projectFilterApplied || - upload.projects.includes(projectInfo.id), - [projectInfo, projectFilterApplied] - ); - - const filteredUploads = useMemo( - () => uploads.filter(uploadFilterPredicate), - [uploads, uploadFilterPredicate] - ); - const filteredFinishedUploads = useMemo( - () => finishedUploads.filter(uploadFilterPredicate), - [finishedUploads, uploadFilterPredicate] - ); - - return ( -
- {props.errorMessage != null && ErrorMessage(props.errorMessage)} - {ongoingUploads.length > 0 && RefreshButton()} - {projectInfo != null && hasUploadFromAnotherProject && ( -
- { - setProjectFilter((projectFilter) => !projectFilter); - }} - /> - -
- )} - {filteredUploads.length > 0 && ( - - )} - {filteredFinishedUploads.length > 0 && - ClearAllMessagesButton( - () => - props.actions.clearMessages( - filteredFinishedUploads.map((u) => u.id) - ), - projectFilterApplied && hasUploadFromAnotherProject - ? 'Clear These Messages' - : 'Clear All Messages' - )} - {props.errorMessage == null && - projectInfo != null && - filteredUploads.length === 0 && ( - - )} -
- ); -}; -export default AllUploads; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/DateTime.tsx b/packages/libs/user-datasets-legacy/src/lib/Components/DateTime.tsx deleted file mode 100644 index 535a5994a7..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/DateTime.tsx +++ /dev/null @@ -1,23 +0,0 @@ -import React from 'react'; - -type Value = string | number | Date; - -interface Props { - datetime: Value; -} - -export function DateTime(props: Props) { - try { - const dateObj = new Date(props.datetime); - const isoString = dateObj.toISOString(); - const [_, date = 'Unknown', time = ''] = - dateObj.toISOString().match(/(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2}).*/) ?? []; - return ( -
- {date} {time} -
- ); - } catch { - return
Unknown
; - } -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigDatasetDetail.jsx b/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigDatasetDetail.jsx deleted file mode 100644 index 8e987b2467..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigDatasetDetail.jsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react'; - -import Icon from '@veupathdb/wdk-client/lib/Components/Icon/IconAlt'; -import { Mesa, MesaState } from '@veupathdb/coreui/lib/components/Mesa'; - -import { makeClassifier } from '../UserDatasetUtils'; -import UserDatasetDetail from './UserDatasetDetail'; -import BigwigGBrowseUploader from './BigwigGBrowseUploader'; - -const classify = makeClassifier('UserDatasetDetail', 'BigwigDatasetDetail'); - -class BigwigDatasetDetail extends UserDatasetDetail { - constructor(props) { - super(props); - this.renderTracksSection = this.renderTracksSection.bind(this); - this.getTracksTableColumns = this.getTracksTableColumns.bind(this); - } - - getTracksTableColumns() { - const { userDataset, appUrl, config } = this.props; - const { id, type, meta, dependencies } = userDataset; - const name = meta.name; - const { projectId } = config; - const { seqId } = type && type.data ? type.data : { seqId: null }; - - var genome; - dependencies.forEach(function (dependency) { - if (dependency.resourceIdentifier.endsWith('_Genome')) { - var regex = new RegExp( - '-' + dependency.resourceVersion + '_(.*)_Genome' - ); - var genomeList = dependency.resourceIdentifier.match(regex); - genome = genomeList[1]; - } - }); - - return [ - { - key: 'datafileName', - name: 'Filename', - renderCell: ({ row }) => {row.datafileName}, - }, - { - key: 'main', - name: 'Genome Browser Link', - renderCell: ({ row }) => ( - - ), - }, - ]; - } - - renderTracksSection() { - const { userDataset, appUrl, projectName } = this.props; - - const { type } = userDataset; - const { data } = type; - - const rows = data && Array.isArray(data.tracks) ? data.tracks : []; - const columns = this.getTracksTableColumns({ userDataset, appUrl }); - const tracksTableState = MesaState.create({ rows, columns }); - - return !rows.length ? null : userDataset.isInstalled ? ( -
-

- - Genome Browser Tracks -

-
- -
-
- ) : ( -
- This data set isn't installed to {projectName} or contains no files. -
- ); - } - - getPageSections() { - const [headerSection, compatSection, fileSection] = super.getPageSections(); - return [ - headerSection, - compatSection, - this.renderTracksSection, - fileSection, - ]; - } -} - -export default BigwigDatasetDetail; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigGBrowseUploader.jsx b/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigGBrowseUploader.jsx deleted file mode 100644 index f788a86645..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigGBrowseUploader.jsx +++ /dev/null @@ -1,43 +0,0 @@ -import React from 'react'; -import Icon from '@veupathdb/wdk-client/lib/Components/Icon/IconAlt'; - -import './BigwigGBrowseUploader.scss'; - -class BigwigGBrowseUploader extends React.Component { - constructor(props) { - super(props); - this.getGBrowseUrl = this.getGBrowseUrl.bind(this); - } - - getButtons() { - const GBrowseUrl = this.getGBrowseUrl(); - return ( - - - - - - ); - } - - getGBrowseUrl() { - const { sequenceId, genome, datasetName, datafileName } = this.props; - var jbrowseTrackName = datasetName + ' ' + datafileName; - return `/a/jbrowse/index.html?data=/a/service/jbrowse/tracks/${genome}&tracks=gene,${ - jbrowseTrackName || '' - }&highlight=&loc=${sequenceId || ''}`; - } - - render() { - const buttons = this.getButtons(); - return ( -
-
{buttons}
-
- ); - } -} - -export default BigwigGBrowseUploader; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigGBrowseUploader.scss b/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigGBrowseUploader.scss deleted file mode 100644 index 447b00e2e1..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BigwigGBrowseUploader.scss +++ /dev/null @@ -1,37 +0,0 @@ -@import '@veupathdb/wdk-client/lib/Core/Style/palette'; - -.BigwigGBrowseUploader { - display: flex; - width: 100%; - align-items: center; - flex-flow: row nowrap; - - .BigwigGBrowseUploader-Message { - flex: 1; - } - - .BigwigGBrowseUploader-Buttons { - flex: 0 0 auto; - margin-left: 20px; - button[disabled] { - opacity: 0.5; - cursor: not-allowed; - } - } - - .BigwigGBrowseUploader-Icon { - flex: 0 0 auto; - margin-right: 10px; - .wdk-Icon { - font-size: 20px; - transition: all 0.3s; - color: #bbb; - &.fa-check-circle-o { - color: $green; - } - &.fa-spin { - color: $blue; - } - } - } -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BiomDatasetDetail.jsx b/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BiomDatasetDetail.jsx deleted file mode 100644 index 091426f1d4..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/BiomDatasetDetail.jsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Link } from 'react-router-dom'; - -import UserDatasetDetail from './UserDatasetDetail'; - -class BiomDatasetDetail extends UserDatasetDetail { - constructor(props) { - super(props); - this.renderEdaLinkout = this.renderEdaLinkout.bind(this); - } - - renderEdaLinkout() { - const { - config: { displayName }, - userDataset: { isInstalled }, - edaWorkspaceUrl, - } = this.props; - - return !isInstalled || !edaWorkspaceUrl ? null : ( -
-

- - Explore in {displayName} - -

-
- ); - } - - getPageSections() { - const [headerSection, , fileSection] = super.getPageSections(); - - return [headerSection, this.renderEdaLinkout, fileSection]; - } -} - -export default BiomDatasetDetail; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/IsaDatasetDetail.jsx b/packages/libs/user-datasets-legacy/src/lib/Components/Detail/IsaDatasetDetail.jsx deleted file mode 100644 index 6206d36569..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/IsaDatasetDetail.jsx +++ /dev/null @@ -1,52 +0,0 @@ -import { Link } from 'react-router-dom'; - -import UserDatasetDetail from './UserDatasetDetail'; - -class IsaDatasetDetail extends UserDatasetDetail { - constructor(props) { - super(props); - this.renderEdaLinkout = this.renderEdaLinkout.bind(this); - } - - renderEdaLinkout() { - const { - config: { displayName }, - userDataset: { isInstalled }, - edaWorkspaceUrl, - edaMapUrl, - } = this.props; - - if (!isInstalled) return null; - - return ( - <> - {!edaWorkspaceUrl ? null : ( -
-

- - Explore in {displayName} - -

-
- )} - {!edaMapUrl ? null : ( -
-

- - Explore in MapVEu - -

-
- )} - - ); - } - - getPageSections() { - const [headerSection, , fileSection] = super.getPageSections(); - - return [headerSection, this.renderEdaLinkout, fileSection]; - } -} - -export default IsaDatasetDetail; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/RnaSeqDatasetDetail.jsx b/packages/libs/user-datasets-legacy/src/lib/Components/Detail/RnaSeqDatasetDetail.jsx deleted file mode 100644 index 4a73c241da..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/RnaSeqDatasetDetail.jsx +++ /dev/null @@ -1,11 +0,0 @@ -import BigwigDatasetDetail from './BigwigDatasetDetail'; - -class RnaSeqDatasetDetail extends BigwigDatasetDetail { - constructor(props) { - super(props); - this.renderTracksSection = this.renderTracksSection.bind(this); - this.getTracksTableColumns = this.getTracksTableColumns.bind(this); - } -} - -export default RnaSeqDatasetDetail; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/UserDatasetDetail.jsx b/packages/libs/user-datasets-legacy/src/lib/Components/Detail/UserDatasetDetail.jsx deleted file mode 100644 index b59cffb068..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/UserDatasetDetail.jsx +++ /dev/null @@ -1,575 +0,0 @@ -import React from 'react'; - -import Icon from '@veupathdb/wdk-client/lib/Components/Icon/IconAlt'; -import SaveableTextEditor from '@veupathdb/wdk-client/lib/Components/InputControls/SaveableTextEditor'; -import Link from '@veupathdb/wdk-client/lib/Components/Link'; -import { - AnchoredTooltip, - Mesa, - MesaState, -} from '@veupathdb/coreui/lib/components/Mesa'; -import { WdkDependenciesContext } from '@veupathdb/wdk-client/lib/Hooks/WdkDependenciesEffect'; -import { bytesToHuman } from '@veupathdb/wdk-client/lib/Utils/Converters'; - -import NotFound from '@veupathdb/wdk-client/lib/Views/NotFound/NotFound'; - -import { isUserDatasetsCompatibleWdkService } from '../../Service/UserDatasetWrappers'; - -import SharingModal from '../Sharing/UserDatasetSharingModal'; -import UserDatasetStatus from '../UserDatasetStatus'; -import { makeClassifier, normalizePercentage } from '../UserDatasetUtils'; -import { ThemedGrantAccessButton } from '../ThemedGrantAccessButton'; -import { ThemedDeleteButton } from '../ThemedDeleteButton'; - -import { DateTime } from '../DateTime'; - -import './UserDatasetDetail.scss'; - -const classify = makeClassifier('UserDatasetDetail'); - -class UserDatasetDetail extends React.Component { - constructor(props) { - super(props); - this.state = { sharingModalOpen: false }; - - this.onMetaSave = this.onMetaSave.bind(this); - this.isMyDataset = this.isMyDataset.bind(this); - this.validateKey = this.validateKey.bind(this); - this.handleDelete = this.handleDelete.bind(this); - - this.getAttributes = this.getAttributes.bind(this); - this.renderAttributeList = this.renderAttributeList.bind(this); - this.renderHeaderSection = this.renderHeaderSection.bind(this); - this.renderDatasetActions = this.renderDatasetActions.bind(this); - - this.renderCompatibilitySection = - this.renderCompatibilitySection.bind(this); - this.getCompatibilityTableColumns = - this.getCompatibilityTableColumns.bind(this); - - this.openSharingModal = this.openSharingModal.bind(this); - this.renderFileSection = this.renderFileSection.bind(this); - this.closeSharingModal = this.closeSharingModal.bind(this); - this.getFileTableColumns = this.getFileTableColumns.bind(this); - this.renderDetailsSection = this.renderDetailsSection.bind(this); - this.renderAllDatasetsLink = this.renderAllDatasetsLink.bind(this); - } - - isMyDataset() { - const { user, userDataset } = this.props; - return ( - user && userDataset && user.id && user.id === userDataset.ownerUserId - ); - } - - openSharingModal() { - this.setState({ sharingModalOpen: true }); - } - - closeSharingModal() { - this.setState({ sharingModalOpen: false }); - } - - validateKey(key) { - const META_KEYS = ['name', 'summary', 'description']; - if (typeof key !== 'string' || !META_KEYS.includes(key)) - throw new TypeError( - `Can't edit meta for invalid key: ${JSON.stringify(key)}` - ); - } - - onMetaSave(key) { - this.validateKey(key); - return (value) => { - if (typeof value !== 'string') - throw new TypeError( - `onMetaSave: expected input value to be string; got ${typeof value}` - ); - const { userDataset, updateUserDatasetDetail } = this.props; - const meta = { ...userDataset.meta, [key]: value }; - return updateUserDatasetDetail(userDataset, meta); - }; - } - - handleDelete() { - const { baseUrl, isOwner, userDataset, removeUserDataset, dataNoun } = - this.props; - const { sharedWith } = userDataset; - const shareCount = !Array.isArray(sharedWith) ? null : sharedWith.length; - const message = - `Are you sure you want to ${ - isOwner ? 'delete' : 'remove' - } this ${dataNoun.singular.toLowerCase()}? ` + - (!isOwner || !shareCount - ? '' - : `${shareCount} collaborator${ - shareCount === 1 ? '' : 's' - } you've shared with will lose access.`); - - if (window.confirm(message)) { - removeUserDataset(userDataset, baseUrl); - } - } - - renderAllDatasetsLink() { - return ( - - -   All {this.props.workspaceTitle} - - ); - } - - getAttributes() { - const { userDataset, quotaSize, questionMap } = this.props; - const { onMetaSave } = this; - const { - id, - type, - meta, - size, - percentQuotaUsed, - owner, - created, - sharedWith, - questions, - isInstalled, - } = userDataset; - const { display, name, version } = type; - const isOwner = this.isMyDataset(); - - return [ - { - className: classify('Name'), - attribute: this.props.detailsPageTitle, - value: ( - - ), - }, - { - attribute: 'Status', - value: ( - - ), - }, - { - attribute: 'Owner', - value: isOwner ? 'Me' : owner, - }, - { - attribute: 'Description', - value: ( - - ), - }, - { attribute: 'ID', value: id }, - { - attribute: 'Data type', - value: ( - - {display} ({name} {version}) - - ), - }, - { - attribute: 'Summary', - value: ( - - ), - }, - { - attribute: 'Created', - value: , - }, - { attribute: 'Data set size', value: bytesToHuman(size) }, - !isOwner - ? null - : { - attribute: 'Quota usage', - value: `${normalizePercentage(percentQuotaUsed)}% of ${bytesToHuman( - quotaSize - )}`, - }, - !isOwner || !sharedWith || !sharedWith.length - ? null - : { - attribute: 'Shared with', - value: ( -
    - {sharedWith.map((share) => ( -
  • - {share.userDisplayName} <{share.email}>{' '} - -
  • - ))} -
- ), - }, - !questions || !questions.length || !isInstalled - ? null - : { - attribute: 'Available searches', - value: ( -
    - {questions.map((questionName) => { - const q = questionMap[questionName]; - // User dataset searches typically offer changing the dataset through a dropdown - // Ths dropdown is a param, "biom_dataset" on MicrobiomeDB and "rna_seq_dataset" on genomic sites - // Hence the regex: /dataset/ - const ps = q.paramNames.filter((paramName) => - paramName.match(/dataset/) - ); - const urlPath = [ - '', - 'search', - q.outputRecordClassName, - q.urlSegment, - ].join('/'); - const url = - urlPath + - (ps.length === 1 ? '?param.' + ps[0] + '=' + id : ''); - return ( -
  • - {q.displayName} -
  • - ); - })} -
- ), - }, - ].filter((attr) => attr); - } - - renderHeaderSection() { - const AllLink = this.renderAllDatasetsLink; - const AttributeList = this.renderAttributeList; - const DatasetActions = this.renderDatasetActions; - - return ( -
- -
-
- -
-
- -
-
-
- ); - } - - renderAttributeList() { - const attributes = this.getAttributes(); - return ( -
- {attributes.map(({ attribute, value, className }, index) => ( -
-
- {typeof attribute === 'string' ? ( - {attribute}: - ) : ( - attribute - )} -
-
{value}
-
- ))} -
- ); - } - - renderDatasetActions() { - const isOwner = this.isMyDataset(); - return ( -
- {!isOwner ? null : ( - - )} - -
- ); - } - - renderDetailsSection() { - const { userDataset } = this.props; - return ( -
-
-
-            {JSON.stringify(userDataset, null, '  ')}
-          
-
-
- ); - } - - /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - - Files Table - - -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */ - - renderFileSection() { - const { userDataset, appUrl, dataNoun } = this.props; - const fileTableState = MesaState.create({ - columns: this.getFileTableColumns({ userDataset, appUrl }), - rows: userDataset.datafiles, - }); - - return ( -
-

Data Files

-

- - Files in {dataNoun.singular} -

- -
- ); - } - - getFileTableColumns() { - const { userDataset } = this.props; - const { id } = userDataset; - const { wdkService } = this.context; - - return [ - { - key: 'name', - name: 'File Name', - renderCell({ row }) { - const { name } = row; - return {name}; - }, - }, - { - key: 'size', - name: 'File Size', - renderCell({ row }) { - const { size } = row; - return bytesToHuman(size); - }, - }, - { - key: 'download', - name: 'Download', - width: '130px', - headingStyle: { textAlign: 'center' }, - renderCell({ row }) { - const { name } = row; - - const downloadUrl = !isUserDatasetsCompatibleWdkService(wdkService) - ? undefined - : wdkService.getUserDatasetDownloadUrl(id, name); - - const downloadAvailable = downloadUrl != null; - - return ( - - - - ); - }, - }, - ].filter((column) => column); - } - - /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - - Compatible Table - - -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */ - - renderCompatibilitySection() { - const { userDataset, config, dataNoun } = this.props; - const { projectId, displayName } = config; - - const compatibilityTableState = MesaState.create({ - columns: this.getCompatibilityTableColumns(userDataset), - rows: userDataset.dependencies, - }); - - const { buildNumber } = config; - const { isCompatible } = userDataset; - const isCompatibleProject = userDataset.projects.includes(projectId); - - return ( -
-

- Use This {dataNoun.singular} in {displayName} -

-

- - Compatibility Information   - -
- -
-
-

-
- -
- {isCompatibleProject && isCompatible ? ( -

- This {dataNoun.singular.toLowerCase()} is compatible with the - current release, build {buildNumber}, of {projectId}. It is - installed for use. -

- ) : ( -

- This {dataNoun.singular.toLowerCase()} is not compatible with the - current release, build {buildNumber}, of {projectId}. It is - not installed for use. -

- )} -
- ); - } - - getCompatibilityTableColumns() { - const { userDataset } = this.props; - const { projects } = userDataset; - return [ - { - key: 'project', - name: 'VEuPathDB Website', - renderCell() { - return projects.join(', '); - }, - }, - { - key: 'resourceDisplayName', - name: 'Required Resource', - renderCell({ row }) { - const { resourceDisplayName } = row; - return resourceDisplayName; - }, - }, - { - key: 'resourceVersion', - name: 'Required Resource Release', - }, - { - key: 'installedVersion', - name: 'Installed Resource Release', - renderCell({ row }) { - const { compatibilityInfo } = row; - const { currentBuild } = compatibilityInfo ? compatibilityInfo : {}; - return compatibilityInfo === null || currentBuild === null - ? 'N/A' - : currentBuild; - }, - }, - ]; - } - - /* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - - General Rendering - - -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= */ - - // This is needed to resolve downstream typescript errors. - // TypeScript infers that this method returns JSX.Element[]. - // Some classes extending this will return (JSX.Element | null)[]. - // The ReactNode type is better suited, here, since it allows for null values. - /** @return {import("react").ReactNode[]} */ - getPageSections() { - return [ - this.renderHeaderSection, - this.renderCompatibilitySection, - this.renderFileSection, - ]; - } - - render() { - const { - user, - userDataset, - shareUserDatasets, - unshareUserDatasets, - dataNoun, - } = this.props; - const AllDatasetsLink = this.renderAllDatasetsLink; - if (!userDataset) - return ( - - - - ); - const isOwner = this.isMyDataset(); - const { sharingModalOpen } = this.state; - - return ( -
- {this.getPageSections().map((Section, key) => ( -
- ))} - {!isOwner || !sharingModalOpen ? null : ( - - )} -
- ); - } -} - -UserDatasetDetail.contextType = WdkDependenciesContext; - -export default UserDatasetDetail; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/UserDatasetDetail.scss b/packages/libs/user-datasets-legacy/src/lib/Components/Detail/UserDatasetDetail.scss deleted file mode 100644 index e144ccefb0..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Detail/UserDatasetDetail.scss +++ /dev/null @@ -1,106 +0,0 @@ -@import '@veupathdb/wdk-client/lib/Core/Style/palette'; - -.UserDatasetDetail { - details { - margin: 30px 0; - } - hr { - height: 0; - width: 100%; - border: none; - margin: 20px 0 0; - border-bottom: 1px solid rgba(0, 0, 0, 0.15); - } - .MesaComponent { - .DataTable { - table { - width: auto; - } - } - } - - .AllDatasetsLink { - display: block; - padding: 20px 0; - } - - .UserDatasetDetail-SectionTitle { - margin-bottom: 10px; - .wdk-Icon { - color: $blue; - margin-right: 10px; - } - } - - .UserDatasetDetail-Actions { - flex: 0 0 auto; - display: flex; - flex-flow: row nowrap; - justify-content: flex-end; - gap: 1em; - } - - .UserDatasetDetail-Header { - display: flex; - flex-flow: row nowrap; - align-items: flex-start; - - .UserDatasetDetail-Header-Attributes { - flex: 1 1 auto; - } - .UserDatasetDetail-Header-Actions { - flex: 0 0 auto; - } - } - - .wdk-SaveableTextEditor { - fieldset { - padding: 0 3px; - } - fieldset.wdk-SaveableTextEditor-Field { - flex: 0 1 auto; - transition: all 0.5s; - &.wdk-SaveableTextEditor-Field--Editing { - flex: 1 1 auto; - } - } - fieldset.wdk-SaveableTextEditor-Buttons { - min-height: 0; - } - } - - .UserDatasetDetail-AttributeList { - display: flex; - flex-direction: column; - .UserDatasetDetail-AttributeRow { - flex: 0 0 auto; - display: flex; - flex-direction: row; - align-items: baseline; - margin: 0.2em 0; - font-size: 1.4em; - font-weight: 300; - - &.UserDatasetDetail-Name { - font-weight: 400; - font-size: 2.8em; - strong, - b { - font-weight: 400; - } - - .wdk-SaveableTextEditor { - font-style: italic; - } - } - - .UserDatasetDetail-AttributeName { - flex: 0 0 auto; - padding-right: 10px; - } - .UserDatasetDetail-AttributeValue { - flex: 1 1 auto; - } - } - } -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/EmptyState.jsx b/packages/libs/user-datasets-legacy/src/lib/Components/EmptyState.jsx deleted file mode 100644 index 8e2aae8e49..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/EmptyState.jsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import { IconAlt as Icon } from '@veupathdb/wdk-client/lib/Components'; - -class UserDatasetEmptyState extends React.Component { - render() { - const { message } = this.props; - return ( -
- - {typeof message === 'string' ?

{message}

: message} -
- ); - } -} - -export default UserDatasetEmptyState; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/List/UserDatasetList.scss b/packages/libs/user-datasets-legacy/src/lib/Components/List/UserDatasetList.scss deleted file mode 100644 index 4602fec090..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/List/UserDatasetList.scss +++ /dev/null @@ -1,19 +0,0 @@ -.UserDatasetList { - margin-bottom: 30px; -} -.UserDatasetList-EmptyState { - width: 100%; - display: flex; - align-items: center; - flex-direction: column; - justify-content: center; - p { - font-size: 1.3em; - font-weight: 300; - } - .wdk-Icon.EmptyState-Icon { - opacity: 0.1; - font-size: 60px; - margin: 5px auto 20px; - } -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/List/UserDatasetList.tsx b/packages/libs/user-datasets-legacy/src/lib/Components/List/UserDatasetList.tsx deleted file mode 100644 index 68de183e59..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/List/UserDatasetList.tsx +++ /dev/null @@ -1,650 +0,0 @@ -import React from 'react'; - -import { add } from 'lodash'; - -import { - Checkbox, - IconAlt as Icon, - Link, - RealTimeSearchBox as SearchBox, - SaveableTextEditor, -} from '@veupathdb/wdk-client/lib/Components'; -import { - Mesa, - MesaState, - Utils as MesaUtils, -} from '@veupathdb/coreui/lib/components/Mesa'; -import { - MesaColumn, - MesaSortObject, -} from '@veupathdb/coreui/lib/components/Mesa/types'; -import { bytesToHuman } from '@veupathdb/wdk-client/lib/Utils/Converters'; - -import { User } from '@veupathdb/wdk-client/lib/Utils/WdkUser'; - -import { - DataNoun, - UserDataset, - UserDatasetMeta, - UserDatasetShare, -} from '../../Utils/types'; - -import UserDatasetEmptyState from '../EmptyState'; -import SharingModal from '../Sharing/UserDatasetSharingModal'; -import UserDatasetStatus from '../UserDatasetStatus'; -import { normalizePercentage, textCell } from '../UserDatasetUtils'; - -import './UserDatasetList.scss'; -import { DateTime } from '../DateTime'; - -import { ThemedGrantAccessButton } from '../ThemedGrantAccessButton'; -import { ThemedDeleteButton } from '../ThemedDeleteButton'; - -interface Props { - baseUrl: string; - user: User; - location: any; - projectId: string; - projectName: string; - userDatasets: UserDataset[]; - filterByProject: boolean; - shareUserDatasets: ( - userDatasetIds: number[], - recipientUserIds: number[] - ) => any; - unshareUserDatasets: ( - userDatasetIds: number[], - recipientUserIds: number[] - ) => any; - removeUserDataset: (dataset: UserDataset) => any; - updateUserDatasetDetail: ( - userDataset: UserDataset, - meta: UserDatasetMeta - ) => any; - updateProjectFilter: (filterByProject: boolean) => any; - quotaSize: number; - dataNoun: DataNoun; -} - -interface State { - selectedRows: number[]; - uiState: { sort: MesaSortObject }; - searchTerm: string; - sharingModalOpen: boolean; - editingCache: any; -} - -interface MesaDataCellProps { - row: UserDataset; - column: MesaColumn; - rowIndex: number; - columnIndex: number; - inline?: boolean; -} - -class UserDatasetList extends React.Component { - constructor(props: Props) { - super(props); - this.state = { - selectedRows: [], - uiState: { - sort: { - columnKey: 'created', - direction: 'asc', - }, - }, - editingCache: {}, - sharingModalOpen: false, - searchTerm: '', - }; - - this.onRowSelect = this.onRowSelect.bind(this); - this.onRowDeselect = this.onRowDeselect.bind(this); - this.isRowSelected = this.isRowSelected.bind(this); - this.onMultipleRowSelect = this.onMultipleRowSelect.bind(this); - this.onMultipleRowDeselect = this.onMultipleRowDeselect.bind(this); - - this.onSort = this.onSort.bind(this); - this.getColumns = this.getColumns.bind(this); - this.isMyDataset = this.isMyDataset.bind(this); - this.getEventHandlers = this.getEventHandlers.bind(this); - this.filterAndSortRows = this.filterAndSortRows.bind(this); - this.onSearchTermChange = this.onSearchTermChange.bind(this); - this.onMetaAttributeSaveFactory = - this.onMetaAttributeSaveFactory.bind(this); - - this.renderOwnerCell = this.renderOwnerCell.bind(this); - this.renderStatusCell = this.renderStatusCell.bind(this); - - this.openSharingModal = this.openSharingModal.bind(this); - this.closeSharingModal = this.closeSharingModal.bind(this); - this.toggleProjectScope = this.toggleProjectScope.bind(this); - } - - isRowSelected(row: UserDataset): boolean { - const id: number = row.id; - const { selectedRows } = this.state; - return selectedRows.includes(id); - } - - isMyDataset(dataset: UserDataset): boolean { - const { user } = this.props; - return user.id === dataset.ownerUserId; - } - - onSearchTermChange(searchTerm: string) { - this.setState({ searchTerm }); - } - - onMetaAttributeSaveFactory(dataset: UserDataset, attrKey: string) { - const { meta } = dataset; - const { updateUserDatasetDetail } = this.props; - return (value: string) => - updateUserDatasetDetail(dataset, { ...meta, [attrKey]: value }); - } - - renderSharedWithCell(cellProps: MesaDataCellProps) { - const dataset: UserDataset = cellProps.row; - return !dataset.sharedWith || !dataset.sharedWith.length - ? null - : dataset.sharedWith.map((share) => share.userDisplayName).join(', '); - } - - renderStatusCell(cellProps: MesaDataCellProps) { - const userDataset: UserDataset = cellProps.row; - const { baseUrl, projectId, projectName } = this.props; - return ( - - ); - } - - renderOwnerCell(cellProps: MesaDataCellProps) { - const row: UserDataset = cellProps.row; - const { owner } = row; - return this.isMyDataset(row) ? ( - Me - ) : ( - {owner} - ); - } - - getColumns(): any[] { - const { baseUrl, user } = this.props; - function isOwner(ownerId: number): boolean { - return user.id === ownerId; - } - return [ - { - key: 'meta.name', - sortable: true, - name: 'Name / ID', - helpText: '', - renderCell: (cellProps: MesaDataCellProps) => { - const dataset: UserDataset = cellProps.row; - const saveName = this.onMetaAttributeSaveFactory(dataset, 'name'); - return ( - ( - - {value} -
- ({dataset.id}) -
- )} - /> - ); - }, - }, - { - key: 'summary', - name: 'Summary', - style: { maxWidth: '300px' }, - renderCell: (cellProps: MesaDataCellProps) => { - const dataset: UserDataset = cellProps.row; - const saveSummary = this.onMetaAttributeSaveFactory( - dataset, - 'summary' - ); - return ( -
- -
- ); - }, - }, - { - key: 'type', - name: 'Type', - sortable: true, - renderCell: textCell('type', (datasetType: any) => { - const display: string = datasetType.display; - const version: string = datasetType.version; - return ( - - {display} ({version}) - - ); - }), - }, - { - key: 'projects', - sortable: true, - name: 'VEuPathDB Websites', - renderCell(cellProps: MesaDataCellProps) { - const userDataset: UserDataset = cellProps.row; - const { projects } = userDataset; - return projects.join(', '); - }, - }, - { - key: 'status', - className: 'StatusColumn', - name: 'Status', - style: { textAlign: 'center' }, - renderCell: this.renderStatusCell, - }, - { - key: 'owner', - name: 'Owner', - sortable: true, - renderCell: this.renderOwnerCell, - }, - { - key: 'sharedWith', - name: 'Shared With', - sortable: true, - renderCell: this.renderSharedWithCell, - }, - { - key: 'created', - name: 'Created', - sortable: true, - renderCell: textCell('created', (created: number) => ( - - )), - }, - { - key: 'datafiles', - name: 'File Count', - renderCell: textCell('datafiles', (files: any[]) => files.length), - }, - { - key: 'size', - name: 'Size', - sortable: true, - renderCell: textCell('size', (size: number) => bytesToHuman(size)), - }, - { - key: 'percentQuotaUsed', - name: 'Quota Usage', - sortable: true, - renderCell: textCell('percentQuotaUsed', (percent: number) => - percent ? `${normalizePercentage(percent)}%` : null - ), - }, - ].filter((column) => column); - } - - onRowSelect(row: UserDataset): void { - const id: number = row.id; - const { selectedRows } = this.state; - if (selectedRows.includes(id)) return; - const newSelection: number[] = [...selectedRows, id]; - this.setState({ selectedRows: newSelection }); - } - - onRowDeselect(row: UserDataset): void { - const id: number = row.id; - const { selectedRows } = this.state; - if (!selectedRows.includes(id)) return; - const newSelection: number[] = selectedRows.filter( - (selectedId) => selectedId !== id - ); - this.setState({ selectedRows: newSelection }); - } - - onMultipleRowSelect(rows: UserDataset[]): void { - if (!rows.length) return; - const { selectedRows } = this.state; - const unselectedRows = rows - .filter((dataset: UserDataset) => !selectedRows.includes(dataset.id)) - .map((dataset: UserDataset) => dataset.id); - if (!unselectedRows.length) return; - const newSelection: number[] = [...selectedRows, ...unselectedRows]; - this.setState({ selectedRows: newSelection }); - } - - onMultipleRowDeselect(rows: UserDataset[]): void { - if (!rows.length) return; - const { selectedRows } = this.state; - const deselectedIds: number[] = rows.map((row: UserDataset) => row.id); - const newSelection = selectedRows.filter( - (id) => !deselectedIds.includes(id) - ); - this.setState({ selectedRows: newSelection }); - } - - onSort(column: MesaColumn, direction: string): void { - const key: string = column.key; - const { state } = this; - const { setSortColumnKey, setSortDirection } = MesaState; - const updatedState = setSortDirection( - setSortColumnKey(state, key), - direction - ); - this.setState(updatedState); - } - - getEventHandlers() { - return { - onSort: this.onSort, - onRowSelect: this.onRowSelect, - onRowDeselect: this.onRowDeselect, - onMultipleRowSelect: this.onMultipleRowSelect, - onMultipleRowDeselect: this.onMultipleRowDeselect, - }; - } - - getTableActions() { - const { isMyDataset } = this; - const { removeUserDataset, dataNoun } = this.props; - return [ - { - callback: (rows: UserDataset[]) => { - this.openSharingModal(); - }, - element: ( - null} - /> - ), - selectionRequired: true, - }, - { - callback: (userDatasets: UserDataset[]) => { - const [noun, pronoun] = - userDatasets.length === 1 - ? [`this ${this.props.dataNoun.singular.toLowerCase()}`, 'it'] - : [`these ${this.props.dataNoun.plural.toLowerCase()}`, 'them']; - - const affectedUsers: UserDatasetShare[] = userDatasets.reduce( - ( - affectedUserList: UserDatasetShare[], - userDataset: UserDataset - ) => { - if (!isMyDataset(userDataset)) return affectedUserList; - if (!userDataset.sharedWith || userDataset.sharedWith.length) - return affectedUserList; - const newlyAffectedUsers = userDataset.sharedWith.filter( - (sharedWithUser: UserDatasetShare) => { - return ( - affectedUserList.find( - (affectedUser) => - affectedUser.user === sharedWithUser.user - ) != null - ); - } - ); - return [...affectedUserList, ...newlyAffectedUsers]; - }, - [] - ); - - if ( - !window.confirm( - `Are you sure you want to delete ${noun}? ` + - (affectedUsers.length - ? `You have shared ${pronoun} with ${affectedUsers} users.` - : '') - ) - ) - return; - userDatasets.forEach((userDataset) => removeUserDataset(userDataset)); - }, - element: ( - null} /> - ), - selectionRequired: true, - }, - ]; - } - - getTableOptions() { - const { isRowSelected, toggleProjectScope } = this; - const { userDatasets, projectName, filterByProject, dataNoun } = this.props; - const emptyMessage = !userDatasets.length ? ( -

- This page is empty because you do not have any{' '} - {dataNoun.plural.toLowerCase()}. -

- ) : filterByProject ? ( - -

- You have no {projectName} data sets. -

-
- -
- ) : ( - -

Your search returned no results.

-
- -
- ); - return { - isRowSelected, - showToolbar: true, - renderEmptyState() { - return ( - - - - ); - }, - }; - } - - filterAndSortRows(rows: UserDataset[]): UserDataset[] { - const { searchTerm, uiState } = this.state; - const { projectName, filterByProject } = this.props; - const sort: MesaSortObject = uiState.sort; - if (filterByProject) - rows = rows.filter((dataset) => dataset.projects.includes(projectName)); - if (searchTerm && searchTerm.length) - rows = this.filterRowsBySearchTerm([...rows], searchTerm); - if (sort.columnKey.length) rows = this.sortRowsByColumnKey([...rows], sort); - return [...rows]; - } - - filterRowsBySearchTerm( - rows: UserDataset[], - searchTerm?: string - ): UserDataset[] { - if (!searchTerm || !searchTerm.length) return rows; - return rows.filter((dataset: UserDataset) => { - const searchableRow: string = JSON.stringify(dataset).toLowerCase(); - return searchableRow.indexOf(searchTerm.toLowerCase()) !== -1; - }); - } - - getColumnSortValueMapper(columnKey: string | null) { - if (columnKey === null) return (data: any) => data; - switch (columnKey) { - case 'type': - return (data: UserDataset, index: number): string => - data.type.display.toLowerCase(); - case 'meta.name': - return (data: UserDataset) => data.meta.name.toLowerCase(); - default: - return (data: any, index: number) => { - return typeof data[columnKey] !== 'undefined' - ? data[columnKey] - : null; - }; - } - } - - sortRowsByColumnKey( - rows: UserDataset[], - sort: MesaSortObject - ): UserDataset[] { - const direction: string = sort.direction; - const columnKey: string = sort.columnKey; - const mappedValue = this.getColumnSortValueMapper(columnKey); - const sorted = [...rows].sort(MesaUtils.sortFactory(mappedValue)); - return direction === 'asc' ? sorted : sorted.reverse(); - } - - closeSharingModal() { - const sharingModalOpen = false; - this.setState({ sharingModalOpen }); - } - - openSharingModal() { - const sharingModalOpen = true; - this.setState({ sharingModalOpen }); - } - - toggleProjectScope(newValue: boolean) { - this.props.updateProjectFilter(newValue); - } - - render() { - const { isRowSelected, toggleProjectScope } = this; - const { - userDatasets, - user, - projectName, - shareUserDatasets, - unshareUserDatasets, - filterByProject, - quotaSize, - dataNoun, - } = this.props; - const { uiState, selectedRows, searchTerm, sharingModalOpen } = this.state; - - const rows = userDatasets; - const selectedDatasets = rows.filter(isRowSelected); - const columns = this.getColumns(); - const actions = this.getTableActions(); - const options = this.getTableOptions(); - const eventHandlers = this.getEventHandlers(); - const filteredRows = this.filterAndSortRows(rows); - - const tableState = { - rows, - columns, - options, - actions, - filteredRows, - selectedRows, - eventHandlers, - uiState: { - ...uiState, - emptinessCulprit: userDatasets.length ? 'search' : null, - }, - }; - - const totalSize = userDatasets - .filter((ud) => ud.ownerUserId === user.id) - .map((ud) => ud.size) - .reduce(add, 0); - - const totalPercent = totalSize / quotaSize; - - const offerProjectToggle = userDatasets.some(({ projects }) => - projects.some((project) => project !== projectName) - ); - - return ( -
- -
- {userDatasets.length > 0 && ( -
- {sharingModalOpen && selectedDatasets.length ? ( - - ) : null} - -
- Showing {filteredRows.length} of {rows.length}{' '} - {rows.length === 1 - ? dataNoun.singular.toLowerCase() - : dataNoun.plural.toLowerCase()} -
- {offerProjectToggle && ( -
- {' '} -
toggleProjectScope(!filterByProject)} - style={{ display: 'inline-block' }} - > - Only show {dataNoun.plural.toLowerCase()} related to{' '} - {projectName} -
-
- )} -
- {bytesToHuman(totalSize)} ( - {normalizePercentage(totalPercent)}%) of{' '} - {bytesToHuman(quotaSize)} used -
-
- )} -
-
-
- ); - } -} - -export default UserDatasetList; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/NoDatasetsMessage.tsx b/packages/libs/user-datasets-legacy/src/lib/Components/NoDatasetsMessage.tsx deleted file mode 100644 index 58f08028ce..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/NoDatasetsMessage.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Link } from '@veupathdb/wdk-client/lib/Components'; - -interface Props { - baseUrl: string; - hasDirectUpload: boolean; - helpRoute: string; -} - -function NoDatasetsMessage({ baseUrl, hasDirectUpload, helpRoute }: Props) { - return ( -
-
- You do not have any data sets. -
-
    - {hasDirectUpload ? ( -
  • - Try adding a data set using the{' '} - New upload section above. -
  • - ) : ( -
  • - To add a data set, go to{' '} - VEuPathDB Galaxy. -
  • - )} -
  • - For an overview of the functionality, see the{' '} - Help page. -
  • -
-
- ); -} - -export default NoDatasetsMessage; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingModal.jsx b/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingModal.jsx deleted file mode 100644 index ecf3fad558..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingModal.jsx +++ /dev/null @@ -1,526 +0,0 @@ -import React from 'react'; - -import { - IconAlt as Icon, - Loading, - Modal, - TextBox, -} from '@veupathdb/wdk-client/lib/Components'; -import { WdkDependenciesContext } from '@veupathdb/wdk-client/lib/Hooks/WdkDependenciesEffect'; - -import { isUserDatasetsCompatibleWdkService } from '../../Service/UserDatasetWrappers'; -import { DateTime } from '../DateTime'; - -import './UserDatasetSharingModal.scss'; - -const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email); - -class UserDatasetSharingModal extends React.Component { - constructor(props) { - super(props); - this.state = { - recipients: [], - recipientInput: null, - processing: false, - succeeded: null, - }; - this.renderShareItem = this.renderShareItem.bind(this); - this.renderShareList = this.renderShareList.bind(this); - this.renderDatasetList = this.renderDatasetList.bind(this); - this.renderDatasetItem = this.renderDatasetItem.bind(this); - this.renderRecipientItem = this.renderRecipientItem.bind(this); - this.renderRecipientList = this.renderRecipientList.bind(this); - this.renderRecipientForm = this.renderRecipientForm.bind(this); - - this.handleTextChange = this.handleTextChange.bind(this); - this.handleRecipientAdd = this.handleRecipientAdd.bind(this); - this.isMyDataset = this.isMyDataset.bind(this); - this.verifyRecipient = this.verifyRecipient.bind(this); - this.removeRecipient = this.removeRecipient.bind(this); - this.getDatasetNoun = this.getDatasetNoun.bind(this); - this.disqualifyRecipient = this.disqualifyRecipient.bind(this); - - this.submitShare = this.submitShare.bind(this); - this.renderEmptyState = this.renderEmptyState.bind(this); - this.unshareWithUser = this.unshareWithUser.bind(this); - this.isRecipientValid = this.isRecipientValid.bind(this); - this.renderViewContent = this.renderViewContent.bind(this); - this.isDatasetShareable = this.isDatasetShareable.bind(this); - this.getValidRecipients = this.getValidRecipients.bind(this); - this.getShareableDatasets = this.getShareableDatasets.bind(this); - this.renderSharingButtons = this.renderSharingButtons.bind(this); - } - - isMyDataset(dataset) { - const { user } = this.props; - return dataset && dataset.ownerUserId && dataset.ownerUserId === user.id; - } - - handleTextChange(recipientInput = null) { - this.setState({ recipientInput }); - } - - getDatasetNoun() { - return this.props.datasets.length === 1 - ? `this ${this.props.dataNoun.singular.toLowerCase()}` - : `these ${this.props.dataNoun.plural.toLowerCase()}`; - } - - verifyRecipient(recipientEmail) { - if (typeof recipientEmail !== 'string' || !recipientEmail.length) - throw new TypeError( - `verifyRecipient: bad email received (${recipientEmail})` - ); - - const { wdkService } = this.context; - - if (!isUserDatasetsCompatibleWdkService(wdkService)) { - throw new Error( - `verifyRecipient: must have a properly configured UserDatasetsCompatibleWdkService` - ); - } - - return wdkService - .getUserIdsByEmail([recipientEmail]) - .then(({ results }) => { - const foundUsers = results.find((result) => - Object.keys(result).includes(recipientEmail) - ); - - if (!results.length || !foundUsers) { - return this.disqualifyRecipient( - recipientEmail, - - This email is not associated with a VEuPathDB account.
{' '} - {recipientEmail} will not receive {this.getDatasetNoun()}. -
- ); - } - - const uid = foundUsers[recipientEmail]; - - if (uid === this.props.user.id) { - return this.disqualifyRecipient( - recipientEmail, - - Sorry, you cannot share a{' '} - {this.props.dataNoun.singular.toLowerCase()} with yourself. - - ); - } else { - return this.acceptRecipient(recipientEmail, uid); - } - }) - .catch((err) => { - console.error( - `verifyRecipient: error checking if '${recipientEmail}' exists.`, - err - ); - return this.disqualifyRecipient( - recipientEmail, - An unknown error occurred. - ); - }); - } - - removeRecipient(recipient) { - const { email } = recipient; - const { onClose } = this.props; - const recipients = [ - ...this.state.recipients.filter((user) => user.email !== email), - ]; - return recipients.length ? this.setState({ recipients }) : onClose(); - } - - acceptRecipient(recipientEmail, id) { - const { recipients } = this.state; - const acceptedRecipient = { - id, - verified: true, - email: recipientEmail, - error: null, - }; - this.setState({ - recipients: recipients.map((recipient) => { - return recipient.email === recipientEmail - ? acceptedRecipient - : recipient; - }), - }); - } - - disqualifyRecipient(recipientEmail, reason) { - const { recipients } = this.state; - const disqualifiedRecipient = { - email: recipientEmail, - verified: false, - error: reason, - }; - this.setState({ - recipients: recipients.map((recipient) => { - return recipient.email === recipientEmail - ? disqualifiedRecipient - : recipient; - }), - }); - } - - handleRecipientAdd() { - const { recipientInput, recipients } = this.state; - if (!isValidEmail(recipientInput)) - return alert('Please enter a valid email to share with.'); - if ( - recipients.find( - (recipient) => - recipient.email.toLowerCase() === recipientInput.toLowerCase() - ) - ) - return alert('This email has already been entered.'); - this.setState( - { - recipientInput: null, - recipients: [ - ...recipients, - { - email: recipientInput, - verified: null, - id: null, - }, - ], - }, - () => this.verifyRecipient(recipientInput) - ); - } - - renderEmptyState() { - return ( - - This {this.props.dataNoun.singular.toLowerCase()} hasn't been shared - yet. - - ); - } - - unshareWithUser(datasetId, userId) { - if ( - !window.confirm( - `Are you sure you want to stop sharing ${this.getDatasetNoun()} with this user?` - ) - ) - return; - const { unshareUserDatasets } = this.props; - if (typeof unshareUserDatasets !== 'function') - throw new TypeError( - 'UserDatasetSharingModal:unshareWithUser: expected unshareUserDatasets to be function. Got: ' + - typeof unshareUserDatasets - ); - unshareUserDatasets([datasetId], [userId]); - } - - renderShareItem(share, index, userDataset) { - const { user, time, userDisplayName } = share; - return ( -
- Shared with {userDisplayName}{' '} - - -
- ); - } - - unselectDataset(dataset) { - const { deselectDataset } = this.props; - if (typeof deselectDataset !== 'function') return; - deselectDataset(dataset); - } - - renderDatasetItem(userDataset) { - const { sharedWith, id, meta } = userDataset; - const { name } = meta; - const isOwner = this.isMyDataset(userDataset); - const { deselectDataset, dataNoun } = this.props; - - const EmptyState = this.renderEmptyState; - const ShareList = this.renderShareList; - - return ( -
-
- -
- -
-

{name}

- {!isOwner ? ( - - This {dataNoun.singular.toLowerCase()} has been shared with you. - Only the owner can share it. - - ) : Array.isArray(sharedWith) && sharedWith.length ? ( - - ) : ( - - )} -
- -
- {typeof deselectDataset !== 'function' ? null : ( - - )} -
-
- ); - } - - renderRecipientItem(recipient, index) { - const { email, verified, error } = recipient; - const invalid = verified === false; - const userIcon = - verified === null - ? 'circle-o-notch fa-spin' - : verified - ? 'user-circle' - : 'user-times danger'; - - return ( -
-
- -
-
-

{email}

- {invalid ? ( - {error} - ) : ( - `will receive ${this.getDatasetNoun()}` - )} -
-
- -
-
- ); - } - - renderRecipientList({ recipients }) { - return !Array.isArray(recipients) || !recipients.length ? ( -

-   No recipients. -

- ) : ( - recipients.map(this.renderRecipientItem) - ); - } - - renderShareList({ userDataset }) { - const { sharedWith } = userDataset; - return !Array.isArray(sharedWith) || !sharedWith.length - ? null - : sharedWith.map((share, index) => - this.renderShareItem(share, index, userDataset) - ); - } - - renderDatasetList({ datasets }) { - return !Array.isArray(datasets) || !datasets.length - ? null - : datasets.map(this.renderDatasetItem); - } - - isRecipientValid(recipient = {}) { - return recipient.verified && recipient.id !== this.props.user.id; - } - - isDatasetShareable(dataset = {}) { - return dataset.ownerUserId === this.props.user.id; - } - - submitShare() { - const recipients = this.getValidRecipients(); - const datasets = this.getShareableDatasets(); - if (!datasets.length) return; - const { shareUserDatasets } = this.props; - - this.setState({ processing: true }, () => { - shareUserDatasets( - datasets.map(({ id }) => id), - recipients.map(({ id }) => id) - ) - .then((response) => { - if (response.type !== 'user-datasets/sharing-success') throw response; - this.setState({ processing: false, succeeded: true }); - }) - .catch((err) => { - console.error('submitShare: rejected', err); - this.setState({ processing: false, succeeded: false }); - }); - }); - } - - renderRecipientForm() { - const { recipientInput } = this.state; - const { handleTextChange, handleRecipientAdd } = this; - - return ( -
e.preventDefault()} - > - - - - ); - } - - getValidRecipients() { - const { recipients } = this.state; - return recipients.filter(this.isRecipientValid); - } - - getShareableDatasets() { - const { datasets } = this.props; - return datasets.filter(this.isDatasetShareable); - } - - renderSharingButtons() { - const datasets = this.getShareableDatasets(); - const recipients = this.getValidRecipients(); - const { dataNoun } = this.props; - - return ( -
- -
- ); - } - - renderViewContent() { - const { recipients, succeeded } = this.state; - const { datasets, onClose, dataNoun } = this.props; - const datasetNoun = this.getDatasetNoun(); - - const DatasetList = this.renderDatasetList; - const RecipientList = this.renderRecipientList; - const RecipientForm = this.renderRecipientForm; - const SharingButtons = this.renderSharingButtons; - const CloseButton = () => ( - - ); - - switch (succeeded) { - case true: - return ( -
- -

Shared successfully.

- -
- ); - case false: - return ( -
- -

Error Sharing {dataNoun.plural}.

-

- An error occurred while sharing your{' '} - {dataNoun.plural.toLowerCase()}. Please try again. -

- -
- ); - default: - return ( -
-
-

- Share {datasetNoun}: -

- -
-
-

- With the following recipients: -

- - - -
-
- ); - } - } - - render() { - const { onClose } = this.props; - const { processing } = this.state; - const ViewContent = this.renderViewContent; - - return ( - -
- (typeof onClose === 'function' ? onClose() : null)} - /> -
- {processing ? : } -
- ); - } -} - -UserDatasetSharingModal.contextType = WdkDependenciesContext; - -export default UserDatasetSharingModal; diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingModal.scss b/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingModal.scss deleted file mode 100644 index ace63c4364..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingModal.scss +++ /dev/null @@ -1,192 +0,0 @@ -@import '@veupathdb/wdk-client/lib/Core/Style/palette'; - -.UserDataset-SharingModal { - background-color: white; - color: black; - padding: 30px 20px; - display: flex; - flex-flow: column nowrap; - border-radius: 10px; - box-shadow: 0 0 20px rgba(0, 0, 0, 0.2); - max-height: 95vh; - overflow-y: auto; - min-width: 650px; - max-width: 100%; - - .UserDataset-SharingModal-CloseBar { - text-align: right; - } - .UserDataset-SharingModal-FormView { - margin-top: -50px; - display: flex; - flex-direction: column; - min-height: 250px; - } - - .UserDataset-SharingModal-RecipientSection, - .UserDataset-SharingModal-DatasetSection { - flex: 1; - } - - .NoRecipients { - font-size: 1.1em; - text-align: center; - font-style: italic; - opacity: 0.5; - } - .fa, - button .fa { - margin-right: 0; - } - - .UserDatasetSharing-SectionName { - margin: 10px 0 -5px; - font-weight: 200; - } - - fieldset { - margin: 0; - padding: 0; - border: 0; - display: flex; - align-items: center; - flex-flow: row nowrap; - input { - flex: 1; - border-radius: 4px; - padding: 0 5px !important; - height: 30px; - border-color: rgba(0, 0, 0, 0.2) !important; - } - } - hr { - margin: 20px 0 0; - border: none; - border-bottom: 1px solid rgba(0, 0, 0, 0.3); - } - - .UserDatasetSharing-RecipientForm { - display: flex; - margin: 0; - align-items: center; - input { - flex: 1; - box-sizing: border-box; - height: 35px; - border-color: rgba(0, 0, 0, 0.3) !important; - border-radius: 5px; - background: none; - padding: 10px !important; - background: none; - } - } - - .UserDatasetSharing-Dataset, - .UserDatasetSharing-Recipient { - border: 1px solid rgba(0, 0, 0, 0.1); - border-radius: 5px; - display: flex; - margin-top: 5px; - align-items: center; - flex-flow: row nowrap; - - .removalLink { - cursor: pointer; - position: relative; - &:hover { - color: $red; - } - z-index: 20; - .wdk-Icon { - padding-right: 5px; - } - } - - &.invalid { - opacity: 0.4; - transition: opacity 0.3s; - &:hover { - opacity: 1; - } - } - - .wdk-Icon { - font-size: 1.5em; - margin-left: 10px; - &.fa-times-circle:hover { - color: $red; - } - &.unshareRecipient { - font-size: 15px; - margin-left: 5px; - } - } - .UserDatasetSharing-Dataset-Details, - .UserDatasetSharing-Recipient-Details { - flex: 1 1 auto; - h3 { - line-height: 1em; - margin-top: -5px; - } - .fa-times { - font-size: 0.8em; - } - } - .UserDatasetSharing-Dataset-Actions, - .UserDatasetSharing-Recipient-Actions { - margin-right: 10px; - } - .UserDatasetSharing-Dataset-Icon, - .UserDatasetSharing-Recipient-Icon { - flex: 0 0 auto; - } - .UserDatasetSharing-Dataset-Details, - .UserDatasetSharing-Dataset-Icon, - .UserDatasetSharing-Recipient-Details, - .UserDatasetSharing-Recipient-Icon { - padding: 10px; - } - } - - h3 { - padding: 5px 0; - } - - .UserDatasetSharing-Buttons { - display: flex; - margin: 30px 0 0; - flex-direction: row-reverse; - button { - flex: 0 0 auto; - } - } -} - -.UserDataset-SharingModal-StatusView { - width: 100%; - text-align: center; - padding: 20px 5px; - h2 { - font-weight: 200; - text-align: center; - } - button { - margin-top: 15px; - } - .wdk-Icon { - font-size: 50px; - margin-bottom: 20px; - } -} - -.SharingModal-Close { - font-size: 1.2em; - position: relative; - top: -10px; - z-index: 100; - margin-bottom: -50px; - cursor: pointer; - &:hover { - color: $red; - } -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingReducer.ts b/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingReducer.ts deleted file mode 100644 index 22ae03422d..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/Sharing/UserDatasetSharingReducer.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { differenceWith, unionWith } from 'lodash'; - -import { - Action, - SharingSuccessAction, - SHARING_SUCCESS, -} from '../../Actions/UserDatasetsActions'; - -import { UserDataset, UserDatasetShare } from '../../Utils/types'; - -type Response = SharingSuccessAction['payload']['response']; -type ShareOperation = keyof Response; - -type State = Record< - string, - { - isLoading: boolean; - resource?: UserDataset; - } ->; - -const initialState: State = {}; - -const handleAdd = handleOperation('add'); -const handleDelete = handleOperation('delete'); - -export default function reduce( - state: State = initialState, - action: Action -): State { - switch (action.type) { - case SHARING_SUCCESS: - return handleAdd(handleDelete(state, action.payload), action.payload); - default: - return state; - } -} - -function handleOperation(operation: ShareOperation) { - return function ( - state: State, - payload: SharingSuccessAction['payload'] - ): State { - const sharesByTargetId = payload.response[operation]; - - if (sharesByTargetId == null) return state; - - return Object.entries(sharesByTargetId).reduce( - (state, [userDatasetId, shares]) => { - const entry = state[userDatasetId]; - // entry can be undefined - if (entry == null || entry.resource == null || shares == null) { - return state; - } - const sharedWith = - operation === 'add' - ? unionWith(entry.resource.sharedWith, shares, shareComparator) - : differenceWith( - entry.resource.sharedWith, - shares, - shareComparator - ); - - return { - ...state, - [userDatasetId]: { - ...entry, - resource: { - ...entry.resource, - sharedWith, - }, - }, - }; - }, - state - ); - }; -} - -function shareComparator(share1: UserDatasetShare, share2: UserDatasetShare) { - return share1.user === share2.user; -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/ThemedDeleteButton.tsx b/packages/libs/user-datasets-legacy/src/lib/Components/ThemedDeleteButton.tsx deleted file mode 100644 index 7e52e21a6a..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/ThemedDeleteButton.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { useUITheme } from '@veupathdb/coreui/lib/components/theming'; -import { MesaButton, Trash } from '@veupathdb/coreui'; -import { gray, mutedRed } from '@veupathdb/coreui/lib/definitions/colors'; -import { ThemedButtonProps } from './ThemedGrantAccessButton'; - -export function ThemedDeleteButton({ buttonText, onPress }: ThemedButtonProps) { - const theme = useUITheme(); - return ( - - ); -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/ThemedGrantAccessButton.tsx b/packages/libs/user-datasets-legacy/src/lib/Components/ThemedGrantAccessButton.tsx deleted file mode 100644 index f2a5058413..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/ThemedGrantAccessButton.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { useUITheme } from '@veupathdb/coreui/lib/components/theming'; -import { MesaButton, Share } from '@veupathdb/coreui'; - -export type ThemedButtonProps = { - buttonText: string; - onPress: () => null; -}; - -export function ThemedGrantAccessButton({ - buttonText, - onPress, -}: ThemedButtonProps) { - const theme = useUITheme(); - return ( - - ); -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/UploadForm.scss b/packages/libs/user-datasets-legacy/src/lib/Components/UploadForm.scss deleted file mode 100644 index cfdd8f1b2c..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/UploadForm.scss +++ /dev/null @@ -1,68 +0,0 @@ -.UploadForm { - h2 { - color: #222; - font-size: 1.5em; - font-weight: 500; - margin: 0; - padding: 22px 0 8px 0; - } - .formSection > label { - font-size: medium; - } - #data-set-name { - min-width: 300px; - } - #data-set-summary { - width: 100%; - } - #data-set-description { - width: 100%; - height: 8em; - } - #data-set-url { - width: 100%; - max-width: 51em; - } - .formInfo { - width: 80%; - text-align: justify; - } - .formSection { - margin: 1em 0; - } - select { - max-width: 450px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } - & &--UploadMethodSelector { - margin-top: 2em; - margin-bottom: 3.5em; - - li > label { - display: grid; - grid-template-columns: auto 10em 1fr; - - label { - font-size: medium; - margin-left: 0.25em; - } - } - } - & &--UploadMethodSelector &--FixedUploadItem { - display: grid; - grid-template-columns: 10em 1fr; - - label { - font-size: medium; - margin-left: 0.25em; - } - } - & &--UploadMethodField { - &__disabled { - opacity: 0.3; - pointer-events: none; - } - } -} diff --git a/packages/libs/user-datasets-legacy/src/lib/Components/UploadForm.tsx b/packages/libs/user-datasets-legacy/src/lib/Components/UploadForm.tsx deleted file mode 100644 index bca1113975..0000000000 --- a/packages/libs/user-datasets-legacy/src/lib/Components/UploadForm.tsx +++ /dev/null @@ -1,550 +0,0 @@ -import React, { - FormEvent, - ReactNode, - useCallback, - useEffect, - useMemo, - useState, -} from 'react'; - -import { keyBy } from 'lodash'; - -import Icon from '@veupathdb/wdk-client/lib/Components/Icon/IconAlt'; -import { - TextBox, - TextArea, - FileInput, - RadioList, - SingleSelect, -} from '@veupathdb/wdk-client/lib/Components'; - -import { makeClassNameHelper } from '@veupathdb/wdk-client/lib/Utils/ComponentUtils'; -import { StrategySummary } from '@veupathdb/wdk-client/lib/Utils/WdkUser'; - -import { State } from '../StoreModules/UserDatasetUploadStoreModule'; -import { - CompatibleRecordTypes, - DatasetUploadTypeConfigEntry, - NewUserDataset, - ResultUploadConfig, -} from '../Utils/types'; - -import './UploadForm.scss'; - -const cx = makeClassNameHelper('UploadForm'); - -interface Props { - baseUrl: string; - datasetUploadType: DatasetUploadTypeConfigEntry; - projectId: string; - badUploadMessage: State['badUploadMessage']; - urlParams: Record; - strategyOptions: StrategySummary[]; - resultUploadConfig?: ResultUploadConfig; - clearBadUpload: () => void; - submitForm: (newUserDataset: FormSubmission, redirectTo?: string) => void; - supportedFileUploadTypes: string[]; - maxSizeBytes?: number; -} - -type DataUploadMode = 'file' | 'url' | 'strategy' | 'step'; - -type DataUploadSelection = - | { type: 'file'; file?: File } - | { type: 'url'; url?: string } - | { - type: 'result'; - stepId?: number; - compatibleRecordTypes?: CompatibleRecordTypes; - }; - -type CompleteDataUploadSelection = Required; - -interface FormContent { - name: string; - summary: string; - description: string; - dataUploadSelection: DataUploadSelection; -} - -export type FormValidation = InvalidForm | ValidForm; - -export interface InvalidForm { - valid: false; - errors: string[]; -} - -export interface ValidForm { - valid: true; - submission: FormSubmission; -} - -export interface FormSubmission extends Omit { - dataUploadSelection: CompleteDataUploadSelection; -} - -function UploadForm({ - badUploadMessage, - baseUrl, - datasetUploadType, - projectId, - urlParams, - strategyOptions, - resultUploadConfig, - clearBadUpload, - submitForm, - supportedFileUploadTypes, - maxSizeBytes, -}: Props) { - const strategyOptionsByStrategyId = useMemo( - () => keyBy(strategyOptions, (option) => option.strategyId), - [strategyOptions] - ); - - const { useFixedUploadMethod: useFixedUploadMethodStr } = urlParams; - - const useFixedUploadMethod = useMemo( - () => useFixedUploadMethodStr === 'true', - [useFixedUploadMethodStr] - ); - - const displayUrlUploadMethod = - datasetUploadType.formConfig.uploadMethodConfig.url?.offer !== false; - - const displayStrategyUploadMethod = - datasetUploadType.formConfig.uploadMethodConfig.result?.offerStrategyUpload; - - const enableStrategyUploadMethod = - Boolean(displayStrategyUploadMethod) && strategyOptions.length > 0; - - const [name, setName] = useState(urlParams.datasetName ?? ''); - const [summary, setSummary] = useState(urlParams.datasetSummary ?? ''); - const [description, setDescription] = useState( - urlParams.datasetDescription ?? '' - ); - - const [dataUploadMode, setDataUploadMode] = useState( - urlParams.datasetStepId - ? 'step' - : urlParams.datasetStrategyRootStepId && enableStrategyUploadMethod - ? 'strategy' - : urlParams.datasetUrl && displayUrlUploadMethod - ? 'url' - : 'file' - ); - const [file, setFile] = useState(); - const [url, setUrl] = useState(urlParams.datasetUrl ?? ''); - const initialStepId = useMemo(() => { - const parsedStepIdParam = Number(urlParams.datasetStepId); - - if (isFinite(parsedStepIdParam)) { - return parsedStepIdParam; - } - - const parsedStrategyIdParam = Number(urlParams.datasetStrategyId); - - return !enableStrategyUploadMethod || !isFinite(parsedStrategyIdParam) - ? strategyOptions[0]?.rootStepId - : strategyOptionsByStrategyId[parsedStrategyIdParam]?.rootStepId; - }, [ - urlParams.datasetStepId, - urlParams.datasetStrategyId, - strategyOptions, - strategyOptionsByStrategyId, - enableStrategyUploadMethod, - ]); - const [stepId, setStepId] = useState(initialStepId); - - useEffect(() => { - setStepId(initialStepId); - }, [initialStepId]); - - const [errorMessages, setErrorMessages] = useState([]); - const [submitting, setSubmitting] = useState(false); - - const dataUploadSelection = useMemo((): DataUploadSelection => { - if (dataUploadMode === 'file') { - return { type: 'file', file }; - } - - if (dataUploadMode === 'url') { - return { type: 'url', url }; - } - - if (resultUploadConfig == null) { - throw new Error('This data set type does not support result uploads.'); - } - - if (stepId == null) { - return { type: 'result' }; - } - - return { - type: 'result', - stepId, - compatibleRecordTypes: resultUploadConfig.compatibleRecordTypes, - }; - }, [dataUploadMode, file, url, resultUploadConfig, stepId]); - - const onSubmit = useCallback( - (event: FormEvent) => { - event.preventDefault(); - - const formValidation = validateForm( - projectId, - datasetUploadType, - enableStrategyUploadMethod, - { - name, - summary, - description, - dataUploadSelection, - } - ); - - if (!formValidation.valid) { - setErrorMessages(formValidation.errors); - } else { - setSubmitting(true); - submitForm(formValidation.submission, `${baseUrl}/recent`); - } - }, - [ - baseUrl, - projectId, - datasetUploadType, - enableStrategyUploadMethod, - name, - summary, - description, - dataUploadSelection, - submitForm, - ] - ); - - useEffect(() => { - if (badUploadMessage != null) { - setErrorMessages([badUploadMessage.message]); - setSubmitting(false); - } - }, [badUploadMessage]); - - useEffect(() => { - return () => { - clearBadUpload(); - }; - }, [clearBadUpload]); - - const nameInputProps = datasetUploadType.formConfig.name?.inputProps; - const summaryInputProps = datasetUploadType.formConfig.summary?.inputProps; - const descriptionInputProps = - datasetUploadType.formConfig.description?.inputProps; - - const summaryRequired = summaryInputProps?.required ?? true; - const descriptionRequired = descriptionInputProps?.required ?? true; - - const defaultFileInputField = ( - `.${fileUploadType}`) - .join(',')} - required={dataUploadMode === 'file'} - disabled={dataUploadMode !== 'file' || useFixedUploadMethod} - maxSizeBytes={maxSizeBytes} - onChange={(file) => { - const fileWithSpacedRemovedFromName = - file && new File([file], file?.name.replace(/\s+/g, '_'), file); - setFile(fileWithSpacedRemovedFromName ?? undefined); - }} - /> - ); - const renderFileInput = - datasetUploadType.formConfig.uploadMethodConfig.file?.render; - const fileInputField = - renderFileInput == null - ? defaultFileInputField - : renderFileInput({ fieldNode: defaultFileInputField }); - - const uploadMethodItems = [ - { - value: 'file', - disabled: useFixedUploadMethod, - display: ( - - - Upload File - -
- {fileInputField} -
-
- ), - }, - ] - .concat( - !displayUrlUploadMethod - ? [] - : [ - { - value: 'url', - disabled: useFixedUploadMethod, - display: ( - - - Upload URL - - - - ), - }, - ] - ) - .concat( - !displayStrategyUploadMethod - ? [] - : [ - { - value: 'strategy', - disabled: !enableStrategyUploadMethod || useFixedUploadMethod, - display: ( - - - Upload Strategy - -
- ({ - value: `${option.rootStepId}`, - display: `${option.name}${!option.isSaved ? '*' : ''}`, - }))} - required={dataUploadMode === 'strategy'} - onChange={(value) => { - setStepId(Number(value)); - }} - /> -
-
- ), - }, - ] - ); - - return ( -
- {errorMessages.length > 0 && } -
-

{datasetUploadType.uploadTitle}

-
- - Name - -
- -
-
- - Summary - - -
-
- - Description - -