diff --git a/package-lock.json b/package-lock.json index cf249ad1d..f63cd72ab 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-alpha.85", + "@iqss/dataverse-client-javascript": "v2.1.0-pr421.6d795bb", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -1954,9 +1954,9 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-alpha.85", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-alpha.85/9f7d8be5c1fbe022c11fcc6d956bbf1cc4821776", - "integrity": "sha512-2aEA0MdkhFjugxImJ3a334jlVbwmNMDVsFXnwPutbbkqn89UMXClMxADkqlmLQ3/4dOMtOrdVDitxYsYmYZ6RQ==", + "version": "2.1.0-pr421.6d795bb", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.1.0-pr421.6d795bb/f4fcb31c1f3f1fe4fd84c3f81bdab8d00555d83a", + "integrity": "sha512-EGWgECeBuemUswYnxgJiZtVw+XVYC/V6R/mC4qtvFMsFwqBmvX84JBqgoEVUnLLJ1PHPd+oYEbmUgJt0Vqc0hg==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", diff --git a/package.json b/package.json index 3b8b27edf..4a647ea32 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "@dnd-kit/sortable": "8.0.0", "@dnd-kit/utilities": "3.2.2", "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-alpha.85", + "@iqss/dataverse-client-javascript": "v2.1.0-pr421.6d795bb", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", diff --git a/public/locales/en/shared.json b/public/locales/en/shared.json index d4dbc4e26..e00b67b0b 100644 --- a/public/locales/en/shared.json +++ b/public/locales/en/shared.json @@ -220,6 +220,9 @@ "selectFileMultiple": "Select files to add", "dragDropSingle": "Drag and drop file here.", "dragDropMultiple": "Drag and drop files and/or directories here.", + "uploadWidgetHelp": "Select files or drag and drop into the upload widget. Maximum of {{maxFilesPerUpload}} files per upload.", + "uploadWidgetMaxFilesHelp": "Maximum of {{maxFilesAvailableToUpload}} files available to upload.", + "uploadWidgetStorageQuotaHelp": "Storage quota: {{storageQuotaRemaining}} remaining.", "cancelUpload": "Cancel upload", "uploadFailed": "There was an error uploading this file.", "loadingConfiguration": "Loading configuration", diff --git a/public/locales/es/shared.json b/public/locales/es/shared.json index a3010a550..d22e10dcd 100644 --- a/public/locales/es/shared.json +++ b/public/locales/es/shared.json @@ -220,6 +220,9 @@ "selectFileMultiple": "Seleccionar ficheros para agregar", "dragDropSingle": "Arrastra y suelta el fichero aquí.", "dragDropMultiple": "Arrastra y suelta ficheros y/o directorios aquí.", + "uploadWidgetHelp": "Selecciona ficheros o arrástralos y suéltalos en el widget de carga. Máximo de {{maxFilesPerUpload}} ficheros por carga.", + "uploadWidgetMaxFilesHelp": "Máximo de {{maxFilesAvailableToUpload}} ficheros disponibles para cargar.", + "uploadWidgetStorageQuotaHelp": "Cuota de almacenamiento: {{storageQuotaRemaining}} restantes.", "cancelUpload": "Cancelar carga", "uploadFailed": "Ocurrió un error al cargar este fichero.", "loadingConfiguration": "Cargando configuración", diff --git a/src/dataset/domain/models/DatasetUploadLimits.ts b/src/dataset/domain/models/DatasetUploadLimits.ts new file mode 100644 index 000000000..6dbf22205 --- /dev/null +++ b/src/dataset/domain/models/DatasetUploadLimits.ts @@ -0,0 +1,4 @@ +export interface DatasetUploadLimits { + numberOfFilesRemaining?: number + storageQuotaRemaining?: number +} diff --git a/src/dataset/domain/repositories/DatasetRepository.ts b/src/dataset/domain/repositories/DatasetRepository.ts index 4a3e332dd..1bd783499 100644 --- a/src/dataset/domain/repositories/DatasetRepository.ts +++ b/src/dataset/domain/repositories/DatasetRepository.ts @@ -11,6 +11,7 @@ import { FormattedCitation, CitationFormat } from '../models/DatasetCitation' import { DatasetLicenseUpdateRequest } from '../models/DatasetLicenseUpdateRequest' import { CollectionSummary } from '@/collection/domain/models/CollectionSummary' import { DatasetVersionPaginationInfo } from '../models/DatasetVersionPaginationInfo' +import { DatasetUploadLimits } from '../models/DatasetUploadLimits' export interface DatasetRepository { getByPersistentId: ( @@ -68,4 +69,5 @@ export interface DatasetRepository { link(datasetId: string | number, collectionIdOrAlias: string | number): Promise unlink(datasetId: string | number, collectionIdOrAlias: string | number): Promise getDatasetLinkedCollections: (datasetId: string | number) => Promise + getDatasetUploadLimits: (datasetId: string | number) => Promise } diff --git a/src/dataset/domain/useCases/getDatasetUploadLimits.ts b/src/dataset/domain/useCases/getDatasetUploadLimits.ts new file mode 100644 index 000000000..fb9a5f63f --- /dev/null +++ b/src/dataset/domain/useCases/getDatasetUploadLimits.ts @@ -0,0 +1,9 @@ +import { DatasetRepository } from '../repositories/DatasetRepository' +import { DatasetUploadLimits } from '../models/DatasetUploadLimits' + +export async function getDatasetUploadLimits( + datasetId: string | number, + datasetRepository: DatasetRepository +): Promise { + return datasetRepository.getDatasetUploadLimits(datasetId) +} diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index 7dd2f3354..a26660169 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -43,7 +43,8 @@ import { unlinkDataset, getDatasetLinkedCollections, updateTermsOfAccess, - updateDatasetLicense + updateDatasetLicense, + getDatasetUploadLimits } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' import { DatasetPaginationInfo } from '../../domain/models/DatasetPaginationInfo' @@ -62,6 +63,7 @@ import { requireAppConfig } from '../../../config' import { AxiosResponse } from 'axios' import { JSDataverseReadErrorHandler } from '@/shared/helpers/JSDataverseReadErrorHandler' import { CollectionSummary } from '@/collection/domain/models/CollectionSummary' +import { DatasetUploadLimits } from '@/dataset/domain/models/DatasetUploadLimits' const includeDeaccessioned = true @@ -468,4 +470,8 @@ export class DatasetJSDataverseRepository implements DatasetRepository { updateTermsOfAccess(datasetId: string | number, termsOfAccess: TermsOfAccess): Promise { return updateTermsOfAccess.execute(datasetId, termsOfAccess) } + + getDatasetUploadLimits(datasetId: string | number): Promise { + return getDatasetUploadLimits.execute(datasetId) + } } diff --git a/src/sections/replace-file/ReplaceFile.tsx b/src/sections/replace-file/ReplaceFile.tsx index ea8382456..401782721 100644 --- a/src/sections/replace-file/ReplaceFile.tsx +++ b/src/sections/replace-file/ReplaceFile.tsx @@ -1,6 +1,7 @@ import { useEffect } from 'react' import { useTranslation } from 'react-i18next' import { Col, Row, Tabs } from '@iqss/dataverse-design-system' +import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' import { FileRepository } from '@/files/domain/repositories/FileRepository' import { useFile } from '../file/useFile' import { useLoading } from '../../shared/contexts/loading/LoadingContext' @@ -13,6 +14,7 @@ import styles from './ReplaceFile.module.scss' interface ReplaceFileProps { fileRepository: FileRepository + datasetRepository: DatasetRepository fileIdFromParams: number datasetPidFromParams: string datasetVersionFromParams: string @@ -27,6 +29,7 @@ export enum ReplaceFileReferrer { export const ReplaceFile = ({ fileRepository, + datasetRepository, fileIdFromParams, datasetPidFromParams, datasetVersionFromParams, @@ -78,6 +81,7 @@ export const ReplaceFile = ({
Promise } | { fileRepository: FileRepository + datasetRepository: DatasetRepository datasetPersistentId: string storageType: StorageType operationType: OperationType.ADD_FILES_TO_DATASET originalFile?: never referrer?: never + fetchUploadLimits?: ( + datasetId: string | number, + datasetRepository: DatasetRepository + ) => Promise } export type StorageType = 'S3' @@ -37,11 +49,13 @@ export enum OperationType { export const FileUploader = ({ fileRepository, + datasetRepository, datasetPersistentId, storageType, operationType, originalFile, - referrer + referrer, + fetchUploadLimits }: FileUploaderProps) => { const { fixityAlgorithm, isLoadingFixityAlgorithm } = useGetFixityAlgorithm(fileRepository) @@ -67,7 +81,9 @@ export const FileUploader = ({ diff --git a/src/sections/shared/file-uploader/FileUploaderPanel.tsx b/src/sections/shared/file-uploader/FileUploaderPanel.tsx index 5fbb1986b..899f94bd8 100644 --- a/src/sections/shared/file-uploader/FileUploaderPanel.tsx +++ b/src/sections/shared/file-uploader/FileUploaderPanel.tsx @@ -7,22 +7,31 @@ import { Stack } from '@iqss/dataverse-design-system' import { FileRepository } from '@/files/domain/repositories/FileRepository' import { QueryParamKey, Route } from '@/sections/Route.enum' import { DatasetNonNumericVersionSearchParam } from '@/dataset/domain/models/Dataset' +import { DatasetRepository } from '@/dataset/domain/repositories/DatasetRepository' import { ReplaceFileReferrer } from '@/sections/replace-file/ReplaceFile' import { useFileUploaderContext } from './context/FileUploaderContext' import FileUploadInput from './file-upload-input/FileUploadInput' import { UploadedFilesList } from './uploaded-files-list/UploadedFilesList' import { ConfirmLeaveModal } from './confirm-leave-modal/ConfirmLeaveModal' +import { DatasetUploadLimits } from '@/dataset/domain/models/DatasetUploadLimits' interface FileUploaderPanelProps { fileRepository: FileRepository + datasetRepository: DatasetRepository datasetPersistentId: string referrer?: ReplaceFileReferrer + fetchUploadLimits?: ( + datasetId: string | number, + datasetRepository: DatasetRepository + ) => Promise } const FileUploaderPanel = ({ fileRepository, + datasetRepository, datasetPersistentId, - referrer + referrer, + fetchUploadLimits }: FileUploaderPanelProps) => { const { t } = useTranslation('shared') const navigate = useNavigate() @@ -101,7 +110,12 @@ const FileUploaderPanel = ({ return ( - + {uploadedFiles.length > 0 && ( Promise } const limit = 6 const semaphore = new Semaphore(limit) -const FileUploadInput = ({ fileRepository, datasetPersistentId }: FileUploadInputProps) => { +const maxFilesPerUpload = 1000 + +const FileUploadInput = ({ + fileRepository, + datasetRepository, + datasetPersistentId, + fetchUploadLimits +}: FileUploadInputProps) => { const { fileUploaderState, addFile, @@ -44,6 +59,7 @@ const FileUploadInput = ({ fileRepository, datasetPersistentId }: FileUploadInpu const inputRef = useRef(null) const [isDragging, setIsDragging] = useState(false) + const { uploadLimit } = useUploadLimit(datasetPersistentId, datasetRepository, fetchUploadLimits) const totalFiles = Object.keys(fileUploaderState.files).length @@ -267,6 +283,27 @@ const FileUploadInput = ({ fileRepository, datasetPersistentId }: FileUploadInpu {t('fileUploader.accordionTitle')} +

+ {t('fileUploader.uploadWidgetHelp', { + maxFilesPerUpload: maxFilesPerUpload.toLocaleString() + })} + {uploadLimit.maxFilesAvailableToUploadFormatted && ( + <> + {' '} + {t('fileUploader.uploadWidgetMaxFilesHelp', { + maxFilesAvailableToUpload: uploadLimit.maxFilesAvailableToUploadFormatted + })} + + )} + {uploadLimit.storageQuotaRemainingFormatted && ( + <> + {' '} + {t('fileUploader.uploadWidgetStorageQuotaHelp', { + storageQuotaRemaining: uploadLimit.storageQuotaRemainingFormatted + })} + + )} +