[CST-4499] Version history (WIP) - Version page added (redirecting to item's page)

This commit is contained in:
Davide Negretti
2021-09-13 17:56:55 +02:00
parent ce399cb764
commit b4111fe4b1
10 changed files with 185 additions and 5 deletions

View File

@@ -34,5 +34,14 @@ export function getEntityEditRoute(entityType: string, itemId: string) {
return new URLCombiner(getEntityPageRoute(entityType, itemId), ITEM_EDIT_PATH).toString(); return new URLCombiner(getEntityPageRoute(entityType, itemId), ITEM_EDIT_PATH).toString();
} }
/**
* Get the route to an item's version
* @param versionId the ID of the version for which the route will be retrieved
*/
export function getItemVersionRoute(versionId: string) {
return new URLCombiner(getItemModuleRoute(), ITEM_VERSION_PATH, versionId).toString();
}
export const ITEM_EDIT_PATH = 'edit'; export const ITEM_EDIT_PATH = 'edit';
export const ITEM_VERSION_PATH = 'version';
export const UPLOAD_BITSTREAM_PATH = 'bitstreams/new'; export const UPLOAD_BITSTREAM_PATH = 'bitstreams/new';

View File

@@ -3,6 +3,7 @@ import { RouterModule } from '@angular/router';
import { ItemPageResolver } from './item-page.resolver'; import { ItemPageResolver } from './item-page.resolver';
import { AuthenticatedGuard } from '../core/auth/authenticated.guard'; import { AuthenticatedGuard } from '../core/auth/authenticated.guard';
import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver'; import { ItemBreadcrumbResolver } from '../core/breadcrumbs/item-breadcrumb.resolver';
import { VersionResolver } from './version-page/version.resolver';
import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service'; import { DSOBreadcrumbsService } from '../core/breadcrumbs/dso-breadcrumbs.service';
import { LinkService } from '../core/cache/builders/link.service'; import { LinkService } from '../core/cache/builders/link.service';
import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component'; import { UploadBitstreamComponent } from './bitstreams/upload/upload-bitstream.component';
@@ -12,6 +13,7 @@ import { MenuItemType } from '../shared/menu/initial-menus-state';
import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model'; import { LinkMenuItemModel } from '../shared/menu/menu-item/models/link.model';
import { ThemedItemPageComponent } from './simple/themed-item-page.component'; import { ThemedItemPageComponent } from './simple/themed-item-page.component';
import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component'; import { ThemedFullItemPageComponent } from './full/themed-full-item-page.component';
import { VersionPageComponent } from './version-page/version-page/version-page.component';
@NgModule({ @NgModule({
imports: [ imports: [
@@ -58,6 +60,18 @@ import { ThemedFullItemPageComponent } from './full/themed-full-item-page.compon
}], }],
}, },
}, },
},
{
path: 'version',
children: [
{
path: ':id',
component: VersionPageComponent,
resolve: {
dso: VersionResolver,
},
}
],
} }
]) ])
], ],
@@ -67,6 +81,7 @@ import { ThemedFullItemPageComponent } from './full/themed-full-item-page.compon
DSOBreadcrumbsService, DSOBreadcrumbsService,
LinkService, LinkService,
ItemPageAdministratorGuard, ItemPageAdministratorGuard,
VersionResolver,
] ]
}) })

View File

