added functionality to view a previous version (#5913)

This commit is contained in:
Urse Daniel
2020-07-27 11:29:29 +03:00
committed by GitHub
parent 8d43155c14
commit 7c09fb1fb9
26 changed files with 337 additions and 111 deletions

View File

@@ -74,6 +74,19 @@ export const appRoutes: Routes = [
} }
] ]
}, },
{
path: 'files/:nodeId/:versionId/view',
component: AppComponent,
canActivate: [AuthGuardEcm],
canActivateChild: [AuthGuardEcm],
outlet: 'overlay',
children: [
{
path: '',
loadChildren: () => import('./components/file-view/file-view.module').then(m => m.FileViewModule)
}
]
},
{ {
path: 'preview/blob', path: 'preview/blob',
component: AppComponent, component: AppComponent,

View File

@@ -108,7 +108,8 @@
<mat-card> <mat-card>
<mat-card-content> <mat-card-content>
<adf-version-manager [node]="node" <adf-version-manager [node]="node"
(uploadError)="onUploadError($event)"> (uploadError)="onUploadError($event)"
(viewVersion)="onViewVersion($event)">
</adf-version-manager> </adf-version-manager>
</mat-card-content> </mat-card-content>
</mat-card> </mat-card>
@@ -318,6 +319,7 @@
<adf-viewer <adf-viewer
[blobFile]="content" [blobFile]="content"
[nodeId]="nodeId" [nodeId]="nodeId"
[versionId]="versionId"
[showRightSidebar]="showRightSidebar" [showRightSidebar]="showRightSidebar"
[showLeftSidebar]="showLeftSidebar" [showLeftSidebar]="showLeftSidebar"
[allowGoBack]="allowGoBack" [allowGoBack]="allowGoBack"

View File

@@ -30,6 +30,7 @@ import { PreviewService } from '../../services/preview.service';
export class FileViewComponent implements OnInit { export class FileViewComponent implements OnInit {
nodeId: string = null; nodeId: string = null;
versionId: string = null;
displayEmptyMetadata = false; displayEmptyMetadata = false;
expanded: boolean; expanded: boolean;
multi = false; multi = false;
@@ -72,6 +73,7 @@ export class FileViewComponent implements OnInit {
ngOnInit() { ngOnInit() {
this.route.params.subscribe((params) => { this.route.params.subscribe((params) => {
const id = params.nodeId; const id = params.nodeId;
this.versionId = params.versionId;
if (id) { if (id) {
this.nodeApiService.getNode(id).subscribe( this.nodeApiService.getNode(id).subscribe(
(node) => { (node) => {
@@ -92,6 +94,10 @@ export class FileViewComponent implements OnInit {
}); });
} }
onViewVersion(versionId: string) {
this.preview.showResource(this.nodeId, versionId);
}
onViewerVisibilityChanged() { onViewerVisibilityChanged() {
const primaryUrl = this.router.parseUrl(this.router.url).root.children[PRIMARY_OUTLET].toString(); const primaryUrl = this.router.parseUrl(this.router.url).root.children[PRIMARY_OUTLET].toString();
this.router.navigateByUrl(primaryUrl); this.router.navigateByUrl(primaryUrl);

View File

@@ -32,7 +32,9 @@
[newFileVersion]="newFileVersion" [newFileVersion]="newFileVersion"
[allowDownload]="allowDownload" [allowDownload]="allowDownload"
[showComments]="showComments" [showComments]="showComments"
(uploadError)="uploadError($event)"></adf-version-manager> (uploadError)="uploadError($event)"
(viewVersion)="onViewVersion($event)"
></adf-version-manager>
</section> </section>
<section mat-dialog-content *ngIf="readOnly"> <section mat-dialog-content *ngIf="readOnly">
<adf-version-list [node]="contentEntry" [showActions]="false"></adf-version-list> <adf-version-list [node]="contentEntry" [showActions]="false"></adf-version-list>

View File

@@ -19,6 +19,7 @@ import { Component, Inject, ViewEncapsulation } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { MinimalNodeEntryEntity } from '@alfresco/js-api'; import { MinimalNodeEntryEntity } from '@alfresco/js-api';
import { MatSnackBar } from '@angular/material/snack-bar'; import { MatSnackBar } from '@angular/material/snack-bar';
import { PreviewService } from '../../services/preview.service';
@Component({ @Component({
templateUrl: './version-manager-dialog-adapter.component.html', templateUrl: './version-manager-dialog-adapter.component.html',
@@ -34,7 +35,8 @@ export class VersionManagerDialogAdapterComponent {
readOnly = false; readOnly = false;
showVersionComparison = false; showVersionComparison = false;
constructor(@Inject(MAT_DIALOG_DATA) data: any, constructor(private previewService: PreviewService,
@Inject(MAT_DIALOG_DATA) data: any,
private snackBar: MatSnackBar, private snackBar: MatSnackBar,
private containingDialog?: MatDialogRef<VersionManagerDialogAdapterComponent>) { private containingDialog?: MatDialogRef<VersionManagerDialogAdapterComponent>) {
this.contentEntry = data.contentEntry; this.contentEntry = data.contentEntry;
@@ -51,6 +53,11 @@ export class VersionManagerDialogAdapterComponent {
this.containingDialog.close(); this.containingDialog.close();
} }
onViewVersion(versionId: string) {
this.previewService.showResource(this.contentEntry.id, versionId);
this.close();
}
hideVersionComparison(isCancelled: boolean | Node) { hideVersionComparison(isCancelled: boolean | Node) {
if (isCancelled) { if (isCancelled) {
this.showVersionComparison = false; this.showVersionComparison = false;

View File

@@ -26,9 +26,13 @@ export class PreviewService {
constructor(private router: Router) {} constructor(private router: Router) {}
showResource(resourceId): void { showResource(resourceId, versionId?): void {
if (versionId) {
this.router.navigate([{outlets: {overlay: ['files', resourceId, versionId, 'view']}}]);
} else {
this.router.navigate([{outlets: {overlay: ['files', resourceId, 'view']}}]); this.router.navigate([{outlets: {overlay: ['files', resourceId, 'view']}}]);
} }
}
showBlob(name: string, content: Blob): void { showBlob(name: string, content: Blob): void {
this.name = name; this.name = name;

View File

@@ -22,6 +22,7 @@ Displays the version history of a node in a [Version Manager component](version-
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| allowDownload | `boolean` | true | Enable/disable downloading a version of the current node. | | allowDownload | `boolean` | true | Enable/disable downloading a version of the current node. |
| allowViewVersions | `boolean` | true | Enable/disable viewing a version of the current node. |
| node | [`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) | | The target node. | | node | [`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) | | The target node. |
| showActions | `boolean` | true | Toggles showing/hiding of version actions | | showActions | `boolean` | true | Toggles showing/hiding of version actions |
| showComments | `boolean` | true | Toggles showing/hiding of comments | | showComments | `boolean` | true | Toggles showing/hiding of comments |
@@ -32,6 +33,7 @@ Displays the version history of a node in a [Version Manager component](version-
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| deleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`>` | Emitted when a version is deleted | | deleted | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`>` | Emitted when a version is deleted |
| restored | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`>` | Emitted when a version is restored | | restored | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`>` | Emitted when a version is restored |
| viewVersion | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when viewing a version |
## Details ## Details

View File

@@ -42,6 +42,7 @@ Displays the version history of a node with the ability to upload a new version.
| uploadCancel | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<boolean>` | Emitted when an cancelling during upload. | | uploadCancel | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<boolean>` | Emitted when an cancelling during upload. |
| uploadError | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`>` | Emitted when an error occurs during upload. | | uploadError | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`>` | Emitted when an error occurs during upload. |
| uploadSuccess | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`>` | Emitted when a file is uploaded successfully. | | uploadSuccess | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<`[`Node`](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md)`>` | Emitted when a file is uploaded successfully. |
| viewVersion | [`EventEmitter`](https://angular.io/api/core/EventEmitter)`<string>` | Emitted when viewing a version. |
## Details ## Details

View File

@@ -103,6 +103,7 @@ See the [Custom layout](#custom-layout) section for full details of all availabl
| thumbnailsTemplate | [`TemplateRef`](https://angular.io/api/core/TemplateRef)`<any>` | null | The template for the pdf thumbnails. | | thumbnailsTemplate | [`TemplateRef`](https://angular.io/api/core/TemplateRef)`<any>` | null | The template for the pdf thumbnails. |
| urlFile | `string` | "" | If you want to load an external file that does not come from ACS you can use this URL to specify where to load the file from. | | urlFile | `string` | "" | If you want to load an external file that does not come from ACS you can use this URL to specify where to load the file from. |
| urlFileViewer | `string` | null | Viewer to use with the `urlFile` address (`pdf`, `image`, `media`, `text`). Used when `urlFile` has no filename and extension. | | urlFileViewer | `string` | null | Viewer to use with the `urlFile` address (`pdf`, `image`, `media`, `text`). Used when `urlFile` has no filename and extension. |
| versionId | `string` | null | Version Id of the file to load. |
### Events ### Events

View File

@@ -14,7 +14,8 @@ Allows folders and/or files to be downloaded, with multiple nodes packed as a '.
```html ```html
<adf-toolbar> <adf-toolbar>
<button mat-icon-button <button mat-icon-button
[adfNodeDownload]="documentList.selection"> [adfNodeDownload]="documentList.selection"
[version]="version">
<mat-icon>get_app</mat-icon> <mat-icon>get_app</mat-icon>
</button> </button>
</adf-toolbar> </adf-toolbar>
@@ -31,3 +32,4 @@ Allows folders and/or files to be downloaded, with multiple nodes packed as a '.
| Name | Type | Default value | Description | | Name | Type | Default value | Description |
| ---- | ---- | ------------- | ----------- | | ---- | ---- | ------------- | ----------- |
| nodes | [`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)` \| `[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`[]` | | Nodes to download. | | nodes | [`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)` \| `[`NodeEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/NodeEntry.md)`[]` | | Nodes to download. |
| version | [`VersionEntry`](https://github.com/Alfresco/alfresco-js-api/blob/master/src/alfresco-core-rest-api/docs/VersionEntry.md) | | [Node's](https://github.com/Alfresco/alfresco-js-api/blob/development/src/api/content-rest-api/docs/Node.md) version to download. |

View File

@@ -23,7 +23,8 @@ import {
LoginPage, LoginPage,
UploadActions, UploadActions,
UserModel, UserModel,
UsersActions UsersActions,
ViewerPage
} from '@alfresco/adf-testing'; } from '@alfresco/adf-testing';
import { browser, by, element } from 'protractor'; import { browser, by, element } from 'protractor';
import { FileModel } from '../../models/ACS/file.model'; import { FileModel } from '../../models/ACS/file.model';
@@ -41,6 +42,7 @@ describe('Version component actions', () => {
const uploadDialog = new UploadDialogPage(); const uploadDialog = new UploadDialogPage();
const apiService = new ApiService(); const apiService = new ApiService();
const usersActions = new UsersActions(apiService); const usersActions = new UsersActions(apiService);
const viewerPage = new ViewerPage();
let acsUser: UserModel; let acsUser: UserModel;
@@ -151,6 +153,18 @@ describe('Version component actions', () => {
await contentServicesPage.checkContentIsDisplayed(txtFileModel.name); await contentServicesPage.checkContentIsDisplayed(txtFileModel.name);
}); });
it('[C362240] Should be possible to view a previous document version', async () => {
await contentServicesPage.versionManagerContent(fileModelVersionTwo.name);
await versionManagePage.viewFileVersion('1.0');
await viewerPage.expectUrlToContain('1.0');
});
it('[C362241] Should be possible to download a previous document version', async () => {
await viewerPage.clickDownloadButton();
await FileBrowserUtil.isFileDownloaded(fileModelVersionTwo.name);
await viewerPage.clickCloseButton();
});
it('[C307033] Should be possible to cancel the upload of a new version', async () => { it('[C307033] Should be possible to cancel the upload of a new version', async () => {
await browser.refresh(); await browser.refresh();
await contentServicesPage.versionManagerContent(txtFileModel.name); await contentServicesPage.versionManagerContent(txtFileModel.name);

View File

@@ -17,7 +17,7 @@
import * as path from 'path'; import * as path from 'path';
import { BrowserActions, BrowserVisibility, TogglePage } from '@alfresco/adf-testing'; import { BrowserActions, BrowserVisibility, TogglePage } from '@alfresco/adf-testing';
import { browser, by, element } from 'protractor'; import { browser, by, element, ElementFinder } from 'protractor';
export class VersionManagePage { export class VersionManagePage {
@@ -173,6 +173,12 @@ export class VersionManagePage {
await BrowserActions.click(restoreButton); await BrowserActions.click(restoreButton);
} }
async viewFileVersion(version): Promise<void> {
await this.clickActionButton(version);
const viewButton: ElementFinder = element(by.id(`adf-version-list-action-view-${version}`));
await BrowserActions.click(viewButton);
}
async checkActionsArePresent(version: string): Promise<void> { async checkActionsArePresent(version: string): Promise<void> {
await BrowserVisibility.waitUntilElementIsVisible(element(by.id(`adf-version-list-action-download-${version}`))); await BrowserVisibility.waitUntilElementIsVisible(element(by.id(`adf-version-list-action-download-${version}`)));
await BrowserVisibility.waitUntilElementIsVisible(element(by.id(`adf-version-list-action-delete-${version}`))); await BrowserVisibility.waitUntilElementIsVisible(element(by.id(`adf-version-list-action-delete-${version}`)));

View File

@@ -16,10 +16,11 @@
*/ */
import { browser } from 'protractor'; import { browser } from 'protractor';
import { ApiService, LoginPage, UploadActions, UserModel, UsersActions, ViewerPage } from '@alfresco/adf-testing'; import { ApiService, BrowserActions, FileBrowserUtil, LoginPage, UploadActions, UserModel, UsersActions, ViewerPage } from '@alfresco/adf-testing';
import { ContentServicesPage } from '../../core/pages/content-services.page'; import { ContentServicesPage } from '../../core/pages/content-services.page';
import { FileModel } from '../../models/ACS/file.model'; import { FileModel } from '../../models/ACS/file.model';
import { NavigationBarPage } from '../../core/pages/navigation-bar.page'; import { NavigationBarPage } from '../../core/pages/navigation-bar.page';
import { VersionManagePage } from '../pages/version-manager.page';
describe('Viewer', () => { describe('Viewer', () => {
@@ -32,6 +33,7 @@ describe('Viewer', () => {
const uploadActions = new UploadActions(apiService); const uploadActions = new UploadActions(apiService);
const usersActions = new UsersActions(apiService); const usersActions = new UsersActions(apiService);
const versionManagePage = new VersionManagePage();
const acsUser = new UserModel(); const acsUser = new UserModel();
let txtFileUploaded; let txtFileUploaded;
@@ -40,6 +42,11 @@ describe('Viewer', () => {
'location': browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_path 'location': browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_path
}); });
const fileModelVersionTwo = new FileModel({
'name': browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_name,
'location': browser.params.resources.Files.ADF_DOCUMENTS.TXT.file_location
});
beforeAll(async () => { beforeAll(async () => {
await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password); await apiService.getInstance().login(browser.params.testConfig.admin.email, browser.params.testConfig.admin.password);
await usersActions.createUser(acsUser); await usersActions.createUser(acsUser);
@@ -84,4 +91,22 @@ describe('Viewer', () => {
await viewerPage.clickOnTab('Versions'); await viewerPage.clickOnTab('Versions');
await viewerPage.checkTabIsActive('Versions'); await viewerPage.checkTabIsActive('Versions');
}); });
it('[C362242] Should the Viewer be able to view a previous version of a file', async () => {
await viewerPage.clickCloseButton();
await contentServicesPage.versionManagerContent(txtFileInfo.name);
await BrowserActions.click(versionManagePage.showNewVersionButton);
await versionManagePage.uploadNewVersionFile(fileModelVersionTwo.location);
await versionManagePage.closeVersionDialog();
await contentServicesPage.doubleClickRow(txtFileUploaded.entry.name);
await viewerPage.clickInfoButton();
await viewerPage.clickOnTab('Versions');
await versionManagePage.viewFileVersion('1.0');
await viewerPage.expectUrlToContain('1.0');
});
it('[C362265] Should the Viewer be able to download a previous version of a file', async () => {
await viewerPage.clickDownloadButton();
await FileBrowserUtil.isFileDownloaded(txtFileInfo.name);
});
}); });

View File

@@ -4,6 +4,7 @@
"RESTORE": "Restore", "RESTORE": "Restore",
"DELETE": "Delete", "DELETE": "Delete",
"DOWNLOAD": "Download", "DOWNLOAD": "Download",
"VIEW": "View",
"UPLOAD": { "UPLOAD": {
"TITLE": "Upload new version", "TITLE": "Upload new version",
"TOOLTIP": "Restriction: you must upload a file with the same name to create a new version of it", "TOOLTIP": "Restriction: you must upload a file with the same name to create a new version of it",

View File

@@ -12,6 +12,14 @@
<div *ngIf="showActions"> <div *ngIf="showActions">
<mat-menu [id]="'adf-version-list-action-menu-'+version.entry.id" <mat-menu [id]="'adf-version-list-action-menu-'+version.entry.id"
#versionMenu="matMenu" yPosition="below" xPosition="before"> #versionMenu="matMenu" yPosition="below" xPosition="before">
<ng-container *adf-acs-version="'7'">
<button *ngIf="allowViewVersions"
[id]="'adf-version-list-action-view-'+version.entry.id"
mat-menu-item
(click)="onViewVersion(version.entry.id)">
{{ 'ADF_VERSION_LIST.ACTIONS.VIEW' | translate }}
</button>
</ng-container>
<button <button
[id]="'adf-version-list-action-restore-'+version.entry.id" [id]="'adf-version-list-action-restore-'+version.entry.id"
[disabled]="!canUpdate()" [disabled]="!canUpdate()"

View File

@@ -228,6 +228,13 @@ describe('VersionListComponent', () => {
expect(alfrescoApiService.contentApi.getContentUrl).toHaveBeenCalledWith(nodeId, true); expect(alfrescoApiService.contentApi.getContentUrl).toHaveBeenCalledWith(nodeId, true);
}); });
it('should be able to view a version', () => {
spyOn(component.viewVersion, 'emit');
component.onViewVersion('1.0');
fixture.detectChanges();
expect(component.viewVersion.emit).toHaveBeenCalledWith('1.0');
});
it('should NOT be able to download a version if configured so', () => { it('should NOT be able to download a version if configured so', () => {
const versionEntry = { const versionEntry = {
entry: { entry: {

View File

@@ -48,6 +48,10 @@ export class VersionListComponent implements OnChanges {
@Input() @Input()
allowDownload = true; allowDownload = true;
/** Enable/disable viewing a version of the current node. */
@Input()
allowViewVersions = true;
/** Toggles showing/hiding of version actions */ /** Toggles showing/hiding of version actions */
@Input() @Input()
showActions = true; showActions = true;
@@ -60,6 +64,10 @@ export class VersionListComponent implements OnChanges {
@Output() @Output()
deleted: EventEmitter<Node> = new EventEmitter<Node>(); deleted: EventEmitter<Node> = new EventEmitter<Node>();
/** Emitted when viewing a version */
@Output()
viewVersion: EventEmitter<string> = new EventEmitter<string>();
constructor(private alfrescoApi: AlfrescoApiService, constructor(private alfrescoApi: AlfrescoApiService,
private contentService: ContentService, private contentService: ContentService,
private dialog: MatDialog) { private dialog: MatDialog) {
@@ -92,6 +100,10 @@ export class VersionListComponent implements OnChanges {
} }
} }
onViewVersion(versionId) {
this.viewVersion.emit(versionId);
}
loadVersionHistory() { loadVersionHistory() {
this.isLoading = true; this.isLoading = true;
this.versionsApi.listVersionHistory(this.node.id).then((versionPaging: VersionPaging) => { this.versionsApi.listVersionHistory(this.node.id).then((versionPaging: VersionPaging) => {

View File

@@ -38,7 +38,8 @@
[allowDownload]="allowDownload" [allowDownload]="allowDownload"
[showComments]="showComments" [showComments]="showComments"
(deleted)="refresh($event)" (deleted)="refresh($event)"
(restored)="refresh($event)"> (restored)="refresh($event)"
(viewVersion)="onViewVersion($event)">
</adf-version-list> </adf-version-list>
</div> </div>
</div> </div>

View File

@@ -73,6 +73,13 @@ describe('VersionManagerComponent', () => {
expect(component.uploadState).toBe('open'); expect(component.uploadState).toBe('open');
}); });
it('should be able to view a version', () => {
spyOn(component.viewVersion, 'emit');
component.onViewVersion('1.0');
fixture.detectChanges();
expect(component.viewVersion.emit).toHaveBeenCalledWith('1.0');
});
it('should display comments for versions when not configured otherwise', async(() => { it('should display comments for versions when not configured otherwise', async(() => {
fixture.detectChanges(); fixture.detectChanges();
fixture.whenStable().then(() => { fixture.whenStable().then(() => {

View File

@@ -75,6 +75,10 @@ export class VersionManagerComponent implements OnInit {
@Output() @Output()
uploadCancel: EventEmitter<boolean> = new EventEmitter<boolean>(); uploadCancel: EventEmitter<boolean> = new EventEmitter<boolean>();
/** Emitted when viewing a version. */
@Output()
viewVersion: EventEmitter<string> = new EventEmitter<string>();
@ViewChild('versionList', { static: true }) @ViewChild('versionList', { static: true })
versionListComponent: VersionListComponent; versionListComponent: VersionListComponent;
@@ -117,6 +121,10 @@ export class VersionManagerComponent implements OnInit {
this.uploadCancel.emit(true); this.uploadCancel.emit(true);
} }
onViewVersion(versionId: string) {
this.viewVersion.emit(versionId);
}
toggleNewVersion() { toggleNewVersion() {
this.uploadState = this.uploadState === 'open' ? 'close' : 'open'; this.uploadState = this.uploadState === 'open' ? 'close' : 'open';
} }

View File

@@ -26,10 +26,11 @@ import { CoreTestingModule } from '../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core'; import { TranslateModule } from '@ngx-translate/core';
@Component({ @Component({
template: '<div [adfNodeDownload]="selection"></div>' template: '<div [adfNodeDownload]="selection" [version]="version"></div>'
}) })
class TestComponent { class TestComponent {
selection; selection;
version;
} }
describe('NodeDownloadDirective', () => { describe('NodeDownloadDirective', () => {
@@ -91,6 +92,22 @@ describe('NodeDownloadDirective', () => {
expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.id, true); expect(contentService.getContentUrl).toHaveBeenCalledWith(node.entry.id, true);
}); });
it('should download selected node version as file', () => {
component.version = {
entry: {
id: '1.0'
}
};
spyOn(contentService, 'getVersionContentUrl');
const node = {entry: {id: 'node-id', isFile: true}};
component.selection = [node];
fixture.detectChanges();
element.triggerEventHandler('click', null);
expect(contentService.getVersionContentUrl).toHaveBeenCalledWith(node.entry.id, '1.0', true);
});
it('should download selected shared node as file', () => { it('should download selected shared node as file', () => {
spyOn(contentService, 'getContentUrl'); spyOn(contentService, 'getContentUrl');
const node = { entry: { nodeId: 'shared-node-id', isFile: true } }; const node = { entry: { nodeId: 'shared-node-id', isFile: true } };

View File

@@ -19,7 +19,7 @@ import { Directive, Input, HostListener } from '@angular/core';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { AlfrescoApiService } from '../services/alfresco-api.service'; import { AlfrescoApiService } from '../services/alfresco-api.service';
import { DownloadZipDialogComponent } from '../dialogs/download-zip/download-zip.dialog'; import { DownloadZipDialogComponent } from '../dialogs/download-zip/download-zip.dialog';
import { NodeEntry } from '@alfresco/js-api'; import { NodeEntry, VersionEntry } from '@alfresco/js-api';
import { DownloadService } from '../services/download.service'; import { DownloadService } from '../services/download.service';
/** /**
@@ -35,6 +35,10 @@ export class NodeDownloadDirective {
@Input('adfNodeDownload') @Input('adfNodeDownload')
nodes: NodeEntry | NodeEntry[]; nodes: NodeEntry | NodeEntry[];
/** Node's version to download. */
@Input()
version: VersionEntry;
@HostListener('click') @HostListener('click')
onClick() { onClick() {
this.downloadNodes(this.nodes); this.downloadNodes(this.nodes);
@@ -101,8 +105,14 @@ export class NodeDownloadDirective {
// nodeId for Shared node // nodeId for Shared node
const id = (<any> node.entry).nodeId || node.entry.id; const id = (<any> node.entry).nodeId || node.entry.id;
const url = contentApi.getContentUrl(id, true); let url, fileName;
const fileName = node.entry.name; if (this.version) {
url = contentApi.getVersionContentUrl(id, this.version.entry.id, true);
fileName = this.version.entry.name;
} else {
url = contentApi.getContentUrl(id, true);
fileName = node.entry.name;
}
this.downloadService.downloadUrl(url, fileName); this.downloadService.downloadUrl(url, fileName);
} }

View File

@@ -82,7 +82,8 @@
[attr.aria-label]="'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate" [attr.aria-label]="'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}" title="{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}"
data-automation-id="adf-toolbar-download" data-automation-id="adf-toolbar-download"
[adfNodeDownload]="nodeEntry"> [adfNodeDownload]="nodeEntry"
[version]="versionEntry">
<mat-icon>file_download</mat-icon> <mat-icon>file_download</mat-icon>
</button> </button>

View File

@@ -20,7 +20,7 @@ import {
Input, OnChanges, Output, TemplateRef, Input, OnChanges, Output, TemplateRef,
ViewEncapsulation, OnInit, OnDestroy ViewEncapsulation, OnInit, OnDestroy
} from '@angular/core'; } from '@angular/core';
import { RenditionPaging, SharedLinkEntry, Node, RenditionEntry, NodeEntry } from '@alfresco/js-api'; import { SharedLinkEntry, Node, Version, RenditionEntry, NodeEntry, VersionEntry } from '@alfresco/js-api';
import { BaseEvent } from '../../events'; import { BaseEvent } from '../../events';
import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service'; import { LogService } from '../../services/log.service';
@@ -74,6 +74,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@Input() @Input()
nodeId: string = null; nodeId: string = null;
/** Version Id of the file to load. */
@Input()
versionId: string = null;
/** Shared link id (to display shared file). */ /** Shared link id (to display shared file). */
@Input() @Input()
sharedLinkId: string = null; sharedLinkId: string = null;
@@ -206,6 +210,7 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
viewerType = 'unknown'; viewerType = 'unknown';
isLoading = false; isLoading = false;
nodeEntry: NodeEntry; nodeEntry: NodeEntry;
versionEntry: VersionEntry;
extensionTemplates: { template: TemplateRef<any>, isVisible: boolean }[] = []; extensionTemplates: { template: TemplateRef<any>, isVisible: boolean }[] = [];
externalExtensions: string[] = []; externalExtensions: string[] = [];
@@ -255,6 +260,17 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
).subscribe((node) => this.onNodeUpdated(node)) ).subscribe((node) => this.onNodeUpdated(node))
); );
this.subscriptions.push(
this.viewUtils.viewerTypeChange.subscribe((type: string) => {
this.viewerType = type;
})
);
this.subscriptions.push(
this.viewUtils.urlFileContentChange.subscribe((content: string) => {
this.urlFileContent = content;
})
);
this.loadExtensions(); this.loadExtensions();
} }
@@ -298,9 +314,20 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.apiService.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] }).then( this.apiService.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] }).then(
(node: NodeEntry) => { (node: NodeEntry) => {
this.nodeEntry = node; this.nodeEntry = node;
if (this.versionId) {
this.apiService.versionsApi.getVersion(this.nodeId, this.versionId).then(
(version: VersionEntry) => {
this.versionEntry = version;
this.setUpNodeFile(node.entry, version.entry).then(() => {
this.isLoading = false;
});
}
);
} else {
this.setUpNodeFile(node.entry).then(() => { this.setUpNodeFile(node.entry).then(() => {
this.isLoading = false; this.isLoading = false;
}); });
}
}, },
() => { () => {
this.isLoading = false; this.isLoading = false;
@@ -353,21 +380,23 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.scrollTop(); this.scrollTop();
} }
private async setUpNodeFile(data: Node) { private async setUpNodeFile(nodeData: Node, versionData?: Version) {
let setupNode; let setupNode;
if (data.content) { if (versionData && versionData.content) {
this.mimeType = data.content.mimeType; this.mimeType = versionData.content.mimeType;
} else if (nodeData.content) {
this.mimeType = nodeData.content.mimeType;
} }
this.fileTitle = this.getDisplayName(data.name); this.fileTitle = this.getDisplayName(nodeData.name);
this.urlFileContent = this.apiService.contentApi.getContentUrl(data.id); this.urlFileContent = versionData ? this.apiService.contentApi.getVersionContentUrl(this.nodeId, versionData.id) :
this.urlFileContent = this.cacheBusterNumber ? this.urlFileContent + '&' + this.cacheBusterNumber : this.urlFileContent; this.urlFileContent = this.cacheBusterNumber ? this.urlFileContent + '&' + this.cacheBusterNumber : this.urlFileContent;
this.extension = this.getFileExtension(data.name); this.extension = this.getFileExtension(versionData ? versionData.name : nodeData.name);
this.fileName = data.name; this.fileName = versionData ? versionData.name : nodeData.name;
this.viewerType = this.getViewerTypeByExtension(this.extension); this.viewerType = this.getViewerTypeByExtension(this.extension);
if (this.viewerType === 'unknown') { if (this.viewerType === 'unknown') {
@@ -375,12 +404,12 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
} }
if (this.viewerType === 'unknown') { if (this.viewerType === 'unknown') {
setupNode = this.displayNodeRendition(data.id); setupNode = this.viewUtils.displayNodeRendition(nodeData.id, versionData ? versionData.id : undefined);
} }
this.extensionChange.emit(this.extension); this.extensionChange.emit(this.extension);
this.sidebarRightTemplateContext.node = data; this.sidebarRightTemplateContext.node = nodeData;
this.sidebarLeftTemplateContext.node = data; this.sidebarLeftTemplateContext.node = nodeData;
this.scrollTop(); this.scrollTop();
return setupNode; return setupNode;
@@ -603,25 +632,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
} }
} }
private async displayNodeRendition(nodeId: string) {
try {
const rendition = await this.resolveRendition(nodeId, 'pdf');
if (rendition) {
const renditionId = rendition.entry.id;
if (renditionId === 'pdf') {
this.viewerType = 'pdf';
} else if (renditionId === 'imgpreview') {
this.viewerType = 'image';
}
this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, renditionId);
}
} catch (err) {
this.logService.error(err);
}
}
private async displaySharedLinkRendition(sharedId: string) { private async displaySharedLinkRendition(sharedId: string) {
try { try {
const rendition: RenditionEntry = await this.apiService.renditionsApi.getSharedLinkRendition(sharedId, 'pdf'); const rendition: RenditionEntry = await this.apiService.renditionsApi.getSharedLinkRendition(sharedId, 'pdf');
@@ -643,69 +653,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
} }
} }
private async resolveRendition(nodeId: string, renditionId: string): Promise<RenditionEntry> {
renditionId = renditionId.toLowerCase();
const supportedRendition: RenditionPaging = await this.apiService.renditionsApi.getRenditions(nodeId);
let rendition: RenditionEntry = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
if (!rendition) {
renditionId = 'imgpreview';
rendition = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
}
if (rendition) {
const status: string = rendition.entry.status.toString();
if (status === 'NOT_CREATED') {
try {
await this.apiService.renditionsApi.createRendition(nodeId, { id: renditionId }).then(() => {
this.viewerType = 'in_creation';
});
rendition = await this.waitRendition(nodeId, renditionId);
} catch (err) {
this.logService.error(err);
}
}
}
return rendition;
}
private async waitRendition(nodeId: string, renditionId: string): Promise<RenditionEntry> {
let currentRetry: number = 0;
return new Promise<RenditionEntry>((resolve, reject) => {
const intervalId = setInterval(() => {
currentRetry++;
if (this.maxRetries >= currentRetry) {
this.apiService.renditionsApi.getRendition(nodeId, renditionId).then((rendition: RenditionEntry) => {
const status: string = rendition.entry.status.toString();
if (status === 'CREATED') {
if (renditionId === 'pdf') {
this.viewerType = 'pdf';
} else if (renditionId === 'imgpreview') {
this.viewerType = 'image';
}
this.urlFileContent = this.apiService.contentApi.getRenditionUrl(nodeId, renditionId);
clearInterval(intervalId);
return resolve(rendition);
}
}, () => {
this.viewerType = 'error_in_creation';
return reject();
});
} else {
this.isLoading = false;
this.viewerType = 'error_in_creation';
clearInterval(intervalId);
}
}, this.TRY_TIMEOUT);
});
}
checkExtensions(extensionAllowed) { checkExtensions(extensionAllowed) {
if (typeof extensionAllowed === 'string') { if (typeof extensionAllowed === 'string') {
return this.extension.toLowerCase() === extensionAllowed.toLowerCase(); return this.extension.toLowerCase() === extensionAllowed.toLowerCase();

View File

@@ -19,6 +19,7 @@ import { Injectable } from '@angular/core';
import { RenditionEntry, RenditionPaging } from '@alfresco/js-api'; import { RenditionEntry, RenditionPaging } from '@alfresco/js-api';
import { AlfrescoApiService } from '../../services/alfresco-api.service'; import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service'; import { LogService } from '../../services/log.service';
import { Subject } from 'rxjs';
@Injectable({ @Injectable({
providedIn: 'root' providedIn: 'root'
@@ -54,6 +55,17 @@ export class ViewUtilService {
media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/ogg', 'audio/wav'] media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/ogg', 'audio/wav']
}; };
/**
* Timeout used for setInterval.
*/
TRY_TIMEOUT: number = 10000;
/**
* Subscribers needed for ViewerComponent to update the viewerType and urlFileContent.
*/
viewerTypeChange: Subject<string> = new Subject<string>();
urlFileContentChange: Subject<string> = new Subject<string>();
constructor(private apiService: AlfrescoApiService, constructor(private apiService: AlfrescoApiService,
private logService: LogService) { private logService: LogService) {
} }
@@ -167,4 +179,110 @@ export class ViewUtilService {
return new Promise<RenditionEntry>((resolve) => resolve(rendition)); return new Promise<RenditionEntry>((resolve) => resolve(rendition));
} }
async displayNodeRendition(nodeId: string, versionId?: string) {
try {
const rendition = await this.resolveNodeRendition(nodeId, 'pdf', versionId);
if (rendition) {
const renditionId = rendition.entry.id;
if (renditionId === 'pdf') {
this.viewerTypeChange.next('pdf');
} else if (renditionId === 'imgpreview') {
this.viewerTypeChange.next('image');
}
const urlFileContent = versionId ? this.apiService.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
this.apiService.contentApi.getRenditionUrl(nodeId, renditionId);
this.urlFileContentChange.next(urlFileContent);
}
} catch (err) {
this.logService.error(err);
}
}
private async resolveNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<RenditionEntry> {
renditionId = renditionId.toLowerCase();
const supportedRendition: RenditionPaging = versionId ? await this.apiService.versionsApi.listVersionRenditions(nodeId, versionId) :
await this.apiService.renditionsApi.getRenditions(nodeId);
let rendition: RenditionEntry = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
if (!rendition) {
renditionId = 'imgpreview';
rendition = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
}
if (rendition) {
const status: string = rendition.entry.status.toString();
if (status === 'NOT_CREATED') {
try {
if (versionId) {
await this.apiService.versionsApi.createVersionRendition(nodeId, versionId, {id: renditionId}).then(() => {
this.viewerTypeChange.next('in_creation');
});
} else {
await this.apiService.renditionsApi.createRendition(nodeId, {id: renditionId}).then(() => {
this.viewerTypeChange.next('in_creation');
});
}
rendition = await this.waitNodeRendition(nodeId, renditionId, versionId);
} catch (err) {
this.logService.error(err);
}
}
}
return rendition;
}
private async waitNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<RenditionEntry> {
let currentRetry: number = 0;
return new Promise<RenditionEntry>((resolve, reject) => {
const intervalId = setInterval(() => {
currentRetry++;
if (this.maxRetries >= currentRetry) {
if (!versionId) {
this.apiService.versionsApi.getVersionRendition(nodeId, versionId, renditionId).then((rendition: RenditionEntry) => {
this.handleNodeRendition(rendition, nodeId, renditionId, versionId);
clearInterval(intervalId);
return resolve(rendition);
}, () => {
this.viewerTypeChange.next('error_in_creation');
return reject();
});
} else {
this.apiService.renditionsApi.getRendition(nodeId, renditionId).then((rendition: RenditionEntry) => {
this.handleNodeRendition(rendition, nodeId, renditionId);
clearInterval(intervalId);
return resolve(rendition);
}, () => {
this.viewerTypeChange.next('error_in_creation');
return reject();
});
}
} else {
this.viewerTypeChange.next('error_in_creation');
clearInterval(intervalId);
}
}, this.TRY_TIMEOUT);
});
}
private async handleNodeRendition(rendition: RenditionEntry, nodeId: string, renditionId: string, versionId?: string) {
const status: string = rendition.entry.status.toString();
if (status === 'CREATED') {
if (renditionId === 'pdf') {
this.viewerTypeChange.next('pdf');
} else if (renditionId === 'imgpreview') {
this.viewerTypeChange.next('image');
}
const urlFileContent = versionId ? this.apiService.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
this.apiService.contentApi.getRenditionUrl(nodeId, renditionId);
this.urlFileContentChange.next(urlFileContent);
}
}
} }

View File

@@ -637,4 +637,8 @@ export class ViewerPage {
const unknownFormatLabel = this.unknownFormat.element(by.css(`.adf-viewer__unknown-label`)); const unknownFormatLabel = this.unknownFormat.element(by.css(`.adf-viewer__unknown-label`));
return BrowserActions.getText(unknownFormatLabel); return BrowserActions.getText(unknownFormatLabel);
} }
async expectUrlToContain(text: string): Promise<void> {
await expect(browser.getCurrentUrl()).toContain(text);
}
} }