mirror of
https://github.com/Alfresco/alfresco-content-app.git
synced 2025-07-31 17:38:28 +00:00
[ACA-100] View a previous version (#1562)
* added a feature to view a previous node version * added latest versions * added single selection to match the latest ADF change * hide version list dialog when opening the viewer * pass versionId to adf-viewer * add view versions rendition routes for: shared, recent files, favorites, search. * added functionality to download a previous version of a node * small fix - no need to clear the store * remove unused import * removed changes * Delete package-lock.json * revert * update to last dependencies * ACA-3601: Add e2e tests for Preview of a file's previous version * Address comments * ACA-3601: Make sub-tests execute independently * update dependencies * ACA-3601: Add licenses * Add type * ACA-3601: Refactor execution Co-authored-by: kristian <kristian.dimitrov@alfresco.com> Co-authored-by: Denys Vuika <denys.vuika@alfresco.com>
This commit is contained in:
@@ -111,6 +111,19 @@ export const APP_ROUTES: Routes = [
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'view/:nodeId/:versionId',
|
||||
outlet: 'viewer',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
data: {
|
||||
navigateSource: 'personal-files'
|
||||
},
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -125,6 +138,19 @@ export const APP_ROUTES: Routes = [
|
||||
sortingPreferenceKey: 'personal-files'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'view/:nodeId/:versionId',
|
||||
outlet: 'viewer',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
data: {
|
||||
navigateSource: 'personal-files'
|
||||
},
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
// deprecated, backwards compatibility with ACA 1.8
|
||||
{
|
||||
path: 'preview/:nodeId',
|
||||
@@ -283,6 +309,19 @@ export const APP_ROUTES: Routes = [
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'view/:nodeId/:versionId',
|
||||
outlet: 'viewer',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
data: {
|
||||
navigateSource: 'favorites'
|
||||
},
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -321,6 +360,19 @@ export const APP_ROUTES: Routes = [
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'view/:nodeId/:versionId',
|
||||
outlet: 'viewer',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
data: {
|
||||
navigateSource: 'recent-files'
|
||||
},
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -357,6 +409,19 @@ export const APP_ROUTES: Routes = [
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'view/:nodeId/:versionId',
|
||||
outlet: 'viewer',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
data: {
|
||||
navigateSource: 'shared'
|
||||
},
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
canActivateChild: [AppSharedRuleGuard],
|
||||
@@ -400,6 +465,19 @@ export const APP_ROUTES: Routes = [
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'view/:nodeId/:versionId',
|
||||
outlet: 'viewer',
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
data: {
|
||||
navigateSource: 'search'
|
||||
},
|
||||
loadChildren: () => import('./components/viewer/viewer.module').then((m) => m.AppViewerModule)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@@ -6,6 +6,7 @@
|
||||
[fileName]="fileName"
|
||||
[maxRetries]="'viewer.maxRetries' | adfAppConfig"
|
||||
[nodeId]="nodeId"
|
||||
[versionId]="versionId"
|
||||
[allowNavigate]="navigateMultiple"
|
||||
[allowRightSidebar]="true"
|
||||
[allowPrint]="false"
|
||||
|
@@ -32,10 +32,11 @@ import {
|
||||
ClosePreviewAction,
|
||||
ViewerActionTypes,
|
||||
ViewNodeAction,
|
||||
ReloadDocumentListAction
|
||||
ReloadDocumentListAction,
|
||||
SetCurrentNodeVersionAction
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { ContentActionRef, SelectionState } from '@alfresco/adf-extensions';
|
||||
import { MinimalNodeEntryEntity, SearchRequest } from '@alfresco/js-api';
|
||||
import { MinimalNodeEntryEntity, SearchRequest, VersionEntry } from '@alfresco/js-api';
|
||||
import { Component, HostListener, OnDestroy, OnInit, ViewEncapsulation } from '@angular/core';
|
||||
import { ActivatedRoute, Router, PRIMARY_OUTLET } from '@angular/router';
|
||||
import { UserPreferencesService, ObjectUtils, UploadService, AlfrescoApiService } from '@alfresco/adf-core';
|
||||
@@ -58,6 +59,7 @@ export class AppViewerComponent implements OnInit, OnDestroy {
|
||||
fileName: string;
|
||||
folderId: string = null;
|
||||
nodeId: string = null;
|
||||
versionId: string = null;
|
||||
node: MinimalNodeEntryEntity;
|
||||
selection: SelectionState;
|
||||
infoDrawerOpened$: Observable<boolean>;
|
||||
@@ -135,6 +137,14 @@ export class AppViewerComponent implements OnInit, OnDestroy {
|
||||
this.route.params.subscribe((params) => {
|
||||
this.folderId = params.folderId;
|
||||
const { nodeId } = params;
|
||||
this.versionId = params.versionId;
|
||||
if (this.versionId) {
|
||||
this.apiService.versionsApi.getVersion(nodeId, this.versionId).then((version: VersionEntry) => {
|
||||
if (version) {
|
||||
this.store.dispatch(new SetCurrentNodeVersionAction(version));
|
||||
}
|
||||
});
|
||||
}
|
||||
if (nodeId) {
|
||||
this.displayNode(nodeId);
|
||||
}
|
||||
@@ -151,9 +161,10 @@ export class AppViewerComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
this.actions$
|
||||
.pipe(ofType<ClosePreviewAction>(ViewerActionTypes.ClosePreview), takeUntil(this.onDestroy$))
|
||||
.subscribe(() => this.navigateToFileLocation());
|
||||
this.actions$.pipe(ofType<ClosePreviewAction>(ViewerActionTypes.ClosePreview), takeUntil(this.onDestroy$)).subscribe(() => {
|
||||
this.store.dispatch(new SetCurrentNodeVersionAction(null));
|
||||
this.navigateToFileLocation();
|
||||
});
|
||||
|
||||
this.content.nodesDeleted.pipe(takeUntil(this.onDestroy$)).subscribe(() => this.navigateToFileLocation());
|
||||
|
||||
@@ -173,6 +184,7 @@ export class AppViewerComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.store.dispatch(new SetCurrentNodeVersionAction(null));
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
@@ -5,11 +5,7 @@
|
||||
{{ 'VERSION.DIALOG.TITLE' | translate }}
|
||||
</header>
|
||||
<section mat-dialog-content *ngIf="!isTypeList">
|
||||
<adf-version-comparison
|
||||
id="adf-version-comparison"
|
||||
[newFileVersion]="file"
|
||||
[node]="node"
|
||||
></adf-version-comparison>
|
||||
<adf-version-comparison id="adf-version-comparison" [newFileVersion]="file" [node]="node"></adf-version-comparison>
|
||||
<adf-version-upload
|
||||
id="adf-version-upload-button"
|
||||
[node]="node"
|
||||
@@ -30,6 +26,7 @@
|
||||
[allowDownload]="'adf-version-manager.allowDownload' | adfAppConfig: true"
|
||||
(deleted)="refresh($event)"
|
||||
(restored)="refresh($event)"
|
||||
(viewVersion)="onViewingVersion($event)"
|
||||
></adf-version-list>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -37,19 +37,20 @@ import {
|
||||
VersionUploadComponent
|
||||
} from '@alfresco/adf-content-services';
|
||||
import { TranslateFakeLoader, TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { AppStore, UnlockWriteAction } from '@alfresco/aca-shared/store';
|
||||
import { AppStore, UnlockWriteAction, ViewNodeExtras, ViewNodeVersionAction } from '@alfresco/aca-shared/store';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
|
||||
describe('NodeVersionsDialogComponent', () => {
|
||||
let fixture: ComponentFixture<NodeVersionsDialogComponent>;
|
||||
let component: NodeVersionsDialogComponent;
|
||||
let store: Store<AppStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
CoreModule.forRoot(),
|
||||
AppTestingModule,
|
||||
MatDialogModule,
|
||||
RouterTestingModule.withRoutes([]),
|
||||
TranslateModule.forRoot({
|
||||
loader: { provide: TranslateLoader, useClass: TranslateFakeLoader }
|
||||
})
|
||||
@@ -153,4 +154,14 @@ describe('NodeVersionsDialogComponent', () => {
|
||||
component.handleUpload(nodeEvent);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new UnlockWriteAction(nodeEvent.value));
|
||||
});
|
||||
|
||||
it('should view a previous version of a node', () => {
|
||||
component.isTypeList = false;
|
||||
const versionId = '1.0';
|
||||
const location: ViewNodeExtras = {
|
||||
location: '/'
|
||||
};
|
||||
component.onViewingVersion(versionId);
|
||||
expect(store.dispatch).toHaveBeenCalledWith(new ViewNodeVersionAction(component.node.id, versionId, location));
|
||||
});
|
||||
});
|
||||
|
@@ -23,12 +23,13 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppStore, SnackbarErrorAction, UnlockWriteAction } from '@alfresco/aca-shared/store';
|
||||
import { AppStore, SnackbarErrorAction, UnlockWriteAction, ViewNodeVersionAction } from '@alfresco/aca-shared/store';
|
||||
import { MinimalNodeEntryEntity, Node } from '@alfresco/js-api';
|
||||
import { Component, EventEmitter, Inject, Output, ViewEncapsulation } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
|
||||
import { Store } from '@ngrx/store';
|
||||
import { NodeEntityEvent } from '@alfresco/adf-content-services';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
@Component({
|
||||
templateUrl: './node-versions.dialog.html',
|
||||
@@ -44,7 +45,12 @@ export class NodeVersionsDialogComponent {
|
||||
@Output()
|
||||
refreshEvent: EventEmitter<Node> = new EventEmitter<Node>();
|
||||
|
||||
constructor(@Inject(MAT_DIALOG_DATA) data: any, private store: Store<AppStore>, private dialogRef: MatDialogRef<NodeVersionsDialogComponent>) {
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) data: any,
|
||||
private store: Store<AppStore>,
|
||||
private dialogRef: MatDialogRef<NodeVersionsDialogComponent>,
|
||||
private router: Router
|
||||
) {
|
||||
this.node = data.node;
|
||||
this.file = data.file;
|
||||
this.isTypeList = data.isTypeList !== undefined ? data.isTypeList : true;
|
||||
@@ -68,4 +74,12 @@ export class NodeVersionsDialogComponent {
|
||||
refresh(node: Node) {
|
||||
this.refreshEvent.emit(node);
|
||||
}
|
||||
|
||||
onViewingVersion(versionId: string) {
|
||||
this.store.dispatch(
|
||||
new ViewNodeVersionAction(this.node.id, versionId, {
|
||||
location: this.router.url
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -186,6 +186,7 @@ export class NodeActionsService {
|
||||
|
||||
this.isSitesDestinationAvailable = false;
|
||||
const data: ContentNodeSelectorComponentData = {
|
||||
selectionMode: 'single',
|
||||
title: title,
|
||||
currentFolderId: currentParentFolderId,
|
||||
actionName: action,
|
||||
|
@@ -62,6 +62,7 @@ export class NodeTemplateService {
|
||||
});
|
||||
|
||||
const data: ContentNodeSelectorComponentData = {
|
||||
selectionMode: 'single',
|
||||
title: this.title(config.selectionType),
|
||||
actionName: 'NEXT',
|
||||
dropdownHideMyFiles: true,
|
||||
|
@@ -23,9 +23,9 @@
|
||||
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import { AppStore, DownloadNodesAction, NodeActionTypes, NodeInfo, getAppSelection } from '@alfresco/aca-shared/store';
|
||||
import { AppStore, DownloadNodesAction, NodeActionTypes, NodeInfo, getAppSelection, getCurrentVersion } from '@alfresco/aca-shared/store';
|
||||
import { DownloadZipDialogComponent } from '@alfresco/adf-core';
|
||||
import { MinimalNodeEntity } from '@alfresco/js-api';
|
||||
import { MinimalNodeEntity, Version } from '@alfresco/js-api';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { Actions, Effect, ofType } from '@ngrx/effects';
|
||||
@@ -49,7 +49,16 @@ export class DownloadEffects {
|
||||
.pipe(take(1))
|
||||
.subscribe((selection) => {
|
||||
if (selection && !selection.isEmpty) {
|
||||
this.downloadNodes(selection.nodes);
|
||||
this.store
|
||||
.select(getCurrentVersion)
|
||||
.pipe(take(1))
|
||||
.subscribe((version) => {
|
||||
if (version) {
|
||||
this.downloadFileVersion(selection.nodes[0].entry, version.entry);
|
||||
} else {
|
||||
this.downloadNodes(selection.nodes);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -99,6 +108,12 @@ export class DownloadEffects {
|
||||
}
|
||||
}
|
||||
|
||||
private downloadFileVersion(node: NodeInfo, version: Version) {
|
||||
if (node && version) {
|
||||
this.download(this.contentApi.getVersionContentUrl(node.id, version.id, true), version.name);
|
||||
}
|
||||
}
|
||||
|
||||
private downloadZip(nodes: Array<NodeInfo>) {
|
||||
if (nodes && nodes.length > 0) {
|
||||
const nodeIds = nodes.map((node) => node.id);
|
||||
|
@@ -33,11 +33,13 @@ import {
|
||||
ViewNodeAction,
|
||||
getCurrentFolder,
|
||||
getAppSelection,
|
||||
FullscreenViewerAction
|
||||
FullscreenViewerAction,
|
||||
ViewNodeVersionAction
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { Router, UrlTree, UrlSegmentGroup, PRIMARY_OUTLET, UrlSegment } from '@angular/router';
|
||||
import { Store, createSelector } from '@ngrx/store';
|
||||
import { AppExtensionService } from '@alfresco/aca-shared';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
export const fileToPreview = createSelector(getAppSelection, getCurrentFolder, (selection, folder) => {
|
||||
return {
|
||||
@@ -48,7 +50,13 @@ export const fileToPreview = createSelector(getAppSelection, getCurrentFolder, (
|
||||
|
||||
@Injectable()
|
||||
export class ViewerEffects {
|
||||
constructor(private store: Store<AppStore>, private actions$: Actions, private router: Router, private extensions: AppExtensionService) {}
|
||||
constructor(
|
||||
private store: Store<AppStore>,
|
||||
private actions$: Actions,
|
||||
private router: Router,
|
||||
private extensions: AppExtensionService,
|
||||
private dialog: MatDialog
|
||||
) {}
|
||||
|
||||
@Effect({ dispatch: false })
|
||||
fullscreenViewer$ = this.actions$.pipe(
|
||||
@@ -112,6 +120,31 @@ export class ViewerEffects {
|
||||
})
|
||||
);
|
||||
|
||||
@Effect({ dispatch: false })
|
||||
viewNodeVersion$ = this.actions$.pipe(
|
||||
ofType<ViewNodeVersionAction>(ViewerActionTypes.ViewNodeVersion),
|
||||
map((action) => {
|
||||
this.dialog.closeAll();
|
||||
if (action.viewNodeExtras) {
|
||||
const { location, path } = action.viewNodeExtras;
|
||||
if (location) {
|
||||
const navigation = this.getNavigationCommands(location);
|
||||
this.router.navigate([...navigation, { outlets: { viewer: ['view', action.nodeId, action.versionId] } }], {
|
||||
queryParams: { location }
|
||||
});
|
||||
}
|
||||
|
||||
if (path) {
|
||||
this.router.navigate(['view', { outlets: { viewer: [action.nodeId, action.versionId] } }], {
|
||||
queryParams: { path }
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.router.navigate(['view', { outlets: { viewer: [action.nodeId, action.versionId] } }]);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
private displayPreview(nodeId: string, parentId: string) {
|
||||
if (!nodeId) {
|
||||
return;
|
||||
|
@@ -47,6 +47,7 @@ export const INITIAL_APP_STATE: AppState = {
|
||||
navigation: {
|
||||
currentFolder: null
|
||||
},
|
||||
currentNodeVersion: null,
|
||||
infoDrawerOpened: false,
|
||||
infoDrawerMetadataAspect: '',
|
||||
showFacetFilter: true,
|
||||
|
@@ -38,7 +38,8 @@ import {
|
||||
SetInfoDrawerStateAction,
|
||||
SetInfoDrawerMetadataAspectAction,
|
||||
SetSettingsParameterAction,
|
||||
SetHeaderColorAction
|
||||
SetHeaderColorAction,
|
||||
SetCurrentNodeVersionAction
|
||||
} from '@alfresco/aca-shared/store';
|
||||
import { INITIAL_APP_STATE } from '../initial-state';
|
||||
|
||||
@@ -67,6 +68,9 @@ export function appReducer(state: AppState = INITIAL_APP_STATE, action: Action):
|
||||
case AppActionTypes.SetCurrentFolder:
|
||||
newState = updateCurrentFolder(state, action as SetCurrentFolderAction);
|
||||
break;
|
||||
case AppActionTypes.SetCurrentVersion:
|
||||
newState = updateCurrentNodeVersion(state, action as SetCurrentNodeVersionAction);
|
||||
break;
|
||||
case AppActionTypes.SetCurrentUrl:
|
||||
newState = updateCurrentUrl(state, action as SetCurrentUrlAction);
|
||||
break;
|
||||
@@ -155,6 +159,12 @@ function updateCurrentFolder(state: AppState, action: SetCurrentFolderAction) {
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateCurrentNodeVersion(state: AppState, action: SetCurrentNodeVersionAction) {
|
||||
const newState = { ...state };
|
||||
newState.currentNodeVersion = action.payload;
|
||||
return newState;
|
||||
}
|
||||
|
||||
function updateCurrentUrl(state: AppState, action: SetCurrentUrlAction) {
|
||||
const newState = { ...state };
|
||||
newState.navigation.url = action.payload;
|
||||
|
Reference in New Issue
Block a user