diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 6f8e34e0f..35d77fc43 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -172,6 +172,11 @@ export const routes: Routes = [ import('./core/components/request-access/request-access.component').then((mod) => mod.RequestAccessComponent), data: { skipBreadcrumbs: true }, }, + { + path: 'files/:fileGuid', + loadComponent: () => + import('@osf/features/files/pages/file-detail/file-detail.component').then((c) => c.FileDetailComponent), + }, { path: '**', loadComponent: () => diff --git a/src/app/core/constants/ngxs-states.constant.ts b/src/app/core/constants/ngxs-states.constant.ts index b6dd46146..c16df5cb4 100644 --- a/src/app/core/constants/ngxs-states.constant.ts +++ b/src/app/core/constants/ngxs-states.constant.ts @@ -1,5 +1,6 @@ import { ProviderState } from '@core/store/provider'; import { UserState } from '@core/store/user'; +import { FilesState } from '@osf/features/files/store'; import { MeetingsState } from '@osf/features/meetings/store'; import { ProjectMetadataState } from '@osf/features/project/metadata/store'; import { ProjectOverviewState } from '@osf/features/project/overview/store'; @@ -28,4 +29,5 @@ export const STATES = [ ProjectMetadataState, LicensesState, RegionsState, + FilesState, ]; diff --git a/src/app/features/project/files/components/create-folder-dialog/create-folder-dialog.component.html b/src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.html similarity index 81% rename from src/app/features/project/files/components/create-folder-dialog/create-folder-dialog.component.html rename to src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.html index 6d45824fe..cbeb82b17 100644 --- a/src/app/features/project/files/components/create-folder-dialog/create-folder-dialog.component.html +++ b/src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.html @@ -2,8 +2,8 @@
diff --git a/src/app/features/project/files/components/create-folder-dialog/create-folder-dialog.component.spec.ts b/src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.spec.ts similarity index 100% rename from src/app/features/project/files/components/create-folder-dialog/create-folder-dialog.component.spec.ts rename to src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.spec.ts diff --git a/src/app/features/project/files/components/create-folder-dialog/create-folder-dialog.component.ts b/src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.ts similarity index 100% rename from src/app/features/project/files/components/create-folder-dialog/create-folder-dialog.component.ts rename to src/app/features/files/components/create-folder-dialog/create-folder-dialog.component.ts diff --git a/src/app/features/project/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.html b/src/app/features/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.html similarity index 80% rename from src/app/features/project/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.html rename to src/app/features/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.html index 9abef56b5..7d1ac831c 100644 --- a/src/app/features/project/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.html +++ b/src/app/features/files/components/edit-file-metadata-dialog/edit-file-metadata-dialog.component.html @@ -1,16 +1,16 @@
-

{{ 'project.files.detail.fileMetadata.fields.title' | translate }}

+

{{ 'files.detail.fileMetadata.fields.title' | translate }}

-

{{ 'project.files.detail.fileMetadata.fields.description' | translate }}

+

{{ 'files.detail.fileMetadata.fields.description' | translate }}

-

{{ 'project.files.detail.fileMetadata.fields.resourceType' | translate }}

+

{{ 'files.detail.fileMetadata.fields.resourceType' | translate }}

-

{{ 'project.files.detail.fileMetadata.fields.resourceLanguage' | translate }}

+

{{ 'files.detail.fileMetadata.fields.resourceLanguage' | translate }}

-

{{ 'project.files.detail.keywords.title' | translate }}

+

{{ 'files.detail.keywords.title' | translate }}

diff --git a/src/app/features/project/files/components/file-keywords/file-keywords.component.scss b/src/app/features/files/components/file-keywords/file-keywords.component.scss similarity index 100% rename from src/app/features/project/files/components/file-keywords/file-keywords.component.scss rename to src/app/features/files/components/file-keywords/file-keywords.component.scss diff --git a/src/app/features/project/files/components/file-keywords/file-keywords.component.spec.ts b/src/app/features/files/components/file-keywords/file-keywords.component.spec.ts similarity index 100% rename from src/app/features/project/files/components/file-keywords/file-keywords.component.spec.ts rename to src/app/features/files/components/file-keywords/file-keywords.component.spec.ts diff --git a/src/app/features/project/files/components/file-keywords/file-keywords.component.ts b/src/app/features/files/components/file-keywords/file-keywords.component.ts similarity index 87% rename from src/app/features/project/files/components/file-keywords/file-keywords.component.ts rename to src/app/features/files/components/file-keywords/file-keywords.component.ts index bdafbefc7..7991a5740 100644 --- a/src/app/features/project/files/components/file-keywords/file-keywords.component.ts +++ b/src/app/features/files/components/file-keywords/file-keywords.component.ts @@ -11,10 +11,11 @@ import { ChangeDetectionStrategy, Component, DestroyRef, inject } from '@angular import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, ReactiveFormsModule, Validators } from '@angular/forms'; -import { ProjectFilesSelectors, UpdateTags } from '@osf/features/project/files/store'; import { CustomValidators } from '@osf/shared/helpers'; import { InputLimits } from '@shared/constants'; +import { FilesSelectors, UpdateTags } from '../../store'; + @Component({ selector: 'osf-file-keywords', imports: [Button, Chip, Skeleton, InputText, ReactiveFormsModule, TranslatePipe], @@ -26,9 +27,9 @@ export class FileKeywordsComponent { private readonly actions = createDispatchMap({ updateTags: UpdateTags }); private readonly destroyRef = inject(DestroyRef); - readonly tags = select(ProjectFilesSelectors.getFileTags); - readonly isTagsLoading = select(ProjectFilesSelectors.isFileTagsLoading); - readonly file = select(ProjectFilesSelectors.getOpenedFile); + readonly tags = select(FilesSelectors.getFileTags); + readonly isTagsLoading = select(FilesSelectors.isFileTagsLoading); + readonly file = select(FilesSelectors.getOpenedFile); keywordControl = new FormControl('', { nonNullable: true, diff --git a/src/app/features/project/files/components/file-metadata/file-metadata.component.html b/src/app/features/files/components/file-metadata/file-metadata.component.html similarity index 85% rename from src/app/features/project/files/components/file-metadata/file-metadata.component.html rename to src/app/features/files/components/file-metadata/file-metadata.component.html index 926738703..e6038fa22 100644 --- a/src/app/features/project/files/components/file-metadata/file-metadata.component.html +++ b/src/app/features/files/components/file-metadata/file-metadata.component.html @@ -1,6 +1,6 @@
-

{{ 'project.files.detail.fileMetadata.title' | translate }}

+

{{ 'files.detail.fileMetadata.title' | translate }}

@@ -10,7 +10,7 @@

{{ 'project.files.detail.fileMetadata.title' | translate }}

diff --git a/src/app/features/project/files/components/file-metadata/file-metadata.component.scss b/src/app/features/files/components/file-metadata/file-metadata.component.scss similarity index 100% rename from src/app/features/project/files/components/file-metadata/file-metadata.component.scss rename to src/app/features/files/components/file-metadata/file-metadata.component.scss diff --git a/src/app/features/project/files/components/file-metadata/file-metadata.component.spec.ts b/src/app/features/files/components/file-metadata/file-metadata.component.spec.ts similarity index 100% rename from src/app/features/project/files/components/file-metadata/file-metadata.component.spec.ts rename to src/app/features/files/components/file-metadata/file-metadata.component.spec.ts diff --git a/src/app/features/project/files/components/file-metadata/file-metadata.component.ts b/src/app/features/files/components/file-metadata/file-metadata.component.ts similarity index 87% rename from src/app/features/project/files/components/file-metadata/file-metadata.component.ts rename to src/app/features/files/components/file-metadata/file-metadata.component.ts index 10b04affb..0e87413ed 100644 --- a/src/app/features/project/files/components/file-metadata/file-metadata.component.ts +++ b/src/app/features/files/components/file-metadata/file-metadata.component.ts @@ -12,10 +12,9 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute } from '@angular/router'; -import { ProjectFilesSelectors, SetFileMetadata } from '@osf/features/project/files/store'; - import { FileMetadataFields } from '../../constants'; import { PatchFileMetadata } from '../../models'; +import { FilesSelectors, SetFileMetadata } from '../../store'; import { EditFileMetadataDialogComponent } from '../edit-file-metadata-dialog/edit-file-metadata-dialog.component'; import { environment } from 'src/environments/environment'; @@ -34,8 +33,8 @@ export class FileMetadataComponent { private readonly dialogService = inject(DialogService); private readonly translateService = inject(TranslateService); - fileMetadata = select(ProjectFilesSelectors.getFileCustomMetadata); - isLoading = select(ProjectFilesSelectors.isFileMetadataLoading); + fileMetadata = select(FilesSelectors.getFileCustomMetadata); + isLoading = select(FilesSelectors.isFileMetadataLoading); readonly fileGuid = toSignal(this.route.params.pipe(map((params) => params['fileGuid'])) ?? of(undefined)); @@ -60,7 +59,7 @@ export class FileMetadataComponent { .open(EditFileMetadataDialogComponent, { width: '448px', focusOnShow: false, - header: this.translateService.instant('project.files.detail.fileMetadata.edit'), + header: this.translateService.instant('files.detail.fileMetadata.edit'), closeOnEscape: true, modal: true, closable: true, diff --git a/src/app/features/project/files/components/file-project-metadata/file-project-metadata.component.html b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.html similarity index 51% rename from src/app/features/project/files/components/file-project-metadata/file-project-metadata.component.html rename to src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.html index b71dd21f0..98142cf0f 100644 --- a/src/app/features/project/files/components/file-project-metadata/file-project-metadata.component.html +++ b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.html @@ -1,35 +1,37 @@
-

{{ 'project.files.detail.projectMetadata.title' | translate }}

+

+ {{ 'files.detail.resourceMetadata.title.' + resourceType() | translate }} +

- @if (isProjectMetadataLoading()) { + @if (isResourceMetadataLoading()) { } @else { - @for (funder of projectMetadata()?.funders; track $index) { + @for (funder of resourceMetadata()?.funders; track $index) {
@if (funder.funderName) {
-

{{ 'project.files.detail.projectMetadata.fields.funder' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.funder' | translate }}

{{ funder.funderName }}
} @if (funder.awardTitle) {
-

{{ 'project.files.detail.projectMetadata.fields.awardTitle' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.awardTitle' | translate }}

{{ funder.awardTitle }}
} @if (funder.awardTitle) {
-

{{ 'project.files.detail.projectMetadata.fields.awardNumber' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.awardNumber' | translate }}

{{ funder.awardTitle }}
} @if (funder.awardUri) {
-

{{ 'project.files.detail.projectMetadata.fields.awardUri' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.awardUri' | translate }}

{{ funder.awardUri }}
} @@ -37,72 +39,72 @@

{{ 'project.files.detail.projectMetadata.fields.awardUri' | translate }}

-

{{ 'project.files.detail.projectMetadata.fields.title' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.title' | translate }}

- {{ projectMetadata()?.title }} + {{ resourceMetadata()?.title }}
- @if (projectMetadata()?.description) { + @if (resourceMetadata()?.description) {
-

{{ 'project.files.detail.projectMetadata.fields.description' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.description' | translate }}

- {{ projectMetadata()?.description }} + {{ resourceMetadata()?.description }}
} - @if (projectMetadata()?.resourceTypeGeneral) { + @if (resourceMetadata()?.resourceTypeGeneral) {
-

{{ 'project.files.detail.projectMetadata.fields.resourceType' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.resourceType' | translate }}

- {{ projectMetadata()?.resourceTypeGeneral }} + {{ resourceMetadata()?.resourceTypeGeneral }}
} - @if (projectMetadata()?.language) { + @if (resourceMetadata()?.language) {
-

{{ 'project.files.detail.projectMetadata.fields.resourceLanguage' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.resourceLanguage' | translate }}

- {{ projectMetadata()?.language }} + {{ resourceMetadata()?.language }}
} - @if (projectMetadata()?.dateCreated) { + @if (resourceMetadata()?.dateCreated) {
-

{{ 'project.files.detail.projectMetadata.fields.dateCreated' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.dateCreated' | translate }}

- {{ projectMetadata()?.dateCreated | date: 'MMMM d, y' }} + {{ resourceMetadata()?.dateCreated | date: 'MMMM d, y' }}
} - @if (projectMetadata()?.dateModified) { + @if (resourceMetadata()?.dateModified) {
-

{{ 'project.files.detail.projectMetadata.fields.dateModified' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.dateModified' | translate }}

- {{ projectMetadata()?.dateModified | date: 'MMMM d, y' }} + {{ resourceMetadata()?.dateModified | date: 'MMMM d, y' }}
} } - @if (isProjectContributorsLoading()) { + @if (isResourceContributorsLoading()) { } @else { @if (contributors()?.length) {
-

{{ 'project.files.detail.projectMetadata.fields.contributors' | translate }}

+

{{ 'files.detail.resourceMetadata.fields.contributors' | translate }}

@for (contributor of contributors(); track $index) { - {{ contributor.name }} + {{ contributor.fullName }} @if (!$last) { } diff --git a/src/app/features/project/files/components/file-project-metadata/file-project-metadata.component.scss b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.scss similarity index 100% rename from src/app/features/project/files/components/file-project-metadata/file-project-metadata.component.scss rename to src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.scss diff --git a/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts new file mode 100644 index 000000000..ca59b6b86 --- /dev/null +++ b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.spec.ts @@ -0,0 +1,22 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FileResourceMetadataComponent } from './file-resource-metadata.component'; + +describe('FileResourceMetadataComponent', () => { + let component: FileResourceMetadataComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FileResourceMetadataComponent], + }).compileComponents(); + + fixture = TestBed.createComponent(FileResourceMetadataComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.ts b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.ts new file mode 100644 index 000000000..8043b6459 --- /dev/null +++ b/src/app/features/files/components/file-resource-metadata/file-resource-metadata.component.ts @@ -0,0 +1,25 @@ +import { select } from '@ngxs/store'; + +import { TranslatePipe } from '@ngx-translate/core'; + +import { Skeleton } from 'primeng/skeleton'; + +import { DatePipe } from '@angular/common'; +import { ChangeDetectionStrategy, Component, input } from '@angular/core'; + +import { FilesSelectors } from '../../store'; + +@Component({ + selector: 'osf-file-resource-metadata', + imports: [DatePipe, TranslatePipe, Skeleton], + templateUrl: './file-resource-metadata.component.html', + styleUrl: './file-resource-metadata.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FileResourceMetadataComponent { + resourceType = input('nodes'); + resourceMetadata = select(FilesSelectors.getResourceMetadata); + contributors = select(FilesSelectors.getContributors); + isResourceMetadataLoading = select(FilesSelectors.isResourceMetadataLoading); + isResourceContributorsLoading = select(FilesSelectors.isResourceContributorsLoading); +} diff --git a/src/app/features/project/files/components/file-revisions/file-revisions.component.html b/src/app/features/files/components/file-revisions/file-revisions.component.html similarity index 77% rename from src/app/features/project/files/components/file-revisions/file-revisions.component.html rename to src/app/features/files/components/file-revisions/file-revisions.component.html index 7fb8c95a3..2c7e02e54 100644 --- a/src/app/features/project/files/components/file-revisions/file-revisions.component.html +++ b/src/app/features/files/components/file-revisions/file-revisions.component.html @@ -1,6 +1,6 @@
-

{{ 'project.files.detail.revisions.title' | translate }}

+

{{ 'files.detail.revisions.title' | translate }}

@if (isLoading()) { @@ -18,12 +18,12 @@

{{ 'project.files.detail.revisions.title' | translate }}

  • @@ -31,12 +31,12 @@

    {{ 'project.files.detail.revisions.title' | translate }}

  • @@ -45,7 +45,7 @@

    {{ 'project.files.detail.revisions.title' | translate }}

    diff --git a/src/app/features/project/files/components/file-revisions/file-revisions.component.scss b/src/app/features/files/components/file-revisions/file-revisions.component.scss similarity index 100% rename from src/app/features/project/files/components/file-revisions/file-revisions.component.scss rename to src/app/features/files/components/file-revisions/file-revisions.component.scss diff --git a/src/app/features/project/files/components/file-revisions/file-revisions.component.spec.ts b/src/app/features/files/components/file-revisions/file-revisions.component.spec.ts similarity index 100% rename from src/app/features/project/files/components/file-revisions/file-revisions.component.spec.ts rename to src/app/features/files/components/file-revisions/file-revisions.component.spec.ts diff --git a/src/app/features/project/files/components/file-revisions/file-revisions.component.ts b/src/app/features/files/components/file-revisions/file-revisions.component.ts similarity index 80% rename from src/app/features/project/files/components/file-revisions/file-revisions.component.ts rename to src/app/features/files/components/file-revisions/file-revisions.component.ts index b0b4ab493..2fc72a763 100644 --- a/src/app/features/project/files/components/file-revisions/file-revisions.component.ts +++ b/src/app/features/files/components/file-revisions/file-revisions.component.ts @@ -13,8 +13,10 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; import { toSignal } from '@angular/core/rxjs-interop'; import { ActivatedRoute } from '@angular/router'; -import { ProjectFilesSelectors } from '@osf/features/project/files/store'; -import { CopyButtonComponent, InfoIconComponent } from '@shared/components'; +import { CopyButtonComponent } from '@osf/shared/components'; +import { InfoIconComponent } from '@osf/shared/components/info-icon/info-icon.component'; + +import { FilesSelectors } from '../../store'; import { environment } from 'src/environments/environment'; @@ -31,16 +33,16 @@ import { environment } from 'src/environments/environment'; Button, DatePipe, TranslatePipe, - InfoIconComponent, CopyButtonComponent, Skeleton, + InfoIconComponent, ], }) export class FileRevisionsComponent { private readonly route = inject(ActivatedRoute); - readonly fileRevisions = select(ProjectFilesSelectors.getFileRevisions); - readonly isLoading = select(ProjectFilesSelectors.isFileRevisionsLoading); + readonly fileRevisions = select(FilesSelectors.getFileRevisions); + readonly isLoading = select(FilesSelectors.isFileRevisionsLoading); readonly fileGuid = toSignal(this.route.params.pipe(map((params) => params['fileGuid'])) ?? of(undefined)); downloadRevision(version: string): void { diff --git a/src/app/features/project/files/components/index.ts b/src/app/features/files/components/index.ts similarity index 57% rename from src/app/features/project/files/components/index.ts rename to src/app/features/files/components/index.ts index 3325c793f..80c02901d 100644 --- a/src/app/features/project/files/components/index.ts +++ b/src/app/features/files/components/index.ts @@ -2,9 +2,7 @@ export { CreateFolderDialogComponent } from './create-folder-dialog/create-folde export { EditFileMetadataDialogComponent } from './edit-file-metadata-dialog/edit-file-metadata-dialog.component'; export { FileKeywordsComponent } from './file-keywords/file-keywords.component'; export { FileMetadataComponent } from './file-metadata/file-metadata.component'; -export { FileProjectMetadataComponent } from './file-project-metadata/file-project-metadata.component'; +export { FileResourceMetadataComponent } from './file-resource-metadata/file-resource-metadata.component'; +export { FileRevisionsComponent } from './file-revisions/file-revisions.component'; export { MoveFileDialogComponent } from './move-file-dialog/move-file-dialog.component'; export { RenameFileDialogComponent } from './rename-file-dialog/rename-file-dialog.component'; -export { FileRevisionsComponent } from '@osf/features/project/files/components/file-revisions/file-revisions.component'; -export { FileDetailComponent } from '@osf/features/project/files/pages/file-detail/file-detail.component'; -export { FilesTreeComponent } from '@shared/components/files-tree/files-tree.component'; diff --git a/src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.html b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.html similarity index 87% rename from src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.html rename to src/app/features/files/components/move-file-dialog/move-file-dialog.component.html index 563817c83..fb2589340 100644 --- a/src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.html +++ b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.html @@ -6,7 +6,7 @@
    cost-shield -

    {{ 'project.files.dialogs.moveFile.storage' | translate }}

    +

    {{ 'files.dialogs.moveFile.storage' | translate }}

    @@ -37,10 +37,7 @@

    {{ 'project.files.dialogs.moveFile.storage' | translate } @else if (config.data.file.id === file.id) { - diff --git a/src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.scss b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.scss similarity index 100% rename from src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.scss rename to src/app/features/files/components/move-file-dialog/move-file-dialog.component.scss diff --git a/src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.spec.ts b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts similarity index 100% rename from src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.spec.ts rename to src/app/features/files/components/move-file-dialog/move-file-dialog.component.spec.ts diff --git a/src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.ts b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts similarity index 88% rename from src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.ts rename to src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts index 2cb2615be..0b4ba48e4 100644 --- a/src/app/features/project/files/components/move-file-dialog/move-file-dialog.component.ts +++ b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts @@ -14,13 +14,13 @@ import { ChangeDetectionStrategy, Component, computed, DestroyRef, inject, signa import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { + FilesSelectors, GetFiles, GetMoveFileFiles, GetRootFolderFiles, - ProjectFilesSelectors, SetCurrentFolder, SetMoveFileCurrentFolder, -} from '@osf/features/project/files/store'; +} from '@osf/features/files/store'; import { IconComponent, LoadingSpinnerComponent } from '@shared/components'; import { OsfFile } from '@shared/models'; import { FilesService, ToastService } from '@shared/services'; @@ -41,15 +41,15 @@ export class MoveFileDialogComponent { private readonly translateService = inject(TranslateService); private readonly toastService = inject(ToastService); - protected readonly files = select(ProjectFilesSelectors.getMoveFileFiles); - protected readonly isLoading = select(ProjectFilesSelectors.isMoveFileFilesLoading); - protected readonly currentFolder = select(ProjectFilesSelectors.getMoveFileCurrentFolder); - private readonly rootFolders = select(ProjectFilesSelectors.getRootFolders); + protected readonly files = select(FilesSelectors.getMoveFileFiles); + protected readonly isLoading = select(FilesSelectors.isMoveFileFilesLoading); + protected readonly currentFolder = select(FilesSelectors.getMoveFileCurrentFolder); + private readonly rootFolders = select(FilesSelectors.getRootFolders); protected readonly isFilesUpdating = signal(false); protected readonly isFolderSame = computed(() => { return this.currentFolder()?.id === this.config.data.file.relationships.parentFolderId; }); - protected readonly provider = select(ProjectFilesSelectors.getProvider); + protected readonly provider = select(FilesSelectors.getProvider); protected readonly dispatch = createDispatchMap({ getMoveFileFiles: GetMoveFileFiles, @@ -105,7 +105,7 @@ export class MoveFileDialogComponent { let path = this.currentFolder()?.path; if (!path) { - throw new Error(this.translateService.instant('project.files.dialogs.moveFile.pathError')); + throw new Error(this.translateService.instant('files.dialogs.moveFile.pathError')); } if (!this.currentFolder()?.relationships.parentFolderLink) { diff --git a/src/app/features/project/files/components/rename-file-dialog/rename-file-dialog.component.html b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.html similarity index 82% rename from src/app/features/project/files/components/rename-file-dialog/rename-file-dialog.component.html rename to src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.html index 03cf87dde..2abb1b505 100644 --- a/src/app/features/project/files/components/rename-file-dialog/rename-file-dialog.component.html +++ b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.html @@ -2,8 +2,8 @@
    diff --git a/src/app/features/project/files/components/rename-file-dialog/rename-file-dialog.component.spec.ts b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.spec.ts similarity index 100% rename from src/app/features/project/files/components/rename-file-dialog/rename-file-dialog.component.spec.ts rename to src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.spec.ts diff --git a/src/app/features/project/files/components/rename-file-dialog/rename-file-dialog.component.ts b/src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.ts similarity index 100% rename from src/app/features/project/files/components/rename-file-dialog/rename-file-dialog.component.ts rename to src/app/features/files/components/rename-file-dialog/rename-file-dialog.component.ts diff --git a/src/app/features/project/files/models/data/embed-content.const.ts b/src/app/features/files/constants/embed-content.constants.ts similarity index 100% rename from src/app/features/project/files/models/data/embed-content.const.ts rename to src/app/features/files/constants/embed-content.constants.ts diff --git a/src/app/features/files/constants/file-metadata-fields.constants.ts b/src/app/features/files/constants/file-metadata-fields.constants.ts new file mode 100644 index 000000000..0b9617fa4 --- /dev/null +++ b/src/app/features/files/constants/file-metadata-fields.constants.ts @@ -0,0 +1,8 @@ +import { MetadataField } from '../models'; + +export const FileMetadataFields: MetadataField[] = [ + { key: 'title', label: 'files.detail.fileMetadata.fields.title' }, + { key: 'description', label: 'files.detail.fileMetadata.fields.description' }, + { key: 'resourceTypeGeneral', label: 'files.detail.fileMetadata.fields.resourceType' }, + { key: 'language', label: 'files.detail.fileMetadata.fields.resourceLanguage' }, +]; diff --git a/src/app/features/project/files/models/data/file-provider.const.ts b/src/app/features/files/constants/file-provider.constants.ts similarity index 90% rename from src/app/features/project/files/models/data/file-provider.const.ts rename to src/app/features/files/constants/file-provider.constants.ts index 546a640d5..01df352e3 100644 --- a/src/app/features/project/files/models/data/file-provider.const.ts +++ b/src/app/features/files/constants/file-provider.constants.ts @@ -6,4 +6,5 @@ export const FileProvider = { OneDrive: 'onedrive', WebDav: 'webdav', S3: 's3', + GitHub: 'github', }; diff --git a/src/app/features/files/constants/index.ts b/src/app/features/files/constants/index.ts new file mode 100644 index 000000000..72af33152 --- /dev/null +++ b/src/app/features/files/constants/index.ts @@ -0,0 +1,3 @@ +export * from './embed-content.constants'; +export * from './file-metadata-fields.constants'; +export * from './file-provider.constants'; diff --git a/src/app/features/project/files/enums/file-detail-tab.enum.ts b/src/app/features/files/enums/file-detail-tab.enum.ts similarity index 100% rename from src/app/features/project/files/enums/file-detail-tab.enum.ts rename to src/app/features/files/enums/file-detail-tab.enum.ts diff --git a/src/app/features/files/enums/index.ts b/src/app/features/files/enums/index.ts new file mode 100644 index 000000000..01282d755 --- /dev/null +++ b/src/app/features/files/enums/index.ts @@ -0,0 +1 @@ +export * from './file-detail-tab.enum'; diff --git a/src/app/features/files/files.routes.ts b/src/app/features/files/files.routes.ts new file mode 100644 index 000000000..e9ebf8b53 --- /dev/null +++ b/src/app/features/files/files.routes.ts @@ -0,0 +1,30 @@ +import { Routes } from '@angular/router'; + +import { FilesContainerComponent } from './pages/files-container/files-container.component'; + +export const filesRoutes: Routes = [ + { + path: '', + component: FilesContainerComponent, + children: [ + { + path: '', + loadComponent: () => import('@osf/features/files/pages/files/files.component').then((c) => c.FilesComponent), + }, + { + path: ':fileGuid', + loadComponent: () => + import('@osf/features/files/pages/file-detail/file-detail.component').then((c) => c.FileDetailComponent), + children: [ + { + path: 'metadata', + loadComponent: () => + import('@osf/features/files/pages/community-metadata/community-metadata.component').then( + (c) => c.CommunityMetadataComponent + ), + }, + ], + }, + ], + }, +]; diff --git a/src/app/features/project/files/mappers/file-custom-metadata.mapper.ts b/src/app/features/files/mappers/file-custom-metadata.mapper.ts similarity index 79% rename from src/app/features/project/files/mappers/file-custom-metadata.mapper.ts rename to src/app/features/files/mappers/file-custom-metadata.mapper.ts index 7b9e66674..dc75ca1e3 100644 --- a/src/app/features/project/files/mappers/file-custom-metadata.mapper.ts +++ b/src/app/features/files/mappers/file-custom-metadata.mapper.ts @@ -1,6 +1,7 @@ -import { FileCustomMetadata, OsfFileCustomMetadata } from '@osf/features/project/files/models'; import { ApiData } from '@osf/shared/models'; +import { FileCustomMetadata, OsfFileCustomMetadata } from '../models'; + export function MapFileCustomMetadata(data: ApiData): OsfFileCustomMetadata { return { id: data.id, diff --git a/src/app/features/files/mappers/file-revision.mapper.ts b/src/app/features/files/mappers/file-revision.mapper.ts new file mode 100644 index 000000000..764b7718d --- /dev/null +++ b/src/app/features/files/mappers/file-revision.mapper.ts @@ -0,0 +1,13 @@ +import { ApiData } from '@osf/shared/models'; + +import { FileRevisionJsonApi, OsfFileRevision } from '../models'; + +export function MapFileRevision(data: ApiData[]): OsfFileRevision[] { + const revision = data.map((revision) => ({ + downloads: revision.attributes.extra.downloads, + hashes: { md5: revision.attributes.extra.hashes?.md5, sha256: revision.attributes.extra.hashes?.sha256 }, + dateTime: new Date(revision.attributes.modified_utc), + version: revision.attributes.version, + })); + return revision; +} diff --git a/src/app/features/files/mappers/index.ts b/src/app/features/files/mappers/index.ts new file mode 100644 index 000000000..96736c05c --- /dev/null +++ b/src/app/features/files/mappers/index.ts @@ -0,0 +1,3 @@ +export * from './file-custom-metadata.mapper'; +export * from './file-revision.mapper'; +export * from './resource-metadata.mapper'; diff --git a/src/app/features/project/files/mappers/project-metadata.mapper.ts b/src/app/features/files/mappers/resource-metadata.mapper.ts similarity index 66% rename from src/app/features/project/files/mappers/project-metadata.mapper.ts rename to src/app/features/files/mappers/resource-metadata.mapper.ts index caf5a309f..c4f1bf7bf 100644 --- a/src/app/features/project/files/mappers/project-metadata.mapper.ts +++ b/src/app/features/files/mappers/resource-metadata.mapper.ts @@ -1,13 +1,12 @@ -import { - GetProjectCustomMetadataResponse, - GetProjectShortInfoResponse, - OsfProjectMetadata, -} from '@osf/features/project/files/models'; +import { ResourceMetadata } from '@osf/shared/models'; -export function MapProjectMetadata( - shortInfo: GetProjectShortInfoResponse, - customMetadata: GetProjectCustomMetadataResponse -): OsfProjectMetadata { +import { GetResourceCustomMetadataResponse } from '../models/get-resource-custom-metadata-response.model'; +import { GetResourceShortInfoResponse } from '../models/get-resource-short-info-response.model'; + +export function MapResourceMetadata( + shortInfo: GetResourceShortInfoResponse, + customMetadata: GetResourceCustomMetadataResponse +): ResourceMetadata { return { title: shortInfo.data.attributes.title, description: shortInfo.data.attributes.description, diff --git a/src/app/features/project/files/models/responses/create-folder-response.model.ts b/src/app/features/files/models/create-folder-response.model.ts similarity index 100% rename from src/app/features/project/files/models/responses/create-folder-response.model.ts rename to src/app/features/files/models/create-folder-response.model.ts diff --git a/src/app/features/project/files/models/osf-models/file-custom-metadata.model.ts b/src/app/features/files/models/file-custom-metadata.model.ts similarity index 100% rename from src/app/features/project/files/models/osf-models/file-custom-metadata.model.ts rename to src/app/features/files/models/file-custom-metadata.model.ts diff --git a/src/app/features/project/files/models/osf-models/file-revisions.model.ts b/src/app/features/files/models/file-revisions.model.ts similarity index 100% rename from src/app/features/project/files/models/osf-models/file-revisions.model.ts rename to src/app/features/files/models/file-revisions.model.ts diff --git a/src/app/features/project/files/models/osf-models/file-target.model.ts b/src/app/features/files/models/file-target.model.ts similarity index 94% rename from src/app/features/project/files/models/osf-models/file-target.model.ts rename to src/app/features/files/models/file-target.model.ts index 84485a11e..85858e99d 100644 --- a/src/app/features/project/files/models/osf-models/file-target.model.ts +++ b/src/app/features/files/models/file-target.model.ts @@ -1,4 +1,5 @@ export interface OsfFileTarget { + id: string; title: string; description: string; category: string; @@ -18,4 +19,5 @@ export interface OsfFileTarget { currentUserIsContributorOrGroupMember: boolean; wikiEnabled: boolean; public: boolean; + type: string; } diff --git a/src/app/features/project/files/models/files-metadata-fields.ts b/src/app/features/files/models/files-metadata-fields.model.ts similarity index 52% rename from src/app/features/project/files/models/files-metadata-fields.ts rename to src/app/features/files/models/files-metadata-fields.model.ts index 73534a682..7d06ec9a1 100644 --- a/src/app/features/project/files/models/files-metadata-fields.ts +++ b/src/app/features/files/models/files-metadata-fields.model.ts @@ -1,4 +1,4 @@ -import { OsfFileCustomMetadata } from './osf-models/file-custom-metadata.model'; +import { OsfFileCustomMetadata } from './file-custom-metadata.model'; export interface MetadataField { key: keyof OsfFileCustomMetadata; diff --git a/src/app/features/project/files/models/responses/get-project-contributors-response.model.ts b/src/app/features/files/models/get-contributors-response.model.ts similarity index 74% rename from src/app/features/project/files/models/responses/get-project-contributors-response.model.ts rename to src/app/features/files/models/get-contributors-response.model.ts index 66f1475dd..ed19f0e76 100644 --- a/src/app/features/project/files/models/responses/get-project-contributors-response.model.ts +++ b/src/app/features/files/models/get-contributors-response.model.ts @@ -1,6 +1,6 @@ import { ApiData, JsonApiResponse } from '@osf/shared/models'; -export type GetProjectContributorsResponse = JsonApiResponse< +export type GetContributorsResponse = JsonApiResponse< ApiData< { full_name: string; diff --git a/src/app/features/files/models/get-custom-metadata-response.model.ts b/src/app/features/files/models/get-custom-metadata-response.model.ts new file mode 100644 index 000000000..947edeff0 --- /dev/null +++ b/src/app/features/files/models/get-custom-metadata-response.model.ts @@ -0,0 +1,26 @@ +import { ApiData, JsonApiResponse } from '@osf/shared/models'; + +export type GetCustomMetadataResponse = JsonApiResponse, null>; + +export interface MetadataEmbedResponse { + custom_metadata: JsonApiResponse< + ApiData< + { + language: string; + resource_type_general: string; + funders: { + funder_name: string; + funder_identifier: string; + funder_identifier_type: string; + award_number: string; + award_uri: string; + award_title: string; + }[]; + }, + null, + null, + null + >, + null + >; +} diff --git a/src/app/features/project/files/models/responses/get-file-metadata-reponse.model.ts b/src/app/features/files/models/get-file-metadata-response.model.ts similarity index 100% rename from src/app/features/project/files/models/responses/get-file-metadata-reponse.model.ts rename to src/app/features/files/models/get-file-metadata-response.model.ts diff --git a/src/app/features/project/files/models/responses/get-file-revisions-response.model.ts b/src/app/features/files/models/get-file-revisions-response.model.ts similarity index 100% rename from src/app/features/project/files/models/responses/get-file-revisions-response.model.ts rename to src/app/features/files/models/get-file-revisions-response.model.ts diff --git a/src/app/features/project/files/models/responses/get-file-target-response.model.ts b/src/app/features/files/models/get-file-target-response.model.ts similarity index 100% rename from src/app/features/project/files/models/responses/get-file-target-response.model.ts rename to src/app/features/files/models/get-file-target-response.model.ts diff --git a/src/app/features/project/files/models/responses/get-project-custom-metadata-response.model.ts b/src/app/features/files/models/get-resource-custom-metadata-response.model.ts similarity index 74% rename from src/app/features/project/files/models/responses/get-project-custom-metadata-response.model.ts rename to src/app/features/files/models/get-resource-custom-metadata-response.model.ts index b4e3028f1..34b6a7f89 100644 --- a/src/app/features/project/files/models/responses/get-project-custom-metadata-response.model.ts +++ b/src/app/features/files/models/get-resource-custom-metadata-response.model.ts @@ -1,11 +1,11 @@ import { ApiData, JsonApiResponse } from '@osf/shared/models'; -export type GetProjectCustomMetadataResponse = JsonApiResponse< - ApiData, +export type GetResourceCustomMetadataResponse = JsonApiResponse< + ApiData, null >; -export interface ProjectMetadataEmbedResponse { +export interface ResourceMetadataEmbedResponse { custom_metadata: JsonApiResponse< ApiData< { diff --git a/src/app/features/files/models/get-resource-short-info-response.model.ts b/src/app/features/files/models/get-resource-short-info-response.model.ts new file mode 100644 index 000000000..8b50228f3 --- /dev/null +++ b/src/app/features/files/models/get-resource-short-info-response.model.ts @@ -0,0 +1,16 @@ +import { ApiData, JsonApiResponse } from '@osf/shared/models'; + +export type GetResourceShortInfoResponse = JsonApiResponse< + ApiData< + { + title: string; + description: string; + date_created: string; + date_modified: string; + }, + null, + null, + null + >, + null +>; diff --git a/src/app/features/project/files/models/responses/get-project-short-info-response.model.ts b/src/app/features/files/models/get-short-info-response.model.ts similarity index 79% rename from src/app/features/project/files/models/responses/get-project-short-info-response.model.ts rename to src/app/features/files/models/get-short-info-response.model.ts index 80a8835a0..da3aed695 100644 --- a/src/app/features/project/files/models/responses/get-project-short-info-response.model.ts +++ b/src/app/features/files/models/get-short-info-response.model.ts @@ -1,6 +1,6 @@ import { ApiData, JsonApiResponse } from '@shared/models'; -export type GetProjectShortInfoResponse = JsonApiResponse< +export type GetShortInfoResponse = JsonApiResponse< ApiData< { title: string; diff --git a/src/app/features/files/models/index.ts b/src/app/features/files/models/index.ts new file mode 100644 index 000000000..f3e000f81 --- /dev/null +++ b/src/app/features/files/models/index.ts @@ -0,0 +1,12 @@ +export * from './create-folder-response.model'; +export * from './file-custom-metadata.model'; +export * from './file-revisions.model'; +export * from './file-target.model'; +export * from './files-metadata-fields.model'; +export * from './get-contributors-response.model'; +export * from './get-custom-metadata-response.model'; +export * from './get-file-metadata-response.model'; +export * from './get-file-revisions-response.model'; +export * from './get-file-target-response.model'; +export * from './get-short-info-response.model'; +export * from './patch-file-metadata.model'; diff --git a/src/app/features/project/files/models/requests/patch-file-metadata.model.ts b/src/app/features/files/models/patch-file-metadata.model.ts similarity index 100% rename from src/app/features/project/files/models/requests/patch-file-metadata.model.ts rename to src/app/features/files/models/patch-file-metadata.model.ts diff --git a/src/app/features/project/files/pages/community-metadata/community-metadata.component.html b/src/app/features/files/pages/community-metadata/community-metadata.component.html similarity index 100% rename from src/app/features/project/files/pages/community-metadata/community-metadata.component.html rename to src/app/features/files/pages/community-metadata/community-metadata.component.html diff --git a/src/app/features/project/files/pages/community-metadata/community-metadata.component.scss b/src/app/features/files/pages/community-metadata/community-metadata.component.scss similarity index 100% rename from src/app/features/project/files/pages/community-metadata/community-metadata.component.scss rename to src/app/features/files/pages/community-metadata/community-metadata.component.scss diff --git a/src/app/features/project/files/pages/community-metadata/community-metadata.component.spec.ts b/src/app/features/files/pages/community-metadata/community-metadata.component.spec.ts similarity index 100% rename from src/app/features/project/files/pages/community-metadata/community-metadata.component.spec.ts rename to src/app/features/files/pages/community-metadata/community-metadata.component.spec.ts diff --git a/src/app/features/project/files/pages/community-metadata/community-metadata.component.ts b/src/app/features/files/pages/community-metadata/community-metadata.component.ts similarity index 100% rename from src/app/features/project/files/pages/community-metadata/community-metadata.component.ts rename to src/app/features/files/pages/community-metadata/community-metadata.component.ts diff --git a/src/app/features/project/files/pages/file-detail/file-detail.component.html b/src/app/features/files/pages/file-detail/file-detail.component.html similarity index 84% rename from src/app/features/project/files/pages/file-detail/file-detail.component.html rename to src/app/features/files/pages/file-detail/file-detail.component.html index 0ef4b7ea3..9169ca030 100644 --- a/src/app/features/project/files/pages/file-detail/file-detail.component.html +++ b/src/app/features/files/pages/file-detail/file-detail.component.html @@ -2,9 +2,9 @@ - {{ 'project.files.detail.tabs.details' | translate }} - {{ 'project.files.detail.tabs.revisions' | translate }} - {{ 'project.files.detail.tabs.keywords' | translate }} + {{ 'files.detail.tabs.details' | translate }} + {{ 'files.detail.tabs.revisions' | translate }} + {{ 'files.detail.tabs.keywords' | translate }} @@ -13,14 +13,14 @@
    @@ -91,7 +91,7 @@ } @else { - + }
    diff --git a/src/app/features/project/files/pages/file-detail/file-detail.component.scss b/src/app/features/files/pages/file-detail/file-detail.component.scss similarity index 100% rename from src/app/features/project/files/pages/file-detail/file-detail.component.scss rename to src/app/features/files/pages/file-detail/file-detail.component.scss diff --git a/src/app/features/project/files/pages/file-detail/file-detail.component.spec.ts b/src/app/features/files/pages/file-detail/file-detail.component.spec.ts similarity index 100% rename from src/app/features/project/files/pages/file-detail/file-detail.component.spec.ts rename to src/app/features/files/pages/file-detail/file-detail.component.spec.ts diff --git a/src/app/features/project/files/pages/file-detail/file-detail.component.ts b/src/app/features/files/pages/file-detail/file-detail.component.ts similarity index 65% rename from src/app/features/project/files/pages/file-detail/file-detail.component.ts rename to src/app/features/files/pages/file-detail/file-detail.component.ts index bd8cb5be6..8a0c74f1b 100644 --- a/src/app/features/project/files/pages/file-detail/file-detail.component.ts +++ b/src/app/features/files/pages/file-detail/file-detail.component.ts @@ -1,4 +1,4 @@ -import { select, Store } from '@ngxs/store'; +import { createDispatchMap, select, Store } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; @@ -6,33 +6,34 @@ import { Button } from 'primeng/button'; import { Menu } from 'primeng/menu'; import { Tab, TabList, Tabs } from 'primeng/tabs'; -import { EMPTY, switchMap } from 'rxjs'; +import { switchMap } from 'rxjs'; import { ChangeDetectionStrategy, Component, DestroyRef, HostBinding, inject } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { LoadingSpinnerComponent, SubHeaderComponent } from '@osf/shared/components'; +import { OsfFile } from '@shared/models'; +import { CustomConfirmationService, ToastService } from '@shared/services'; + import { FileKeywordsComponent, FileMetadataComponent, - FileProjectMetadataComponent, + FileResourceMetadataComponent, FileRevisionsComponent, -} from '@osf/features/project/files/components'; -import { FileDetailTab } from '@osf/features/project/files/enums/file-detail-tab.enum'; -import { embedDynamicJs, embedStaticHtml } from '@osf/features/project/files/models'; +} from '../../components'; +import { embedDynamicJs, embedStaticHtml } from '../../constants'; +import { FileDetailTab } from '../../enums'; import { DeleteEntry, + FilesSelectors, GetFile, GetFileMetadata, - GetFileProjectContributors, - GetFileProjectMetadata, + GetFileResourceContributors, + GetFileResourceMetadata, GetFileRevisions, - ProjectFilesSelectors, -} from '@osf/features/project/files/store'; -import { LoadingSpinnerComponent, SubHeaderComponent } from '@shared/components'; -import { OsfFile } from '@shared/models'; -import { CustomConfirmationService, ToastService } from '@shared/services'; +} from '../../store'; @Component({ selector: 'osf-file-detail', @@ -49,7 +50,7 @@ import { CustomConfirmationService, ToastService } from '@shared/services'; FileKeywordsComponent, FileRevisionsComponent, FileMetadataComponent, - FileProjectMetadataComponent, + FileResourceMetadataComponent, ], templateUrl: './file-detail.component.html', styleUrl: './file-detail.component.scss', @@ -66,10 +67,20 @@ export class FileDetailComponent { readonly toastService = inject(ToastService); readonly customConfirmationService = inject(CustomConfirmationService); - file = select(ProjectFilesSelectors.getOpenedFile); - isFileLoading = select(ProjectFilesSelectors.isOpenedFileLoading); + private readonly actions = createDispatchMap({ + getFile: GetFile, + getFileRevisions: GetFileRevisions, + getFileMetadata: GetFileMetadata, + getFileResourceMetadata: GetFileResourceMetadata, + getFileResourceContributors: GetFileResourceContributors, + deleteEntry: DeleteEntry, + }); + + file = select(FilesSelectors.getOpenedFile); + isFileLoading = select(FilesSelectors.isOpenedFileLoading); safeLink: SafeResourceUrl | null = null; - projectId: string | null = null; + resourceId = ''; + resourceType = ''; isIframeLoading = true; @@ -81,66 +92,60 @@ export class FileDetailComponent { embedItems = [ { - label: 'project.files.detail.actions.copyDynamicIframe', + label: 'files.detail.actions.copyDynamicIframe', command: () => this.handleCopyDynamicEmbed(), }, { - label: 'project.files.detail.actions.copyStaticIframe', + label: 'files.detail.actions.copyStaticIframe', command: () => this.handleCopyStaticEmbed(), }, ]; shareItems = [ { - label: 'project.files.detail.actions.share.email', + label: 'files.detail.actions.share.email', command: () => this.handleEmailShare(), }, { - label: 'project.files.detail.actions.share.x', + label: 'files.detail.actions.share.x', command: () => this.handleXShare(), }, { - label: 'project.files.detail.actions.share.facebook', + label: 'files.detail.actions.share.facebook', command: () => this.handleFacebookShare(), }, ]; constructor() { - this.route.parent?.parent?.parent?.parent?.params.subscribe((params) => { - if (params['id']) { - this.projectId = params['id']; - } - }); - this.route.params .pipe( takeUntilDestroyed(this.destroyRef), switchMap((params) => { this.fileGuid = params['fileGuid']; - return this.store - .dispatch(new GetFile(this.fileGuid)) - .pipe(switchMap(() => this.route.parent?.parent?.parent?.params || EMPTY)); + return this.actions.getFile(this.fileGuid); }) ) - .subscribe((parentParams) => { + .subscribe(() => { const link = this.file()?.links.render; if (link) { this.safeLink = this.sanitizer.bypassSecurityTrustResourceUrl(link); } + this.resourceId = this.file()?.target.id || ''; + this.resourceType = this.file()?.target.type || ''; + const fileId = this.file()?.path.replaceAll('/', ''); + if (this.resourceId && this.resourceType) { + this.actions.getFileResourceMetadata(this.resourceId, this.resourceType); + this.actions.getFileResourceContributors(this.resourceId, this.resourceType); - this.projectId = parentParams['id']; - if (this.projectId) { - this.store.dispatch(new GetFileProjectMetadata(this.projectId)); - this.store.dispatch(new GetFileProjectContributors(this.projectId)); - const fileId = this.file()?.path.replaceAll('/', ''); if (fileId) { - this.store.dispatch(new GetFileRevisions(this.projectId, fileId)); + const fileProvider = this.file()?.provider || ''; + this.actions.getFileRevisions(this.resourceId, fileProvider, fileId); } } }); this.route.params.pipe(takeUntilDestroyed(this.destroyRef)).subscribe((params) => { - this.store.dispatch(new GetFileMetadata(params['fileGuid'])); + this.actions.getFileMetadata(params['fileGuid']); }); } @@ -152,7 +157,7 @@ export class FileDetailComponent { navigator.clipboard .writeText(embedHtml) .then(() => { - this.toastService.showSuccess('project.files.detail.toast.copiedToClipboard'); + this.toastService.showSuccess('files.detail.toast.copiedToClipboard'); }) .catch((err) => { this.toastService.showError(err.message); @@ -160,21 +165,23 @@ export class FileDetailComponent { } deleteEntry(link: string): void { - if (this.projectId) { - this.store - .dispatch(new DeleteEntry(this.projectId, link)) + if (this.resourceId) { + const redirectUrl = + this.resourceType === 'nodes' ? `/project/${this.resourceId}/files` : `/registry/${this.resourceId}/files`; + this.actions + .deleteEntry(this.resourceId, link) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe(() => { - this.router.navigate(['/project', this.projectId, 'files']); + this.router.navigate([redirectUrl]); }); } } confirmDelete(file: OsfFile): void { this.customConfirmationService.confirmDelete({ - headerKey: 'project.files.dialogs.deleteFile.title', + headerKey: 'files.dialogs.deleteFile.title', messageParams: { name: file.name }, - messageKey: 'project.files.dialogs.deleteFile.message', + messageKey: 'files.dialogs.deleteFile.message', onConfirm: () => this.deleteEntry(file.links.delete), }); } diff --git a/src/app/features/project/files/pages/project-files-container/project-files-container.component.html b/src/app/features/files/pages/files-container/files-container.component.html similarity index 100% rename from src/app/features/project/files/pages/project-files-container/project-files-container.component.html rename to src/app/features/files/pages/files-container/files-container.component.html diff --git a/src/app/features/project/files/pages/project-files-container/project-files-container.component.spec.ts b/src/app/features/files/pages/files-container/files-container.component.spec.ts similarity index 51% rename from src/app/features/project/files/pages/project-files-container/project-files-container.component.spec.ts rename to src/app/features/files/pages/files-container/files-container.component.spec.ts index f2e7467a8..673b9b09c 100644 --- a/src/app/features/project/files/pages/project-files-container/project-files-container.component.spec.ts +++ b/src/app/features/files/pages/files-container/files-container.component.spec.ts @@ -4,18 +4,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { SubHeaderComponent } from '@osf/shared/components'; -import { ProjectFilesContainerComponent } from './project-files-container.component'; +import { FilesContainerComponent } from './files-container.component'; -describe('ProjectFilesContainerComponent', () => { - let component: ProjectFilesContainerComponent; - let fixture: ComponentFixture; +describe('FilesContainerComponent', () => { + let component: FilesContainerComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ProjectFilesContainerComponent, MockComponent(SubHeaderComponent)], + imports: [FilesContainerComponent, MockComponent(SubHeaderComponent)], }).compileComponents(); - fixture = TestBed.createComponent(ProjectFilesContainerComponent); + fixture = TestBed.createComponent(FilesContainerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/features/project/files/pages/project-files-container/project-files-container.component.ts b/src/app/features/files/pages/files-container/files-container.component.ts similarity index 58% rename from src/app/features/project/files/pages/project-files-container/project-files-container.component.ts rename to src/app/features/files/pages/files-container/files-container.component.ts index 21ae90b0e..dc4406436 100644 --- a/src/app/features/project/files/pages/project-files-container/project-files-container.component.ts +++ b/src/app/features/files/pages/files-container/files-container.component.ts @@ -2,9 +2,9 @@ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { RouterOutlet } from '@angular/router'; @Component({ - selector: 'osf-project-files-container', + selector: 'osf-files-container', imports: [RouterOutlet], - templateUrl: './project-files-container.component.html', + templateUrl: './files-container.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export class ProjectFilesContainerComponent {} +export class FilesContainerComponent {} diff --git a/src/app/features/project/files/pages/project-files/project-files.component.html b/src/app/features/files/pages/files/files.component.html similarity index 68% rename from src/app/features/project/files/pages/project-files/project-files.component.html rename to src/app/features/files/pages/files/files.component.html index 00edc7c98..9e25d61ce 100644 --- a/src/app/features/project/files/pages/project-files/project-files.component.html +++ b/src/app/features/files/pages/files/files.component.html @@ -1,4 +1,4 @@ - + @if (!dataLoaded()) { @@ -20,7 +20,7 @@

    {{ option.label }}

    - +

    @@ -29,14 +29,14 @@
    @@ -49,39 +49,40 @@ raised (click)="downloadFolder()" [icon]="'fas fa-download'" - [label]="'project.files.actions.downloadAsZip' | translate" + [label]="'files.actions.downloadAsZip' | translate" > - - - - - + @if (!isViewOnly()) { + + + + + }
    { - let component: ProjectFilesComponent; - let fixture: ComponentFixture; +describe('FilesComponent', () => { + let component: FilesComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [ProjectFilesComponent, MockComponent(SubHeaderComponent)], + imports: [FilesComponent, MockComponent(SubHeaderComponent)], }).compileComponents(); - fixture = TestBed.createComponent(ProjectFilesComponent); + fixture = TestBed.createComponent(FilesComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/features/project/files/pages/project-files/project-files.component.ts b/src/app/features/files/pages/files/files.component.ts similarity index 71% rename from src/app/features/project/files/pages/project-files/project-files.component.ts rename to src/app/features/files/pages/files/files.component.ts index 01463158c..88b5d81bd 100644 --- a/src/app/features/project/files/pages/project-files/project-files.component.ts +++ b/src/app/features/files/pages/files/files.component.ts @@ -28,14 +28,12 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormControl, FormsModule, ReactiveFormsModule } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; -import { CreateFolderDialogComponent } from '@osf/features/project/files/components'; import { CreateFolder, DeleteEntry, GetConfiguredStorageAddons, GetFiles, GetRootFolders, - ProjectFilesSelectors, RenameEntry, ResetState, SetCurrentFolder, @@ -43,10 +41,9 @@ import { SetMoveFileCurrentFolder, SetSearch, SetSort, -} from '@osf/features/project/files/store'; -import { approveFile } from '@osf/features/project/files/utils'; -import { GetProjectById, ProjectOverviewSelectors } from '@osf/features/project/overview/store'; +} from '@osf/features/files/store'; import { ALL_SORT_OPTIONS } from '@osf/shared/constants'; +import { ResourceType } from '@osf/shared/enums'; import { FilesTreeComponent, FormSelectComponent, @@ -57,8 +54,14 @@ import { import { ConfiguredStorageAddon, FilesTreeActions, OsfFile } from '@shared/models'; import { FilesService } from '@shared/services'; +import { CreateFolderDialogComponent } from '../../components'; +import { FileProvider } from '../../constants'; +import { FilesSelectors } from '../../store'; + +import { environment } from 'src/environments/environment'; + @Component({ - selector: 'osf-project-files', + selector: 'osf-files', imports: [ TableModule, Button, @@ -74,12 +77,12 @@ import { FilesService } from '@shared/services'; FilesTreeComponent, FormSelectComponent, ], - templateUrl: './project-files.component.html', - styleUrl: './project-files.component.scss', + templateUrl: './files.component.html', + styleUrl: './files.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, providers: [DialogService, TreeDragDropService], }) -export class ProjectFilesComponent { +export class FilesComponent { @HostBinding('class') classes = 'flex flex-column flex-1 w-full h-full'; private readonly filesService = inject(FilesService); @@ -98,23 +101,21 @@ export class ProjectFilesComponent { setMoveFileCurrentFolder: SetMoveFileCurrentFolder, setSearch: SetSearch, setSort: SetSort, - getProject: GetProjectById, getRootFolders: GetRootFolders, getConfiguredStorageAddons: GetConfiguredStorageAddons, resetState: ResetState, }); - protected readonly files = select(ProjectFilesSelectors.getFiles); - protected readonly isFilesLoading = select(ProjectFilesSelectors.isFilesLoading); - protected readonly currentFolder = select(ProjectFilesSelectors.getCurrentFolder); - protected readonly provider = select(ProjectFilesSelectors.getProvider); - - protected readonly project = select(ProjectOverviewSelectors.getProject); - protected readonly projectId = signal(''); - private readonly rootFolders = select(ProjectFilesSelectors.getRootFolders); - protected isRootFoldersLoading = select(ProjectFilesSelectors.isRootFoldersLoading); - private readonly configuredStorageAddons = select(ProjectFilesSelectors.getConfiguredStorageAddons); - protected isConfiguredStorageAddonsLoading = select(ProjectFilesSelectors.isConfiguredStorageAddonsLoading); + protected readonly files = select(FilesSelectors.getFiles); + protected readonly isFilesLoading = select(FilesSelectors.isFilesLoading); + protected readonly currentFolder = select(FilesSelectors.getCurrentFolder); + protected readonly provider = select(FilesSelectors.getProvider); + + protected readonly resourceId = signal(''); + private readonly rootFolders = select(FilesSelectors.getRootFolders); + protected isRootFoldersLoading = select(FilesSelectors.isRootFoldersLoading); + private readonly configuredStorageAddons = select(FilesSelectors.getConfiguredStorageAddons); + protected isConfiguredStorageAddonsLoading = select(FilesSelectors.isConfiguredStorageAddonsLoading); protected currentRootFolder = model<{ label: string; folder: OsfFile } | null>(null); protected readonly progress = signal(0); protected readonly fileName = signal(''); @@ -122,10 +123,14 @@ export class ProjectFilesComponent { protected readonly searchControl = new FormControl(''); protected readonly sortControl = new FormControl(ALL_SORT_OPTIONS[0].value); + private readonly urlMap = new Map([ + [ResourceType.Project, 'nodes'], + [ResourceType.Registration, 'registrations'], + ]); + protected readonly rootFoldersOptions = computed(() => { const rootFolders = this.rootFolders(); const addons = this.configuredStorageAddons(); - if (rootFolders && addons) { return rootFolders.map((folder) => ({ label: this.getAddonName(addons, folder.provider), @@ -135,43 +140,54 @@ export class ProjectFilesComponent { return []; }); + resourceType = signal( + this.activeRoute.parent?.parent?.snapshot.data['resourceType'] || ResourceType.Project + ); + + protected readonly isViewOnly = computed(() => { + return this.resourceType() === ResourceType.Registration; + }); + + protected readonly isViewOnlyDownloadable = computed(() => { + return this.resourceType() === ResourceType.Registration; + }); + fileIsUploading = signal(false); isFolderOpening = signal(false); sortOptions = ALL_SORT_OPTIONS; + storageProvider = FileProvider.OsfStorage; + protected readonly filesTreeActions: FilesTreeActions = { setCurrentFolder: (folder) => this.actions.setCurrentFolder(folder), setFilesIsLoading: (isLoading) => this.actions.setFilesIsLoading(isLoading), getFiles: (filesLink) => this.actions.getFiles(filesLink), - deleteEntry: (projectId, link) => this.actions.deleteEntry(projectId, link), - renameEntry: (projectId, link, newName) => this.actions.renameEntry(projectId, link, newName), + deleteEntry: (resourceId, link) => this.actions.deleteEntry(resourceId, link), + renameEntry: (resourceId, link, newName) => this.actions.renameEntry(resourceId, link, newName), setMoveFileCurrentFolder: (folder) => this.actions.setMoveFileCurrentFolder(folder), }; constructor() { this.activeRoute.parent?.parent?.parent?.params.subscribe((params) => { if (params['id']) { - this.projectId.set(params['id']); - if (!this.project()) { - this.filesTreeActions.setFilesIsLoading?.(true); - this.actions.getProject(params['id']); - } + this.resourceId.set(params['id']); + this.filesTreeActions.setFilesIsLoading?.(true); } }); effect(() => { - const project = this.project(); + const resourceId = this.resourceId(); - if (project) { - this.actions.getRootFolders(project.links.rootFolder); - this.actions.getConfiguredStorageAddons(project.links.iri); - } + const resourcePath = this.urlMap.get(this.resourceType()!); + const folderLink = `${environment.apiUrl}/${resourcePath}/${resourceId}/files/`; + const iriLink = `${environment.webUrl}/${resourceId}`; + this.actions.getRootFolders(folderLink); + this.actions.getConfiguredStorageAddons(iriLink); }); effect(() => { const rootFolders = this.rootFolders(); - if (rootFolders) { const osfRootFolder = rootFolders.find((folder) => folder.provider === 'osfstorage'); if (osfRootFolder) { @@ -185,15 +201,18 @@ export class ProjectFilesComponent { effect(() => { const currentRootFolder = this.currentRootFolder(); - if (currentRootFolder) { this.actions.setCurrentFolder(currentRootFolder.folder); } }); effect(() => { - if (!this.isConfiguredStorageAddonsLoading() && !this.isRootFoldersLoading()) { + if (this.resourceType() === ResourceType.Registration) { this.dataLoaded.set(true); + } else { + if (!this.isConfiguredStorageAddonsLoading() && !this.isRootFoldersLoading()) { + this.dataLoaded.set(true); + } } }); @@ -243,13 +262,15 @@ export class ProjectFilesComponent { if (event.type === HttpEventType.UploadProgress && event.total) { this.progress.set(Math.round((event.loaded / event.total) * 100)); } - - if (event.type === HttpEventType.Response) { - if (event.body) { - const fileId = event?.body?.data.id; - approveFile(fileId, this.projectId()); - } - } + // [NM] Check if need to create guid here + // if (event.type === HttpEventType.Response) { + // if (event.body) { + // const fileId = event?.body?.data?.id?.split('/').pop(); + // if (fileId) { + // this.filesService.getFileGuid(fileId).pipe(takeUntilDestroyed(this.destroyRef)).subscribe(); + // } + // } + // } }); } @@ -271,7 +292,7 @@ export class ProjectFilesComponent { .open(CreateFolderDialogComponent, { width: '448px', focusOnShow: false, - header: this.translateService.instant('project.files.dialogs.createFolder.title'), + header: this.translateService.instant('files.dialogs.createFolder.title'), closeOnEscape: true, modal: true, closable: true, @@ -291,17 +312,17 @@ export class ProjectFilesComponent { } downloadFolder(): void { - const projectId = this.projectId(); + const resourceId = this.resourceId(); const folderId = this.currentFolder()?.id ?? ''; const isRootFolder = !this.currentFolder()?.relationships?.parentFolderLink; const provider = this.currentRootFolder()?.folder?.provider ?? 'osfstorage'; - if (projectId && folderId) { + if (resourceId && folderId) { if (isRootFolder) { - const link = this.filesService.getFolderDownloadLink(projectId, provider, '', true); + const link = this.filesService.getFolderDownloadLink(resourceId, provider, '', true); window.open(link, '_blank')?.focus(); } else { - const link = this.filesService.getFolderDownloadLink(projectId, provider, folderId, false); + const link = this.filesService.getFolderDownloadLink(resourceId, provider, folderId, false); window.open(link, '_blank')?.focus(); } } diff --git a/src/app/features/files/store/files.actions.ts b/src/app/features/files/store/files.actions.ts new file mode 100644 index 000000000..a7ac92dc4 --- /dev/null +++ b/src/app/features/files/store/files.actions.ts @@ -0,0 +1,153 @@ +import { OsfFile } from '@osf/shared/models'; + +import { PatchFileMetadata } from '../models'; + +export class GetRootFolderFiles { + static readonly type = '[Files] Get Root Folder Files'; + + constructor(public resourceId: string) {} +} + +export class GetFiles { + static readonly type = '[Files] Get Files'; + + constructor(public filesLink: string) {} +} + +export class SetFilesIsLoading { + static readonly type = '[Files] Set Files Loading'; + + constructor(public isLoading: boolean) {} +} + +export class RenameEntry { + static readonly type = '[Files] Rename entry'; + + constructor( + public resourceId: string, + public link: string, + public name: string + ) {} +} + +export class SetSearch { + static readonly type = '[Files] Set Search'; + + constructor(public search: string) {} +} + +export class SetSort { + static readonly type = '[Files] Set Sort'; + + constructor(public sort: string) {} +} + +export class SetCurrentFolder { + static readonly type = '[Files] Set Current Folder'; + + constructor(public folder: OsfFile | null) {} +} + +export class SetMoveFileCurrentFolder { + static readonly type = '[Files] Set Move File Current Folder'; + + constructor(public folder: OsfFile | null) {} +} + +export class GetMoveFileFiles { + static readonly type = '[Files] Get Move File Files'; + + constructor(public filesLink: string) {} +} + +export class GetFile { + static readonly type = '[Files] Get File'; + + constructor(public fileGuid: string) {} +} + +export class GetFileMetadata { + static readonly type = '[Files] Get File Metadata'; + + constructor(public fileGuid: string) {} +} + +export class GetFileResourceMetadata { + static readonly type = '[Files] Get File Resource Metadata'; + + constructor( + public resourceId: string, + public resourceType: string + ) {} +} + +export class GetFileResourceContributors { + static readonly type = '[Files] Get File Resource Contributors'; + + constructor( + public resourceId: string, + public resourceType: string + ) {} +} + +export class SetFileMetadata { + static readonly type = '[Files] Set File Metadata'; + + constructor( + public payload: PatchFileMetadata, + public fileGuid: string + ) {} +} + +export class GetFileRevisions { + static readonly type = '[Files] Get Revisions'; + + constructor( + public resourceId: string, + public fileProvider: string, + public fileId: string + ) {} +} + +export class UpdateTags { + static readonly type = '[Files] Update Tags'; + + constructor( + public tags: string[], + public fileGuid: string + ) {} +} + +export class CreateFolder { + static readonly type = '[Files] Create folder'; + + constructor( + public newFolderLink: string, + public folderName: string + ) {} +} + +export class DeleteEntry { + static readonly type = '[Files] Delete entry'; + + constructor( + public resourceId: string, + public link: string + ) {} +} + +export class GetRootFolders { + static readonly type = '[Files] Get Folders'; + + constructor(public folderLink: string) {} +} + +export class GetConfiguredStorageAddons { + static readonly type = '[Files] Get ConfiguredStorageAddons'; + + constructor(public resourceUri: string) {} +} + +export class ResetState { + static readonly type = '[Files] Reset State'; +} diff --git a/src/app/features/files/store/files.model.ts b/src/app/features/files/store/files.model.ts new file mode 100644 index 000000000..79d621f16 --- /dev/null +++ b/src/app/features/files/store/files.model.ts @@ -0,0 +1,82 @@ +import { ContributorModel, OsfFile, ResourceMetadata } from '@shared/models'; +import { ConfiguredStorageAddon } from '@shared/models/addons'; +import { AsyncStateModel } from '@shared/models/store'; + +import { FileProvider } from '../constants'; +import { OsfFileCustomMetadata, OsfFileRevision } from '../models'; + +export interface FilesStateModel { + files: AsyncStateModel; + moveFileFiles: AsyncStateModel; + currentFolder: OsfFile | null; + moveFileCurrentFolder: OsfFile | null; + search: string; + sort: string; + provider: (typeof FileProvider)[keyof typeof FileProvider]; + openedFile: AsyncStateModel; + fileMetadata: AsyncStateModel; + resourceMetadata: AsyncStateModel; + contributors: AsyncStateModel[] | null>; + fileRevisions: AsyncStateModel; + tags: AsyncStateModel; + rootFolders: AsyncStateModel; + configuredStorageAddons: AsyncStateModel; +} + +export const filesStateDefaults: FilesStateModel = { + files: { + data: [], + isLoading: false, + error: null, + }, + moveFileFiles: { + data: [], + isLoading: false, + error: null, + }, + currentFolder: null, + moveFileCurrentFolder: null, + search: '', + sort: 'name', + provider: FileProvider.OsfStorage, + openedFile: { + data: null, + isLoading: false, + error: null, + }, + fileMetadata: { + data: null, + isLoading: false, + error: null, + }, + resourceMetadata: { + data: null, + isLoading: false, + error: null, + }, + contributors: { + data: null, + isLoading: false, + error: null, + }, + fileRevisions: { + data: null, + isLoading: false, + error: null, + }, + tags: { + data: [], + isLoading: false, + error: null, + }, + rootFolders: { + data: [], + isLoading: true, + error: null, + }, + configuredStorageAddons: { + data: [], + isLoading: true, + error: null, + }, +}; diff --git a/src/app/features/files/store/files.selectors.ts b/src/app/features/files/store/files.selectors.ts new file mode 100644 index 000000000..a7033f803 --- /dev/null +++ b/src/app/features/files/store/files.selectors.ts @@ -0,0 +1,125 @@ +import { Selector } from '@ngxs/store'; + +import { ConfiguredStorageAddon, ContributorModel, OsfFile, ResourceMetadata } from '@shared/models'; + +import { OsfFileCustomMetadata, OsfFileRevision } from '../models'; + +import { FilesStateModel } from './files.model'; +import { FilesState } from './files.state'; + +export class FilesSelectors { + @Selector([FilesState]) + static getFiles(state: FilesStateModel): OsfFile[] { + return state.files.data; + } + + @Selector([FilesState]) + static isFilesLoading(state: FilesStateModel): boolean { + return state.files.isLoading; + } + + @Selector([FilesState]) + static getMoveFileFiles(state: FilesStateModel): OsfFile[] { + return state.moveFileFiles.data; + } + + @Selector([FilesState]) + static isMoveFileFilesLoading(state: FilesStateModel): boolean { + return state.moveFileFiles.isLoading; + } + + @Selector([FilesState]) + static getCurrentFolder(state: FilesStateModel): OsfFile | null { + return state.currentFolder; + } + + @Selector([FilesState]) + static getMoveFileCurrentFolder(state: FilesStateModel): OsfFile | null { + return state.moveFileCurrentFolder; + } + + @Selector([FilesState]) + static getProvider(state: FilesStateModel): string { + return state.provider; + } + + @Selector([FilesState]) + static getOpenedFile(state: FilesStateModel): OsfFile | null { + return state.openedFile.data; + } + + @Selector([FilesState]) + static isOpenedFileLoading(state: FilesStateModel): boolean { + return state.openedFile.isLoading; + } + + @Selector([FilesState]) + static getFileCustomMetadata(state: FilesStateModel): OsfFileCustomMetadata | null { + return state.fileMetadata.data; + } + + @Selector([FilesState]) + static isFileMetadataLoading(state: FilesStateModel): boolean { + return state.fileMetadata.isLoading; + } + + @Selector([FilesState]) + static getResourceMetadata(state: FilesStateModel): ResourceMetadata | null { + return state.resourceMetadata.data; + } + + @Selector([FilesState]) + static isResourceMetadataLoading(state: FilesStateModel): boolean { + return state.resourceMetadata.isLoading; + } + + @Selector([FilesState]) + static getContributors(state: FilesStateModel): Partial[] | null { + return state.contributors.data; + } + + @Selector([FilesState]) + static isResourceContributorsLoading(state: FilesStateModel): boolean { + return state.contributors.isLoading; + } + + @Selector([FilesState]) + static getFileRevisions(state: FilesStateModel): OsfFileRevision[] | null { + return state.fileRevisions.data; + } + + @Selector([FilesState]) + static isFileRevisionsLoading(state: FilesStateModel): boolean { + return state.fileRevisions.isLoading; + } + + @Selector([FilesState]) + static getFileTags(state: FilesStateModel): string[] { + return state.tags.data; + } + + @Selector([FilesState]) + static isFileTagsLoading(state: FilesStateModel): boolean { + return state.tags.isLoading; + } + + @Selector([FilesState]) + static getRootFolders(state: FilesStateModel): OsfFile[] | null { + return state.rootFolders.data; + } + + @Selector([FilesState]) + static isRootFoldersLoading(state: FilesStateModel): boolean { + return state.rootFolders.isLoading; + } + + @Selector([FilesState]) + static getConfiguredStorageAddons(state: FilesStateModel): ConfiguredStorageAddon[] | null { + return state.configuredStorageAddons.data; + } + + @Selector([FilesState]) + static isConfiguredStorageAddonsLoading(state: FilesStateModel): boolean { + return state.configuredStorageAddons.isLoading; + } +} diff --git a/src/app/features/project/files/store/project-files.state.ts b/src/app/features/files/store/files.state.ts similarity index 58% rename from src/app/features/project/files/store/project-files.state.ts rename to src/app/features/files/store/files.state.ts index 413079895..61d6217bb 100644 --- a/src/app/features/project/files/store/project-files.state.ts +++ b/src/app/features/files/store/files.state.ts @@ -1,18 +1,22 @@ import { Action, State, StateContext } from '@ngxs/store'; -import { catchError, finalize, forkJoin, tap, throwError } from 'rxjs'; +import { catchError, finalize, forkJoin, tap } from 'rxjs'; import { inject, Injectable } from '@angular/core'; -import { MapProjectMetadata } from '@osf/features/project/files/mappers'; +import { handleSectionError } from '@osf/shared/helpers'; +import { FilesService, ToastService } from '@shared/services'; + +import { MapResourceMetadata } from '../mappers/resource-metadata.mapper'; + import { CreateFolder, DeleteEntry, GetConfiguredStorageAddons, GetFile, GetFileMetadata, - GetFileProjectContributors, - GetFileProjectMetadata, + GetFileResourceContributors, + GetFileResourceMetadata, GetFileRevisions, GetFiles, GetMoveFileFiles, @@ -27,24 +31,20 @@ import { SetSearch, SetSort, UpdateTags, -} from '@osf/features/project/files/store/project-files.actions'; -import { ProjectFilesStateModel } from '@osf/features/project/files/store/project-files.model'; -import { ToastService } from '@shared/services'; -import { FilesService } from '@shared/services/files.service'; - -import { projectFilesStateDefaults } from '../models'; +} from './files.actions'; +import { filesStateDefaults, FilesStateModel } from './files.model'; @Injectable() -@State({ - name: 'projectFilesState', - defaults: projectFilesStateDefaults, +@State({ + name: 'filesState', + defaults: filesStateDefaults, }) -export class ProjectFilesState { +export class FilesState { filesService = inject(FilesService); toastService = inject(ToastService); @Action(GetMoveFileFiles) - getMoveFileFiles(ctx: StateContext, action: GetMoveFileFiles) { + getMoveFileFiles(ctx: StateContext, action: GetMoveFileFiles) { const state = ctx.getState(); ctx.patchState({ moveFileFiles: { ...state.moveFileFiles, isLoading: true, error: null }, @@ -62,15 +62,14 @@ export class ProjectFilesState { }); }, }), - catchError((error) => this.handleError(ctx, 'moveFileFiles', error)) + catchError((error) => handleSectionError(ctx, 'moveFileFiles', error)) ); } @Action(GetFiles) - getFiles(ctx: StateContext, action: GetFiles) { + getFiles(ctx: StateContext, action: GetFiles) { const state = ctx.getState(); ctx.patchState({ files: { ...state.files, isLoading: true, error: null } }); - return this.filesService.getFiles(action.filesLink, state.search, state.sort).pipe( tap({ next: (files) => { @@ -83,28 +82,28 @@ export class ProjectFilesState { }); }, }), - catchError((error) => this.handleError(ctx, 'files', error)) + catchError((error) => handleSectionError(ctx, 'files', error)) ); } @Action(SetFilesIsLoading) - setFilesIsLoading(ctx: StateContext, action: SetFilesIsLoading) { + setFilesIsLoading(ctx: StateContext, action: SetFilesIsLoading) { const state = ctx.getState(); ctx.patchState({ files: { ...state.files, isLoading: action.isLoading, error: null } }); } @Action(SetCurrentFolder) - setSelectedFolder(ctx: StateContext, action: SetCurrentFolder) { + setSelectedFolder(ctx: StateContext, action: SetCurrentFolder) { ctx.patchState({ currentFolder: action.folder }); } @Action(SetMoveFileCurrentFolder) - setMoveFileSelectedFolder(ctx: StateContext, action: SetMoveFileCurrentFolder) { + setMoveFileSelectedFolder(ctx: StateContext, action: SetMoveFileCurrentFolder) { ctx.patchState({ moveFileCurrentFolder: action.folder }); } @Action(CreateFolder) - createFolder(ctx: StateContext, action: CreateFolder) { + createFolder(ctx: StateContext, action: CreateFolder) { const state = ctx.getState(); ctx.patchState({ files: { ...state.files, isLoading: true, error: null } }); @@ -114,7 +113,7 @@ export class ProjectFilesState { } @Action(DeleteEntry) - deleteEntry(ctx: StateContext, action: DeleteEntry) { + deleteEntry(ctx: StateContext, action: DeleteEntry) { return this.filesService.deleteEntry(action.link).pipe( tap({ next: () => { @@ -122,7 +121,7 @@ export class ProjectFilesState { if (selectedFolder?.relationships.filesLink) { ctx.dispatch(new GetFiles(selectedFolder?.relationships.filesLink)); } else { - ctx.dispatch(new GetRootFolderFiles(action.projectId)); + ctx.dispatch(new GetRootFolderFiles(action.resourceId)); } }, }) @@ -130,7 +129,7 @@ export class ProjectFilesState { } @Action(RenameEntry) - renameEntry(ctx: StateContext, action: RenameEntry) { + renameEntry(ctx: StateContext, action: RenameEntry) { const state = ctx.getState(); ctx.patchState({ files: { ...state.files, isLoading: true, error: null } }); @@ -141,7 +140,7 @@ export class ProjectFilesState { if (selectedFolder?.relationships.filesLink) { ctx.dispatch(new GetFiles(selectedFolder?.relationships.filesLink)); } else { - ctx.dispatch(new GetRootFolderFiles(action.projectId)); + ctx.dispatch(new GetRootFolderFiles(action.resourceId)); } }, }) @@ -149,17 +148,17 @@ export class ProjectFilesState { } @Action(SetSearch) - setSearch(ctx: StateContext, action: SetSearch) { + setSearch(ctx: StateContext, action: SetSearch) { ctx.patchState({ search: action.search }); } @Action(SetSort) - setSort(ctx: StateContext, action: SetSort) { + setSort(ctx: StateContext, action: SetSort) { ctx.patchState({ sort: action.sort }); } @Action(GetFile) - getFile(ctx: StateContext, action: GetFile) { + getFile(ctx: StateContext, action: GetFile) { const state = ctx.getState(); ctx.patchState({ openedFile: { ...state.openedFile, isLoading: true, error: null } }); ctx.patchState({ tags: { ...state.tags, isLoading: true, error: null } }); @@ -171,12 +170,12 @@ export class ProjectFilesState { ctx.patchState({ tags: { data: file.tags, isLoading: false, error: null } }); }, }), - catchError((error) => this.handleError(ctx, 'openedFile', error)) + catchError((error) => handleSectionError(ctx, 'openedFile', error)) ); } @Action(GetFileMetadata) - getFileMetadata(ctx: StateContext, action: GetFileMetadata) { + getFileMetadata(ctx: StateContext, action: GetFileMetadata) { const state = ctx.getState(); ctx.patchState({ fileMetadata: { ...state.fileMetadata, isLoading: true, error: null } }); @@ -186,12 +185,12 @@ export class ProjectFilesState { ctx.patchState({ fileMetadata: { data: metadata, isLoading: false, error: null } }); }, }), - catchError((error) => this.handleError(ctx, 'fileMetadata', error)) + catchError((error) => handleSectionError(ctx, 'fileMetadata', error)) ); } @Action(SetFileMetadata) - setFileMetadata(ctx: StateContext, action: SetFileMetadata) { + setFileMetadata(ctx: StateContext, action: SetFileMetadata) { const state = ctx.getState(); ctx.patchState({ fileMetadata: { ...state.fileMetadata, isLoading: true, error: null } }); @@ -203,25 +202,25 @@ export class ProjectFilesState { } }, }), - catchError((error) => this.handleError(ctx, 'fileMetadata', error)) + catchError((error) => handleSectionError(ctx, 'fileMetadata', error)) ); } - @Action(GetFileProjectMetadata) - getFileProjectMetadata(ctx: StateContext, action: GetFileProjectMetadata) { + @Action(GetFileResourceMetadata) + getFileResourceMetadata(ctx: StateContext, action: GetFileResourceMetadata) { const state = ctx.getState(); - ctx.patchState({ projectMetadata: { ...state.projectMetadata, isLoading: true, error: null } }); + ctx.patchState({ resourceMetadata: { ...state.resourceMetadata, isLoading: true, error: null } }); forkJoin({ - projectShortInfo: this.filesService.getProjectShortInfo(action.projectId), - projectMetadata: this.filesService.getProjectCustomMetadata(action.projectId), + resourceShortInfo: this.filesService.getResourceShortInfo(action.resourceId, action.resourceType), + resourceMetadata: this.filesService.getCustomMetadata(action.resourceId), }) - .pipe(catchError((error) => this.handleError(ctx, 'projectMetadata', error))) + .pipe(catchError((error) => handleSectionError(ctx, 'resourceMetadata', error))) .subscribe((results) => { - const projectMetadata = MapProjectMetadata(results.projectShortInfo, results.projectMetadata); + const resourceMetadata = MapResourceMetadata(results.resourceShortInfo, results.resourceMetadata); ctx.patchState({ - projectMetadata: { - data: projectMetadata, + resourceMetadata: { + data: resourceMetadata, isLoading: false, error: null, }, @@ -229,38 +228,38 @@ export class ProjectFilesState { }); } - @Action(GetFileProjectContributors) - getFileProjectContributors(ctx: StateContext, action: GetFileProjectContributors) { + @Action(GetFileResourceContributors) + getFileResourceContributors(ctx: StateContext, action: GetFileResourceContributors) { const state = ctx.getState(); ctx.patchState({ contributors: { ...state.contributors, isLoading: true, error: null } }); - return this.filesService.getProjectContributors(action.projectId).pipe( + return this.filesService.getResourceContributors(action.resourceId, action.resourceType).pipe( tap({ next: (contributors) => { ctx.patchState({ contributors: { data: contributors, isLoading: false, error: null } }); }, }), - catchError((error) => this.handleError(ctx, 'contributors', error)) + catchError((error) => handleSectionError(ctx, 'contributors', error)) ); } @Action(GetFileRevisions) - getFileRevisions(ctx: StateContext, action: GetFileRevisions) { + getFileRevisions(ctx: StateContext, action: GetFileRevisions) { const state = ctx.getState(); ctx.patchState({ fileRevisions: { ...state.fileRevisions, isLoading: true, error: null } }); - return this.filesService.getFileRevisions(action.projectId, state.provider, action.fileId).pipe( + return this.filesService.getFileRevisions(action.resourceId, action.fileProvider, action.fileId).pipe( tap({ next: (revisions) => { ctx.patchState({ fileRevisions: { data: revisions, isLoading: false, error: null } }); }, }), - catchError((error) => this.handleError(ctx, 'fileRevisions', error)) + catchError((error) => handleSectionError(ctx, 'fileRevisions', error)) ); } @Action(UpdateTags) - updateTags(ctx: StateContext, action: UpdateTags) { + updateTags(ctx: StateContext, action: UpdateTags) { const state = ctx.getState(); ctx.patchState({ tags: { ...state.tags, isLoading: true, error: null } }); @@ -270,12 +269,12 @@ export class ProjectFilesState { ctx.patchState({ tags: { data: file.tags, isLoading: false, error: null } }); }, }), - catchError((error) => this.handleError(ctx, 'tags', error)) + catchError((error) => handleSectionError(ctx, 'tags', error)) ); } @Action(GetRootFolders) - getRootFolders(ctx: StateContext, action: GetRootFolders) { + getRootFolders(ctx: StateContext, action: GetRootFolders) { const state = ctx.getState(); ctx.patchState({ rootFolders: { ...state.rootFolders, isLoading: true } }); @@ -290,12 +289,12 @@ export class ProjectFilesState { }, }), }), - catchError((error) => this.handleError(ctx, 'rootFolders', error)) + catchError((error) => handleSectionError(ctx, 'rootFolders', error)) ); } @Action(GetConfiguredStorageAddons) - getConfiguredStorageAddons(ctx: StateContext, action: GetConfiguredStorageAddons) { + getConfiguredStorageAddons(ctx: StateContext, action: GetConfiguredStorageAddons) { const state = ctx.getState(); ctx.patchState({ configuredStorageAddons: { ...state.configuredStorageAddons, isLoading: true } }); @@ -310,41 +309,12 @@ export class ProjectFilesState { }, }), }), - finalize(() => { - ctx.patchState({ configuredStorageAddons: { ...state.configuredStorageAddons, isLoading: false } }); - }), - catchError((error) => this.handleError(ctx, 'configuredStorageAddons', error)) + catchError((error) => handleSectionError(ctx, 'configuredStorageAddons', error)) ); } @Action(ResetState) - resetState(ctx: StateContext) { - ctx.patchState(projectFilesStateDefaults); - } - - private handleError( - ctx: StateContext, - section: - | 'files' - | 'moveFileFiles' - | 'openedFile' - | 'fileMetadata' - | 'projectMetadata' - | 'contributors' - | 'fileRevisions' - | 'tags' - | 'rootFolders' - | 'configuredStorageAddons', - error: Error - ) { - ctx.patchState({ - [section]: { - ...ctx.getState()[section], - isLoading: false, - error: error.message, - }, - }); - this.toastService.showError(error.message); - return throwError(() => error); + resetState(ctx: StateContext) { + ctx.patchState(filesStateDefaults); } } diff --git a/src/app/features/files/store/index.ts b/src/app/features/files/store/index.ts new file mode 100644 index 000000000..04f101f76 --- /dev/null +++ b/src/app/features/files/store/index.ts @@ -0,0 +1,3 @@ +export * from './files.actions'; +export * from './files.selectors'; +export * from './files.state'; diff --git a/src/app/features/moderation/components/bulk-upload/bulk-upload.component.html b/src/app/features/moderation/components/bulk-upload/bulk-upload.component.html index d9d0c619f..206118b6b 100644 --- a/src/app/features/moderation/components/bulk-upload/bulk-upload.component.html +++ b/src/app/features/moderation/components/bulk-upload/bulk-upload.component.html @@ -18,7 +18,7 @@

    {{ 'moderation.bulkUpload' | translate }}

    raised variant="text" severity="success" - [label]="'project.files.dialogs.uploadFile.title' | translate" + [label]="'files.dialogs.uploadFile.title' | translate" (click)="chooseCallback()" />
    diff --git a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html index d82ef794d..aa5746e53 100644 --- a/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html +++ b/src/app/features/moderation/components/collection-moderation-submissions/collection-moderation-submissions.component.html @@ -18,7 +18,7 @@
    { - let component: FileProjectMetadataComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [FileProjectMetadataComponent], - }).compileComponents(); - - fixture = TestBed.createComponent(FileProjectMetadataComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/features/project/files/components/file-project-metadata/file-project-metadata.component.ts b/src/app/features/project/files/components/file-project-metadata/file-project-metadata.component.ts deleted file mode 100644 index 57b5df777..000000000 --- a/src/app/features/project/files/components/file-project-metadata/file-project-metadata.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { select } from '@ngxs/store'; - -import { TranslatePipe } from '@ngx-translate/core'; - -import { Skeleton } from 'primeng/skeleton'; - -import { DatePipe } from '@angular/common'; -import { ChangeDetectionStrategy, Component } from '@angular/core'; - -import { ProjectFilesSelectors } from '@osf/features/project/files/store'; - -@Component({ - selector: 'osf-file-project-metadata', - imports: [DatePipe, TranslatePipe, Skeleton], - templateUrl: './file-project-metadata.component.html', - styleUrl: './file-project-metadata.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, -}) -export class FileProjectMetadataComponent { - projectMetadata = select(ProjectFilesSelectors.getProjectMetadata); - contributors = select(ProjectFilesSelectors.getProjectContributors); - isProjectMetadataLoading = select(ProjectFilesSelectors.isProjectMetadataLoading); - isProjectContributorsLoading = select(ProjectFilesSelectors.isProjectContributorsLoading); -} diff --git a/src/app/features/project/files/constants/file-metadata-fields.const.ts b/src/app/features/project/files/constants/file-metadata-fields.const.ts deleted file mode 100644 index 6aadeb040..000000000 --- a/src/app/features/project/files/constants/file-metadata-fields.const.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { MetadataField } from '../models'; - -export const FileMetadataFields: MetadataField[] = [ - { key: 'title', label: 'project.files.detail.fileMetadata.fields.title' }, - { key: 'description', label: 'project.files.detail.fileMetadata.fields.description' }, - { key: 'resourceTypeGeneral', label: 'project.files.detail.fileMetadata.fields.resourceType' }, - { key: 'language', label: 'project.files.detail.fileMetadata.fields.resourceLanguage' }, -]; diff --git a/src/app/features/project/files/constants/index.ts b/src/app/features/project/files/constants/index.ts deleted file mode 100644 index 8ccd501a6..000000000 --- a/src/app/features/project/files/constants/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './file-metadata-fields.const'; diff --git a/src/app/features/project/files/mappers/file-revision.mapper.ts b/src/app/features/project/files/mappers/file-revision.mapper.ts deleted file mode 100644 index 6b3267a73..000000000 --- a/src/app/features/project/files/mappers/file-revision.mapper.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { OsfFileRevision } from '@osf/features/project/files/models/osf-models/file-revisions.model'; -import { FileRevisionJsonApi } from '@osf/features/project/files/models/responses/get-file-revisions-response.model'; -import { ApiData } from '@shared/models'; - -export function MapFileRevision(data: ApiData[]): OsfFileRevision[] { - return data.map((revision) => ({ - downloads: revision.attributes.extra.downloads, - hashes: { md5: revision.attributes.extra.hashes.md5, sha256: revision.attributes.extra.hashes.sha256 }, - dateTime: new Date(revision.attributes.modified_utc), - version: revision.attributes.version, - })); -} diff --git a/src/app/features/project/files/mappers/index.ts b/src/app/features/project/files/mappers/index.ts deleted file mode 100644 index bfa419062..000000000 --- a/src/app/features/project/files/mappers/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './file-custom-metadata.mapper'; -export * from './file-revision.mapper'; -export * from './project-metadata.mapper'; -export * from '@shared/mappers/files/files.mapper'; diff --git a/src/app/features/project/files/models/data/project-files-state-defaults.const.ts b/src/app/features/project/files/models/data/project-files-state-defaults.const.ts deleted file mode 100644 index 0b959d759..000000000 --- a/src/app/features/project/files/models/data/project-files-state-defaults.const.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { FileProvider } from '@osf/features/project/files/models'; - -export const projectFilesStateDefaults = { - files: { - data: [], - isLoading: false, - error: null, - }, - moveFileFiles: { - data: [], - isLoading: false, - error: null, - }, - currentFolder: null, - moveFileCurrentFolder: null, - search: '', - sort: 'name', - provider: FileProvider.OsfStorage, - openedFile: { - data: null, - isLoading: false, - error: null, - }, - fileMetadata: { - data: null, - isLoading: false, - error: null, - }, - projectMetadata: { - data: null, - isLoading: false, - error: null, - }, - contributors: { - data: null, - isLoading: false, - error: null, - }, - fileRevisions: { - data: null, - isLoading: false, - error: null, - }, - tags: { - data: [], - isLoading: false, - error: null, - }, - rootFolders: { - data: [], - isLoading: true, - error: null, - }, - configuredStorageAddons: { - data: [], - isLoading: true, - error: null, - }, -}; diff --git a/src/app/features/project/files/models/index.ts b/src/app/features/project/files/models/index.ts deleted file mode 100644 index 923d1245d..000000000 --- a/src/app/features/project/files/models/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -// OSF Models -export * from './osf-models/file-custom-metadata.model'; -export * from './osf-models/file-project-contributor.model'; -export * from './osf-models/file-revisions.model'; -export * from './osf-models/file-target.model'; -export * from './osf-models/project-custom-metadata.model'; -export * from './osf-models/project-short-info.model'; - -// Response Models -export * from './responses/create-folder-response.model'; -export * from './responses/get-file-metadata-reponse.model'; -export * from './responses/get-file-revisions-response.model'; -export * from './responses/get-file-target-response.model'; -export * from './responses/get-project-contributors-response.model'; -export * from './responses/get-project-custom-metadata-response.model'; -export * from './responses/get-project-short-info-response.model'; - -// Request Models -export * from './requests/patch-file-metadata.model'; - -// Constants -export * from './data/embed-content.const'; -export * from './data/file-provider.const'; -export * from './data/project-files-state-defaults.const'; -export * from './files-metadata-fields'; diff --git a/src/app/features/project/files/models/osf-models/file-project-contributor.model.ts b/src/app/features/project/files/models/osf-models/file-project-contributor.model.ts deleted file mode 100644 index b31c96060..000000000 --- a/src/app/features/project/files/models/osf-models/file-project-contributor.model.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface OsfFileProjectContributor { - id: string; - name: string; - active: boolean; -} diff --git a/src/app/features/project/files/models/osf-models/project-short-info.model.ts b/src/app/features/project/files/models/osf-models/project-short-info.model.ts deleted file mode 100644 index 424b9e43e..000000000 --- a/src/app/features/project/files/models/osf-models/project-short-info.model.ts +++ /dev/null @@ -1,7 +0,0 @@ -export interface OsfProjectShortInfo { - id: string; - title: string; - description: string; - dateCreated: string; - dateModified: string; -} diff --git a/src/app/features/project/files/pages/index.ts b/src/app/features/project/files/pages/index.ts deleted file mode 100644 index 17695906d..000000000 --- a/src/app/features/project/files/pages/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export { CommunityMetadataComponent } from './community-metadata/community-metadata.component'; -export { FileDetailComponent } from './file-detail/file-detail.component'; -export { ProjectFilesComponent } from './project-files/project-files.component'; -export { ProjectFilesContainerComponent } from './project-files-container/project-files-container.component'; diff --git a/src/app/features/project/files/project-files.routes.ts b/src/app/features/project/files/project-files.routes.ts deleted file mode 100644 index 44f13d430..000000000 --- a/src/app/features/project/files/project-files.routes.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Routes } from '@angular/router'; - -import { ProjectFilesContainerComponent } from './pages/project-files-container/project-files-container.component'; - -export const projectFilesRoutes: Routes = [ - { - path: '', - component: ProjectFilesContainerComponent, - children: [ - { - path: '', - loadComponent: () => - import('@osf/features/project/files/pages/project-files/project-files.component').then( - (c) => c.ProjectFilesComponent - ), - }, - { - path: ':fileGuid', - loadComponent: () => - import('@osf/features/project/files/pages/file-detail/file-detail.component').then( - (c) => c.FileDetailComponent - ), - children: [ - { - path: 'metadata', - loadComponent: () => - import('@osf/features/project/files/pages/community-metadata/community-metadata.component').then( - (c) => c.CommunityMetadataComponent - ), - }, - ], - }, - ], - }, -]; diff --git a/src/app/features/project/files/store/index.ts b/src/app/features/project/files/store/index.ts deleted file mode 100644 index b4f8c0ff3..000000000 --- a/src/app/features/project/files/store/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './project-files.actions'; -export * from './project-files.selectors'; -export * from './project-files.state'; diff --git a/src/app/features/project/files/store/project-files.actions.ts b/src/app/features/project/files/store/project-files.actions.ts deleted file mode 100644 index 105f16f7d..000000000 --- a/src/app/features/project/files/store/project-files.actions.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { PatchFileMetadata } from '@osf/features/project/files/models'; -import { OsfFile } from '@shared/models'; - -export class GetRootFolderFiles { - static readonly type = '[Project Files] Get Root Folder Files'; - - constructor(public projectId: string) {} -} - -export class GetFiles { - static readonly type = '[Project Files] Get Files'; - - constructor(public filesLink: string) {} -} - -export class SetFilesIsLoading { - static readonly type = '[Project Files] Set Files Loading'; - - constructor(public isLoading: boolean) {} -} - -export class GetFile { - static readonly type = '[Project Files] Get File'; - - constructor(public fileGuid: string) {} -} - -export class GetFileMetadata { - static readonly type = '[Project Files] Get File Metadata'; - - constructor(public fileGuid: string) {} -} - -export class GetFileProjectMetadata { - static readonly type = '[Project Files] Get File Project Metadata'; - - constructor(public projectId: string) {} -} - -export class GetMoveFileFiles { - static readonly type = '[Project Files] Get Move File Files'; - - constructor(public filesLink: string) {} -} - -export class SetCurrentFolder { - static readonly type = '[Project Files] Set Current Folder'; - - constructor(public folder: OsfFile | null) {} -} - -export class SetMoveFileCurrentFolder { - static readonly type = '[Project Files] Set Move File Files'; - - constructor(public folder: OsfFile | null) {} -} - -export class CreateFolder { - static readonly type = '[Project Files] Create folder'; - - constructor( - public newFolderLink: string, - public folderName: string - ) {} -} - -export class DeleteEntry { - static readonly type = '[Project Files] Delete entry'; - - constructor( - public projectId: string, - public link: string - ) {} -} -export class RenameEntry { - static readonly type = '[Project Files] Rename entry'; - - constructor( - public projectId: string, - public link: string, - public name: string - ) {} -} - -export class SetSearch { - static readonly type = '[Project Files] Set Search'; - - constructor(public search: string) {} -} - -export class SetSort { - static readonly type = '[Project Files] Set Sort'; - - constructor(public sort: string) {} -} - -export class GetFileProjectContributors { - static readonly type = '[Project Files] Get Projects Contributors'; - - constructor(public projectId: string) {} -} - -export class SetFileMetadata { - static readonly type = '[Project Files] Set File Metadata'; - - constructor( - public payload: PatchFileMetadata, - public fileGuid: string - ) {} -} - -export class GetFileRevisions { - static readonly type = '[Project Files] Get Revisions'; - - constructor( - public projectId: string, - public fileId: string - ) {} -} - -export class UpdateTags { - static readonly type = '[Project Files] Update Tags'; - - constructor( - public tags: string[], - public fileGuid: string - ) {} -} - -export class GetRootFolders { - static readonly type = '[Project Files] Get Folders'; - - constructor(public folderLink: string) {} -} - -export class GetConfiguredStorageAddons { - static readonly type = '[Project Files] Get ConfiguredStorageAddons'; - - constructor(public resourceUri: string) {} -} - -export class ResetState { - static readonly type = '[Project Files] Reset State'; -} diff --git a/src/app/features/project/files/store/project-files.model.ts b/src/app/features/project/files/store/project-files.model.ts deleted file mode 100644 index b97c74559..000000000 --- a/src/app/features/project/files/store/project-files.model.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - FileProvider, - OsfFileCustomMetadata, - OsfFileProjectContributor, - OsfFileRevision, - OsfProjectMetadata, -} from '@osf/features/project/files/models'; -import { OsfFile } from '@shared/models'; -import { ConfiguredStorageAddon } from '@shared/models/addons'; -import { AsyncStateModel } from '@shared/models/store'; - -export interface ProjectFilesStateModel { - files: AsyncStateModel; - moveFileFiles: AsyncStateModel; - currentFolder: OsfFile | null; - moveFileCurrentFolder: OsfFile | null; - search: string; - sort: string; - provider: (typeof FileProvider)[keyof typeof FileProvider]; - openedFile: AsyncStateModel; - fileMetadata: AsyncStateModel; - projectMetadata: AsyncStateModel; - contributors: AsyncStateModel; - fileRevisions: AsyncStateModel; - tags: AsyncStateModel; - rootFolders: AsyncStateModel; - configuredStorageAddons: AsyncStateModel; -} diff --git a/src/app/features/project/files/store/project-files.selectors.ts b/src/app/features/project/files/store/project-files.selectors.ts deleted file mode 100644 index 24c6fda03..000000000 --- a/src/app/features/project/files/store/project-files.selectors.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { Selector } from '@ngxs/store'; - -import { - OsfFileCustomMetadata, - OsfFileProjectContributor, - OsfFileRevision, - OsfProjectMetadata, -} from '@osf/features/project/files/models'; -import { OsfFile } from '@shared/models'; -import { ConfiguredStorageAddon } from '@shared/models/addons'; - -import { ProjectFilesStateModel } from './project-files.model'; -import { ProjectFilesState } from './project-files.state'; - -export class ProjectFilesSelectors { - @Selector([ProjectFilesState]) - static getFiles(state: ProjectFilesStateModel): OsfFile[] { - return state.files.data; - } - - @Selector([ProjectFilesState]) - static isFilesLoading(state: ProjectFilesStateModel): boolean { - return state.files.isLoading; - } - - @Selector([ProjectFilesState]) - static getMoveFileFiles(state: ProjectFilesStateModel): OsfFile[] { - return state.moveFileFiles.data; - } - - @Selector([ProjectFilesState]) - static isMoveFileFilesLoading(state: ProjectFilesStateModel): boolean { - return state.moveFileFiles.isLoading; - } - - @Selector([ProjectFilesState]) - static getCurrentFolder(state: ProjectFilesStateModel): OsfFile | null { - return state.currentFolder; - } - - @Selector([ProjectFilesState]) - static getMoveFileCurrentFolder(state: ProjectFilesStateModel): OsfFile | null { - return state.moveFileCurrentFolder; - } - - @Selector([ProjectFilesState]) - static getProvider(state: ProjectFilesStateModel): string { - return state.provider; - } - - @Selector([ProjectFilesState]) - static getOpenedFile(state: ProjectFilesStateModel): OsfFile | null { - return state.openedFile.data; - } - - @Selector([ProjectFilesState]) - static isOpenedFileLoading(state: ProjectFilesStateModel): boolean { - return state.openedFile.isLoading; - } - - @Selector([ProjectFilesState]) - static getFileCustomMetadata(state: ProjectFilesStateModel): OsfFileCustomMetadata | null { - return state.fileMetadata.data; - } - - @Selector([ProjectFilesState]) - static isFileMetadataLoading(state: ProjectFilesStateModel): boolean { - return state.fileMetadata.isLoading; - } - - @Selector([ProjectFilesState]) - static getProjectMetadata(state: ProjectFilesStateModel): OsfProjectMetadata | null { - return state.projectMetadata.data; - } - - @Selector([ProjectFilesState]) - static isProjectMetadataLoading(state: ProjectFilesStateModel): boolean { - return state.projectMetadata.isLoading; - } - - @Selector([ProjectFilesState]) - static getProjectContributors(state: ProjectFilesStateModel): OsfFileProjectContributor[] | null { - return state.contributors.data; - } - - @Selector([ProjectFilesState]) - static isProjectContributorsLoading(state: ProjectFilesStateModel): boolean { - return state.contributors.isLoading; - } - - @Selector([ProjectFilesState]) - static getFileRevisions(state: ProjectFilesStateModel): OsfFileRevision[] | null { - return state.fileRevisions.data; - } - - @Selector([ProjectFilesState]) - static isFileRevisionsLoading(state: ProjectFilesStateModel): boolean { - return state.fileRevisions.isLoading; - } - - @Selector([ProjectFilesState]) - static getFileTags(state: ProjectFilesStateModel): string[] { - return state.tags.data; - } - - @Selector([ProjectFilesState]) - static isFileTagsLoading(state: ProjectFilesStateModel): boolean { - return state.tags.isLoading; - } - - @Selector([ProjectFilesState]) - static getRootFolders(state: ProjectFilesStateModel): OsfFile[] | null { - return state.rootFolders.data; - } - - @Selector([ProjectFilesState]) - static isRootFoldersLoading(state: ProjectFilesStateModel): boolean { - return state.rootFolders.isLoading; - } - - @Selector([ProjectFilesState]) - static getConfiguredStorageAddons(state: ProjectFilesStateModel): ConfiguredStorageAddon[] | null { - return state.configuredStorageAddons.data; - } - - @Selector([ProjectFilesState]) - static isConfiguredStorageAddonsLoading(state: ProjectFilesStateModel): boolean { - return state.configuredStorageAddons.isLoading; - } -} diff --git a/src/app/features/project/files/utils/approve-file.helper.ts b/src/app/features/project/files/utils/approve-file.helper.ts deleted file mode 100644 index 87af40df6..000000000 --- a/src/app/features/project/files/utils/approve-file.helper.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { environment } from 'src/environments/environment'; - -export function approveFile(fileId: string, projectId: string): void { - const link = `${environment.apiUrlV1}/${projectId}/files/${fileId}/`; - - const iframe = document.createElement('iframe'); - iframe.style.display = 'none'; - iframe.src = link; - - iframe.onload = () => { - setTimeout(() => iframe.remove(), 3000); - }; - - document.body.appendChild(iframe); -} diff --git a/src/app/features/project/files/utils/index.ts b/src/app/features/project/files/utils/index.ts deleted file mode 100644 index 22377b7a2..000000000 --- a/src/app/features/project/files/utils/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './approve-file.helper'; diff --git a/src/app/features/project/project.routes.ts b/src/app/features/project/project.routes.ts index 8d9961a60..444d534d7 100644 --- a/src/app/features/project/project.routes.ts +++ b/src/app/features/project/project.routes.ts @@ -15,7 +15,6 @@ import { } from '@osf/shared/stores'; import { AnalyticsState } from './analytics/store'; -import { ProjectFilesState } from './files/store'; import { SettingsState } from './settings/store'; export const projectRoutes: Routes = [ @@ -42,11 +41,8 @@ export const projectRoutes: Routes = [ }, { path: 'files', - loadChildren: () => import('../project/files/project-files.routes').then((mod) => mod.projectFilesRoutes), - providers: [provideStates([ProjectFilesState])], - data: { - context: ResourceType.Project, - }, + loadChildren: () => import('@osf/features/files/files.routes').then((mod) => mod.filesRoutes), + data: { resourceType: ResourceType.Project }, }, { path: 'registrations', diff --git a/src/app/features/registries/components/custom-step/custom-step.component.html b/src/app/features/registries/components/custom-step/custom-step.component.html index a34f93398..f2113e9eb 100644 --- a/src/app/features/registries/components/custom-step/custom-step.component.html +++ b/src/app/features/registries/components/custom-step/custom-step.component.html @@ -146,7 +146,7 @@

    } } @case (FieldType.File) { -

    {{ 'project.files.actions.uploadFile' | translate }}

    +

    {{ 'files.actions.uploadFile' | translate }}

    {{ 'shared.files.limitText' | translate }}

    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 139d456a4..4afacc16e 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 @@ -96,7 +96,7 @@ export class CustomStepComponent implements OnDestroy { stepForm!: FormGroup; - attachedFiles: Record[]> = {}; + attachedFiles: Record[]> = {}; constructor() { this.route.params.pipe(takeUntilDestroyed()).subscribe((params) => { @@ -181,7 +181,7 @@ export class CustomStepComponent implements OnDestroy { onAttachFile(file: OsfFile, questionKey: string): void { this.attachedFiles[questionKey] = this.attachedFiles[questionKey] || []; - if (!this.attachedFiles[questionKey].some((f) => f.id === file.id)) { + if (!this.attachedFiles[questionKey].some((f) => f.file_id === file.id)) { this.attachedFiles[questionKey].push(file); this.stepForm.patchValue({ [questionKey]: [...(this.attachedFiles[questionKey] || []), file], @@ -189,20 +189,36 @@ export class CustomStepComponent implements OnDestroy { const otherFormValues = { ...this.stepForm.value }; delete otherFormValues[questionKey]; this.updateAction.emit({ - [questionKey]: [...this.attachedFiles[questionKey].map((f) => FilesMapper.toFilePayload(f as OsfFile))], + [questionKey]: [ + ...this.attachedFiles[questionKey].map((f) => { + if (f.file_id) { + const { name, ...payload } = f; + return payload; + } + return FilesMapper.toFilePayload(f as OsfFile); + }), + ], ...otherFormValues, }); } } - removeFromAttachedFiles(file: Partial, questionKey: string): void { + removeFromAttachedFiles(file: Partial, questionKey: string): void { if (this.attachedFiles[questionKey]) { - this.attachedFiles[questionKey] = this.attachedFiles[questionKey].filter((f) => f.id !== file.id); + this.attachedFiles[questionKey] = this.attachedFiles[questionKey].filter((f) => f.file_id !== file.file_id); this.stepForm.patchValue({ [questionKey]: this.attachedFiles[questionKey], }); this.updateAction.emit({ - [questionKey]: [...this.attachedFiles[questionKey].map((f) => FilesMapper.toFilePayload(f as OsfFile))], + [questionKey]: [ + ...this.attachedFiles[questionKey].map((f) => { + if (f.file_id) { + const { name, ...payload } = f; + return payload; + } + return FilesMapper.toFilePayload(f as OsfFile); + }), + ], }); } } 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 f49641a6b..9cbcb880d 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 @@ -12,7 +12,7 @@ raised severity="success" [icon]="'fas fa-plus'" - [label]="'project.files.actions.createFolder' | translate" + [label]="'files.actions.createFolder' | translate" (click)="createFolder()" > @@ -23,7 +23,7 @@ raised severity="success" [icon]="'fas fa-upload'" - [label]="'project.files.actions.uploadFile' | translate" + [label]="'files.actions.uploadFile' | translate" (click)="fileInput.click()" > @@ -35,7 +35,7 @@
    { + this.selectFile(file); + }); } } } diff --git a/src/app/features/registry/pages/registry-files/registry-files.component.html b/src/app/features/registry/pages/registry-files/registry-files.component.html deleted file mode 100644 index 531f597e8..000000000 --- a/src/app/features/registry/pages/registry-files/registry-files.component.html +++ /dev/null @@ -1,50 +0,0 @@ - - -@if (!dataLoaded()) { - -} @else { -
    -
    -
    - - -
    - -
    -
    - - - -
    - - - -
    -} diff --git a/src/app/features/registry/pages/registry-files/registry-files.component.ts b/src/app/features/registry/pages/registry-files/registry-files.component.ts deleted file mode 100644 index a4bf463d2..000000000 --- a/src/app/features/registry/pages/registry-files/registry-files.component.ts +++ /dev/null @@ -1,166 +0,0 @@ -import { createDispatchMap, select } from '@ngxs/store'; - -import { TranslatePipe } from '@ngx-translate/core'; - -import { TreeDragDropService } from 'primeng/api'; -import { Button } from 'primeng/button'; -import { DialogService } from 'primeng/dynamicdialog'; - -import { debounceTime, EMPTY, Observable, skip } from 'rxjs'; - -import { ChangeDetectionStrategy, Component, DestroyRef, effect, inject, signal } from '@angular/core'; -import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; -import { FormControl } from '@angular/forms'; -import { ActivatedRoute, Router } from '@angular/router'; - -import { - FilesTreeComponent, - FormSelectComponent, - LoadingSpinnerComponent, - SearchInputComponent, - SubHeaderComponent, -} from '@shared/components'; -import { ALL_SORT_OPTIONS } from '@shared/constants'; -import { FilesTreeActions, OsfFile } from '@shared/models'; -import { FilesService } from '@shared/services'; - -import { - GetRegistryFiles, - SetCurrentFolder, - SetSearch, - SetSort, -} from '../../store/registry-files/registry-files.actions'; -import { RegistryFilesSelectors } from '../../store/registry-files/registry-files.selectors'; -import { GetRegistryById } from '../../store/registry-overview/registry-overview.actions'; -import { RegistryOverviewSelectors } from '../../store/registry-overview/registry-overview.selectors'; - -@Component({ - selector: 'osf-registry-files', - imports: [ - SubHeaderComponent, - TranslatePipe, - Button, - FilesTreeComponent, - FormSelectComponent, - SearchInputComponent, - LoadingSpinnerComponent, - ], - templateUrl: './registry-files.component.html', - styleUrl: './registry-files.component.scss', - changeDetection: ChangeDetectionStrategy.OnPush, - providers: [DialogService, TreeDragDropService], -}) -export class RegistryFilesComponent { - protected readonly registry = select(RegistryOverviewSelectors.getRegistry); - protected readonly isRegistryLoading = select(RegistryOverviewSelectors.isRegistryLoading); - protected readonly files = select(RegistryFilesSelectors.getFiles); - protected readonly isFilesLoading = select(RegistryFilesSelectors.isFilesLoading); - protected readonly currentFolder = select(RegistryFilesSelectors.getCurrentFolder); - private readonly router = inject(Router); - private readonly route = inject(ActivatedRoute); - private readonly filesService = inject(FilesService); - private readonly destroyRef = inject(DestroyRef); - - private readonly actions = createDispatchMap({ - setCurrentFolder: SetCurrentFolder, - getFiles: GetRegistryFiles, - getRootFolderFiles: GetRegistryFiles, - getRegistryById: GetRegistryById, - setSearch: SetSearch, - setSort: SetSort, - }); - - protected readonly filesTreeActions: FilesTreeActions = { - setCurrentFolder: (folder) => this.actions.setCurrentFolder(folder), - getFiles: (filesLink) => this.actions.getFiles(filesLink), - }; - - protected readonly searchControl = new FormControl(''); - protected readonly sortControl = new FormControl(ALL_SORT_OPTIONS[0].value); - - protected isFolderOpening = signal(false); - protected registryId = signal(''); - protected dataLoaded = signal(false); - - protected readonly sortOptions = ALL_SORT_OPTIONS; - protected readonly provider = 'osfstorage'; - - constructor() { - this.route.parent?.params.subscribe((params) => { - const id = params['id']; - if (id) { - this.registryId.set(id); - if (!this.registry()) { - this.actions.getRegistryById(id); - } - } - }); - - effect(() => { - const registry = this.registry(); - - if (registry) { - this.actions.getFiles(registry.links.files).subscribe(() => this.dataLoaded.set(true)); - } - }); - - this.searchControl.valueChanges - .pipe(skip(1), takeUntilDestroyed(this.destroyRef), debounceTime(500)) - .subscribe((searchText) => { - this.actions.setSearch(searchText ?? ''); - if (!this.isFolderOpening()) { - this.updateFilesList(); - } - }); - - this.sortControl.valueChanges.pipe(skip(1), takeUntilDestroyed(this.destroyRef)).subscribe((sort) => { - this.actions.setSort(sort ?? ''); - if (!this.isFolderOpening()) { - this.updateFilesList(); - } - }); - } - - folderIsOpening(value: boolean): void { - this.isFolderOpening.set(value); - if (value) { - this.searchControl.setValue(''); - this.sortControl.setValue(ALL_SORT_OPTIONS[0].value); - } - } - - updateFilesList(): Observable { - const currentFolder = this.currentFolder(); - if (currentFolder?.relationships.filesLink) { - return this.actions.getFiles(currentFolder?.relationships.filesLink).pipe(takeUntilDestroyed(this.destroyRef)); - } - - const registry = this.registry(); - - if (registry) { - return this.actions.getFiles(registry.links.files); - } - - return EMPTY; - } - - navigateToFile(file: OsfFile) { - this.router.navigate([file.guid], { relativeTo: this.route }); - } - - downloadFolder(): void { - const registryId = this.registry()?.id; - const folderId = this.currentFolder()?.id ?? ''; - const isRootFolder = !this.currentFolder()?.relationships?.parentFolderLink; - - if (registryId) { - if (isRootFolder || !folderId) { - const link = this.filesService.getFolderDownloadLink(registryId, this.provider, '', true); - window.open(link, '_blank')?.focus(); - } else { - const link = this.filesService.getFolderDownloadLink(registryId, this.provider, folderId, false); - window.open(link, '_blank')?.focus(); - } - } - } -} diff --git a/src/app/features/registry/registry.routes.ts b/src/app/features/registry/registry.routes.ts index 0a0fd0d58..398be1c9e 100644 --- a/src/app/features/registry/registry.routes.ts +++ b/src/app/features/registry/registry.routes.ts @@ -3,7 +3,6 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; import { RegistryComponentsState } from '@osf/features/registry/store/registry-components'; -import { RegistryFilesState } from '@osf/features/registry/store/registry-files'; import { RegistryLinksState } from '@osf/features/registry/store/registry-links'; import { RegistryMetadataState } from '@osf/features/registry/store/registry-metadata'; import { RegistryOverviewState } from '@osf/features/registry/store/registry-overview'; @@ -81,12 +80,8 @@ export const registryRoutes: Routes = [ }, { path: 'files', - loadComponent: () => - import('./pages/registry-files/registry-files.component').then((c) => c.RegistryFilesComponent), - providers: [provideStates([RegistryFilesState])], - data: { - context: ResourceType.Registration, - }, + loadChildren: () => import('@osf/features/files/files.routes').then((mod) => mod.filesRoutes), + data: { resourceType: ResourceType.Registration }, }, { path: 'components', diff --git a/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.html b/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.html index 670da525f..dbcb2445c 100644 --- a/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.html +++ b/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.html @@ -110,7 +110,7 @@

    class="w-10rem btn-full-width" [label]="'settings.addons.form.buttons.authorize' | translate" type="submit" - [disabled]="!isFormValid() || isSubmitting()" + [disabled]="!isFormValid || isSubmitting()" [loading]="isSubmitting()" > } @else { @@ -126,7 +126,7 @@

    class="w-10rem btn-full-width" [label]="'settings.addons.form.buttons.reconnect' | translate" type="submit" - [disabled]="!isFormValid() || isSubmitting()" + [disabled]="!isFormValid || isSubmitting()" > }

    diff --git a/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.ts b/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.ts index 980aa140f..8bf5f5e3a 100644 --- a/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.ts +++ b/src/app/shared/components/addons/addon-setup-account-form/addon-setup-account-form.component.ts @@ -33,14 +33,14 @@ export class AddonSetupAccountFormComponent { protected readonly formControls = AddonFormControls; + get isFormValid() { + return this.addonForm().valid; + } + protected readonly addonForm = computed>(() => { return this.addonFormService.initializeForm(this.addon()); }); - protected readonly isFormValid = computed(() => { - return this.addonForm().valid; - }); - protected readonly isAccessSecretKeysFormat = computed(() => { return this.addon().credentialsFormat === CredentialsFormat.ACCESS_SECRET_KEYS; }); @@ -63,7 +63,7 @@ export class AddonSetupAccountFormComponent { }); protected handleSubmit(): void { - if (!this.isFormValid()) return; + if (!this.isFormValid) return; const formValue = this.addonForm().value; const payload = this.addonFormService.generateAuthorizedAddonPayload( diff --git a/src/app/shared/components/copy-button/copy-button.component.html b/src/app/shared/components/copy-button/copy-button.component.html index c36f64f20..f426260cb 100644 --- a/src/app/shared/components/copy-button/copy-button.component.html +++ b/src/app/shared/components/copy-button/copy-button.component.html @@ -4,7 +4,8 @@ icon="fas fa-clone" [label]="label()" text - [pTooltip]="tooltip() | translate" + [pTooltip]="(copyItem() ? tooltip() : 'common.labels.noData') | translate" + [disabled]="!copyItem()" (click)="copy()" > diff --git a/src/app/shared/components/file-link/file-link.component.html b/src/app/shared/components/file-link/file-link.component.html new file mode 100644 index 000000000..372199538 --- /dev/null +++ b/src/app/shared/components/file-link/file-link.component.html @@ -0,0 +1,14 @@ +
    + @if (hasProjectedContent()) { + + } @else { + + } +
    diff --git a/src/app/features/registry/pages/registry-files/registry-files.component.scss b/src/app/shared/components/file-link/file-link.component.scss similarity index 100% rename from src/app/features/registry/pages/registry-files/registry-files.component.scss rename to src/app/shared/components/file-link/file-link.component.scss diff --git a/src/app/features/registry/pages/registry-files/registry-files.component.spec.ts b/src/app/shared/components/file-link/file-link.component.spec.ts similarity index 50% rename from src/app/features/registry/pages/registry-files/registry-files.component.spec.ts rename to src/app/shared/components/file-link/file-link.component.spec.ts index 2dd8158f1..3e88dedfe 100644 --- a/src/app/features/registry/pages/registry-files/registry-files.component.spec.ts +++ b/src/app/shared/components/file-link/file-link.component.spec.ts @@ -1,17 +1,17 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RegistryFilesComponent } from './registry-files.component'; +import { FileLinkComponent } from './file-link.component'; -describe('RegistryFilesComponent', () => { - let component: RegistryFilesComponent; - let fixture: ComponentFixture; +describe('FileLinkComponent', () => { + let component: FileLinkComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [RegistryFilesComponent], + imports: [FileLinkComponent], }).compileComponents(); - fixture = TestBed.createComponent(RegistryFilesComponent); + fixture = TestBed.createComponent(FileLinkComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/shared/components/file-link/file-link.component.ts b/src/app/shared/components/file-link/file-link.component.ts new file mode 100644 index 000000000..d494d838a --- /dev/null +++ b/src/app/shared/components/file-link/file-link.component.ts @@ -0,0 +1,46 @@ +import { Tag } from 'primeng/tag'; + +import { + ChangeDetectionStrategy, + Component, + computed, + contentChild, + DestroyRef, + ElementRef, + inject, + input, +} from '@angular/core'; +import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { Router } from '@angular/router'; + +import { FilesService } from '@osf/shared/services'; + +@Component({ + selector: 'osf-file-link', + imports: [Tag], + templateUrl: './file-link.component.html', + styleUrl: './file-link.component.scss', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class FileLinkComponent { + file = input.required<{ file_id: string; file_name: string }>(); + content = contentChild('content'); + + private readonly filesService = inject(FilesService); + private readonly router = inject(Router); + private readonly destroyRef = inject(DestroyRef); + + hasProjectedContent = computed(() => !!this.content()); + + navigateToFile() { + const fileId = this.file().file_id; + if (fileId) { + this.filesService + .getFileGuid(fileId) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe((file) => { + this.router.navigate(['/files', file.guid]); + }); + } + } +} diff --git a/src/app/shared/components/file-menu/file-menu.component.ts b/src/app/shared/components/file-menu/file-menu.component.ts index 149a8caa0..57cf1da05 100644 --- a/src/app/shared/components/file-menu/file-menu.component.ts +++ b/src/app/shared/components/file-menu/file-menu.component.ts @@ -29,17 +29,17 @@ export class FileMenuComponent { icon: 'fas fa-share', items: [ { - label: 'project.files.detail.actions.share.email', + label: 'files.detail.actions.share.email', icon: 'fas fa-envelope', command: () => this.emitAction(FileMenuType.Share, { type: 'email' }), }, { - label: 'project.files.detail.actions.share.x', + label: 'files.detail.actions.share.x', icon: 'fab fa-square-x-twitter', command: () => this.emitAction(FileMenuType.Share, { type: 'twitter' }), }, { - label: 'project.files.detail.actions.share.facebook', + label: 'files.detail.actions.share.facebook', icon: 'fab fa-facebook', command: () => this.emitAction(FileMenuType.Share, { type: 'facebook' }), }, @@ -50,12 +50,12 @@ export class FileMenuComponent { icon: 'fas fa-code', items: [ { - label: 'project.files.detail.actions.copyDynamicIframe', + label: 'files.detail.actions.copyDynamicIframe', icon: 'fas fa-file-code', command: () => this.emitAction(FileMenuType.Embed, { type: 'dynamic' }), }, { - label: 'project.files.detail.actions.copyStaticIframe', + label: 'files.detail.actions.copyStaticIframe', icon: 'fas fa-file-code', command: () => this.emitAction(FileMenuType.Embed, { type: 'static' }), }, diff --git a/src/app/shared/components/files-tree/files-tree.component.html b/src/app/shared/components/files-tree/files-tree.component.html index a1d66f1f1..75189dbcd 100644 --- a/src/app/shared/components/files-tree/files-tree.component.html +++ b/src/app/shared/components/files-tree/files-tree.component.html @@ -10,7 +10,7 @@ @if (isDragOver()) {
    -

    {{ 'project.files.dropText' | translate }}

    +

    {{ 'files.dropText' | translate }}

    }
    @@ -94,11 +94,11 @@ @if (!files().length) {
    @if (viewOnly()) { -

    {{ 'project.files.emptyState' | translate }}

    +

    {{ 'files.emptyState' | translate }}

    } @else {
    -

    {{ 'project.files.dropText' | translate }}

    +

    {{ 'files.dropText' | translate }}

    }
    diff --git a/src/app/shared/components/files-tree/files-tree.component.ts b/src/app/shared/components/files-tree/files-tree.component.ts index a27ce8fdc..35b28b39e 100644 --- a/src/app/shared/components/files-tree/files-tree.component.ts +++ b/src/app/shared/components/files-tree/files-tree.component.ts @@ -25,8 +25,8 @@ import { } from '@angular/core'; import { ActivatedRoute, Router } from '@angular/router'; -import { MoveFileDialogComponent, RenameFileDialogComponent } from '@osf/features/project/files/components'; -import { embedDynamicJs, embedStaticHtml } from '@osf/features/project/files/models'; +import { MoveFileDialogComponent, RenameFileDialogComponent } from '@osf/features/files/components'; +import { embedDynamicJs, embedStaticHtml } from '@osf/features/files/constants'; import { FileMenuType } from '@osf/shared/enums'; import { FileMenuComponent, LoadingSpinnerComponent } from '@shared/components'; import { StopPropagationDirective } from '@shared/directives'; @@ -128,9 +128,9 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { if (files && files.length > 0) { this.customConfirmationService.confirmAccept({ - headerKey: 'project.files.dialogs.uploadFile.title', + headerKey: 'files.dialogs.uploadFile.title', messageParams: { name: files[0].name }, - messageKey: 'project.files.dialogs.uploadFile.message', + messageKey: 'files.dialogs.uploadFile.message', acceptLabelKey: 'common.buttons.upload', onConfirm: () => this.uploadFileConfirmed.emit(files[0]), }); @@ -149,7 +149,13 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { openEntry(file: OsfFile) { if (file.kind === 'file') { - this.entryFileClicked.emit(file); + if (file.guid) { + this.entryFileClicked.emit(file); + } else { + this.filesService.getFileGuid(file.id).subscribe((file) => { + this.entryFileClicked.emit(file); + }); + } } else { this.actions().setFilesIsLoading?.(true); this.folderIsOpening.emit(true); @@ -247,9 +253,9 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { confirmDelete(file: OsfFile): void { this.customConfirmationService.confirmDelete({ - headerKey: 'project.files.dialogs.deleteFile.title', + headerKey: 'files.dialogs.deleteFile.title', messageParams: { name: file.name }, - messageKey: 'project.files.dialogs.deleteFile.message', + messageKey: 'files.dialogs.deleteFile.message', acceptLabelKey: 'common.buttons.remove', onConfirm: () => this.deleteEntry(file.links.delete), }); @@ -265,7 +271,7 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { .open(RenameFileDialogComponent, { width: '448px', focusOnShow: false, - header: this.translateService.instant('project.files.dialogs.renameFile.title'), + header: this.translateService.instant('files.dialogs.renameFile.title'), closeOnEscape: true, modal: true, closable: true, @@ -319,8 +325,8 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { .subscribe(() => { const header = action === 'move' - ? this.translateService.instant('project.files.dialogs.moveFile.title') - : this.translateService.instant('project.files.dialogs.copyFile.title'); + ? this.translateService.instant('files.dialogs.moveFile.title') + : this.translateService.instant('files.dialogs.copyFile.title'); this.dialogService.open(MoveFileDialogComponent, { width: '552px', @@ -351,7 +357,7 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { navigator.clipboard .writeText(embedHtml) .then(() => { - this.toastService.showSuccess(this.translateService.instant('project.files.toast.copiedToClipboard')); + this.toastService.showSuccess('files.toast.copiedToClipboard'); }) .catch((err) => { this.toastService.showError(err.message); @@ -363,12 +369,12 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { const dropNode = event.dropNode as OsfFile; this.customConfirmationService.confirmAccept({ - headerKey: 'project.files.dialogs.moveFile.title', + headerKey: 'files.dialogs.moveFile.title', messageParams: { dragNodeName: dragNode.name, dropNodeName: dropNode.previousFolder ? 'parent folder' : dropNode.name, }, - messageKey: 'project.files.dialogs.moveFile.message', + messageKey: 'files.dialogs.moveFile.message', onConfirm: async () => { await this.dropFileToFolder(event); }, diff --git a/src/app/shared/components/index.ts b/src/app/shared/components/index.ts index a1c1589d2..e5eabe591 100644 --- a/src/app/shared/components/index.ts +++ b/src/app/shared/components/index.ts @@ -7,6 +7,7 @@ export { EducationHistoryComponent } from './education-history/education-history export { EducationHistoryDialogComponent } from './education-history-dialog/education-history-dialog.component'; export { EmploymentHistoryComponent } from './employment-history/employment-history.component'; export { EmploymentHistoryDialogComponent } from './employment-history-dialog/employment-history-dialog.component'; +export { FileLinkComponent } from './file-link/file-link.component'; export { FileMenuComponent } from './file-menu/file-menu.component'; export { FilesTreeComponent } from './files-tree/files-tree.component'; export { FilterChipsComponent } from './filter-chips/filter-chips.component'; diff --git a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html index d4392c874..bf98e1db7 100644 --- a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html +++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.html @@ -22,7 +22,7 @@

    @if (reviewData()[question.responseKey!].length) {
    @for (file of reviewData()[question.responseKey!]; track file.id) { - + }
    } @else { diff --git a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts index 846aa407e..080c02764 100644 --- a/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts +++ b/src/app/shared/components/registration-blocks-data/registration-blocks-data.component.ts @@ -9,9 +9,11 @@ import { INPUT_VALIDATION_MESSAGES } from '@osf/shared/constants'; import { FieldType } from '@osf/shared/enums'; import { Question } from '@osf/shared/models'; +import { FileLinkComponent } from '../file-link/file-link.component'; + @Component({ selector: 'osf-registration-blocks-data', - imports: [Tag, TranslatePipe, Message], + imports: [Tag, TranslatePipe, Message, FileLinkComponent], templateUrl: './registration-blocks-data.component.html', styleUrl: './registration-blocks-data.component.scss', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/src/app/shared/components/shared-metadata/components/project-metadata-funding/project-metadata-funding.component.html b/src/app/shared/components/shared-metadata/components/project-metadata-funding/project-metadata-funding.component.html index b551468ff..6efd901ea 100644 --- a/src/app/shared/components/shared-metadata/components/project-metadata-funding/project-metadata-funding.component.html +++ b/src/app/shared/components/shared-metadata/components/project-metadata-funding/project-metadata-funding.component.html @@ -15,21 +15,19 @@

    {{ 'project.overview.metadata.fundingSupport' | translate }}

    @for (funder of funders(); track funder.funder_identifier) {
    -

    {{ 'project.files.detail.projectMetadata.fields.funder' | translate }}: {{ funder.funder_name }}

    +

    {{ 'files.detail.resourceMetadata.fields.funder' | translate }}: {{ funder.funder_name }}

    -

    {{ 'project.files.detail.projectMetadata.fields.awardTitle' | translate }}: {{ funder.award_title }}

    +

    {{ 'files.detail.resourceMetadata.fields.awardTitle' | translate }}: {{ funder.award_title }}

    @if (funder.award_uri) {

    - {{ 'project.files.detail.projectMetadata.fields.awardUri' | translate }}: + {{ 'files.detail.resourceMetadata.fields.awardUri' | translate }}: {{ funder.award_title }}

    } @if (funder.award_number) { -

    - {{ 'project.files.detail.projectMetadata.fields.awardNumber' | translate }} : {{ funder.award_number }} -

    +

    {{ 'files.detail.resourceMetadata.fields.awardNumber' | translate }} : {{ funder.award_number }}

    } @if (funder.award_uri) { diff --git a/src/app/shared/components/shared-metadata/dialogs/resource-information-dialog/resource-information-dialog.component.html b/src/app/shared/components/shared-metadata/dialogs/resource-information-dialog/resource-information-dialog.component.html index 99d6ed586..45ee9b76f 100644 --- a/src/app/shared/components/shared-metadata/dialogs/resource-information-dialog/resource-information-dialog.component.html +++ b/src/app/shared/components/shared-metadata/dialogs/resource-information-dialog/resource-information-dialog.component.html @@ -1,7 +1,7 @@
    [] = [ { value: SortType.NameAZ, - label: 'project.files.sort.nameAZ', + label: 'files.sort.nameAZ', }, { value: SortType.NameZA, - label: 'project.files.sort.nameZA', + label: 'files.sort.nameZA', }, { value: SortType.LastModifiedOldest, - label: 'project.files.sort.lastModifiedOldest', + label: 'files.sort.lastModifiedOldest', }, { value: SortType.LastModifiedNewest, - label: 'project.files.sort.lastModifiedNewest', + label: 'files.sort.lastModifiedNewest', }, ]; export const COLLECTION_SUBMISSIONS_SORT_OPTIONS: CustomOption[] = [ { value: SortType.LastModifiedNewest, - label: 'project.files.sort.lastModifiedNewest', + label: 'files.sort.lastModifiedNewest', }, { value: SortType.LastModifiedOldest, - label: 'project.files.sort.lastModifiedOldest', + label: 'files.sort.lastModifiedOldest', }, { value: SortType.TitleAZ, - label: 'project.files.sort.nameAZ', + label: 'files.sort.nameAZ', }, { value: SortType.TitleZA, - label: 'project.files.sort.nameZA', + label: 'files.sort.nameZA', }, ]; diff --git a/src/app/shared/mappers/addon.mapper.ts b/src/app/shared/mappers/addon.mapper.ts index 971133d7f..7589ba85e 100644 --- a/src/app/shared/mappers/addon.mapper.ts +++ b/src/app/shared/mappers/addon.mapper.ts @@ -81,7 +81,9 @@ export class AddonMapper { static fromOperationInvocationResponse(response: OperationInvocationResponseJsonApi): OperationInvocation { const operationResult = response.attributes.operation_result; - const isOperationResult = 'items' in operationResult && 'total_count' in operationResult; + // [NM] TODO: Double check this condition + // const isOperationResult = 'items' in operationResult && 'total_count' in operationResult; + const isOperationResult = 'items' in operationResult; const mappedOperationResult = isOperationResult ? operationResult.items.map((item: StorageItemResponseJsonApi) => ({ diff --git a/src/app/shared/mappers/files/files.mapper.ts b/src/app/shared/mappers/files/files.mapper.ts index 4efe19d31..39fceca67 100644 --- a/src/app/shared/mappers/files/files.mapper.ts +++ b/src/app/shared/mappers/files/files.mapper.ts @@ -1,4 +1,4 @@ -import { FileTargetResponse } from '@osf/features/project/files/models/responses/get-file-target-response.model'; +import { FileTargetResponse } from '@osf/features/files/models'; import { FileLinks, FileRelationshipsResponse, @@ -46,8 +46,11 @@ export function MapFile( parentFolderLink: file?.relationships?.parent_folder?.links?.related?.href, parentFolderId: file?.relationships?.parent_folder?.data?.id, filesLink: file?.relationships?.files?.links?.related?.href, + uploadLink: file?.links?.upload, + newFolderLink: file?.links?.new_folder, }, target: { + id: file?.embeds?.target.data.id, title: file?.embeds?.target.data.attributes.title, description: file?.embeds?.target.data.attributes.description, category: file?.embeds?.target.data.attributes.category, @@ -61,8 +64,21 @@ export function MapFile( tags: file?.embeds?.target.data.attributes.tags, nodeLicense: file?.embeds?.target.data.attributes.node_license, analyticsKey: file?.embeds?.target.data.attributes.analytics_key, + type: file?.embeds?.target.data.type, + currentUserCanComment: file?.embeds?.target.data.attributes.current_user_can_comment, + currentUserIsContributor: file?.embeds?.target.data.attributes.current_user_is_contributor, + currentUserIsContributorOrGroupMember: + file?.embeds?.target.data.attributes.current_user_is_contributor_or_group_member, + currentUserPermissions: file?.embeds?.target.data.attributes.current_user_permissions, + wikiEnabled: file?.embeds?.target.data.attributes.wiki_enabled, + public: file?.embeds?.target.data.attributes.public, }, - } as OsfFile; + currentUserCanComment: file.attributes.current_user_can_comment, + currentVersion: file.attributes.current_version, + lastTouched: file.attributes.last_touched, + previousFolder: false, + showAsUnviewed: file.attributes.show_as_unviewed, + }; } export function MapFileVersions(fileVersions: FileVersionsResponseJsonApi): OsfFileVersion[] { diff --git a/src/app/shared/mappers/index.ts b/src/app/shared/mappers/index.ts index d80702797..893f2d106 100644 --- a/src/app/shared/mappers/index.ts +++ b/src/app/shared/mappers/index.ts @@ -4,6 +4,7 @@ export * from './collections'; export * from './components'; export * from './contributors'; export * from './duplicates.mapper'; +export * from './files/files.mapper'; export * from './filters'; export * from './institutions'; export * from './licenses.mapper'; diff --git a/src/app/shared/models/files/file.model.ts b/src/app/shared/models/files/file.model.ts index 5f3ed04da..18cb93708 100644 --- a/src/app/shared/models/files/file.model.ts +++ b/src/app/shared/models/files/file.model.ts @@ -1,4 +1,4 @@ -import { OsfFileTarget } from '@osf/features/project/files/models'; +import { OsfFileTarget } from '@osf/features/files/models'; export interface OsfFile { id: string; diff --git a/src/app/shared/models/files/get-files-response.model.ts b/src/app/shared/models/files/get-files-response.model.ts index 7d73543d3..b2daf7a2a 100644 --- a/src/app/shared/models/files/get-files-response.model.ts +++ b/src/app/shared/models/files/get-files-response.model.ts @@ -1,4 +1,4 @@ -import { FileTargetResponse } from '@osf/features/project/files/models/responses/get-file-target-response.model'; +import { FileTargetResponse } from '@osf/features/files/models'; import { ApiData, JsonApiResponse } from '@shared/models'; export type GetFilesResponse = JsonApiResponse; diff --git a/src/app/shared/models/files/index.ts b/src/app/shared/models/files/index.ts index 5335894fd..642857f30 100644 --- a/src/app/shared/models/files/index.ts +++ b/src/app/shared/models/files/index.ts @@ -6,3 +6,4 @@ export * from './file-version-json-api.model'; export * from './files-tree-actions.interface'; export * from './get-configured-storage-addons.model'; export * from './get-files-response.model'; +export * from './resource-files-links.model'; diff --git a/src/app/shared/models/files/resource-files-links.model.ts b/src/app/shared/models/files/resource-files-links.model.ts new file mode 100644 index 000000000..640cd2ea2 --- /dev/null +++ b/src/app/shared/models/files/resource-files-links.model.ts @@ -0,0 +1,4 @@ +export interface ResourceFilesLinks { + rootFolder: string; + iri: string; +} diff --git a/src/app/shared/models/index.ts b/src/app/shared/models/index.ts index 804cfabdb..3484b6584 100644 --- a/src/app/shared/models/index.ts +++ b/src/app/shared/models/index.ts @@ -33,6 +33,7 @@ export * from './provider'; export * from './query-params.model'; export * from './registration'; export * from './resource-card'; +export * from './resource-metadata.model'; export * from './resource-overview.model'; export * from './search'; export * from './select-option.model'; diff --git a/src/app/features/project/files/models/osf-models/project-custom-metadata.model.ts b/src/app/shared/models/resource-metadata.model.ts similarity index 89% rename from src/app/features/project/files/models/osf-models/project-custom-metadata.model.ts rename to src/app/shared/models/resource-metadata.model.ts index fcb5d2322..46fcd5f37 100644 --- a/src/app/features/project/files/models/osf-models/project-custom-metadata.model.ts +++ b/src/app/shared/models/resource-metadata.model.ts @@ -1,4 +1,4 @@ -export interface OsfProjectMetadata { +export interface ResourceMetadata { title: string; description: string; dateCreated: Date; diff --git a/src/app/shared/services/files.service.ts b/src/app/shared/services/files.service.ts index 40ff4acd5..05fafdb74 100644 --- a/src/app/shared/services/files.service.ts +++ b/src/app/shared/services/files.service.ts @@ -4,32 +4,26 @@ import { catchError, map } from 'rxjs/operators'; import { HttpEvent } from '@angular/common/http'; import { inject, Injectable } from '@angular/core'; -import { - MapFile, - MapFileCustomMetadata, - MapFileRevision, - MapFiles, - MapFileVersions, -} from '@osf/features/project/files/mappers'; +import { MapFileCustomMetadata, MapFileRevision } from '@osf/features/files/mappers'; import { CreateFolderResponse, FileCustomMetadata, FileTargetResponse, + GetCustomMetadataResponse, GetFileMetadataResponse, GetFileRevisionsResponse, GetFileTargetResponse, - GetProjectContributorsResponse, - GetProjectCustomMetadataResponse, - GetProjectShortInfoResponse, + GetShortInfoResponse, OsfFileCustomMetadata, - OsfFileProjectContributor, OsfFileRevision, PatchFileMetadata, -} from '@osf/features/project/files/models'; +} from '@osf/features/files/models'; import { AddFileResponse, ApiData, ConfiguredStorageAddon, + ContributorModel, + ContributorResponse, FileLinks, FileRelationshipsResponse, FileResponse, @@ -44,6 +38,9 @@ import { import { JsonApiService } from '@shared/services'; import { ToastService } from '@shared/services/toast.service'; +import { ResourceType } from '../enums'; +import { ContributorsMapper, MapFile, MapFiles, MapFileVersions } from '../mappers'; + import { environment } from 'src/environments/environment'; @Injectable({ @@ -54,6 +51,11 @@ export class FilesService { readonly toastService = inject(ToastService); filesFields = 'name,guid,kind,extra,size,path,materialized_path,date_modified,parent_folder,files'; + private readonly urlMap = new Map([ + [ResourceType.Project, 'nodes'], + [ResourceType.Registration, 'registrations'], + ]); + getFiles(filesLink: string, search: string, sort: string): Observable { const params: Record = { sort: sort, @@ -97,9 +99,13 @@ export class FilesService { } createFolder(link: string, folderName: string): Observable { - return this.jsonApiService - .put(link, null, { name: folderName }) - .pipe(map((response) => MapFile(response))); + return this.jsonApiService.put(link, null, { name: folderName }).pipe( + map((response) => MapFile(response)), + catchError((error) => { + this.toastService.showError(error.error.message); + return throwError(() => error); + }) + ); } getFolder(link: string): Observable { @@ -113,7 +119,12 @@ export class FilesService { } deleteEntry(link: string) { - return this.jsonApiService.delete(link); + return this.jsonApiService.delete(link).pipe( + catchError((error) => { + this.toastService.showError(error.error.message); + return throwError(() => error); + }) + ); } renameEntry(link: string, name: string, conflict = ''): Observable { @@ -155,6 +166,16 @@ export class FilesService { .pipe(map((response) => MapFile(response.data))); } + getFileGuid(id: string): Observable { + const params = { + create_guid: 'true', + }; + + return this.jsonApiService + .get(`${environment.apiUrl}/files/${id}/`, params) + .pipe(map((response) => MapFile(response.data))); + } + getFileById(fileGuid: string): Observable { return this.jsonApiService .get(`${environment.apiUrl}/files/${fileGuid}/`) @@ -178,40 +199,28 @@ export class FilesService { .pipe(map((response) => MapFileCustomMetadata(response.data))); } - getProjectShortInfo(resourceId: string): Observable { + getResourceShortInfo(resourceId: string, resourceType: string): Observable { const params = { 'fields[nodes]': 'title,description,date_created,date_modified', - embed: 'bibliographic_contributors', }; - return this.jsonApiService.get(`${environment.apiUrl}/nodes/${resourceId}/`, params); + return this.jsonApiService.get( + `${environment.apiUrl}/${resourceType}/${resourceId}/`, + params + ); } - getProjectCustomMetadata(resourceId: string): Observable { - return this.jsonApiService.get( + getCustomMetadata(resourceId: string): Observable { + return this.jsonApiService.get( `${environment.apiUrl}/guids/${resourceId}/?embed=custom_metadata&resolve=false` ); } - getProjectContributors(resourceId: string): Observable { - const params = { - 'page[size]': '50', - 'fields[users]': 'full_name,active', - }; - + getResourceContributors(resourceId: string, resourceType: string): Observable[]> { return this.jsonApiService - .get( - `${environment.apiUrl}/nodes/${resourceId}/contributors_and_group_members/`, - params - ) - .pipe( - map((response) => - response.data.map((user) => ({ - id: user.id, - name: user.attributes.full_name, - active: user.attributes.active, - })) - ) - ); + .get< + JsonApiResponse + >(`${environment.apiUrl}/${resourceType}/${resourceId}/bibliographic_contributors/`) + .pipe(map((response) => ContributorsMapper.fromResponse(response.data))); } patchFileMetadata(data: PatchFileMetadata, fileGuid: string): Observable { diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 5a1c35c56..4dcd02c10 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -791,119 +791,6 @@ "openResources": "Open Resources" } }, - "files": { - "title": "Files", - "storageLocation": "OSF Storage", - "searchPlaceholder": "Search your projects", - "sort": { - "placeholder": "Sort", - "nameAZ": "Name: A-Z", - "nameZA": "Name: Z-A", - "lastModifiedOldest": "Last modified: oldest to newest", - "lastModifiedNewest": "Last modified: newest to oldest" - }, - "actions": { - "downloadAsZip": "Download As Zip", - "createFolder": "Create Folder", - "uploadFile": "Upload File" - }, - "dialogs": { - "uploadFile": { - "title": "Upload file", - "message": "Are you sure you want to update {{name}}?" - }, - "createFolder": { - "title": "Create folder", - "folderName": "New folder name", - "folderNamePlaceholder": "Please enter a folder name" - }, - "renameFile": { - "title": "Rename file", - "newName": "New name", - "enterNewName": "Enter new name", - "renameLabel": "Please rename the file" - }, - "moveFile": { - "cannotMove": "Cannot move to the same folder", - "title": "Move file", - "message": "Are you sure you want to move {{dragNodeName}} to {{dropNodeName}} ?", - "storage": "OSF Storage", - "pathError": "Path is not specified!" - }, - "copyFile": { - "title": "Copy file" - }, - "deleteFile": { - "title": "Delete File", - "message": "Are you sure you want to delete {{name}}?" - } - }, - "dropText": "Drop a file to upload", - "emptyState": "This folder is empty", - "detail": { - "backToList": "Back to list of files", - "tabs": { - "details": "Details", - "revisions": "Revisions", - "keywords": "Keywords" - }, - "actions": { - "addCommunityMetadata": "Add community Metadata", - "copyDynamicIframe": "Copy dynamic JS iFrame", - "copyStaticIframe": "Copy static HTML iFrame", - "share": { - "email": "Email", - "x": "X", - "facebook": "Facebook" - } - }, - "fileMetadata": { - "title": "File Metadata", - "edit": "Edit", - "fields": { - "title": "Title", - "description": "Description", - "resourceType": "Resource Type", - "resourceLanguage": "Resource Language" - } - }, - "projectMetadata": { - "title": "Project Metadata", - "edit": "Edit", - "fields": { - "funder": "Funder", - "awardTitle": "Award title", - "awardNumber": "Award number", - "awardUri": "Award URI", - "title": "Title", - "description": "Description", - "resourceType": "Resource type", - "resourceLanguage": "Resource language", - "dateCreated": "Date created", - "dateModified": "Date modified", - "contributors": "Contributors" - } - }, - "toast": { - "copiedToClipboard": "Copied to clipboard" - }, - "keywords": { - "title": "Keywords" - }, - "revisions": { - "title": "Revisions", - "actions": { - "copyMd5": "Copy MD5", - "copySha2": "Copy SHA-2", - "download": "Download" - }, - "tooltips": { - "md5": "MD5 is an algorithm used to verify data integrity", - "sha2": "SHA-2 is a cryptographic hash function designed by the NSA used to verify data integrity." - } - } - } - }, "metadata": { "addSubjects": "Add subjects...", "description": { @@ -1026,6 +913,122 @@ "success": "Project has been successfully deleted." } }, + "files": { + "title": "Files", + "storageLocation": "OSF Storage", + "searchPlaceholder": "Search your projects", + "sort": { + "placeholder": "Sort", + "nameAZ": "Name: A-Z", + "nameZA": "Name: Z-A", + "lastModifiedOldest": "Last modified: oldest to newest", + "lastModifiedNewest": "Last modified: newest to oldest" + }, + "actions": { + "downloadAsZip": "Download As Zip", + "createFolder": "Create Folder", + "uploadFile": "Upload File" + }, + "dialogs": { + "uploadFile": { + "title": "Upload file", + "message": "Are you sure you want to update {{name}}?" + }, + "createFolder": { + "title": "Create folder", + "folderName": "New folder name", + "folderNamePlaceholder": "Please enter a folder name" + }, + "renameFile": { + "title": "Rename file", + "newName": "New name", + "enterNewName": "Enter new name", + "renameLabel": "Please rename the file" + }, + "moveFile": { + "cannotMove": "Cannot move to the same folder", + "title": "Move file", + "message": "Are you sure you want to move {{dragNodeName}} to {{dropNodeName}} ?", + "storage": "OSF Storage", + "pathError": "Path is not specified!" + }, + "copyFile": { + "title": "Copy file" + }, + "deleteFile": { + "title": "Delete File", + "message": "Are you sure you want to delete {{name}}?" + } + }, + "dropText": "Drop a file to upload", + "emptyState": "This folder is empty", + "detail": { + "backToList": "Back to list of files", + "tabs": { + "details": "Details", + "revisions": "Revisions", + "keywords": "Keywords" + }, + "actions": { + "addCommunityMetadata": "Add community Metadata", + "copyDynamicIframe": "Copy dynamic JS iFrame", + "copyStaticIframe": "Copy static HTML iFrame", + "share": { + "email": "Email", + "x": "X", + "facebook": "Facebook" + } + }, + "fileMetadata": { + "title": "File Metadata", + "edit": "Edit", + "fields": { + "title": "Title", + "description": "Description", + "resourceType": "Resource Type", + "resourceLanguage": "Resource Language" + } + }, + "resourceMetadata": { + "title": { + "nodes": "Project Metadata", + "registrations": "Registration Metadata" + }, + "edit": "Edit", + "fields": { + "funder": "Funder", + "awardTitle": "Award title", + "awardNumber": "Award number", + "awardUri": "Award URI", + "title": "Title", + "description": "Description", + "resourceType": "Resource type", + "resourceLanguage": "Resource language", + "dateCreated": "Date created", + "dateModified": "Date modified", + "contributors": "Contributors" + } + }, + "toast": { + "copiedToClipboard": "Copied to clipboard" + }, + "keywords": { + "title": "Keywords" + }, + "revisions": { + "title": "Revisions", + "actions": { + "copyMd5": "Copy MD5", + "copySha2": "Copy SHA-2", + "download": "Download" + }, + "tooltips": { + "md5": "MD5 is an algorithm used to verify data integrity", + "sha2": "SHA-2 is a cryptographic hash function designed by the NSA used to verify data integrity." + } + } + } + }, "collections": { "helpDialog": { "header": "Search help", @@ -1109,7 +1112,7 @@ "confirmationDialogHeader": "Add to Collection", "selectProject": "Select a project", "project": "Project", - "projectMetadata": "Project Metadata", + "resourceMetadata": "Project Metadata", "projectMetadataMessage": "Updates made in this section will update the project.", "projectContributors": "Project Contributors", "collectionMetadata": "Collection Metadata", diff --git a/src/assets/styles/overrides/tree.scss b/src/assets/styles/overrides/tree.scss index c8edbddf1..60b70baff 100644 --- a/src/assets/styles/overrides/tree.scss +++ b/src/assets/styles/overrides/tree.scss @@ -35,7 +35,8 @@ } .p-tree-root { - overflow: hidden; + overflow: auto; + min-height: 180px; } .p-tree-node-content {