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

@@ -26,10 +26,11 @@ import { CoreTestingModule } from '../testing/core.testing.module';
import { TranslateModule } from '@ngx-translate/core';
@Component({
template: '<div [adfNodeDownload]="selection"></div>'
template: '<div [adfNodeDownload]="selection" [version]="version"></div>'
})
class TestComponent {
selection;
version;
}
describe('NodeDownloadDirective', () => {
@@ -91,6 +92,22 @@ describe('NodeDownloadDirective', () => {
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', () => {
spyOn(contentService, 'getContentUrl');
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 { AlfrescoApiService } from '../services/alfresco-api.service';
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';
/**
@@ -35,6 +35,10 @@ export class NodeDownloadDirective {
@Input('adfNodeDownload')
nodes: NodeEntry | NodeEntry[];
/** Node's version to download. */
@Input()
version: VersionEntry;
@HostListener('click')
onClick() {
this.downloadNodes(this.nodes);
@@ -101,8 +105,14 @@ export class NodeDownloadDirective {
// nodeId for Shared node
const id = (<any> node.entry).nodeId || node.entry.id;
const url = contentApi.getContentUrl(id, true);
const fileName = node.entry.name;
let url, fileName;
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);
}

View File

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

View File

@@ -20,7 +20,7 @@ import {
Input, OnChanges, Output, TemplateRef,
ViewEncapsulation, OnInit, OnDestroy
} 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 { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
@@ -74,6 +74,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@Input()
nodeId: string = null;
/** Version Id of the file to load. */
@Input()
versionId: string = null;
/** Shared link id (to display shared file). */
@Input()
sharedLinkId: string = null;
@@ -206,6 +210,7 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
viewerType = 'unknown';
isLoading = false;
nodeEntry: NodeEntry;
versionEntry: VersionEntry;
extensionTemplates: { template: TemplateRef<any>, isVisible: boolean }[] = [];
externalExtensions: string[] = [];
@@ -255,6 +260,17 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
).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();
}
@@ -298,9 +314,20 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.apiService.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] }).then(
(node: NodeEntry) => {
this.nodeEntry = node;
this.setUpNodeFile(node.entry).then(() => {
this.isLoading = false;
});
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.isLoading = false;
});
}
},
() => {
this.isLoading = false;
@@ -353,21 +380,23 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.scrollTop();
}
private async setUpNodeFile(data: Node) {
private async setUpNodeFile(nodeData: Node, versionData?: Version) {
let setupNode;
if (data.content) {
this.mimeType = data.content.mimeType;
if (versionData && versionData.content) {
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.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);
if (this.viewerType === 'unknown') {
@@ -375,12 +404,12 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
}
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.sidebarRightTemplateContext.node = data;
this.sidebarLeftTemplateContext.node = data;
this.sidebarRightTemplateContext.node = nodeData;
this.sidebarLeftTemplateContext.node = nodeData;
this.scrollTop();
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) {
try {
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) {
if (typeof extensionAllowed === 'string') {
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 { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
import { Subject } from 'rxjs';
@Injectable({
providedIn: 'root'
@@ -54,6 +55,17 @@ export class ViewUtilService {
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,
private logService: LogService) {
}
@@ -167,4 +179,110 @@ export class ViewUtilService {
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);
}
}
}