diff --git a/package-lock.json b/package-lock.json index cf249ad1d..3ca2e9b14 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": "2.0.0-alpha.89", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -1952,42 +1952,6 @@ } } }, - "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==", - "license": "MIT", - "dependencies": { - "@types/node": "^18.15.11", - "@types/turndown": "^5.0.1", - "axios": "^1.12.2", - "turndown": "^7.1.2", - "typescript": "^4.9.5" - } - }, - "node_modules/@iqss/dataverse-client-javascript/node_modules/@types/node": { - "version": "18.19.127", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.127.tgz", - "integrity": "sha512-gSjxjrnKXML/yo0BO099uPixMqfpJU0TKYjpfLU7TrtA2WWDki412Np/RSTPRil1saKBhvVVKzVx/p/6p94nVA==", - "license": "MIT", - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/@iqss/dataverse-client-javascript/node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, "node_modules/@iqss/dataverse-design-system": { "resolved": "packages/design-system", "link": true @@ -8569,12 +8533,6 @@ "license": "MIT", "optional": true }, - "node_modules/@types/turndown": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/@types/turndown/-/turndown-5.0.5.tgz", - "integrity": "sha512-TL2IgGgc7B5j78rIccBtlYAnkuv8nUQqhQc+DSYV5j9Be9XOcm/SKOVRuA47xAVI3680Tk9B1d8flK2GWT2+4w==", - "license": "MIT" - }, "node_modules/@types/unist": { "version": "2.0.11", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz", @@ -9670,6 +9628,7 @@ "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, "license": "MIT" }, "node_modules/at-least-node": { @@ -9760,6 +9719,7 @@ "version": "1.12.2", "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "dev": true, "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -11017,6 +10977,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" @@ -12026,6 +11987,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.4.0" @@ -12693,6 +12655,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", @@ -13964,6 +13927,7 @@ "version": "1.15.11", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "dev": true, "funding": [ { "type": "individual", @@ -14039,6 +14003,7 @@ "version": "4.0.4", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", @@ -21346,6 +21311,7 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -21355,6 +21321,7 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -24527,6 +24494,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "dev": true, "license": "MIT" }, "node_modules/psl": { @@ -28414,12 +28382,6 @@ "react": ">=15.0.0" } }, - "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", - "license": "MIT" - }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", diff --git a/package.json b/package.json index 3b8b27edf..847eddfa0 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": "2.0.0-alpha.89", "@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 + })} + + )} +