@@ -31,6 +31,7 @@ import { MediaViewerComponent } from './media-viewer/media-viewer.component';
import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/media-viewer-video.component'; import { MediaViewerVideoComponent } from './media-viewer/media-viewer-video/media-viewer-video.component';
import { MediaViewerImageComponent } from './media-viewer/media-viewer-image/media-viewer-image.component'; import { MediaViewerImageComponent } from './media-viewer/media-viewer-image/media-viewer-image.component';
import { NgxGalleryModule } from '@kolkov/ngx-gallery'; import { NgxGalleryModule } from '@kolkov/ngx-gallery';
import { VersionPageComponent } from './version-page/version-page/version-page.component';
const ENTRY_COMPONENTS = [ const ENTRY_COMPONENTS = [
// put only entry components that use custom decorator // put only entry components that use custom decorator
@@ -60,7 +61,8 @@ const DECLARATIONS = [
AbstractIncrementalListComponent, AbstractIncrementalListComponent,
MediaViewerComponent, MediaViewerComponent,
MediaViewerVideoComponent, MediaViewerVideoComponent,
MediaViewerImageComponent MediaViewerImageComponent,
VersionPageComponent,
]; ];
@NgModule({ @NgModule({
@@ -72,7 +74,7 @@ const DECLARATIONS = [
StatisticsModule.forRoot(), StatisticsModule.forRoot(),
JournalEntitiesModule.withEntryComponents(), JournalEntitiesModule.withEntryComponents(),
ResearchEntitiesModule.withEntryComponents(), ResearchEntitiesModule.withEntryComponents(),
NgxGalleryModule, NgxGalleryModule,
], ],
declarations: [ declarations: [
...DECLARATIONS ...DECLARATIONS

View File

@@ -0,0 +1,25 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { VersionPageComponent } from './version-page.component';
describe('VersionPageComponent', () => {
let component: VersionPageComponent;
let fixture: ComponentFixture<VersionPageComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ VersionPageComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(VersionPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,61 @@
import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { RemoteData } from '../../../core/data/remote-data';
import { ActivatedRoute, Router } from '@angular/router';
import { AuthService } from '../../../core/auth/auth.service';
import { map, switchMap } from 'rxjs/operators';
import {
getFirstCompletedRemoteData,
getFirstSucceededRemoteDataPayload,
redirectOn4xx
} from '../../../core/shared/operators';
import { VersionDataService } from '../../../core/data/version-data.service';
import { Version } from '../../../core/shared/version.model';
import { Item } from '../../../core/shared/item.model';
import { getItemPageRoute } from '../../item-page-routing-paths';
import { getPageNotFoundRoute } from '../../../app-routing-paths';
@Component({
selector: 'ds-version-page',
templateUrl: './version-page.component.html',
styleUrls: ['./version-page.component.scss']
})
export class VersionPageComponent implements OnInit {
versionRD$: Observable<RemoteData<Version>>;
itemRD$: Observable<RemoteData<Item>>;
constructor(
protected route: ActivatedRoute,
private router: Router,
private versionService: VersionDataService,
private authService: AuthService,
) {
}
ngOnInit(): void {
/* Retrieve version from resolver or redirect on 4xx */
this.versionRD$ = this.route.data.pipe(
map((data) => data.dso as RemoteData<Version>),
redirectOn4xx(this.router, this.authService),
);
/* Retrieve item from version and reroute to item's page or handle missing item */
this.versionRD$.pipe(
getFirstSucceededRemoteDataPayload(),
switchMap((version) => version.item),
redirectOn4xx(this.router, this.authService),
getFirstCompletedRemoteData(),
).subscribe((itemRD) => {
console.log(JSON.stringify(itemRD));
if (itemRD.hasNoContent) {
this.router.navigateByUrl(getPageNotFoundRoute(), { skipLocationChange: true });
} else {
const itemUrl = getItemPageRoute(itemRD.payload);
this.router.navigateByUrl(itemUrl);
}
});
}
}

View File

@@ -0,0 +1,54 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, Resolve, Router, RouterStateSnapshot } from '@angular/router';
import { Observable } from 'rxjs';
import { RemoteData } from '../../core/data/remote-data';
import { followLink, FollowLinkConfig } from '../../shared/utils/follow-link-config.model';
import { getFirstCompletedRemoteData } from '../../core/shared/operators';
import { Store } from '@ngrx/store';
import { ResolvedAction } from '../../core/resolving/resolver.actions';
import { Version } from '../../core/shared/version.model';
import { VersionDataService } from '../../core/data/version-data.service';
/**
* The self links defined in this list are expected to be requested somewhere in the near future
* Requesting them as embeds will limit the number of requests
*/
export const VERSION_PAGE_LINKS_TO_FOLLOW: FollowLinkConfig<Version>[] = [
followLink('item'),
];
/**
* This class represents a resolver that requests a specific version before the route is activated
*/
@Injectable()
export class VersionResolver implements Resolve<RemoteData<Version>> {
constructor(
protected versionService: VersionDataService,
protected store: Store<any>,
protected router: Router
) {
}
/**
* Method for resolving a version based on the parameters in the current route
* @param {ActivatedRouteSnapshot} route The current ActivatedRouteSnapshot
* @param {RouterStateSnapshot} state The current RouterStateSnapshot
* @returns Observable<<RemoteData<Item>> Emits the found item based on the parameters in the current route,
* or an error if something went wrong
*/
resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<RemoteData<Version>> {
const versionRD$ = this.versionService.findById(route.params.id,
true,
false,
...VERSION_PAGE_LINKS_TO_FOLLOW
).pipe(
getFirstCompletedRemoteData(),
);
versionRD$.subscribe((versionRD: RemoteData<Version>) => {
this.store.dispatch(new ResolvedAction(state.url, versionRD.payload));
});
return versionRD$;
}
}

View File

@@ -24,13 +24,18 @@
</thead> </thead>
<tbody> <tbody>
<tr *ngFor="let version of versions?.page" [id]="'version-row-' + version.id"> <tr *ngFor="let version of versions?.page" [id]="'version-row-' + version.id">
<td class="version-row-element-version">{{version?.version}}</td> <td class="version-row-element-version">
<a [routerLink]="getVersionRoute(version.id)">{{version.version}}</a>
<span *ngIf="version?.id === itemVersion?.id">*</span>
</td>
<td *ngIf="(hasEpersons$ | async)" class="version-row-element-editor"> <td *ngIf="(hasEpersons$ | async)" class="version-row-element-editor">
<span *ngVar="(version?.eperson | async)?.payload as eperson"> <span *ngVar="(version?.eperson | async)?.payload as eperson">
<a *ngIf="eperson" [href]="'mailto:' + eperson?.email">{{eperson?.name}}</a> <a *ngIf="eperson" [href]="'mailto:' + eperson?.email">{{eperson?.name}}</a>
</span> </span>
</td> </td>
<td class="version-row-element-date">{{version?.created | date : 'yyyy-MM-dd HH:mm:ss'}}</td> <td class="version-row-element-date">
{{version?.created | date : 'yyyy-MM-dd HH:mm:ss'}}
</td>
<td class="version-row-element-summary"> <td class="version-row-element-summary">
<ng-container *ngIf="isThisBeingEdited(version); then editSummary else showSummary"></ng-container> <ng-container *ngIf="isThisBeingEdited(version); then editSummary else showSummary"></ng-container>
<ng-template #showSummary>{{version?.summary}}</ng-template> <ng-template #showSummary>{{version?.summary}}</ng-template>

View File

@@ -21,7 +21,7 @@ import { AlertType } from '../../alert/aletr-type';
import { followLink } from '../../utils/follow-link-config.model'; import { followLink } from '../../utils/follow-link-config.model';
import { hasValue, hasValueOperator } from '../../empty.util'; import { hasValue, hasValueOperator } from '../../empty.util';
import { PaginationService } from '../../../core/pagination/pagination.service'; import { PaginationService } from '../../../core/pagination/pagination.service';
import { getItemPageRoute } from '../../../item-page/item-page-routing-paths'; import { getItemPageRoute, getItemVersionRoute } from '../../../item-page/item-page-routing-paths';
import { FormBuilder } from '@angular/forms'; import { FormBuilder } from '@angular/forms';
import { NgbModal } from '@ng-bootstrap/ng-bootstrap'; import { NgbModal } from '@ng-bootstrap/ng-bootstrap';
import { ItemVersionsSummaryModalComponent } from './item-versions-summary-modal/item-versions-summary-modal.component'; import { ItemVersionsSummaryModalComponent } from './item-versions-summary-modal/item-versions-summary-modal.component';
@@ -192,6 +192,14 @@ export class ItemVersionsComponent implements OnInit {
this.versionBeingEditedId = undefined; this.versionBeingEditedId = undefined;
} }
/**
* Get the route to the specified version
* @param versionId the ID of the version for which the route will be retrieved
*/
getVersionRoute(versionId: string) {
return getItemVersionRoute(versionId);
}
/** /**
* Applies changes to version currently being edited * Applies changes to version currently being edited
*/ */
@@ -250,6 +258,7 @@ export class ItemVersionsComponent implements OnInit {
} }
); );
}); });
// TODO non usare subscribe annidate
/*version.item.pipe( /*version.item.pipe(
getFirstSucceededRemoteDataPayload(), getFirstSucceededRemoteDataPayload(),
switchMap((getItemRes) => this.itemService.delete(getItemRes.id)) switchMap((getItemRes) => this.itemService.delete(getItemRes.id))