diff --git a/.eslintrc.json b/.eslintrc.json index 3177f5517..dbdefa329 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -42,9 +42,19 @@ "unused-imports/no-unused-imports": "error", "unused-imports/no-unused-vars": [ "warn", - { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" } + { + "vars": "all", + "varsIgnorePattern": "^_", + "args": "after-used", + "argsIgnorePattern": "^_" + } + ], + "@typescript-eslint/no-empty-function": [ + "error", + { + "allow": ["arrowFunctions"] + } ], - "@typescript-eslint/no-empty-function": ["error", { "allow": ["arrowFunctions"] }], "react/react-in-jsx-scope": "off", "prettier/prettier": [ "error", diff --git a/dev.Dockerfile b/dev.Dockerfile index 5e474faf8..fb0b8ee2e 100644 --- a/dev.Dockerfile +++ b/dev.Dockerfile @@ -10,6 +10,7 @@ RUN npm run build WORKDIR /usr/src/app COPY package.json ./ +COPY package-lock.json ./ COPY .npmrc ./ RUN npm install diff --git a/package-lock.json b/package-lock.json index 9cab908e7..a73e91aca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr97.418bf5e", + "@iqss/dataverse-client-javascript": "2.0.0-pr99.c36f1db", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -24,6 +24,7 @@ "i18next": "22.4.9", "i18next-browser-languagedetector": "7.0.1", "i18next-http-backend": "2.1.1", + "moment-timezone": "0.5.43", "react-bootstrap": "2.7.2", "react-bootstrap-icons": "1.10.3", "react-i18next": "12.1.5", @@ -3588,9 +3589,9 @@ }, "node_modules/@iqss/dataverse-client-javascript": { "name": "@IQSS/dataverse-client-javascript", - "version": "2.0.0-pr97.418bf5e", - "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr97.418bf5e/0d604232d6f567b41328143c2d5d27513d069380", - "integrity": "sha512-wKRR1ORFkFSoFoCrWsCQvcm1vExIIrVN3pJbIl9Nk8lopP7zkUEB1xcAU+UVShbGktIZIa20TQMFl0ONafDiZw==", + "version": "2.0.0-pr99.c36f1db", + "resolved": "https://npm.pkg.github.com/download/@IQSS/dataverse-client-javascript/2.0.0-pr99.c36f1db/3f49037b14e53295c39ce787cce53f20b2558ba6", + "integrity": "sha512-KzMVzB420eKKaOuwDEpvAB/k1RrW3Le/ZJcVtjxFk/Wvxov2Jl1npbwy4SXQWasEXaJWslohn2KRkBfBDoTHTQ==", "license": "MIT", "dependencies": { "@types/node": "^18.15.11", @@ -31965,6 +31966,25 @@ "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", "dev": true }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.43", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.43.tgz", + "integrity": "sha512-72j3aNyuIsDxdF1i7CEgV2FfxM1r6aaqJyLB2vwb33mXYyoyLly+F1zbWqhA3/bVIoJ4szlUoMbUnVdid32NUQ==", + "dependencies": { + "moment": "^2.29.4" + }, + "engines": { + "node": "*" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", diff --git a/package.json b/package.json index 0c61160a5..af7cf0bdb 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ }, "dependencies": { "@faker-js/faker": "7.6.0", - "@iqss/dataverse-client-javascript": "2.0.0-pr97.418bf5e", + "@iqss/dataverse-client-javascript": "2.0.0-pr99.c36f1db", "@iqss/dataverse-design-system": "*", "@istanbuljs/nyc-config-typescript": "1.0.2", "@tanstack/react-table": "8.9.2", @@ -28,6 +28,7 @@ "i18next": "22.4.9", "i18next-browser-languagedetector": "7.0.1", "i18next-http-backend": "2.1.1", + "moment-timezone": "0.5.43", "react-bootstrap": "2.7.2", "react-bootstrap-icons": "1.10.3", "react-i18next": "12.1.5", diff --git a/public/locales/en/dataset.json b/public/locales/en/dataset.json index 2e847daeb..e08244985 100644 --- a/public/locales/en/dataset.json +++ b/public/locales/en/dataset.json @@ -61,6 +61,30 @@ "uploadFiles": "Upload Files" }, "alerts": { + "publishInProgress": { + "heading": "Publish in Progress", + "alertText": "The dataset is locked while the persistent identifiers are being registered or updated, and/or the physical files are being validated." + }, + "filesUpdated": { + "heading": "Success!", + "alertText": "One or more files have been updated." + }, + "termsUpdated": { + "heading": "Success!", + "alertText": "The terms for this dataset have been updated." + }, + "thumbnailUpdated": { + "heading": "Success!", + "alertText": "Dataset thumbnail updated." + }, + "datasetDeleted": { + "heading": "Success!", + "alertText": "This dataset draft has been deleted." + }, + "metadataUpdated": { + "heading": "Success!", + "alertText": "The metadata for this dataset has been updated." + }, "draftVersion": { "heading": "This draft version needs to be published", "alertText": "When ready for sharing, please publish it so that others can see these changes" diff --git a/public/locales/en/files.json b/public/locales/en/files.json index 3e8938625..98fc2b0bb 100644 --- a/public/locales/en/files.json +++ b/public/locales/en/files.json @@ -12,6 +12,7 @@ "pageSize": "Files per page" }, "tabularData": { + "name": "Tabular Data", "variables": "Variables", "observations": "Observations" }, @@ -112,6 +113,14 @@ "title": "Access File", "headers": { "fileAccess": "File Access" + }, + "downloadOptions": { + "title": "Download Options", + "options": { + "original": "Original File Format", + "RData": "R Data", + "tabular": "Tab-Delimited" + } } }, "editFilesMenu": { diff --git a/src/alert/domain/models/Alert.ts b/src/alert/domain/models/Alert.ts new file mode 100644 index 000000000..d48051847 --- /dev/null +++ b/src/alert/domain/models/Alert.ts @@ -0,0 +1,23 @@ +import { AlertVariant } from '@iqss/dataverse-design-system/dist/components/alert/AlertVariant' + +export enum AlertMessageKey { + DRAFT_VERSION = 'draftVersion', + REQUESTED_VERSION_NOT_FOUND = 'requestedVersionNotFound', + REQUESTED_VERSION_NOT_FOUND_SHOW_DRAFT = 'requestedVersionNotFoundShowDraft', + SHARE_UNPUBLISHED_DATASET = 'shareUnpublishedDataset', + UNPUBLISHED_DATASET = 'unpublishedDataset', + METADATA_UPDATED = 'metadataUpdated', + FILES_UPDATED = 'filesUpdated', + TERMS_UPDATED = 'termsUpdated', + THUMBNAIL_UPDATED = 'thumbnailUpdated', + DATASET_DELETED = 'datasetDeleted', + PUBLISH_IN_PROGRESS = 'publishInProgress' +} + +export class Alert { + constructor( + public readonly variant: AlertVariant, + public readonly messageKey: AlertMessageKey, + public dynamicFields?: object + ) {} +} diff --git a/src/dataset/domain/models/Dataset.ts b/src/dataset/domain/models/Dataset.ts index 75098b40d..f500394ae 100644 --- a/src/dataset/domain/models/Dataset.ts +++ b/src/dataset/domain/models/Dataset.ts @@ -1,4 +1,4 @@ -import { AlertVariant } from '@iqss/dataverse-design-system/dist/components/alert/AlertVariant' +import { Alert, AlertMessageKey } from '../../../alert/domain/models/Alert' import { FileDownloadSize } from '../../../files/domain/models/File' export enum DatasetLabelSemanticMeaning { @@ -25,22 +25,6 @@ export class DatasetLabel { ) {} } -export enum DatasetAlertMessageKey { - DRAFT_VERSION = 'draftVersion', - REQUESTED_VERSION_NOT_FOUND = 'requestedVersionNotFound', - REQUESTED_VERSION_NOT_FOUND_SHOW_DRAFT = 'requestedVersionNotFoundShowDraft', - SHARE_UNPUBLISHED_DATASET = 'shareUnpublishedDataset', - UNPUBLISHED_DATASET = 'unpublishedDataset' -} - -export class DatasetAlert { - constructor( - public readonly variant: AlertVariant, - public readonly message: DatasetAlertMessageKey, - public readonly dynamicFields?: object - ) {} -} - export enum MetadataBlockName { CITATION = 'citation', GEOSPATIAL = 'geospatial', @@ -260,20 +244,19 @@ export interface DatasetPermissions { } export interface DatasetLock { - id: number + userPersistentId: string reason: DatasetLockReason } export enum DatasetLockReason { - INGEST = 'ingest', - WORKFLOW = 'workflow', - IN_REVIEW = 'inReview', - DCM_UPLOAD = 'dcmUpload', - GLOBUS_UPLOAD = 'globusUpload', + INGEST = 'Ingest', + WORKFLOW = 'Workflow', + IN_REVIEW = 'InReview', + DCM_UPLOAD = 'DcmUpload', + GLOBUS_UPLOAD = 'GlobusUpload', FINALIZE_PUBLICATION = 'finalizePublication', - - EDIT_IN_PROGRESS = 'editInProgress', - FILE_VALIDATION_FAILED = 'fileValidationFailed' + EDIT_IN_PROGRESS = 'EditInProgress', + FILE_VALIDATION_FAILED = 'FileValidationFailed' } export interface PrivateUrl { @@ -287,7 +270,7 @@ export class Dataset { public readonly version: DatasetVersion, public readonly citation: string, public readonly labels: DatasetLabel[], - public readonly alerts: DatasetAlert[], + public readonly alerts: Alert[], public readonly summaryFields: DatasetMetadataBlock[], public readonly license: DatasetLicense, public readonly metadataBlocks: DatasetMetadataBlocks, @@ -306,8 +289,8 @@ export class Dataset { return this.metadataBlocks[0].fields.title } - public get isLockedFromPublishing(): boolean { - return this.isLockedFromEdits + public checkIsLockedFromPublishing(userPersistentId: string): boolean { + return this.checkIsLockedFromEdits(userPersistentId) } public get isLocked(): boolean { @@ -318,19 +301,56 @@ export class Dataset { return this.locks.some((lock) => lock.reason === DatasetLockReason.WORKFLOW) } - public get isLockedFromEdits(): boolean { + public checkIsLockedFromEdits(userPersistentId: string): boolean { const lockedReasonIsInReview = this.locks.some( (lock) => lock.reason === DatasetLockReason.IN_REVIEW ) - // If the lock reason is workflow and the workflow userId is the same as the current user, then the user can edit - // TODO - Ask how we want to manage pending workflows + + if ( + this.locks.some( + (lock) => + lock.reason === DatasetLockReason.WORKFLOW && lock.userPersistentId === userPersistentId + ) + ) { + return false + } return this.isLocked && !(lockedReasonIsInReview && this.permissions.canPublishDataset) } + public get isLockedFromFileDownload(): boolean { + if (!this.isLocked) { + return false + } + + if ( + this.locks.some((lock) => + [ + DatasetLockReason.FINALIZE_PUBLICATION, + DatasetLockReason.DCM_UPLOAD, + DatasetLockReason.INGEST + ].includes(lock.reason) + ) + ) { + return true + } + + if ( + this.locks.some((lock) => lock.reason === DatasetLockReason.IN_REVIEW) && + !this.permissions.canUpdateDataset + ) { + return true + } + + // If the lock reason is workflow and the workflow userId is different than the current user, then is locked + // TODO - Ask how we want to manage pending workflows + + return false + } + static Builder = class { public readonly labels: DatasetLabel[] = [] - public readonly alerts: DatasetAlert[] = [] + public readonly alerts: Alert[] = [] constructor( public readonly persistentId: string, @@ -403,7 +423,7 @@ export class Dataset { this.version.publishingStatus === DatasetPublishingStatus.DRAFT && this.permissions.canPublishDataset ) { - this.alerts.push(new DatasetAlert('warning', DatasetAlertMessageKey.DRAFT_VERSION)) + this.alerts.push(new Alert('warning', AlertMessageKey.DRAFT_VERSION)) } if (this.version.requestedVersion) { if (this.version.latestVersionStatus == DatasetPublishingStatus.RELEASED) { @@ -412,20 +432,16 @@ export class Dataset { returnedVersion: `${this.version.toString()}` } this.alerts.push( - new DatasetAlert( - 'warning', - DatasetAlertMessageKey.REQUESTED_VERSION_NOT_FOUND, - dynamicFields - ) + new Alert('warning', AlertMessageKey.REQUESTED_VERSION_NOT_FOUND, dynamicFields) ) } else { const dynamicFields = { requestedVersion: this.version.requestedVersion } this.alerts.push( - new DatasetAlert( + new Alert( 'warning', - DatasetAlertMessageKey.REQUESTED_VERSION_NOT_FOUND_SHOW_DRAFT, + AlertMessageKey.REQUESTED_VERSION_NOT_FOUND_SHOW_DRAFT, dynamicFields ) ) @@ -435,14 +451,10 @@ export class Dataset { if (this.permissions.canPublishDataset) { const dynamicFields = { privateUrl: this.privateUrl.urlSnippet + this.privateUrl.token } this.alerts.push( - new DatasetAlert( - 'info', - DatasetAlertMessageKey.SHARE_UNPUBLISHED_DATASET, - dynamicFields - ) + new Alert('info', AlertMessageKey.SHARE_UNPUBLISHED_DATASET, dynamicFields) ) } else { - this.alerts.push(new DatasetAlert('warning', DatasetAlertMessageKey.UNPUBLISHED_DATASET)) + this.alerts.push(new Alert('warning', AlertMessageKey.UNPUBLISHED_DATASET)) } } } diff --git a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts index f8ebf5f97..fe5bb7fc0 100644 --- a/src/dataset/infrastructure/mappers/JSDatasetMapper.ts +++ b/src/dataset/infrastructure/mappers/JSDatasetMapper.ts @@ -3,7 +3,9 @@ import { DatasetMetadataBlock as JSDatasetMetadataBlock, DatasetMetadataBlocks as JSDatasetMetadataBlocks, DatasetMetadataFields as JSDatasetMetadataFields, - DatasetVersionInfo as JSDatasetVersionInfo + DatasetVersionInfo as JSDatasetVersionInfo, + DatasetUserPermissions as JSDatasetPermissions, + DatasetLock as JSDatasetLock } from '@iqss/dataverse-client-javascript' import { DatasetVersionState as JSDatasetVersionState } from '@iqss/dataverse-client-javascript/dist/datasets/domain/models/Dataset' import { @@ -14,6 +16,9 @@ import { DatasetMetadataFields, DatasetVersion, MetadataBlockName, + DatasetPermissions, + DatasetLock, + DatasetLockReason, PrivateUrl } from '../../domain/models/Dataset' @@ -22,6 +27,8 @@ export class JSDatasetMapper { jsDataset: JSDataset, citation: string, summaryFieldsNames: string[], + jsDatasetPermissions: JSDatasetPermissions, + jsDatasetLocks: JSDatasetLock[], requestedVersion?: string, privateUrl?: PrivateUrl ): Dataset { @@ -37,20 +44,12 @@ export class JSDatasetMapper { jsDataset.publicationDate, jsDataset.citationDate ), - { - canDownloadFiles: true, - canUpdateDataset: true, - canPublishDataset: true, - canManageDatasetPermissions: true, - canManageFilesPermissions: true, - canDeleteDataset: true - }, // TODO Connect with dataset permissions - [], // TODO Connect with dataset locks + JSDatasetMapper.toDatasetPermissions(jsDatasetPermissions), + JSDatasetMapper.toLocks(jsDatasetLocks), true, // TODO Connect with dataset hasValidTermsOfAccess true, // TODO Connect with dataset hasOneTabularFileAtLeast true, // TODO Connect with dataset isValid - jsDataset.versionInfo.releaseTime !== undefined && - !isNaN(jsDataset.versionInfo.releaseTime.getTime()), // TODO Connect with dataset isReleased, + JSDatasetMapper.toIsReleased(jsDataset.versionInfo), undefined, // TODO: get dataset thumbnail from Dataverse https://github.com/IQSS/dataverse-frontend/issues/203 privateUrl, [] // TODO: Connect with file download use case @@ -187,4 +186,30 @@ export class JSDatasetMapper { return extraFields } + + static toIsReleased(jsDatasetVersionInfo: JSDatasetVersionInfo): boolean { + return ( + jsDatasetVersionInfo.releaseTime !== undefined && + !isNaN(jsDatasetVersionInfo.releaseTime.getTime()) + ) + } + + static toDatasetPermissions(jsDatasetPermissions: JSDatasetPermissions): DatasetPermissions { + return { + canDownloadFiles: true, // TODO: connect with js-dataverse + canUpdateDataset: jsDatasetPermissions.canEditDataset, + canPublishDataset: jsDatasetPermissions.canPublishDataset, + canManageDatasetPermissions: jsDatasetPermissions.canManageDatasetPermissions, + canManageFilesPermissions: true, // TODO: connect with js-dataverse DatasetPermissions.canManageFilesPermissions + canDeleteDataset: jsDatasetPermissions.canManageDatasetPermissions + } + } + static toLocks(jsDatasetLocks: JSDatasetLock[]): DatasetLock[] { + return jsDatasetLocks.map((jsDatasetLock) => { + return { + userPersistentId: jsDatasetLock.userId, + reason: jsDatasetLock.lockType as unknown as DatasetLockReason + } + }) + } } diff --git a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts index c3fc7fbf0..2b624ca35 100644 --- a/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts +++ b/src/dataset/infrastructure/repositories/DatasetJSDataverseRepository.ts @@ -5,9 +5,13 @@ import { getDatasetCitation, getDatasetSummaryFieldNames, Dataset as JSDataset, + DatasetUserPermissions as JSDatasetPermissions, getPrivateUrlDataset, getPrivateUrlDatasetCitation, - ReadError + getDatasetUserPermissions, + ReadError, + getDatasetLocks, + DatasetLock as JSDatasetLock } from '@iqss/dataverse-client-javascript' import { JSDatasetMapper } from '../mappers/JSDatasetMapper' @@ -23,11 +27,27 @@ export class DatasetJSDataverseRepository implements DatasetRepository { Promise.all([ jsDataset, getDatasetSummaryFieldNames.execute(), - getDatasetCitation.execute(jsDataset.id, this.versionToVersionId(version)) + getDatasetCitation.execute(jsDataset.id, this.versionToVersionId(version)), + getDatasetUserPermissions.execute(jsDataset.id), + getDatasetLocks.execute(jsDataset.id) ]) ) - .then(([jsDataset, summaryFieldsNames, citation]: [JSDataset, string[], string]) => - JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames, requestedVersion) + .then( + ([jsDataset, summaryFieldsNames, citation, jsDatasetPermissions, jsDatasetLocks]: [ + JSDataset, + string[], + string, + JSDatasetPermissions, + JSDatasetLock[] + ]) => + JSDatasetMapper.toDataset( + jsDataset, + citation, + summaryFieldsNames, + jsDatasetPermissions, + jsDatasetLocks, + requestedVersion + ) ) .catch((error: ReadError) => { if (!version) { @@ -44,8 +64,20 @@ export class DatasetJSDataverseRepository implements DatasetRepository { getPrivateUrlDatasetCitation.execute(privateUrlToken) ]) .then(([jsDataset, summaryFieldsNames, citation]: [JSDataset, string[], string]) => - JSDatasetMapper.toDataset(jsDataset, citation, summaryFieldsNames, undefined) - ) + JSDatasetMapper.toDataset( + jsDataset, + citation, + summaryFieldsNames, + { + canEditDataset: true, + canPublishDataset: true, + canManageDatasetPermissions: true, + canDeleteDatasetDraft: true, + canViewUnpublishedDataset: true + }, + [] + ) + ) // TODO Connect with JS dataset permissions and getDatasetLocks.execute(privateUrlToken) when it is available in js-dataverse .catch((error: ReadError) => { throw new Error(error.message) }) diff --git a/src/files/domain/models/File.ts b/src/files/domain/models/File.ts index f5a376b0c..d95434292 100644 --- a/src/files/domain/models/File.ts +++ b/src/files/domain/models/File.ts @@ -1,3 +1,5 @@ +import FileTypeToFriendlyTypeMap from './FileTypeToFriendlyTypeMap' + export enum FileSizeUnit { BYTES = 'B', KILOBYTES = 'KB', @@ -136,15 +138,10 @@ export interface FileLabel { } export class FileType { - constructor(readonly value: string) {} + constructor(readonly value: string, readonly original?: string) {} toDisplayFormat(): string { - const words = this.value.split(' ') - return words - .map((word) => { - return word[0].toUpperCase() + word.substring(1) - }) - .join(' ') + return FileTypeToFriendlyTypeMap[this.value] || FileTypeToFriendlyTypeMap.unknown } } diff --git a/src/files/domain/models/FilePaginationInfo.ts b/src/files/domain/models/FilePaginationInfo.ts index 5f0e1ddcc..fbef8d177 100644 --- a/src/files/domain/models/FilePaginationInfo.ts +++ b/src/files/domain/models/FilePaginationInfo.ts @@ -24,7 +24,7 @@ export class FilePaginationInfo { withPageSize(pageSize: number): FilePaginationInfo { const getNewPage = (oldPageSize: number, newPageSize: number) => { - const newPage = Math.ceil((this.page * oldPageSize) / newPageSize) + const newPage = Math.ceil(((this.page - 1) * oldPageSize + 1) / newPageSize) return newPage > 0 ? newPage : 1 } return new FilePaginationInfo(getNewPage(this.pageSize, pageSize), pageSize, this.totalFiles) diff --git a/src/files/domain/models/FileTypeToFriendlyTypeMap.ts b/src/files/domain/models/FileTypeToFriendlyTypeMap.ts new file mode 100644 index 000000000..6f40517aa --- /dev/null +++ b/src/files/domain/models/FileTypeToFriendlyTypeMap.ts @@ -0,0 +1,228 @@ +const MimeTypeDisplay: Record = { + // Documentation + 'application/pdf': 'Adobe PDF', + 'image/pdf': 'Adobe PDF', + 'text/pdf': 'Adobe PDF', + 'application/x-pdf': 'Adobe PDF', + 'application/cnt': 'Windows Help Contents File', + 'application/msword': 'MS Word', + 'application/vnd.ms-excel': 'MS Excel Spreadsheet', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 'MS Excel Spreadsheet', + 'application/vnd.ms-powerpoint': 'MS Powerpoint', + 'application/vnd.openxmlformats-officedocument.presentationml.presentation': 'MS Powerpoint', + 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'MS Word', + 'application/vnd.oasis.opendocument.spreadsheet': 'OpenOffice Spreadsheet', + 'application/vnd.ms-excel.sheet.macroenabled.12': 'MS Excel Spreadsheet', + // Text + 'text/plain': 'Plain Text', + 'text/x-log': 'Application Log', + 'text/html': 'HTML', + 'application/x-tex': 'LaTeX', + 'text/x-tex': 'LaTeX', + 'text/markdown': 'Markdown Text', + 'text/x-markdown': 'Markdown Text', + 'text/x-r-markdown': 'R Markdown Text', + 'application/rtf': 'Rich Text Format', + 'text/x-rst': 'reStructuredText', + 'text/rtf': 'Rich Text Format', + 'text/richtext': 'Rich Text Format', + 'text/turtle': 'Turtle RDF', + 'application/xml': 'XML', + 'text/xml': 'XML', + // Code + 'text/x-c': 'C++ Source', + 'text/x-c++src': 'C++ Source', + 'text/css': 'Cascading Style Sheet', + 'text/x-fortran': 'Fortran Source Code', + 'application/java-vm': 'Java Class', + 'text/x-java-source': 'Java Source Code', + 'text/javascript': 'Javascript Code', + 'application/javascript': 'Javascript Code', + 'application/x-javascript': 'Javascript Code', + 'text/x-matlab': 'MATLAB Source Code', + 'text/x-mathematica': 'Mathematica Input', + 'text/x-objcsrc': 'Objective-C Source Code', + 'text/x-pascal': 'Pascal Source Code', + 'text/x-perl': 'Perl Script', + 'text/x-perl-script': 'Perl Script', + 'text/php': 'PHP Source Code', + 'application/postscript': 'Postscript', + 'text/x-python': 'Python Source Code', + 'text/x-python-script': 'Python Source Code', + 'text/x-r-source': 'R Source Code', + 'text/x-sh': 'Shell Script', + 'application/x-sh': 'Shell Script', + 'application/x-shellscript': 'Shell Script', + 'application/x-sql': 'SQL Code', + 'text/x-sql': 'SQL Code', + 'application/x-swc': 'Shockwave Flash Component', + 'application/x-msdownload': 'Windows Executable', + 'application/x-ipynb+json': 'Jupyter Notebook', + 'application/x-stata-ado': 'Stata Ado Script', + 'application/x-stata-do': 'Stata Do Script', + 'application/x-stata-dta': 'Stata Data Script', + 'application/x-stata-smcl': 'Stata Markup and Control Language', + 'text/x-stata-syntax': 'Stata Syntax', + 'application/x-stata-syntax': 'Stata Syntax', + 'text/x-spss-syntax': 'SPSS Syntax', + 'application/x-spss-syntax': 'SPSS Syntax', + 'application/x-spss-sps': 'SPSS Script Syntax', + 'text/x-sas-syntax': 'SAS Syntax', + 'application/x-sas-syntax': 'SAS Syntax', + 'type/x-r-syntax': 'R Syntax', + 'application/vnd.wolfram.mathematica.package': 'Wolfram Mathematica Code', + 'application/vnd.wolfram.mathematica': 'Wolfram Mathematica Code', + 'text/x-workflow-description-language': 'Workflow Description Language', + 'text/x-computational-workflow-language': 'Computational Workflow Language', + 'text/x-nextflow': 'Nextflow Script', + 'text/x-r-notebook': 'R Notebook', + 'text/x-ruby-script': 'Ruby Source Code', + 'text/x-dagman': 'DAGMan Workflow', + 'text/x-makefile': 'Makefile Script', + 'text/x-snakemake': 'Snakemake Workflow', + // Ingested Tabular Data + 'text/tab-separated-values': 'Tab-Delimited', + 'text/tsv': 'Tab-Separated Values', + 'text/comma-separated-values': 'Comma Separated Values', + 'text/x-comma-separated-values': 'Comma Separated Values', + 'text/csv': 'Comma Separated Values', + 'text/x-fixed-field': 'Fixed Field Text Data', + 'application/vnd.flographit': 'FloGraphIt Media', + 'application/x-r-data': 'R Data', + 'application/x-rlang-transport': 'R Data', + 'application/x-R-2': 'R Binary', + 'application/x-stata': 'Stata Binary', + 'application/x-stata-6': 'Stata Binary', + 'application/x-stata-13': 'Stata 13 Binary', + 'application/x-stata-14': 'Stata 14 Binary', + 'application/x-stata-15': 'Stata 15 Binary', + 'application/x-spss-por': 'SPSS Portable', + 'application/x-spss-portable': 'SPSS Portable', + 'application/x-spss-sav': 'SPSS Binary', + 'application/x-sas': 'SAS', + 'application/x-sas-transport': 'SAS Transport', + 'application/x-sas-system': 'SAS System', + 'application/x-sas-data': 'SAS Data', + 'application/x-sas-catalog': 'SAS Catalog', + 'application/x-sas-log': 'SAS Log', + 'application/x-sas-output': 'SAS Output', + 'application/softgrid-do': 'Softgrid DTA Script', + 'application/x-dvn-csvspss-zip': 'CSV (w/SPSS card)', + 'application/x-dvn-tabddi-zip': 'TAB (w/DDI)', + 'application/x-emf': 'Extended Metafile', + 'application/x-h5': 'Hierarchical Data Format', + 'application/x-hdf': 'Hierarchical Data Format', + 'application/x-hdf5': 'Hierarchical Data Format', + 'application/geo+json': 'GeoJSON', + 'application/json': 'JSON', + 'application/mathematica': 'Mathematica', + 'application/matlab-mat': 'MATLAB Data', + 'application/x-matlab-data': 'MATLAB Data', + 'application/x-matlab-figure': 'MATLAB Figure', + 'application/x-matlab-workspace': 'MATLAB Workspace', + 'text/x-vcard': 'Virtual Contact File', + 'application/x-xfig': 'MATLAB Figure', + 'application/x-msaccess': 'MS Access', + 'application/netcdf': 'Network Common Data Form', + 'application/x-netcdf': 'Network Common Data Form', + 'application/vnd.lotus-notes': 'Notes Storage Facility', + 'application/x-nsdstat': 'NSDstat', + 'application/vnd.realvnc.bed': 'PLINK Binary', + 'application/vnd.ms-pki.stl': 'STL Format', + 'application/vnd.isac.fcs': 'FCS Data', + 'application/java-serialized-object': 'Java Serialized Object', + 'chemical/x-xyz': 'Co-Ordinate Animation', + // FITS + 'image/fits': 'FITS', + 'application/fits': 'FITS', + // Shape + 'application/dbf': 'dBASE Table for ESRI Shapefile', + 'application/dbase': 'dBASE Table for ESRI Shapefile', + 'application/prj': 'ESRI Shapefile', + 'application/sbn': 'ESRI Spatial Index', + 'application/sbx': 'ESRI Spatial Index', + 'application/shp': 'Shape', + 'application/shx': 'Shape', + 'application/x-esri-shape': 'ESRI Shapefile', + 'application/vnd.google-earth.kml+xml': 'Keyhole Markup Language', + 'application/zipped-shapefile': 'Zipped Shapefiles', + // Archive + 'application/zip': 'ZIP Archive', + 'application/x-zip-compressed': 'ZIP Archive', + 'application/vnd.antix.game-component': 'ATX Archive', + 'application/x-bzip': 'Bzip Archive', + 'application/x-bzip2': 'Bzip Archive', + 'application/vnd.google-earth.kmz': 'Google Earth Archive', + 'application/gzip': 'Gzip Archive', + 'application/x-gzip': 'Gzip Archive', + 'application/x-gzip-compressed': 'Gzip Archive', + 'application/rar': 'RAR Archive', + 'application/x-rar': 'RAR Archive', + 'application/x-rar-compressed': 'RAR Archive', + 'application/tar': 'TAR Archive', + 'application/x-tar': 'TAR Archive', + 'application/x-compressed': 'Compressed Archive', + 'application/x-compressed-tar': 'TAR Archive', + 'application/x-7z-compressed': '7Z Archive', + 'application/x-xz': 'XZ Archive', + 'application/warc': 'Web Archive', + 'application/x-iso9660-image': 'Optical Disc Image', + 'application/vnd.eln+zip': 'ELN Archive', + // Image + 'image/gif': 'GIF Image', + 'image/jpeg': 'JPEG Image', + 'image/jp2': 'JPEG-2000 Image', + 'image/x-portable-bitmap': 'Bitmap Image', + 'image/x-portable-graymap': 'Graymap Image', + 'image/png': 'PNG Image', + 'image/x-portable-anymap': 'Anymap Image', + 'image/x-portable-pixmap': 'Pixmap Image', + 'application/x-msmetafile': 'Enhanced Metafile', + 'application/dicom': 'DICOM Image', + 'image/dicom-rle': 'DICOM Image', + 'image/nii': 'NIfTI Image', + 'image/cmu-raster': 'Raster Image', + 'image/x-rgb': 'RGB Image', + 'image/svg+xml': 'SVG Image', + 'image/tiff': 'TIFF Image', + 'image/bmp': 'Bitmap Image', + 'image/x-xbitmap': 'Bitmap Image', + 'image/RAW': 'Bitmap Image', + 'image/raw': 'Bitmap Image', + 'application/x-tgif': 'TGIF File', + 'image/x-xpixmap': 'Pixmap Image', + 'image/x-xwindowdump': 'X Windows Dump', + 'application/photoshop': 'Photoshop Image', + 'image/vnd.adobe.photoshop': 'Photoshop Image', + 'application/x-photoshop': 'Photoshop Image', + // Audio + 'audio/x-aiff': 'AIFF Audio', + 'audio/mp3': 'MP3 Audio', + 'audio/mpeg': 'MP3 Audio', + 'audio/mp4': 'MPEG-4 Audio', + 'audio/x-m4a': 'MPEG-4 Audio', + 'audio/ogg': 'OGG Audio', + 'audio/wav': 'Waveform Audio', + 'audio/x-wav': 'Waveform Audio', + 'audio/x-wave': 'Waveform Audio', + // Video + 'video/avi': 'AVI Video', + 'video/x-msvideo': 'AVI Video', + 'video/mpeg': 'MPEG Video', + 'video/mp4': 'MPEG-4 Video', + 'video/x-m4v': 'MPEG-4 Video', + 'video/ogg': 'OGG Video', + 'video/quicktime': 'Quicktime Video', + 'video/webm': 'WebM Video', + // Network Data + 'text/xml-graphml': 'GraphML Network Data', + // Other + 'application/octet-stream': 'Unknown', + 'application/x-docker-file': 'Docker Image File', + 'application/x-vagrant-file': 'Vagrant Image File', + // Dataverse-specific + 'application/vnd.dataverse.file-package': 'Dataverse Package', + unknown: 'Unknown' +} + +export default MimeTypeDisplay diff --git a/src/files/infrastructure/mappers/DomainFileMapper.ts b/src/files/infrastructure/mappers/DomainFileMapper.ts index cda15bf92..71082838d 100644 --- a/src/files/infrastructure/mappers/DomainFileMapper.ts +++ b/src/files/infrastructure/mappers/DomainFileMapper.ts @@ -13,7 +13,10 @@ import { import { FileType } from '../../domain/models/File' export class DomainFileMapper { - static toJSPagination(paginationInfo: FilePaginationInfo): { limit?: number; offset?: number } { + static toJSPagination(paginationInfo: FilePaginationInfo): { + limit?: number + offset?: number + } { return { limit: paginationInfo.pageSize, offset: (paginationInfo.page - 1) * paginationInfo.pageSize diff --git a/src/files/infrastructure/mappers/JSFileMapper.ts b/src/files/infrastructure/mappers/JSFileMapper.ts index 316c5019c..3956b6d25 100644 --- a/src/files/infrastructure/mappers/JSFileMapper.ts +++ b/src/files/infrastructure/mappers/JSFileMapper.ts @@ -41,7 +41,7 @@ export class JSFileMapper { this.toFileVersion(jsFile.version, datasetVersion, jsFile.publicationDate), this.toFileName(jsFile.name), this.toFileAccess(jsFile.restricted), - this.toFileType(jsFile.contentType), + this.toFileType(jsFile.contentType, jsFile.originalFormatLabel), this.toFileSize(jsFile.sizeBytes), this.toFileDate(jsFile.creationDate, jsFile.publicationDate, jsFile.embargo), this.toFileDownloads(), @@ -107,8 +107,8 @@ export class JSFileMapper { } } - static toFileType(jsFileContentType: string): FileType { - return new FileType(jsFileContentType) + static toFileType(jsFileContentType: string, jsOriginalFormatLabel?: string): FileType { + return new FileType(jsFileContentType, jsOriginalFormatLabel) } static toFileSize(jsFileSize: number): FileSize { diff --git a/src/sections/alerts/AlertContext.ts b/src/sections/alerts/AlertContext.ts new file mode 100644 index 000000000..48bcc8682 --- /dev/null +++ b/src/sections/alerts/AlertContext.ts @@ -0,0 +1,16 @@ +import { createContext, useContext } from 'react' + +import { Alert, AlertMessageKey } from '../../alert/domain/models/Alert' + +interface DatasetAlertContextProps { + datasetAlerts: Alert[] + addDatasetAlert: (newAlert: Alert) => void + removeDatasetAlert: (alertId: AlertMessageKey) => void +} + +export const AlertContext = createContext({ + datasetAlerts: [], + addDatasetAlert: /* istanbul ignore next */ () => {}, + removeDatasetAlert: /* istanbul ignore next */ () => {} +}) +export const useAlertContext = () => useContext(AlertContext) diff --git a/src/sections/alerts/AlertProvider.tsx b/src/sections/alerts/AlertProvider.tsx new file mode 100644 index 000000000..6504ff8e7 --- /dev/null +++ b/src/sections/alerts/AlertProvider.tsx @@ -0,0 +1,31 @@ +import { PropsWithChildren, useState } from 'react' +import { AlertContext } from './AlertContext' + +import { Alert, AlertMessageKey } from '../../alert/domain/models/Alert' + +export const AlertProvider = ({ children }: PropsWithChildren) => { + const [datasetAlerts, setDatasetAlerts] = useState([]) + + const addDatasetAlert = (newAlert: Alert) => { + // Check if an alert with the same id already exists + const alertExists = datasetAlerts.some((alert) => alert.messageKey === newAlert.messageKey) + + // If it doesn't exist, add it to the array + if (!alertExists) datasetAlerts.push(newAlert) + } + + const removeDatasetAlert = (alertId: AlertMessageKey) => { + setDatasetAlerts(datasetAlerts.filter((alert) => alert.messageKey !== alertId)) + } + + return ( + + {children} + + ) +} diff --git a/src/sections/alerts/Alerts.module.scss b/src/sections/alerts/Alerts.module.scss new file mode 100644 index 000000000..3a63a82e6 --- /dev/null +++ b/src/sections/alerts/Alerts.module.scss @@ -0,0 +1,4 @@ +.container > * { + margin-top: 1em; + margin-right: 0.5em; +} \ No newline at end of file diff --git a/src/sections/alerts/Alerts.tsx b/src/sections/alerts/Alerts.tsx new file mode 100644 index 000000000..e576b838b --- /dev/null +++ b/src/sections/alerts/Alerts.tsx @@ -0,0 +1,32 @@ +import { Alert as AlertComponent } from '@iqss/dataverse-design-system' + +import { useTranslation } from 'react-i18next' +import styles from './Alerts.module.scss' +import parse from 'html-react-parser' +import { useAlertContext } from './AlertContext' +import { Alert } from '../../alert/domain/models/Alert' + +export function Alerts() { + const { t } = useTranslation('dataset') + const { datasetAlerts } = useAlertContext() + return ( +
+ {datasetAlerts.map((alert: Alert, index) => { + const translatedMsg = alert.dynamicFields + ? t(`alerts.${alert.messageKey}.alertText`, alert.dynamicFields) + : t(`alerts.${alert.messageKey}.alertText`) + const translatedHeading = t(`alerts.${alert.messageKey}.heading`) + const alertKey = `alert-${index}` + return ( + + {parse(translatedMsg)} + + ) + })} +
+ ) +} diff --git a/src/sections/dataset/DatasetFactory.tsx b/src/sections/dataset/DatasetFactory.tsx index 1ac3e1e15..a256f892b 100644 --- a/src/sections/dataset/DatasetFactory.tsx +++ b/src/sections/dataset/DatasetFactory.tsx @@ -11,6 +11,7 @@ import { SettingJSDataverseRepository } from '../../settings/infrastructure/Sett import { FilePermissionsProvider } from '../file/file-permissions/FilePermissionsProvider' import { SettingsProvider } from '../settings/SettingsProvider' import { DatasetProvider } from './DatasetProvider' +import { AlertProvider } from '../alerts/AlertProvider' const datasetRepository = new DatasetJSDataverseRepository() const fileRepository = new FileJSDataverseRepository() @@ -24,7 +25,9 @@ export class DatasetFactory { - + + + diff --git a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx index 326518a5e..798e2d451 100644 --- a/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/edit-dataset-menu/EditDatasetMenu.tsx @@ -4,13 +4,16 @@ import { EditDatasetPermissionsMenu } from './EditDatasetPermissionsMenu' import { DeleteDatasetButton } from './DeleteDatasetButton' import { DeaccessionDatasetButton } from './DeaccessionDatasetButton' import { useTranslation } from 'react-i18next' +import { useSession } from '../../../session/SessionContext' interface EditDatasetMenuProps { dataset: Dataset } export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) { - if (!dataset.permissions.canUpdateDataset) { + const { user } = useSession() + + if (!user || !dataset.permissions.canUpdateDataset) { return <> } @@ -21,7 +24,7 @@ export function EditDatasetMenu({ dataset }: EditDatasetMenuProps) { title={t('datasetActionButtons.editDataset.title')} asButtonGroup variant="secondary" - disabled={dataset.isLockedFromEdits}> + disabled={dataset.checkIsLockedFromEdits(user.persistentId)}> {t('datasetActionButtons.editDataset.filesUpload')} diff --git a/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx b/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx index 58ec23cd8..ac7f9cc0d 100644 --- a/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx +++ b/src/sections/dataset/dataset-action-buttons/publish-dataset-menu/PublishDatasetMenu.tsx @@ -2,15 +2,18 @@ import { Dataset, DatasetPublishingStatus } from '../../../../dataset/domain/mod import { DropdownButton, DropdownButtonItem } from '@iqss/dataverse-design-system' import { ChangeCurationStatusMenu } from './ChangeCurationStatusMenu' import { useTranslation } from 'react-i18next' +import { useSession } from '../../../session/SessionContext' interface PublishDatasetMenuProps { dataset: Dataset } export function PublishDatasetMenu({ dataset }: PublishDatasetMenuProps) { + const { user } = useSession() if ( !dataset.version.isLatest || dataset.version.publishingStatus !== DatasetPublishingStatus.DRAFT || + !user || !dataset.permissions.canPublishDataset ) { return <> @@ -24,7 +27,9 @@ export function PublishDatasetMenu({ dataset }: PublishDatasetMenuProps) { asButtonGroup variant="secondary" disabled={ - dataset.isLockedFromPublishing || !dataset.hasValidTermsOfAccess || !dataset.isValid + dataset.checkIsLockedFromPublishing(user.persistentId) || + !dataset.hasValidTermsOfAccess || + !dataset.isValid }> {t('datasetActionButtons.publish.publish')} {dataset.version.isInReview && ( diff --git a/src/sections/dataset/dataset-action-buttons/submit-for-review-button/SubmitForReviewButton.tsx b/src/sections/dataset/dataset-action-buttons/submit-for-review-button/SubmitForReviewButton.tsx index 1f9c18a3a..b352b4065 100644 --- a/src/sections/dataset/dataset-action-buttons/submit-for-review-button/SubmitForReviewButton.tsx +++ b/src/sections/dataset/dataset-action-buttons/submit-for-review-button/SubmitForReviewButton.tsx @@ -1,17 +1,20 @@ import { Dataset, DatasetPublishingStatus } from '../../../../dataset/domain/models/Dataset' import { Button } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' +import { useSession } from '../../../session/SessionContext' interface SubmitForReviewButtonProps { dataset: Dataset } export function SubmitForReviewButton({ dataset }: SubmitForReviewButtonProps) { + const { user } = useSession() if ( !dataset.version.isLatest || dataset.version.publishingStatus !== DatasetPublishingStatus.DRAFT || dataset.isLockedInWorkflow || dataset.permissions.canPublishDataset || + !user || !dataset.permissions.canUpdateDataset ) { return <> @@ -22,7 +25,9 @@ export function SubmitForReviewButton({ dataset }: SubmitForReviewButtonProps) { ) diff --git a/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.tsx b/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.tsx index 53f20b7f0..ff1bd0d61 100644 --- a/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.tsx +++ b/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaFilterByType.tsx @@ -23,13 +23,13 @@ export function FileCriteriaFilterByType({ }: FileCriteriaFilterByTypeProps) { const { t } = useTranslation('files') const [selectedType, setSelectedType] = useState( - criteria.filterByType ?? new FileType('all') + criteria.filterByType ?? new FileType('All') ) const handleTypeChange = (eventKey: string | null) => { if (selectedType.value !== eventKey) { setSelectedType(new FileType(eventKey as string)) onCriteriaChange( - criteria.withFilterByType(eventKey === 'all' ? undefined : (eventKey as string)) + criteria.withFilterByType(eventKey === 'All' ? undefined : (eventKey as string)) ) } } @@ -41,13 +41,15 @@ export function FileCriteriaFilterByType({ return ( + eventKey="All" + className={selectedType.value === 'All' ? styles['selected-option'] : ''}> All diff --git a/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.tsx b/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.tsx index 5096d4001..26194bc35 100644 --- a/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.tsx +++ b/src/sections/dataset/dataset-files/file-criteria-form/FileCriteriaForm.tsx @@ -18,31 +18,36 @@ export function FileCriteriaForm({ onCriteriaChange, filesCountInfo }: FileCriteriaInputsProps) { - if (!filesCountInfo || filesCountInfo.total < MINIMUM_FILES_TO_SHOW_CRITERIA_INPUTS) { - return <> - } + const showFileCriteriaInputs = + filesCountInfo && filesCountInfo.total >= MINIMUM_FILES_TO_SHOW_CRITERIA_INPUTS return ( -
- - - - - - - - - - - - - - - - -
+
+
+ + {showFileCriteriaInputs && ( + + + + )} + + + + + {showFileCriteriaInputs && ( + + + + + + + + + )} +
+
) } diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx index 052d5957e..23f34bd02 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/edit-files-menu/EditFilesMenu.tsx @@ -30,7 +30,7 @@ export function EditFilesMenu({ files, fileSelection }: EditFilesMenuProps) { variant="secondary" id="edit-files-menu" title={t('actions.editFilesMenu.title')} - disabled={dataset.isLockedFromEdits || !dataset.hasValidTermsOfAccess} + disabled={dataset.checkIsLockedFromEdits(user.persistentId) || !dataset.hasValidTermsOfAccess} icon={}>
diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.tsx index 72a53597a..98f157ef9 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/AccessFileMenu.tsx @@ -4,6 +4,7 @@ import { AccessStatus } from './AccessStatus' import { RequestAccessOption } from './RequestAccessOption' import { DropdownButton, DropdownHeader, Tooltip } from '@iqss/dataverse-design-system' import { useTranslation } from 'react-i18next' +import { FileDownloadOptions } from './FileDownloadOptions' interface FileActionButtonAccessFileProps { file: File @@ -23,6 +24,7 @@ export function AccessFileMenu({ file }: FileActionButtonAccessFileProps) { + ) diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.tsx new file mode 100644 index 000000000..61f0e8400 --- /dev/null +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileDownloadOptions.tsx @@ -0,0 +1,30 @@ +import { DropdownHeader } from '@iqss/dataverse-design-system' +import { Download } from 'react-bootstrap-icons' +import { File } from '../../../../../../../../files/domain/models/File' +import { FileTabularDownloadOptions } from './FileTabularDownloadOptions' +import { FileNonTabularDownloadOptions } from './FileNonTabularDownloadOptions' +import { useTranslation } from 'react-i18next' + +interface FileDownloadOptionsProps { + file: File +} + +export function FileDownloadOptions({ file }: FileDownloadOptionsProps) { + const { t } = useTranslation('files') + return ( + <> + + {t('actions.accessFileMenu.downloadOptions.title')} + + + {file.tabularData ? ( + + ) : ( + + )} + + ) +} + +// TODO: Add guestbook support +// TODO: Add file package support diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx new file mode 100644 index 000000000..e7306abb0 --- /dev/null +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileNonTabularDownloadOptions.tsx @@ -0,0 +1,32 @@ +import { File, FileIngestStatus } from '../../../../../../../../files/domain/models/File' +import FileTypeToFriendlyTypeMap from '../../../../../../../../files/domain/models/FileTypeToFriendlyTypeMap' +import { DropdownButtonItem } from '@iqss/dataverse-design-system' +import { useDataset } from '../../../../../../DatasetContext' +import { useTranslation } from 'react-i18next' + +interface FileNonTabularDownloadOptionsProps { + file: File +} + +export function FileNonTabularDownloadOptions({ file }: FileNonTabularDownloadOptionsProps) { + const { t } = useTranslation('files') + const { dataset } = useDataset() + const originalFileFormatIsKnown = + file.type.toDisplayFormat() !== FileTypeToFriendlyTypeMap.unknown + + if (file.tabularData) { + return <> + } + + return ( + + {originalFileFormatIsKnown + ? file.type.toDisplayFormat() + : t('actions.accessFileMenu.downloadOptions.options.original')} + + ) +} diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.tsx new file mode 100644 index 000000000..c4e76fbd1 --- /dev/null +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/access-file-menu/FileTabularDownloadOptions.tsx @@ -0,0 +1,39 @@ +import { File, FileIngestStatus } from '../../../../../../../../files/domain/models/File' +import { DropdownButtonItem } from '@iqss/dataverse-design-system' +import { useDataset } from '../../../../../../DatasetContext' +import { useTranslation } from 'react-i18next' + +interface FileTabularDownloadOptionsProps { + file: File +} + +export function FileTabularDownloadOptions({ file }: FileTabularDownloadOptionsProps) { + const { t } = useTranslation('files') + const { dataset } = useDataset() + const originalFileFormatIsKnown = file.type.original && file.type.original !== 'Unknown' + const downloadDisabled = + file.ingest.status === FileIngestStatus.IN_PROGRESS || + (dataset && dataset.isLockedFromFileDownload) + + if (!file.tabularData) { + return <> + } + + return ( + <> + {originalFileFormatIsKnown && ( + {`${file.type.original} (${t( + 'actions.accessFileMenu.downloadOptions.options.original' + )})`} + )} + + {t('actions.accessFileMenu.downloadOptions.options.tabular')} + + {file.type.original !== 'R Data' && ( + + {t('actions.accessFileMenu.downloadOptions.options.RData')} + + )} + + ) +} diff --git a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/FileOptionsMenu.tsx b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/FileOptionsMenu.tsx index c81d34c77..f336fe594 100644 --- a/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/FileOptionsMenu.tsx +++ b/src/sections/dataset/dataset-files/files-table/file-actions/file-actions-cell/file-action-buttons/file-options-menu/FileOptionsMenu.tsx @@ -24,7 +24,7 @@ export function FileOptionsMenu({ file }: { file: File }) { {t('actions.optionsMenu.title')}}>