From d1a4186161216ce5d0f36eed2691e3809c22f6b1 Mon Sep 17 00:00:00 2001 From: Eryk Kullikowski Date: Fri, 14 Jun 2024 11:32:37 +0200 Subject: [PATCH 01/96] merged file upload use case --- src/files/domain/models/FileUploadState.ts | 8 ++++++++ src/files/domain/repositories/FileRepository.ts | 3 ++- src/files/domain/useCases/uploadFile.ts | 5 +++-- .../infrastructure/FileJSDataverseRepository.ts | 17 ++++++++++------- .../upload-dataset-files/UploadDatasetFiles.tsx | 3 ++- .../file/FileMockFailedUploadRepository.ts | 3 ++- src/stories/file/FileMockRepository.ts | 4 +++- 7 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/files/domain/models/FileUploadState.ts b/src/files/domain/models/FileUploadState.ts index c2e9e3ffd..c55c1f057 100644 --- a/src/files/domain/models/FileUploadState.ts +++ b/src/files/domain/models/FileUploadState.ts @@ -2,6 +2,7 @@ import { FileSize, FileSizeUnit } from './FileMetadata' export interface FileUploadState { progress: number + storageId?: string progressHidden: boolean fileSizeString: string failed: boolean @@ -20,6 +21,7 @@ export class FileUploadTools { const key = this.key(file) const newValue: FileUploadState = { progress: 0, + storageId: undefined, progressHidden: true, fileSizeString: new FileSize(file.size, FileSizeUnit.BYTES).toString(), failed: false, @@ -56,6 +58,12 @@ export class FileUploadTools { return newState } + static storageId(file: File, id: string, oldState: FileUploaderState): FileUploaderState { + const [newState, newValue] = this.toNewState(file, oldState) + newValue.storageId = id + return newState + } + static failed(file: File, oldState: FileUploaderState): FileUploaderState { const [newState, newValue] = this.toNewState(file, oldState) newValue.failed = true diff --git a/src/files/domain/repositories/FileRepository.ts b/src/files/domain/repositories/FileRepository.ts index 5dfb30399..04556a0ba 100644 --- a/src/files/domain/repositories/FileRepository.ts +++ b/src/files/domain/repositories/FileRepository.ts @@ -31,6 +31,7 @@ export interface FileRepository { datasetId: number | string, file: FileHolder, progress: (now: number) => void, - abortController: AbortController + abortController: AbortController, + storageIdSetter: (storageId: string) => void ) => Promise } diff --git a/src/files/domain/useCases/uploadFile.ts b/src/files/domain/useCases/uploadFile.ts index 978c45b95..309a3e8d3 100644 --- a/src/files/domain/useCases/uploadFile.ts +++ b/src/files/domain/useCases/uploadFile.ts @@ -6,11 +6,12 @@ export function uploadFile( file: File, done: () => void, failed: () => void, - progress: (now: number) => void + progress: (now: number) => void, + storageIdSetter: (storageId: string) => void ): () => void { const controller = new AbortController() fileRepository - .uploadFile(datasetId, { file: file }, progress, controller) + .uploadFile(datasetId, { file: file }, progress, controller, storageIdSetter) .then(() => done()) .catch(() => failed()) return () => controller.abort() diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index a42f904b7..fc529c733 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -14,7 +14,8 @@ import { getFileDataTables, getFileDownloadCount, getFileUserPermissions, - ReadError + ReadError, + uploadFile as jsUploadFile } from '@iqss/dataverse-client-javascript' import { FileCriteria } from '../domain/models/FileCriteria' import { DomainFileMapper } from './mappers/DomainFileMapper' @@ -239,12 +240,14 @@ export class FileJSDataverseRepository implements FileRepository { } uploadFile( - _datasetId: number | string, - _file: FileHolder, - _progress: (now: number) => void, - _abortController: AbortController + datasetId: number | string, + file: FileHolder, + progress: (now: number) => void, + abortController: AbortController, + storageIdSetter: (storageId: string) => void ): Promise { - // TODO: - return new Promise(() => {}) + return jsUploadFile + .execute(datasetId, file.file, progress, abortController) + .then(storageIdSetter) } } diff --git a/src/sections/upload-dataset-files/UploadDatasetFiles.tsx b/src/sections/upload-dataset-files/UploadDatasetFiles.tsx index 1efc09eb1..aad33e949 100644 --- a/src/sections/upload-dataset-files/UploadDatasetFiles.tsx +++ b/src/sections/upload-dataset-files/UploadDatasetFiles.tsx @@ -69,7 +69,8 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat setState(FileUploadTools.failed(file, fileUploaderState)) fileUploadFinished(file) }, - (now) => setState(FileUploadTools.progress(file, now, fileUploaderState)) + (now) => setState(FileUploadTools.progress(file, now, fileUploaderState)), + (storageId) => setState(FileUploadTools.storageId(file, storageId, fileUploaderState)) ) setUploadingToCancelMap((x) => x.set(key, cancel)) } diff --git a/src/stories/file/FileMockFailedUploadRepository.ts b/src/stories/file/FileMockFailedUploadRepository.ts index 112153558..b8f3edca0 100644 --- a/src/stories/file/FileMockFailedUploadRepository.ts +++ b/src/stories/file/FileMockFailedUploadRepository.ts @@ -7,7 +7,8 @@ export class FileMocFailedRepository extends FileMockRepository implements FileR _datasetId: number | string, _file: FileHolder, _progress: (now: number) => void, - _abortController: AbortController + _abortController: AbortController, + _storageIdSetter: (storageId: string) => void ): Promise { return Promise.reject(new Error('fail')) } diff --git a/src/stories/file/FileMockRepository.ts b/src/stories/file/FileMockRepository.ts index 673df7c58..7f8878dc2 100644 --- a/src/stories/file/FileMockRepository.ts +++ b/src/stories/file/FileMockRepository.ts @@ -71,7 +71,8 @@ export class FileMockRepository implements FileRepository { _datasetId: number | string, _file: FileHolder, progress: (now: number) => void, - abortController: AbortController + abortController: AbortController, + storageIdSetter: (storageId: string) => void ): Promise { let t: NodeJS.Timeout const sleep = (delay: number) => new Promise((res) => (t = setTimeout(res, delay))) @@ -84,6 +85,7 @@ export class FileMockRepository implements FileRepository { progress(now) //console.log(FileUploadTools.key(_file.file) + ': ' + String(now)) } + storageIdSetter('some-storage-identifier') } return res() } From 2693d45faf40ce040076bb62734ef8e791c1fbf4 Mon Sep 17 00:00:00 2001 From: Eryk Kullikowski Date: Fri, 14 Jun 2024 20:28:41 +0200 Subject: [PATCH 02/96] added file list top file uploader --- src/files/domain/models/FileUploadState.ts | 44 +++++++++++++++++-- .../UploadDatasetFiles.tsx | 12 ++++- .../upload-dataset-files/UploadedFiles.tsx | 43 ++++++++++++++++++ 3 files changed, 94 insertions(+), 5 deletions(-) create mode 100644 src/sections/upload-dataset-files/UploadedFiles.tsx diff --git a/src/files/domain/models/FileUploadState.ts b/src/files/domain/models/FileUploadState.ts index c55c1f057..aacc14fbc 100644 --- a/src/files/domain/models/FileUploadState.ts +++ b/src/files/domain/models/FileUploadState.ts @@ -8,10 +8,15 @@ export interface FileUploadState { failed: boolean done: boolean removed: boolean + fileName: string + fileDir: string + fileType: string + key: string } export interface FileUploaderState { state: Map + uploaded: FileUploadState[] } export class FileUploadTools { @@ -26,11 +31,15 @@ export class FileUploadTools { fileSizeString: new FileSize(file.size, FileSizeUnit.BYTES).toString(), failed: false, done: false, - removed: false + removed: false, + fileName: file.name, + fileDir: file.webkitRelativePath, + fileType: file.type, + key: this.key(file) } newState.set(key, newValue) }) - return { state: newState } + return { state: newState, uploaded: this.toUploaded(newState) } } static key(file: File): string { @@ -48,7 +57,11 @@ export class FileUploadTools { fileSizeString: new FileSize(file.size, FileSizeUnit.BYTES).toString(), failed: false, done: false, - removed: false + removed: false, + fileName: file.name, + fileDir: file.webkitRelativePath, + fileType: file.type, + key: this.key(file) } } @@ -88,12 +101,35 @@ export class FileUploadTools { return newState } + static fileName(file: File, name: string, oldState: FileUploaderState): FileUploaderState { + const [newState, newValue] = this.toNewState(file, oldState) + newValue.fileName = name + return newState + } + + static fileDir(file: File, dir: string, oldState: FileUploaderState): FileUploaderState { + const [newState, newValue] = this.toNewState(file, oldState) + newValue.fileDir = dir + return newState + } + + static delete(fileUploadState: FileUploadState, oldState: FileUploaderState): FileUploaderState { + fileUploadState.removed = true + return { state: oldState.state, uploaded: this.toUploaded(oldState.state) } + } + private static toNewState( file: File, oldState: FileUploaderState ): [FileUploaderState, FileUploadState] { const newValue = this.get(file, oldState) oldState.state.set(this.key(file), newValue) - return [{ state: oldState.state }, newValue] + return [{ state: oldState.state, uploaded: this.toUploaded(oldState.state) }, newValue] + } + + private static toUploaded(state: Map): FileUploadState[] { + return Array.from(state.values()) + .filter((x) => x.done) + .sort((a, b) => (a.fileDir + a.fileName).localeCompare(b.fileDir + b.fileName)) } } diff --git a/src/sections/upload-dataset-files/UploadDatasetFiles.tsx b/src/sections/upload-dataset-files/UploadDatasetFiles.tsx index aad33e949..60e83bfe5 100644 --- a/src/sections/upload-dataset-files/UploadDatasetFiles.tsx +++ b/src/sections/upload-dataset-files/UploadDatasetFiles.tsx @@ -6,8 +6,9 @@ import { useDataset } from '../dataset/DatasetContext' import { PageNotFound } from '../page-not-found/PageNotFound' import { BreadcrumbsGenerator } from '../shared/hierarchy/BreadcrumbsGenerator' import { FileUploader } from './FileUploader' -import { FileUploadTools } from '../../files/domain/models/FileUploadState' +import { FileUploadState, FileUploadTools } from '../../files/domain/models/FileUploadState' import { uploadFile } from '../../files/domain/useCases/uploadFile' +import { UploadedFiles } from './UploadedFiles' interface UploadDatasetFilesProps { fileRepository: FileRepository @@ -99,6 +100,10 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat setState(FileUploadTools.removed(file, fileUploaderState)) } + const deleteFile = (file: FileUploadState) => { + setState(FileUploadTools.delete(file, fileUploaderState)) + } + useEffect(() => { setIsLoading(isLoading) }, [isLoading, setIsLoading]) @@ -127,6 +132,11 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat fileUploaderState={fileUploaderState} cancelUpload={cancelUpload} /> + )} diff --git a/src/sections/upload-dataset-files/UploadedFiles.tsx b/src/sections/upload-dataset-files/UploadedFiles.tsx new file mode 100644 index 000000000..52c4758cf --- /dev/null +++ b/src/sections/upload-dataset-files/UploadedFiles.tsx @@ -0,0 +1,43 @@ +import { Button } from '@iqss/dataverse-design-system' +import { X } from 'react-bootstrap-icons' +import { info } from 'sass' +import { FileUploadState } from '../../files/domain/models/FileUploadState' +import styles from './FileUploader.module.scss' + +interface DatasetFilesProps { + fileUploadState: FileUploadState[] + cancelTitle: string + deleteFile: (file: FileUploadState) => void +} + +export function UploadedFiles({ fileUploadState, cancelTitle, deleteFile }: DatasetFilesProps) { + return ( + <> + {fileUploadState && fileUploadState.filter((x) => x.done).length > 0 ? ( +
+ {fileUploadState.map((file) => ( +
+
+ {file.fileDir} + {file.fileName} +
+
{file.fileSizeString}
+
{null}
+
+ +
+
+ ))} +
+ ) : ( +
{info}
+ )} + + ) +} From f8dc2d018630d100a6039f40b807090bf05891db Mon Sep 17 00:00:00 2001 From: Eryk Kullikowski Date: Sat, 15 Jun 2024 01:02:32 +0200 Subject: [PATCH 03/96] uploaded files list --- src/files/domain/models/FileUploadState.ts | 38 ++++++---- .../FileUploader.module.scss | 4 + .../upload-dataset-files/FileUploader.tsx | 27 ++++--- .../UploadDatasetFiles.tsx | 31 ++++++-- .../upload-dataset-files/UploadedFiles.tsx | 73 ++++++++++++------- 5 files changed, 112 insertions(+), 61 deletions(-) diff --git a/src/files/domain/models/FileUploadState.ts b/src/files/domain/models/FileUploadState.ts index aacc14fbc..e4d4e5cbf 100644 --- a/src/files/domain/models/FileUploadState.ts +++ b/src/files/domain/models/FileUploadState.ts @@ -66,27 +66,35 @@ export class FileUploadTools { } static progress(file: File, now: number, oldState: FileUploaderState): FileUploaderState { - const [newState, newValue] = this.toNewState(file, oldState) - newValue.progress = now - return newState + const fileUploadState = oldState.state.get(this.key(file)) + if (fileUploadState) { + fileUploadState.progress = now + } + return { state: oldState.state, uploaded: this.toUploaded(oldState.state) } } static storageId(file: File, id: string, oldState: FileUploaderState): FileUploaderState { - const [newState, newValue] = this.toNewState(file, oldState) - newValue.storageId = id - return newState + const fileUploadState = oldState.state.get(this.key(file)) + if (fileUploadState) { + fileUploadState.storageId = id + } + return { state: oldState.state, uploaded: this.toUploaded(oldState.state) } } static failed(file: File, oldState: FileUploaderState): FileUploaderState { - const [newState, newValue] = this.toNewState(file, oldState) - newValue.failed = true - return newState + const fileUploadState = oldState.state.get(this.key(file)) + if (fileUploadState) { + fileUploadState.failed = true + } + return { state: oldState.state, uploaded: this.toUploaded(oldState.state) } } static done(file: File, oldState: FileUploaderState): FileUploaderState { - const [newState, newValue] = this.toNewState(file, oldState) - newValue.done = true - return newState + const fileUploadState = oldState.state.get(this.key(file)) + if (fileUploadState) { + fileUploadState.done = true + } + return { state: oldState.state, uploaded: this.toUploaded(oldState.state) } } static removed(file: File, oldState: FileUploaderState): FileUploaderState { @@ -113,8 +121,8 @@ export class FileUploadTools { return newState } - static delete(fileUploadState: FileUploadState, oldState: FileUploaderState): FileUploaderState { - fileUploadState.removed = true + static delete(file: File, oldState: FileUploaderState): FileUploaderState { + oldState.state.delete(this.key(file)) return { state: oldState.state, uploaded: this.toUploaded(oldState.state) } } @@ -129,7 +137,7 @@ export class FileUploadTools { private static toUploaded(state: Map): FileUploadState[] { return Array.from(state.values()) - .filter((x) => x.done) + .filter((x) => !x.removed && x.done) .sort((a, b) => (a.fileDir + a.fileName).localeCompare(b.fileDir + b.fileName)) } } diff --git a/src/sections/upload-dataset-files/FileUploader.module.scss b/src/sections/upload-dataset-files/FileUploader.module.scss index 6299fb9c2..feef43082 100644 --- a/src/sections/upload-dataset-files/FileUploader.module.scss +++ b/src/sections/upload-dataset-files/FileUploader.module.scss @@ -47,3 +47,7 @@ font-size: 1.3em; text-align: center; } + +.uploaded { + padding-left: 1em; +} diff --git a/src/sections/upload-dataset-files/FileUploader.tsx b/src/sections/upload-dataset-files/FileUploader.tsx index c544b909e..4a1f9751c 100644 --- a/src/sections/upload-dataset-files/FileUploader.tsx +++ b/src/sections/upload-dataset-files/FileUploader.tsx @@ -12,6 +12,7 @@ export interface FileUploaderProps { selectText: string fileUploaderState: FileUploaderState cancelUpload: (file: File) => void + cleanFileState: (file: File) => void } export function FileUploader({ @@ -20,7 +21,8 @@ export function FileUploader({ info, selectText, fileUploaderState, - cancelUpload + cancelUpload, + cleanFileState }: FileUploaderProps) { const theme = useTheme() const [files, setFiles] = useState([]) @@ -104,16 +106,21 @@ export function FileUploader({ } } - const handleRemoveFile = (f: File) => { - cancelUpload(f) - setFiles((newFiles) => - newFiles.filter((x) => !FileUploadTools.get(x, fileUploaderState).removed) - ) - } - useEffect(() => { upload(files) - }, [files, fileUploaderState, upload]) + }, [files, upload]) + + useEffect(() => { + setFiles((newFiles) => + newFiles.filter((x) => { + const res = !FileUploadTools.get(x, fileUploaderState).removed + if (!res) { + cleanFileState(x) + } + return res + }) + ) + }, [fileUploaderState, cleanFileState]) const inputRef = useRef(null) @@ -168,7 +175,7 @@ export function FileUploader({ variant="secondary" {...{ size: 'sm' }} withSpacing - onClick={() => handleRemoveFile(file)}> + onClick={() => cancelUpload(file)}> diff --git a/src/sections/upload-dataset-files/UploadDatasetFiles.tsx b/src/sections/upload-dataset-files/UploadDatasetFiles.tsx index 60e83bfe5..75e27a3f5 100644 --- a/src/sections/upload-dataset-files/UploadDatasetFiles.tsx +++ b/src/sections/upload-dataset-files/UploadDatasetFiles.tsx @@ -20,7 +20,6 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat const { t } = useTranslation('uploadDatasetFiles') const [fileUploaderState, setState] = useState(FileUploadTools.createNewState([])) const [uploadingToCancelMap, setUploadingToCancelMap] = useState(new Map void>()) - const [uploadFinished, setUploadFinished] = useState(new Set()) const [semaphore, setSemaphore] = useState(new Set()) const sleep = (delay: number) => new Promise((res) => setTimeout(res, delay)) @@ -44,7 +43,6 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat const fileUploadFinished = (file: File) => { const key = FileUploadTools.key(file) - setUploadFinished((x) => x.add(key)) setUploadingToCancelMap((x) => { x.delete(key) return x @@ -52,9 +50,14 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat releaseSemaphore(file) } + const canUpload = (file: File) => + !uploadingToCancelMap.has(FileUploadTools.key(file)) && + !FileUploadTools.get(file, fileUploaderState).failed && + !FileUploadTools.get(file, fileUploaderState).done + const uploadOneFile = (file: File) => { const key = FileUploadTools.key(file) - if (uploadingToCancelMap.has(key) || uploadFinished.has(key)) { + if (!canUpload(file)) { return } setState(FileUploadTools.showProgressBar(file, fileUploaderState)) @@ -78,29 +81,40 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat const upload = async (files: File[]) => { for (const file of files) { - const key = FileUploadTools.key(file) - if (!uploadingToCancelMap.has(key) && !uploadFinished.has(key)) { + if (canUpload(file)) { await acquireSemaphore(file) uploadOneFile(file) } } } - const cancelUpload = (file: File) => { + const cleanup = (file: File) => { const key = FileUploadTools.key(file) const cancel = uploadingToCancelMap.get(key) if (cancel) { cancel() - releaseSemaphore(file) } setUploadingToCancelMap((x) => { x.delete(key) return x }) + releaseSemaphore(file) + } + + const cancelUpload = (file: File) => { + cleanup(file) setState(FileUploadTools.removed(file, fileUploaderState)) } - const deleteFile = (file: FileUploadState) => { + const deleteFile = (fileUploadState: FileUploadState) => { + setState((x) => { + fileUploadState.removed = true + return { state: x.state, uploaded: x.uploaded } + }) + } + + const cleanFileState = (file: File) => { + cleanup(file) setState(FileUploadTools.delete(file, fileUploaderState)) } @@ -131,6 +145,7 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat selectText={t('select')} fileUploaderState={fileUploaderState} cancelUpload={cancelUpload} + cleanFileState={cleanFileState} /> - {fileUploadState && fileUploadState.filter((x) => x.done).length > 0 ? ( -
- {fileUploadState.map((file) => ( -
-
- {file.fileDir} - {file.fileName} + - ) : ( -
{info}
- )} - + ) : null} +
+ + +
) } From b0dc639d67e2b3887fe67bb1f6c82beaac417d60 Mon Sep 17 00:00:00 2001 From: Eryk Kullikowski Date: Sat, 15 Jun 2024 01:40:14 +0200 Subject: [PATCH 04/96] added info on dirs drop --- public/locales/en/uploadDatasetFiles.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/locales/en/uploadDatasetFiles.json b/public/locales/en/uploadDatasetFiles.json index 29cbc3838..451e2e629 100644 --- a/public/locales/en/uploadDatasetFiles.json +++ b/public/locales/en/uploadDatasetFiles.json @@ -1,6 +1,6 @@ { "breadcrumbActionItem": "Upload files", "cancel": "Cancel upload", - "info": "Drag and drop files here.", + "info": "Drag and drop files and/or directories here.", "select": "Select files to add" } From 854a0b32d7469c78cc24e884de3c67c9c4a06d3a Mon Sep 17 00:00:00 2001 From: Eryk Kulikowski Date: Mon, 17 Jun 2024 12:15:17 +0200 Subject: [PATCH 05/96] save and cancel buttons implementation --- .../domain/repositories/FileRepository.ts | 5 +++ src/files/domain/useCases/addUploadedFile.ts | 17 ++++++++++ .../FileJSDataverseRepository.ts | 7 +++- .../UploadDatasetFiles.tsx | 34 +++++++++++++++++++ .../upload-dataset-files/UploadedFiles.tsx | 26 ++++++++++++-- src/stories/file/FileMockRepository.ts | 8 +++++ 6 files changed, 93 insertions(+), 4 deletions(-) create mode 100644 src/files/domain/useCases/addUploadedFile.ts diff --git a/src/files/domain/repositories/FileRepository.ts b/src/files/domain/repositories/FileRepository.ts index 04556a0ba..ecc0494c3 100644 --- a/src/files/domain/repositories/FileRepository.ts +++ b/src/files/domain/repositories/FileRepository.ts @@ -34,4 +34,9 @@ export interface FileRepository { abortController: AbortController, storageIdSetter: (storageId: string) => void ) => Promise + addUploadedFile: ( + datasetId: number | string, + file: FileHolder, + storageId: string + ) => Promise } diff --git a/src/files/domain/useCases/addUploadedFile.ts b/src/files/domain/useCases/addUploadedFile.ts new file mode 100644 index 000000000..e354bb186 --- /dev/null +++ b/src/files/domain/useCases/addUploadedFile.ts @@ -0,0 +1,17 @@ +import { FileRepository } from '../repositories/FileRepository' + +export function addUploadedFile( + fileRepository: FileRepository, + datasetId: number | string, + file: File, + storageId: string +): void { + fileRepository + .addUploadedFile(datasetId, { file: file }, storageId) + .then(() => { + // console.log('File added to the dataset successfully.') + }) + .catch((error: Error) => { + throw new Error(error.message) + }) +} diff --git a/src/files/infrastructure/FileJSDataverseRepository.ts b/src/files/infrastructure/FileJSDataverseRepository.ts index fc529c733..9747cff7b 100644 --- a/src/files/infrastructure/FileJSDataverseRepository.ts +++ b/src/files/infrastructure/FileJSDataverseRepository.ts @@ -15,7 +15,8 @@ import { getFileDownloadCount, getFileUserPermissions, ReadError, - uploadFile as jsUploadFile + uploadFile as jsUploadFile, + addUploadedFileToDataset } from '@iqss/dataverse-client-javascript' import { FileCriteria } from '../domain/models/FileCriteria' import { DomainFileMapper } from './mappers/DomainFileMapper' @@ -250,4 +251,8 @@ export class FileJSDataverseRepository implements FileRepository { .execute(datasetId, file.file, progress, abortController) .then(storageIdSetter) } + + addUploadedFile(datasetId: number | string, file: FileHolder, storageId: string): Promise { + return addUploadedFileToDataset.execute(datasetId, file.file, storageId) + } } diff --git a/src/sections/upload-dataset-files/UploadDatasetFiles.tsx b/src/sections/upload-dataset-files/UploadDatasetFiles.tsx index 75e27a3f5..de248559c 100644 --- a/src/sections/upload-dataset-files/UploadDatasetFiles.tsx +++ b/src/sections/upload-dataset-files/UploadDatasetFiles.tsx @@ -9,6 +9,7 @@ import { FileUploader } from './FileUploader' import { FileUploadState, FileUploadTools } from '../../files/domain/models/FileUploadState' import { uploadFile } from '../../files/domain/useCases/uploadFile' import { UploadedFiles } from './UploadedFiles' +import { addUploadedFile } from '../../files/domain/useCases/addUploadedFile' interface UploadDatasetFilesProps { fileRepository: FileRepository @@ -118,6 +119,37 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat setState(FileUploadTools.delete(file, fileUploaderState)) } + type MutableFile = { + -readonly [K in keyof File]: File[K] + } + + const stateToFiles = (state: FileUploadState[]) => + state.map((x) => { + const f = new File([], x.fileName, { type: x.fileType }) + const mutable: MutableFile = f + mutable.webkitRelativePath = x.fileDir + const res: File = mutable + return res + }) + + const cleanAllState = () => { + stateToFiles(Array.from(fileUploaderState.state.values())).forEach((file) => { + cleanup(file) + setState(FileUploadTools.delete(file, fileUploaderState)) + }) + } + + const addFiles = (state: FileUploadState[]) => { + stateToFiles(state).forEach((file, index) => + addUploadedFile( + fileRepository, + dataset?.persistentId as string, + file, + state[index].storageId as string + ) + ) + } + useEffect(() => { setIsLoading(isLoading) }, [isLoading, setIsLoading]) @@ -151,6 +183,8 @@ export const UploadDatasetFiles = ({ fileRepository: fileRepository }: UploadDat fileUploadState={fileUploaderState.uploaded} cancelTitle={t('cancel')} deleteFile={deleteFile} + cleanup={cleanAllState} + addFiles={addFiles} /> diff --git a/src/sections/upload-dataset-files/UploadedFiles.tsx b/src/sections/upload-dataset-files/UploadedFiles.tsx index 022f555f9..338366901 100644 --- a/src/sections/upload-dataset-files/UploadedFiles.tsx +++ b/src/sections/upload-dataset-files/UploadedFiles.tsx @@ -2,22 +2,42 @@ import { Button, Card } from '@iqss/dataverse-design-system' import { X } from 'react-bootstrap-icons' import { FileUploadState } from '../../files/domain/models/FileUploadState' import styles from './FileUploader.module.scss' +import { useState } from 'react' interface DatasetFilesProps { fileUploadState: FileUploadState[] cancelTitle: string deleteFile: (file: FileUploadState) => void + cleanup: () => void + addFiles: (fileUploadState: FileUploadState[]) => void } -export function UploadedFiles({ fileUploadState, cancelTitle, deleteFile }: DatasetFilesProps) { +export function UploadedFiles({ + fileUploadState, + cancelTitle, + deleteFile, + cleanup, + addFiles +}: DatasetFilesProps) { + const [saving, setSaving] = useState(false) + const save = () => { + setSaving(true) + addFiles(fileUploadState) + cleanup() + setSaving(false) + } + return (