diff --git a/src/app/features/registries/components/custom-step/custom-step.component.ts b/src/app/features/registries/components/custom-step/custom-step.component.ts index 59721646c..e32768270 100644 --- a/src/app/features/registries/components/custom-step/custom-step.component.ts +++ b/src/app/features/registries/components/custom-step/custom-step.component.ts @@ -29,10 +29,11 @@ import { FormBuilder, FormControl, FormGroup, FormsModule, ReactiveFormsModule, import { ActivatedRoute, Router } from '@angular/router'; import { InfoIconComponent } from '@osf/shared/components'; -import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants'; +import { FILE_COUNT_ATTACHMENTS_LIMIT, INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants'; import { FieldType } from '@osf/shared/enums'; import { CustomValidators, findChangedFields } from '@osf/shared/helpers'; import { FilePayloadJsonApi, OsfFile, PageSchema } from '@osf/shared/models'; +import { ToastService } from '@osf/shared/services'; import { FilesMapper } from '../../mappers/files.mapper'; import { RegistriesSelectors, SetUpdatedFields, UpdateStepValidation } from '../../store'; @@ -77,6 +78,7 @@ export class CustomStepComponent implements OnDestroy { private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); private readonly fb = inject(FormBuilder); + private toastService = inject(ToastService); readonly pages = select(RegistriesSelectors.getPagesSchema); readonly FieldType = FieldType; @@ -180,7 +182,12 @@ export class CustomStepComponent implements OnDestroy { onAttachFile(file: OsfFile, questionKey: string): void { this.attachedFiles[questionKey] = this.attachedFiles[questionKey] || []; + if (!this.attachedFiles[questionKey].some((f) => f.file_id === file.id)) { + if (this.attachedFiles[questionKey].length >= FILE_COUNT_ATTACHMENTS_LIMIT) { + this.toastService.showWarn('shared.files.limitText'); + return; + } this.attachedFiles[questionKey].push(file); this.stepForm.patchValue({ [questionKey]: [...(this.attachedFiles[questionKey] || []), file], diff --git a/src/app/features/registries/components/files-control/files-control.component.html b/src/app/features/registries/components/files-control/files-control.component.html index ea73def20..884f72428 100644 --- a/src/app/features/registries/components/files-control/files-control.component.html +++ b/src/app/features/registries/components/files-control/files-control.component.html @@ -58,7 +58,7 @@ [isLoading]="isFilesLoading()" [actions]="filesTreeActions" [viewOnly]="filesViewOnly()" - [viewOnlyDownloadable]="false" + [viewOnlyDownloadable]="true" [resourceId]="projectId()" [provider]="provider()" (folderIsOpening)="folderIsOpening($event)" diff --git a/src/app/features/registries/components/files-control/files-control.component.ts b/src/app/features/registries/components/files-control/files-control.component.ts index 980a2763e..6bff47341 100644 --- a/src/app/features/registries/components/files-control/files-control.component.ts +++ b/src/app/features/registries/components/files-control/files-control.component.ts @@ -16,8 +16,9 @@ import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { CreateFolderDialogComponent } from '@osf/features/files/components'; import { FilesTreeComponent, LoadingSpinnerComponent } from '@osf/shared/components'; +import { FILE_SIZE_LIMIT } from '@osf/shared/constants'; import { FilesTreeActions, OsfFile } from '@osf/shared/models'; -import { FilesService } from '@osf/shared/services'; +import { FilesService, ToastService } from '@osf/shared/services'; import { CreateFolder, @@ -57,6 +58,7 @@ export class FilesControlComponent { private readonly dialogService = inject(DialogService); private readonly translateService = inject(TranslateService); private readonly destroyRef = inject(DestroyRef); + private toastService = inject(ToastService); readonly files = select(RegistriesSelectors.getFiles); readonly filesTotalCount = select(RegistriesSelectors.getFilesTotalCount); @@ -103,6 +105,10 @@ export class FilesControlComponent { onFileSelected(event: Event): void { const input = event.target as HTMLInputElement; const file = input.files?.[0]; + if (file && file.size > FILE_SIZE_LIMIT) { + this.toastService.showWarn('shared.files.limitText'); + return; + } if (!file) return; this.uploadFile(file); diff --git a/src/app/features/registries/components/metadata/metadata.component.html b/src/app/features/registries/components/metadata/metadata.component.html index 620c66986..75d6a4897 100644 --- a/src/app/features/registries/components/metadata/metadata.component.html +++ b/src/app/features/registries/components/metadata/metadata.component.html @@ -11,7 +11,7 @@

{{ 'common.labels.title' | translate }}

{{ 'shared.title.description' | translate }}

@@ -33,6 +33,7 @@

{{ 'common.labels.description' | translate }}

rows="5" cols="30" pTextarea + [ariaLabel]="'common.labels.description' | translate" > @if ( metadataForm.controls['description'].errors?.['required'] && diff --git a/src/app/shared/components/stepper/stepper.component.html b/src/app/shared/components/stepper/stepper.component.html index 7089f1300..6e5549af5 100644 --- a/src/app/shared/components/stepper/stepper.component.html +++ b/src/app/shared/components/stepper/stepper.component.html @@ -9,6 +9,7 @@ [class.invalid]="step.invalid" [class.current]="i === currentStep().index" (click)="onStepClick(step)" + [attr.aria-label]="step.label | translate" > @if (step.invalid && i !== currentStep().index) { diff --git a/src/app/shared/components/subjects/subjects.component.html b/src/app/shared/components/subjects/subjects.component.html index 862ae009e..e647ef075 100644 --- a/src/app/shared/components/subjects/subjects.component.html +++ b/src/app/shared/components/subjects/subjects.component.html @@ -69,6 +69,7 @@

{{ 'shared.subjects.title' | translate }}

(onNodeSelect)="selectSubject($event.node.data)" (onNodeUnselect)="removeSubject($event.node.data)" [ariaLabel]="'shared.subjects.subjectTree' | translate" + [togglerAriaLabel]="'common.accessibility.toggleTreeNode' | translate" > } diff --git a/src/app/shared/components/text-input/text-input.component.html b/src/app/shared/components/text-input/text-input.component.html index 59f592760..23fdb6139 100644 --- a/src/app/shared/components/text-input/text-input.component.html +++ b/src/app/shared/components/text-input/text-input.component.html @@ -1,8 +1,8 @@ @if (label()) { - + } (); maxLength = input(); - inputId = `input-${Math.random().toString(36).substring(2, 15)}`; + inputId = input(`input-${Math.random().toString(36).substring(2, 15)}`); helpId = `help-${Math.random().toString(36).substring(2, 15)}`; getErrorMessage(): ValidationParams { diff --git a/src/app/shared/constants/files-limits.const.ts b/src/app/shared/constants/files-limits.const.ts new file mode 100644 index 000000000..0f8156270 --- /dev/null +++ b/src/app/shared/constants/files-limits.const.ts @@ -0,0 +1,2 @@ +export const FILE_SIZE_LIMIT = 5 * 1024 * 1024 * 1024; +export const FILE_COUNT_ATTACHMENTS_LIMIT = 1; diff --git a/src/app/shared/constants/index.ts b/src/app/shared/constants/index.ts index 6d9b00431..e8aadc9a5 100644 --- a/src/app/shared/constants/index.ts +++ b/src/app/shared/constants/index.ts @@ -4,6 +4,7 @@ export * from './addons-tab-options.const'; export * from './contributors.constants'; export * from './default-citation-titles.const'; export * from './default-table-params.constants'; +export * from './files-limits.const'; export * from './filter-placeholders'; export * from './input-limits.const'; export * from './input-validation-messages.const'; diff --git a/src/app/shared/services/toast.service.ts b/src/app/shared/services/toast.service.ts index da6ac62e1..dae505bee 100644 --- a/src/app/shared/services/toast.service.ts +++ b/src/app/shared/services/toast.service.ts @@ -13,7 +13,7 @@ export class ToastService { } showWarn(summary: string, params?: unknown) { - this.messageService.add({ severity: 'warn', summary, data: { translationParams: params }, key: 'osf' }); + this.messageService.add({ severity: 'warn', summary, life: 5000, data: { translationParams: params }, key: 'osf' }); } showError(summary: string, params?: unknown) { diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 31123445a..4c8313ca2 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -65,6 +65,7 @@ "customizeOptions": "Customize options", "toggleProjectVisibility": "Toggle project visibility", "tagInput": "Tag input", + "toggleTreeNode": "Toggle tree node", "copyButtonInfo": "Copy to clipboard button" }, "dialogs": {