From a6c6978bb555fe1ba7850206f90025d9ac9ecdf2 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Tue, 30 Sep 2025 19:14:28 +0300 Subject: [PATCH 1/4] fix(moderation): add registration moderation guard --- src/app/app.routes.ts | 6 ++++ .../components/nav-menu/nav-menu.component.ts | 3 +- .../guards/registration-moderation.guard.ts | 35 +++++++++++++++++++ .../features/registries/registries.routes.ts | 3 +- .../mappers/registration-provider.mapper.ts | 1 + .../registration/registration-node.mapper.ts | 1 + .../shared/models/provider/provider.model.ts | 1 + .../provider/registry-provider.model.ts | 1 + .../registration-provider.actions.ts | 2 +- .../registration-provider.state.ts | 32 ++++++++--------- 10 files changed, 65 insertions(+), 20 deletions(-) create mode 100644 src/app/core/guards/registration-moderation.guard.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 2e933f203..9d92791e7 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -157,6 +157,12 @@ export const routes: Routes = [ import('./core/components/request-access/request-access.component').then((mod) => mod.RequestAccessComponent), data: { skipBreadcrumbs: true }, }, + { + path: 'not-found', + loadComponent: () => + import('./core/components/page-not-found/page-not-found.component').then((mod) => mod.PageNotFoundComponent), + data: { skipBreadcrumbs: true }, + }, { path: ':id/files/:provider/:fileId', loadComponent: () => diff --git a/src/app/core/components/nav-menu/nav-menu.component.ts b/src/app/core/components/nav-menu/nav-menu.component.ts index e4f8d4f3e..496ef7c72 100644 --- a/src/app/core/components/nav-menu/nav-menu.component.ts +++ b/src/app/core/components/nav-menu/nav-menu.component.ts @@ -59,7 +59,8 @@ export class NavMenuComponent { preprintReviewsPageVisible: this.canUserViewReviews(), registrationModerationPageVisible: this.provider()?.type === CurrentResourceType.Registrations && - this.provider()?.permissions?.includes(ReviewPermissions.ViewSubmissions), + this.provider()?.permissions?.includes(ReviewPermissions.ViewSubmissions) && + !!this.provider()?.reviewsWorkflow, collectionModerationPageVisible: this.provider()?.type === CurrentResourceType.Collections && this.provider()?.permissions?.includes(ReviewPermissions.ViewSubmissions), diff --git a/src/app/core/guards/registration-moderation.guard.ts b/src/app/core/guards/registration-moderation.guard.ts new file mode 100644 index 000000000..b9286fa5b --- /dev/null +++ b/src/app/core/guards/registration-moderation.guard.ts @@ -0,0 +1,35 @@ +import { Store } from '@ngxs/store'; + +import { map, switchMap, take } from 'rxjs'; + +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; + +import { GetRegistryProvider, RegistrationProviderSelectors } from '@osf/shared/stores/registration-provider'; + +export const registrationModerationGuard: CanActivateFn = (route) => { + const store = inject(Store); + const router = inject(Router); + + const provider = store.selectSnapshot(RegistrationProviderSelectors.getBrandedProvider); + + if (provider?.reviewsWorkflow) { + return true; + } + const id = route.params['providerId']; + return store.dispatch(new GetRegistryProvider(id)).pipe( + switchMap(() => { + return store.select(RegistrationProviderSelectors.getBrandedProvider).pipe( + take(1), + map((provider) => { + if (!provider?.reviewsWorkflow) { + router.navigate(['/not-found']); + return false; + } + + return true; + }) + ); + }) + ); +}; diff --git a/src/app/features/registries/registries.routes.ts b/src/app/features/registries/registries.routes.ts index f52964f32..db6c48175 100644 --- a/src/app/features/registries/registries.routes.ts +++ b/src/app/features/registries/registries.routes.ts @@ -2,6 +2,7 @@ import { provideStates } from '@ngxs/store'; import { Routes } from '@angular/router'; +import { registrationModerationGuard } from '@core/guards/registration-moderation.guard'; import { authGuard } from '@osf/core/guards'; import { RegistriesComponent } from '@osf/features/registries/registries.component'; import { RegistriesState } from '@osf/features/registries/store'; @@ -43,7 +44,7 @@ export const registriesRoutes: Routes = [ }, { path: ':providerId/moderation', - canActivate: [authGuard], + canActivate: [authGuard, registrationModerationGuard], loadChildren: () => import('@osf/features/moderation/registry-moderation.routes').then((c) => c.registryModerationRoutes), }, diff --git a/src/app/shared/mappers/registration-provider.mapper.ts b/src/app/shared/mappers/registration-provider.mapper.ts index 78b9f8dc3..884cd4708 100644 --- a/src/app/shared/mappers/registration-provider.mapper.ts +++ b/src/app/shared/mappers/registration-provider.mapper.ts @@ -34,6 +34,7 @@ export class RegistrationProviderMapper { } : null, iri: response.links.iri, + reviewsWorkflow: response.attributes.reviews_workflow, }; } } diff --git a/src/app/shared/mappers/registration/registration-node.mapper.ts b/src/app/shared/mappers/registration/registration-node.mapper.ts index f168986bc..458cf93ee 100644 --- a/src/app/shared/mappers/registration/registration-node.mapper.ts +++ b/src/app/shared/mappers/registration/registration-node.mapper.ts @@ -85,6 +85,7 @@ export class RegistrationNodeMapper { name: provider.attributes.name, permissions: provider.attributes.permissions, type: CurrentResourceType.Registrations, + reviewsWorkflow: provider.attributes.reviews_workflow, }; } } diff --git a/src/app/shared/models/provider/provider.model.ts b/src/app/shared/models/provider/provider.model.ts index 88dc359c1..68b9a860b 100644 --- a/src/app/shared/models/provider/provider.model.ts +++ b/src/app/shared/models/provider/provider.model.ts @@ -5,6 +5,7 @@ export interface ProviderShortInfoModel { name: string; type: CurrentResourceType; permissions?: ReviewPermissions[]; + reviewsWorkflow?: string; } export interface BaseProviderModel { diff --git a/src/app/shared/models/provider/registry-provider.model.ts b/src/app/shared/models/provider/registry-provider.model.ts index 3c2f82ec6..c42f193e3 100644 --- a/src/app/shared/models/provider/registry-provider.model.ts +++ b/src/app/shared/models/provider/registry-provider.model.ts @@ -8,4 +8,5 @@ export interface RegistryProviderDetails { permissions: ReviewPermissions[]; brand: Brand | null; iri: string; + reviewsWorkflow: string; } diff --git a/src/app/shared/stores/registration-provider/registration-provider.actions.ts b/src/app/shared/stores/registration-provider/registration-provider.actions.ts index 8fe3f37d1..158e0e52e 100644 --- a/src/app/shared/stores/registration-provider/registration-provider.actions.ts +++ b/src/app/shared/stores/registration-provider/registration-provider.actions.ts @@ -3,7 +3,7 @@ const stateName = '[Registry Provider Search]'; export class GetRegistryProvider { static readonly type = `${stateName} Get Registry Provider`; - constructor(public providerName: string) {} + constructor(public providerId: string) {} } export class ClearRegistryProvider { diff --git a/src/app/shared/stores/registration-provider/registration-provider.state.ts b/src/app/shared/stores/registration-provider/registration-provider.state.ts index 82394aadd..393adbaea 100644 --- a/src/app/shared/stores/registration-provider/registration-provider.state.ts +++ b/src/app/shared/stores/registration-provider/registration-provider.state.ts @@ -1,5 +1,4 @@ import { Action, State, StateContext } from '@ngxs/store'; -import { patch } from '@ngxs/store/operators'; import { catchError, of, tap } from 'rxjs'; @@ -30,14 +29,14 @@ export class RegistrationProviderState { const state = ctx.getState(); const currentProvider = state.currentBrandedProvider.data; - - if (currentProvider?.name === action.providerName) { + if (currentProvider && currentProvider?.id === action.providerId) { ctx.dispatch( new SetCurrentProvider({ - id: currentProvider.id, - name: currentProvider.name, + id: currentProvider?.id, + name: currentProvider?.name, type: CurrentResourceType.Registrations, - permissions: currentProvider.permissions, + permissions: currentProvider?.permissions, + reviewsWorkflow: currentProvider?.reviewsWorkflow, }) ); @@ -51,17 +50,15 @@ export class RegistrationProviderState { }, }); - return this.registrationProvidersService.getProviderBrand(action.providerName).pipe( + return this.registrationProvidersService.getProviderBrand(action.providerId).pipe( tap((provider) => { - ctx.setState( - patch({ - currentBrandedProvider: patch({ - data: provider, - isLoading: false, - error: null, - }), - }) - ); + ctx.patchState({ + currentBrandedProvider: { + data: provider, + isLoading: false, + error: null, + }, + }); ctx.dispatch( new SetCurrentProvider({ @@ -69,6 +66,7 @@ export class RegistrationProviderState { name: provider.name, type: CurrentResourceType.Registrations, permissions: provider.permissions, + reviewsWorkflow: provider.reviewsWorkflow, }) ); }), @@ -78,6 +76,6 @@ export class RegistrationProviderState { @Action(ClearRegistryProvider) clearRegistryProvider(ctx: StateContext) { - ctx.setState(patch({ ...REGISTRIES_PROVIDER_SEARCH_STATE_DEFAULTS })); + ctx.patchState({ ...REGISTRIES_PROVIDER_SEARCH_STATE_DEFAULTS }); } } From 0eb6fa857b77822d151e96c1075022ac4f7e3081 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Wed, 1 Oct 2025 14:19:17 +0300 Subject: [PATCH 2/4] fix(files): file menu updates --- src/app/core/guards/is-file.guard.ts | 5 ++- src/app/core/guards/is-project.guard.ts | 4 +-- src/app/core/guards/is-registry.guard.ts | 4 +-- .../file-detail/file-detail.component.ts | 2 +- .../files/pages/files/files.component.html | 1 - .../files/pages/files/files.component.spec.ts | 1 - .../files/pages/files/files.component.ts | 36 ++++++++++++++----- .../features/metadata/metadata.component.ts | 2 +- .../files-widget/files-widget.component.ts | 11 ++++-- .../files-control.component.html | 1 - .../files-tree/files-tree.component.html | 13 +------ .../files-tree/files-tree.component.ts | 9 ++--- 12 files changed, 51 insertions(+), 38 deletions(-) diff --git a/src/app/core/guards/is-file.guard.ts b/src/app/core/guards/is-file.guard.ts index f05adf254..e2e5e38ed 100644 --- a/src/app/core/guards/is-file.guard.ts +++ b/src/app/core/guards/is-file.guard.ts @@ -19,14 +19,13 @@ export const isFileGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => } const currentResource = store.selectSnapshot(CurrentResourceSelectors.getCurrentResource); - if (currentResource && currentResource.id === id) { if (currentResource.type === CurrentResourceType.Files) { if (isMetadataPath) { return true; } if (currentResource.parentId) { - router.navigate(['/', currentResource.parentId, 'files', id]); + router.navigate(['/', currentResource.parentId, 'files', id], { queryParamsHandling: 'preserve' }); return false; } } @@ -46,7 +45,7 @@ export const isFileGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) => return true; } if (resource.parentId) { - router.navigate(['/', resource.parentId, 'files', id]); + router.navigate(['/', resource.parentId, 'files', id], { queryParamsHandling: 'preserve' }); return false; } } diff --git a/src/app/core/guards/is-project.guard.ts b/src/app/core/guards/is-project.guard.ts index 804d80322..d1eb1d6fb 100644 --- a/src/app/core/guards/is-project.guard.ts +++ b/src/app/core/guards/is-project.guard.ts @@ -24,7 +24,7 @@ export const isProjectGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) if (currentResource && !id.startsWith(currentResource.id)) { if (currentResource.type === CurrentResourceType.Projects && currentResource.parentId) { - router.navigate(['/', currentResource.parentId, 'files', id]); + router.navigate(['/', currentResource.parentId, 'files', id], { queryParamsHandling: 'preserve' }); return true; } @@ -53,7 +53,7 @@ export const isProjectGuard: CanMatchFn = (route: Route, segments: UrlSegment[]) } if (resource.type === CurrentResourceType.Projects && resource.parentId) { - router.navigate(['/', resource.parentId, 'files', id]); + router.navigate(['/', resource.parentId, 'files', id], { queryParamsHandling: 'preserve' }); return true; } diff --git a/src/app/core/guards/is-registry.guard.ts b/src/app/core/guards/is-registry.guard.ts index 44a8628c0..63dfe4a28 100644 --- a/src/app/core/guards/is-registry.guard.ts +++ b/src/app/core/guards/is-registry.guard.ts @@ -24,7 +24,7 @@ export const isRegistryGuard: CanMatchFn = (route: Route, segments: UrlSegment[] if (currentResource && !id.startsWith(currentResource.id)) { if (currentResource.type === CurrentResourceType.Registrations && currentResource.parentId) { - router.navigate(['/', currentResource.parentId, 'files', id]); + router.navigate(['/', currentResource.parentId, 'files', id], { queryParamsHandling: 'preserve' }); return true; } @@ -53,7 +53,7 @@ export const isRegistryGuard: CanMatchFn = (route: Route, segments: UrlSegment[] } if (resource.type === CurrentResourceType.Registrations && resource.parentId) { - router.navigate(['/', resource.parentId, 'files', id]); + router.navigate(['/', resource.parentId, 'files', id], { queryParamsHandling: 'preserve' }); return true; } diff --git a/src/app/features/files/pages/file-detail/file-detail.component.ts b/src/app/features/files/pages/file-detail/file-detail.component.ts index 0d7d79740..6b4209ee5 100644 --- a/src/app/features/files/pages/file-detail/file-detail.component.ts +++ b/src/app/features/files/pages/file-detail/file-detail.component.ts @@ -304,7 +304,7 @@ export class FileDetailComponent { copyToClipboard(embedHtml: string): void { this.clipboard.copy(embedHtml); - this.toastService.showSuccess('files.toast.copiedToClipboard'); + this.toastService.showSuccess('files.toast.detail.copiedToClipboard'); } deleteEntry(link: string): void { diff --git a/src/app/features/files/pages/files/files.component.html b/src/app/features/files/pages/files/files.component.html index 5500aa4c6..229824a20 100644 --- a/src/app/features/files/pages/files/files.component.html +++ b/src/app/features/files/pages/files/files.component.html @@ -127,7 +127,6 @@ [isLoading]="isFilesLoading()" [actions]="filesTreeActions" [viewOnly]="!canEdit()" - [viewOnlyDownloadable]="isViewOnlyDownloadable()" [supportUpload]="canUploadFiles()" [resourceId]="resourceId()" [provider]="provider()" diff --git a/src/app/features/files/pages/files/files.component.spec.ts b/src/app/features/files/pages/files/files.component.spec.ts index 8ba77eae6..d10166785 100644 --- a/src/app/features/files/pages/files/files.component.spec.ts +++ b/src/app/features/files/pages/files/files.component.spec.ts @@ -120,7 +120,6 @@ describe('Component: Files', () => { 'isLoading', 'actions', 'viewOnly', - 'viewOnlyDownloadable', 'resourceId', 'provider', 'storage', diff --git a/src/app/features/files/pages/files/files.component.ts b/src/app/features/files/pages/files/files.component.ts index a055b7aff..61ec08c9f 100644 --- a/src/app/features/files/pages/files/files.component.ts +++ b/src/app/features/files/pages/files/files.component.ts @@ -59,7 +59,7 @@ import { } from '@osf/features/files/store'; import { ALL_SORT_OPTIONS, FILE_SIZE_LIMIT } from '@osf/shared/constants'; import { FileMenuType, ResourceType, SupportedFeature, UserPermissions } from '@osf/shared/enums'; -import { hasViewOnlyParam, IS_MEDIUM } from '@osf/shared/helpers'; +import { getViewOnlyParamFromUrl, hasViewOnlyParam, IS_MEDIUM } from '@osf/shared/helpers'; import { CurrentResourceSelectors, GetResourceDetails } from '@osf/shared/stores'; import { FilesTreeComponent, @@ -184,7 +184,21 @@ export class FilesComponent { readonly allowedMenuActions = computed(() => { const provider = this.provider(); const supportedFeatures = this.supportedFeatures()[provider] || []; - return this.mapMenuActions(supportedFeatures); + const hasViewOnly = this.hasViewOnly(); + const isRegistration = this.resourceType() === ResourceType.Registration; + const menuMap = this.mapMenuActions(supportedFeatures); + + const result: Record = { ...menuMap }; + + if (hasViewOnly || isRegistration) { + const allowed = new Set([FileMenuType.Download, FileMenuType.Embed, FileMenuType.Share]); + + (Object.keys(result) as FileMenuType[]).forEach((key) => { + result[key] = allowed.has(key) && menuMap[key]; + }); + } + + return result; }); readonly rootFoldersOptions = computed(() => { @@ -211,15 +225,16 @@ export class FilesComponent { (permission) => permission === UserPermissions.Admin || permission === UserPermissions.Write ); - return !details.isRegistration && hasAdminOrWrite; + return hasAdminOrWrite; }); - readonly isViewOnlyDownloadable = computed( - () => this.allowedMenuActions()[FileMenuType.Download] && this.resourceType() === ResourceType.Registration - ); + readonly isRegistration = computed(() => this.resourceType() === ResourceType.Registration); canUploadFiles = computed( - () => this.supportedFeatures()[this.provider()]?.includes(SupportedFeature.AddUpdateFiles) && this.canEdit() + () => + this.supportedFeatures()[this.provider()]?.includes(SupportedFeature.AddUpdateFiles) && + this.canEdit() && + !this.isRegistration() ); isButtonDisabled = computed(() => this.fileIsUploading() || this.isFilesLoading()); @@ -492,7 +507,12 @@ export class FilesComponent { } navigateToFile(file: OsfFile) { - const url = this.router.createUrlTree([file.guid]).toString(); + let url = file.links?.html ?? ''; + const viewOnlyParam = this.hasViewOnly(); + if (viewOnlyParam) { + const separator = url.includes('?') ? '&' : '?'; + url = `${url}${separator}view_only=${getViewOnlyParamFromUrl(this.router.url)}`; + } window.open(url, '_blank'); } diff --git a/src/app/features/metadata/metadata.component.ts b/src/app/features/metadata/metadata.component.ts index c9bed5269..a3ffe6340 100644 --- a/src/app/features/metadata/metadata.component.ts +++ b/src/app/features/metadata/metadata.component.ts @@ -307,7 +307,7 @@ export class MetadataComponent implements OnInit { .subscribe(() => { this.updateSelectedCedarRecord(selectedRecord.id!); this.cedarFormReadonly.set(true); - this.toastService.showSuccess(this.translateService.instant('files.detail.toast.cedarUpdated')); + this.toastService.showSuccess('files.detail.toast.cedarUpdated'); }); } } diff --git a/src/app/features/project/overview/components/files-widget/files-widget.component.ts b/src/app/features/project/overview/components/files-widget/files-widget.component.ts index ccd0da344..3097d9dd0 100644 --- a/src/app/features/project/overview/components/files-widget/files-widget.component.ts +++ b/src/app/features/project/overview/components/files-widget/files-widget.component.ts @@ -31,7 +31,7 @@ import { SetFilesIsLoading, } from '@osf/features/files/store'; import { FilesTreeComponent, SelectComponent } from '@osf/shared/components'; -import { Primitive } from '@osf/shared/helpers'; +import { getViewOnlyParamFromUrl, hasViewOnlyParam, Primitive } from '@osf/shared/helpers'; import { ConfiguredAddonModel, FileLabelModel, @@ -91,6 +91,8 @@ export class FilesWidgetComponent { return []; }); + readonly hasViewOnly = computed(() => hasViewOnlyParam(this.router)); + private readonly actions = createDispatchMap({ getFiles: GetFiles, setCurrentFolder: SetCurrentFolder, @@ -218,7 +220,12 @@ export class FilesWidgetComponent { } navigateToFile(file: OsfFile) { - const url = this.router.createUrlTree([file.guid]).toString(); + let url = file.links?.html ?? ''; + const viewOnlyParam = this.hasViewOnly(); + if (viewOnlyParam) { + const separator = url.includes('?') ? '&' : '?'; + url = `${url}${separator}view_only=${getViewOnlyParamFromUrl(this.router.url)}`; + } window.open(url, '_blank'); } 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 41f62a227..172236d64 100644 --- a/src/app/features/registries/components/files-control/files-control.component.html +++ b/src/app/features/registries/components/files-control/files-control.component.html @@ -58,7 +58,6 @@ [isLoading]="isFilesLoading()" [actions]="filesTreeActions" [viewOnly]="filesViewOnly()" - [viewOnlyDownloadable]="true" [resourceId]="projectId()" [provider]="provider()" (folderIsOpening)="folderIsOpening($event)" 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 48086e274..1e437042d 100644 --- a/src/app/shared/components/files-tree/files-tree.component.html +++ b/src/app/shared/components/files-tree/files-tree.component.html @@ -71,7 +71,7 @@ {{ file.dateModified | date: 'MMM d, y hh:mm a' }} - @if (!viewOnly() && !viewOnlyDownloadable()) { + @if (isSomeFileActionAllowed) {
- } @else if (viewOnly() && viewOnlyDownloadable()) { -
- -
} } 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 72bdfa333..d52fb6ea9 100644 --- a/src/app/shared/components/files-tree/files-tree.component.ts +++ b/src/app/shared/components/files-tree/files-tree.component.ts @@ -3,7 +3,6 @@ import { select } from '@ngxs/store'; import { TranslatePipe } from '@ngx-translate/core'; import { PrimeTemplate } from 'primeng/api'; -import { Button } from 'primeng/button'; import { PaginatorState } from 'primeng/paginator'; import { Tree, TreeNodeDropEvent } from 'primeng/tree'; @@ -57,7 +56,6 @@ import { LoadingSpinnerComponent } from '../loading-spinner/loading-spinner.comp FileMenuComponent, StopPropagationDirective, CustomPaginatorComponent, - Button, ], templateUrl: './files-tree.component.html', styleUrl: './files-tree.component.scss', @@ -84,7 +82,6 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { resourceId = input.required(); actions = input.required(); viewOnly = input(true); - viewOnlyDownloadable = input(false); provider = input(); allowedMenuActions = input({} as FileMenuFlags); supportUpload = input(true); @@ -104,6 +101,10 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { readonly FileMenuType = FileMenuType; + get isSomeFileActionAllowed(): boolean { + return Object.keys(this.allowedMenuActions()).length > 0; + } + readonly nodes = computed(() => { const currentFolder = this.currentFolder(); const files = this.files(); @@ -389,7 +390,7 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { copyToClipboard(embedHtml: string): void { this.clipboard.copy(embedHtml); - this.toastService.showSuccess('files.toast.copiedToClipboard'); + this.toastService.showSuccess('files.toast.detail.copiedToClipboard'); } async dropNode(event: TreeNodeDropEvent) { From e80e5f26d3c6a159527d1aa68f6ddf6076889aa7 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Wed, 1 Oct 2025 18:21:42 +0300 Subject: [PATCH 3/4] fix(files): reset files pagination --- .../move-file-dialog/move-file-dialog.component.ts | 6 ++++++ src/app/features/files/pages/files/files.component.ts | 2 +- .../shared/components/files-tree/files-tree.component.ts | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts index 349080801..acde40ea3 100644 --- a/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts +++ b/src/app/features/files/components/move-file-dialog/move-file-dialog.component.ts @@ -167,6 +167,7 @@ export class MoveFileDialogComponent { if (file.id) { const filesLink = this.currentFolder()?.relationships.filesLink; const rootFolders = this.rootFolders(); + this.resetPagination(); if (filesLink) { this.dispatch.getFiles(filesLink); } else if (rootFolders) { @@ -176,6 +177,11 @@ export class MoveFileDialogComponent { }); } + resetPagination() { + this.first = 0; + this.pageNumber.set(1); + } + onFilesPageChange(event: PaginatorState): void { this.pageNumber.set(event.page! + 1); this.first = event.first!; diff --git a/src/app/features/files/pages/files/files.component.ts b/src/app/features/files/pages/files/files.component.ts index 61ec08c9f..0ff4735c0 100644 --- a/src/app/features/files/pages/files/files.component.ts +++ b/src/app/features/files/pages/files/files.component.ts @@ -190,7 +190,7 @@ export class FilesComponent { const result: Record = { ...menuMap }; - if (hasViewOnly || isRegistration) { + if (hasViewOnly || isRegistration || !this.canEdit()) { const allowed = new Set([FileMenuType.Download, FileMenuType.Embed, FileMenuType.Share]); (Object.keys(result) as FileMenuType[]).forEach((key) => { 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 d52fb6ea9..e5b8bc872 100644 --- a/src/app/shared/components/files-tree/files-tree.component.ts +++ b/src/app/shared/components/files-tree/files-tree.component.ts @@ -374,6 +374,7 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { }, }) .onClose.subscribe((foldersStack) => { + this.resetPagination(); if (foldersStack) { this.foldersStack = [...foldersStack]; } From d8410793db22f893200600838fd91760e7b581d8 Mon Sep 17 00:00:00 2001 From: NazarMykhalkevych Date: Wed, 1 Oct 2025 23:45:03 +0300 Subject: [PATCH 4/4] fix(files): add provider for create guid --- .../pages/file-redirect/file-redirect.component.ts | 3 ++- .../components/files-tree/files-tree.component.ts | 2 +- src/app/shared/services/files.service.ts | 10 ++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/app/features/files/pages/file-redirect/file-redirect.component.ts b/src/app/features/files/pages/file-redirect/file-redirect.component.ts index dfe42b96b..59ffe020b 100644 --- a/src/app/features/files/pages/file-redirect/file-redirect.component.ts +++ b/src/app/features/files/pages/file-redirect/file-redirect.component.ts @@ -18,10 +18,11 @@ export class FileRedirectComponent { private readonly filesService = inject(FilesService); readonly fileId = this.route.snapshot.paramMap.get('fileId') ?? ''; + readonly provider = this.route.snapshot.paramMap.get('provider') ?? ''; constructor() { if (this.fileId) { this.filesService - .getFileGuid(this.fileId) + .getFileGuid(this.fileId, this.provider) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe((file) => { this.router.navigate([file.guid]); 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 e5b8bc872..1bc2d456c 100644 --- a/src/app/shared/components/files-tree/files-tree.component.ts +++ b/src/app/shared/components/files-tree/files-tree.component.ts @@ -205,7 +205,7 @@ export class FilesTreeComponent implements OnDestroy, AfterViewInit { if (file.guid) { this.entryFileClicked.emit(file); } else { - this.filesService.getFileGuid(file.id).subscribe((file) => { + this.filesService.getFileGuid(file.id, file.provider).subscribe((file) => { this.entryFileClicked.emit(file); }); } diff --git a/src/app/shared/services/files.service.ts b/src/app/shared/services/files.service.ts index b7da9c0d5..54d5715be 100644 --- a/src/app/shared/services/files.service.ts +++ b/src/app/shared/services/files.service.ts @@ -185,14 +185,16 @@ export class FilesService { .pipe(map((response) => MapFile(response.data))); } - getFileGuid(id: string): Observable { + getFileGuid(id: string, storageId?: string): Observable { const params = { create_guid: 'true', }; + let url = `${this.apiUrl}/files/${id}/`; + if (storageId) { + url = `${this.apiUrl}/files/${storageId}/${id}/`; + } - return this.jsonApiService - .get(`${this.apiUrl}/files/${id}/`, params) - .pipe(map((response) => MapFile(response.data))); + return this.jsonApiService.get(url, params).pipe(map((response) => MapFile(response.data))); } getFileById(fileGuid: string): Observable {