split render from viewer

move alfresco render in content pack
This commit is contained in:
eromano
2022-11-21 18:01:04 +01:00
committed by Amedeo Lepore
parent 5a1327d40a
commit 36ee8c8e34
38 changed files with 873 additions and 903 deletions

View File

@@ -2,6 +2,6 @@
[class.adf-readonly]="field.readOnly">
<label class="adf-label" [attr.for]="field.id">{{field.name | translate }}<span class="adf-asterisk"
*ngIf="isRequired()">*</span></label>
<adf-viewer [overlayMode]="false" [urlFile]="field.value" [showViewer]="field.value" [allowGoBack]="false"></adf-viewer>
<!-- <adf-viewer [urlFile]="field.value" [allowGoBack]="false"></adf-viewer>-->
<error-widget [error]="field.validationSummary"></error-widget>
</div>

View File

@@ -1,149 +0,0 @@
<ng-container *ngIf="showToolbar">
<adf-toolbar id="adf-viewer-toolbar" class="adf-viewer-toolbar">
<adf-toolbar-title>
<ng-container *ngIf="allowLeftSidebar">
<button mat-icon-button
[attr.aria-expanded]="showLeftSidebar"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.INFO' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.INFO' | translate }}"
data-automation-id="adf-toolbar-left-sidebar"
[color]="showLeftSidebar ? 'accent' : null"
(click)="toggleLeftSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
</ng-container>
<button *ngIf="allowGoBack"
class="adf-viewer-close-button"
data-automation-id="adf-toolbar-back"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.CLOSE' | translate"
mat-icon-button
title="{{ 'ADF_VIEWER.ACTIONS.CLOSE' | translate }}"
(click)="onBackButtonClick()">
<mat-icon>close</mat-icon>
</button>
</adf-toolbar-title>
<div fxFlex="1 1 auto"
class="adf-viewer__file-title">
<button *ngIf="allowNavigate && canNavigateBefore"
data-automation-id="adf-toolbar-pref-file"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.PREV_FILE' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.PREV_FILE' | translate }}"
(click)="onNavigateBeforeClick($event)">
<mat-icon>navigate_before</mat-icon>
</button>
<img class="adf-viewer__mimeicon"
[alt]="mimeType"
[src]="mimeType | adfMimeTypeIcon"
data-automation-id="adf-file-thumbnail">
<span class="adf-viewer__display-name"
id="adf-viewer-display-name">{{ fileName }}</span>
<button *ngIf="allowNavigate && canNavigateNext"
data-automation-id="adf-toolbar-next-file"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.NEXT_FILE' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.NEXT_FILE' | translate }}"
(click)="onNavigateNextClick($event)">
<mat-icon>navigate_next</mat-icon>
</button>
</div>
<ng-content select="adf-viewer-toolbar-actions"></ng-content>
<ng-container *ngIf="mnuOpenWith"
data-automation-id='adf-toolbar-custom-btn'>
<button id="adf-viewer-openwith"
mat-button
[matMenuTriggerFor]="mnuOpenWith"
data-automation-id="adf-toolbar-open-with">
<span>{{ 'ADF_VIEWER.ACTIONS.OPEN_WITH' | translate }}</span>
<mat-icon>arrow_drop_down</mat-icon>
</button>
<mat-menu #mnuOpenWith="matMenu"
[overlapTrigger]="false">
<ng-content select="adf-viewer-open-with"></ng-content>
</mat-menu>
</ng-container>
<adf-toolbar-divider></adf-toolbar-divider>
<button id="adf-viewer-download"
*ngIf="allowDownload"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.DOWNLOAD' | translate }}"
data-automation-id="adf-toolbar-download"
[adfNodeDownload]="nodeEntry"
[version]="versionEntry">
<mat-icon>file_download</mat-icon>
</button>
<button id="adf-viewer-print"
*ngIf="allowPrint"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.PRINT' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.PRINT' | translate }}"
data-automation-id="adf-toolbar-print"
(click)="printContent()">
<mat-icon>print</mat-icon>
</button>
<button id="adf-viewer-fullscreen"
*ngIf="viewerType !== 'media' && allowFullScreen"
mat-icon-button
[attr.aria-label]="'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.FULLSCREEN' | translate }}"
data-automation-id="adf-toolbar-fullscreen"
(click)="enterFullScreen()">
<mat-icon>fullscreen</mat-icon>
</button>
<ng-container *ngIf="allowRightSidebar">
<adf-toolbar-divider></adf-toolbar-divider>
<button mat-icon-button
[attr.aria-expanded]="showRightSidebar"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.INFO' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.INFO' | translate }}"
data-automation-id="adf-toolbar-sidebar"
[color]="showRightSidebar ? 'accent' : null"
(click)="toggleSidebar()">
<mat-icon>info_outline</mat-icon>
</button>
</ng-container>
<ng-container *ngIf="mnuMoreActions">
<button id="adf-viewer-moreactions"
mat-icon-button
[matMenuTriggerFor]="mnuMoreActions"
[attr.aria-label]="'ADF_VIEWER.ACTIONS.MORE_ACTIONS' | translate"
title="{{ 'ADF_VIEWER.ACTIONS.MORE_ACTIONS' | translate }}"
data-automation-id="adf-toolbar-more-actions">
<mat-icon>more_vert</mat-icon>
</button>
<mat-menu #mnuMoreActions="matMenu"
[overlapTrigger]="false">
<ng-content select="adf-viewer-more-actions"></ng-content>
</mat-menu>
</ng-container>
</adf-toolbar>
</ng-container>
<adf-viewer
(submitFile)="onSubmitFile($event)"
(print)="onPrintContent($event)"
[viewerType]="viewerType"
[fileName]="fileName"
[isLoading]="isLoading"
[allowGoBack]="allowGoBack"
[overlayMode]="overlayMode"
[urlFile]="urlFileContent"
>
</adf-viewer>

View File

@@ -1,409 +0,0 @@
import {
ChangeDetectorRef,
Component,
EventEmitter, HostListener,
Input,
Output,
ViewEncapsulation
} from "@angular/core";
import { ViewUtilService } from "../services/view-util.service";
import {
ContentApi, Node,
NodeEntry,
NodesApi, RenditionEntry,
SharedLinkEntry,
SharedlinksApi, Version,
VersionEntry,
VersionsApi
} from "@alfresco/js-api";
import { AlfrescoApiService, LogService, UploadService } from "../../services";
import { MatDialog } from "@angular/material/dialog";
import { filter, takeUntil } from "rxjs/operators";
import { FileModel } from "../../models";
import { Subject } from "rxjs";
import { RenditionViewerService } from "../services/rendition-viewer.service";
import { BaseEvent } from '../../events';
@Component({
selector: 'adf-alfresco-viewer',
templateUrl: './alfresco-viewer.component.html',
styleUrls: ['./alfresco-viewer.component.scss'],
host: {class: 'adf-viewer'},
encapsulation: ViewEncapsulation.None,
providers: [ViewUtilService]
})
export class AlfrescoViewerComponent {
/** Node Id of the file to load. */
@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;
/** Hide or show the viewer */
@Input()
showViewer = true;
/** Number of times the Viewer will retry fetching content Rendition.
* There is a delay of at least one second between attempts.
*/
@Input()
maxRetries = 30;
/** Allows `back` navigation */
@Input()
allowGoBack = true;
/** Hide or show the toolbar */
@Input()
showToolbar = true;
/** If `true` then show the Viewer as a full page over the current content.
* Otherwise fit inside the parent div.
*/
@Input()
overlayMode = false;
/** Toggles before/next navigation. You can use the arrow buttons to navigate
* between documents in the collection.
*/
@Input()
allowNavigate = false;
/** Toggles the "before" ("<") button. Requires `allowNavigate` to be enabled. */
@Input()
canNavigateBefore = true;
/** Toggles the next (">") button. Requires `allowNavigate` to be enabled. */
@Input()
canNavigateNext = true;
/** Allow the left the sidebar. */
@Input()
allowLeftSidebar = false;
/** Allow the right sidebar. */
@Input()
allowRightSidebar = false;
/** Emitted when the shared link used is not valid. */
@Output()
invalidSharedLink = new EventEmitter();
/** Emitted when user clicks 'Navigate Before' ("<") button. */
@Output()
navigateBefore = new EventEmitter<MouseEvent | KeyboardEvent>();
/** Emitted when user clicks 'Navigate Next' (">") button. */
@Output()
navigateNext = new EventEmitter<MouseEvent | KeyboardEvent>();
/** Toggles right sidebar visibility. Requires `allowRightSidebar` to be set to `true`. */
@Input()
showRightSidebar = false;
/** Toggles left sidebar visibility. Requires `allowLeftSidebar` to be set to `true`. */
@Input()
showLeftSidebar = false;
private onDestroy$ = new Subject<boolean>();
private cacheBusterNumber: number;
private nodeEntry: NodeEntry;
versionEntry: VersionEntry;
isLoading: boolean;
urlFileContent: string;
_sharedLinksApi: SharedlinksApi;
viewerType: any;
fileName: string;
fileExtension: any;
mimeType: string;
get sharedLinksApi(): SharedlinksApi {
this._sharedLinksApi = this._sharedLinksApi ?? new SharedlinksApi(this.apiService.getInstance());
return this._sharedLinksApi;
}
_versionsApi: VersionsApi;
get versionsApi(): VersionsApi {
this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance());
return this._versionsApi;
}
_nodesApi: NodesApi;
get nodesApi(): NodesApi {
this._nodesApi = this._nodesApi ?? new NodesApi(this.apiService.getInstance());
return this._nodesApi;
}
_contentApi: ContentApi;
get contentApi(): ContentApi {
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
return this._contentApi;
}
constructor(private apiService: AlfrescoApiService,
private renditionViewerService: RenditionViewerService,
private viewUtilService: ViewUtilService,
private logService: LogService,
// private contentService: ContentService,
private uploadService: UploadService,
public dialog: MatDialog,
private cdr: ChangeDetectorRef) {
renditionViewerService.maxRetries = this.maxRetries;
}
onNavigateBeforeClick(event: MouseEvent | KeyboardEvent) {
this.navigateBefore.next(event);
}
onNavigateNextClick(event: MouseEvent | KeyboardEvent) {
this.navigateNext.next(event);
}
ngOnInit() {
this.apiService.nodeUpdated.pipe(
filter((node) => node && node.id === this.nodeId &&
(node.name !== this.fileName ||
this.getNodeVersionProperty(this.nodeEntry.entry) !== this.getNodeVersionProperty(node))),
takeUntil(this.onDestroy$)
).subscribe((node) => this.onNodeUpdated(node));
}
private onNodeUpdated(node: Node) {
if (node && node.id === this.nodeId) {
// this.cacheTypeForContent = 'no-cache';
this.generateCacheBusterNumber();
this.isLoading = true;
this.setUpNodeFile(node).then(() => {
this.isLoading = false;
});
}
}
private getNodeVersionProperty(node: Node): string {
return node?.properties['cm:versionLabel'] ?? '';
}
private setupSharedLink() {
this.allowGoBack = false;
this.sharedLinksApi.getSharedLink(this.sharedLinkId).then(
(sharedLinkEntry: SharedLinkEntry) => {
this.setUpSharedLinkFile(sharedLinkEntry);
this.isLoading = false;
},
() => {
this.isLoading = false;
this.logService.error('This sharedLink does not exist');
this.invalidSharedLink.next();
});
}
private setupNode() {
this.nodesApi.getNode(this.nodeId, {include: ['allowableOperations']}).then(
(node: NodeEntry) => {
this.nodeEntry = node;
if (this.versionId) {
this.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.cdr.detectChanges();
});
}
},
() => {
this.isLoading = false;
this.logService.error('This node does not exist');
}
);
}
private async setUpNodeFile(nodeData: Node, versionData?: Version): Promise<void> {
this.isLoading = true;
// this.readOnly = !this.contentService.hasAllowableOperations(nodeData, 'update');
if (versionData && versionData.content) {
this.mimeType = versionData.content.mimeType;
} else if (nodeData.content) {
this.mimeType = nodeData.content.mimeType;
}
const currentFileVersion = this.nodeEntry?.entry?.properties && this.nodeEntry.entry.properties['cm:versionLabel'] ?
encodeURI(this.nodeEntry?.entry?.properties['cm:versionLabel']) : encodeURI('1.0');
this.urlFileContent = versionData ? this.contentApi.getVersionContentUrl(this.nodeId, versionData.id) :
this.contentApi.getContentUrl(this.nodeId);
this.urlFileContent = this.cacheBusterNumber ? this.urlFileContent + '&' + currentFileVersion + '&' + this.cacheBusterNumber :
this.urlFileContent + '&' + currentFileVersion;
const fileExtension = this.viewUtilService.getFileExtension(versionData ? versionData.name : nodeData.name);
this.fileName = versionData ? versionData.name : nodeData.name;
this.viewerType = this.viewUtilService.getViewerType(fileExtension, this.mimeType);
if (this.viewerType === 'unknown') {
if (versionData) {
({
url: this.urlFileContent,
viewerType: this.viewerType
} = await this.renditionViewerService.getNodeRendition(nodeData.id, versionData.id));
} else {
({
url: this.urlFileContent,
viewerType: this.viewerType
} = await this.renditionViewerService.getNodeRendition(nodeData.id));
}
}
this.isLoading = false;
// this.sidebarRightTemplateContext.node = nodeData;
// this.sidebarLeftTemplateContext.node = nodeData;
}
private async setUpSharedLinkFile(details: any) {
this.mimeType = details.entry.content.mimeType;
const fileExtension = this.viewUtilService.getFileExtension(details.entry.name);
this.fileName = details.entry.name;
this.urlFileContent = this.contentApi.getSharedLinkContentUrl(this.sharedLinkId, false);
this.viewerType = this.viewUtilService.getViewerType(fileExtension, this.mimeType);
if (this.viewerType === 'unknown') {
({
url: this.urlFileContent,
viewerType: this.viewerType
} = await this.getSharedLinkRendition(this.sharedLinkId));
}
}
onPrintContent(event: BaseEvent<any>) {
if (!event.defaultPrevented) {
this.viewUtilService.printFileGeneric(this.nodeId, this.mimeType);
}
}
private async getSharedLinkRendition(sharedId: string): Promise<{ url: string, viewerType: string }> {
try {
const rendition: RenditionEntry = await this.sharedLinksApi.getSharedLinkRendition(sharedId, 'pdf');
if (rendition.entry.status.toString() === 'CREATED') {
const urlFileContent = this.contentApi.getSharedLinkRenditionUrl(sharedId, 'pdf');
return {url: urlFileContent, viewerType: 'pdf'}
}
} catch (error) {
this.logService.error(error);
try {
const rendition: RenditionEntry = await this.sharedLinksApi.getSharedLinkRendition(sharedId, 'imgpreview');
if (rendition.entry.status.toString() === 'CREATED') {
const urlFileContent = this.contentApi.getSharedLinkRenditionUrl(sharedId, 'imgpreview');
return {url: urlFileContent, viewerType: 'image'}
}
} catch (renditionError) {
this.logService.error(renditionError);
return null;
}
}
return null;
}
private generateCacheBusterNumber() {
this.cacheBusterNumber = Date.now();
}
onSubmitFile(newImageBlob: Blob) {
if (this?.nodeEntry?.entry?.id) { // && !this.readOnly) {
const newImageFile: File = new File([newImageBlob], this?.nodeEntry?.entry?.name, {type: this?.nodeEntry?.entry?.content?.mimeType});
const newFile = new FileModel(
newImageFile,
{
majorVersion: false,
newVersion: true,
parentId: this?.nodeEntry?.entry?.parentId,
nodeType: this?.nodeEntry?.entry?.content?.mimeType
},
this?.nodeEntry?.entry?.id
);
this.uploadService.addToQueue(...[newFile]);
this.uploadService.uploadFilesInTheQueue();
}
}
isSourceDefined(): boolean {
return !!(this.nodeId || this.sharedLinkId);
}
ngOnChanges() {
if (this.showViewer) {
if (!this.isSourceDefined()) {
throw new Error('A content source attribute value is missing.');
}
if (this.nodeId) {
this.setupNode();
} else if (this.sharedLinkId) {
this.setupSharedLink();
}
}
}
toggleSidebar() {
this.showRightSidebar = !this.showRightSidebar;
if (this.showRightSidebar && this.nodeId) {
this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] });
// .then((nodeEntry: NodeEntry) => {
// // this.sidebarRightTemplateContext.node = nodeEntry.entry;
// });
}
}
toggleLeftSidebar() {
this.showLeftSidebar = !this.showLeftSidebar;
if (this.showRightSidebar && this.nodeId) {
this.nodesApi.getNode(this.nodeId, { include: ['allowableOperations'] });
// .then((nodeEntry: NodeEntry) => {
// // this.sidebarLeftTemplateContext.node = nodeEntry.entry;
// });
}
}
@HostListener('document:keyup', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
if (event && event.defaultPrevented) {
return;
}
const key = event.keyCode;
// Left arrow
if (key === 37 && this.canNavigateBefore) {
event.preventDefault();
this.onNavigateBeforeClick(event);
}
// Right arrow
if (key === 39 && this.canNavigateNext) {
event.preventDefault();
this.onNavigateNextClick(event);
}
}
}

View File

@@ -13,7 +13,7 @@
<mat-icon>zoom_out</mat-icon>
</button>
<div class="adf-viewer__toolbar-page-scale" data-automation-id="adf-page-scale">
<div class="adf-image-viewer__toolbar-page-scale" data-automation-id="adf-page-scale">
{{ currentScaleText }}
</div>

View File

@@ -48,5 +48,20 @@
display: inline-block;
margin-left: 10px;
}
}
&__toolbar {
&-page-scale {
cursor: default;
width: 79px;
height: 24px;
font-size: var(--theme-body-1-font-size);
border: 1px solid var(--theme-border-color);
text-align: center;
line-height: 24px;
margin-left: 4px;
margin-right: 4px;
}
}
}

View File

@@ -79,7 +79,7 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
}
initializeScaling() {
const scaling = this.appConfigService.get<number>('adf-viewer.image-viewer-scaling', undefined) / 100;
const scaling = this.appConfigService.get<number>('adf-viewer-render.image-viewer-scaling', undefined) / 100;
if (scaling) {
this.scale = scaling;
}

View File

@@ -1,4 +1,4 @@
<video controls [ngClass]="{'adf-audio-file': mimeType && mimeType.startsWith('audio')}">
<source [src]="urlFile" [type]="mimeType" (error)="onMediaPlayerError()"/>
<source [src]="urlFile" [type]="mimeType" (error)="onMediaPlayerError($event)"/>
<track *ngFor="let track of tracks" [kind]="track.kind" [label]="track.label" [srclang]="track.srclang" [src]="track.src"/>
</video>

View File

@@ -23,7 +23,7 @@ import { Track } from '../models/viewer.model';
selector: 'adf-media-player',
templateUrl: './media-player.component.html',
styleUrls: ['./media-player.component.scss'],
host: { class: 'adf-media-player' },
host: {class: 'adf-media-player'},
encapsulation: ViewEncapsulation.None
})
export class MediaPlayerComponent implements OnChanges {
@@ -54,7 +54,7 @@ export class MediaPlayerComponent implements OnChanges {
ngOnChanges(changes: SimpleChanges) {
const blobFile = changes['blobFile'];
// const nodeId = changes['nodeId'];
// const nodeId = changes['nodeId'];
if (blobFile && blobFile.currentValue) {
this.urlFile = this.contentService.createTrustedUrl(this.blobFile);
@@ -70,7 +70,7 @@ export class MediaPlayerComponent implements OnChanges {
}
}
onMediaPlayerError() {
this.error.emit();
onMediaPlayerError(event: any) {
this.error.emit(event);
}
}

View File

@@ -593,8 +593,6 @@
}
}
.adf-hidden, [hidden] {
display: none !important;
}

View File

@@ -82,7 +82,7 @@
<span>{{ 'ADF_VIEWER.PAGE_LABEL.OF' | translate }} {{ totalPages }}</span>
</div>
<div class="adf-viewer__toolbar-page-scale" data-automation-id="adf-page-scale">
<div class="adf-pdf-viewer__toolbar-page-scale" data-automation-id="adf-page-scale">
{{ currentScaleText }}
</div>

View File

@@ -1,8 +1,3 @@
.adf-viewer-content-container {
width: 100%;
height: 100%;
}
.adf-pdf-viewer {
width: 100%;
height: 100%;
@@ -21,6 +16,54 @@
background-color: rgba(0, 0, 0, 0.12);
}
&__thumbnails {
width: 180px;
display: flex;
flex-direction: column;
padding: 0;
background: #e6e6e6;
.adf-info-drawer-layout {
display: flex;
flex-direction: column;
flex: 1;
background: #e6e6e6;
}
.adf-info-drawer-layout-header {
margin-bottom: 0;
}
.adf-info-drawer-layout-content {
padding: 0;
height: 100%;
overflow: hidden;
}
.adf-info-drawer-content {
height: 100%;
}
.adf-info-drawer-layout-content > *:last-child {
height: 100%;
overflow: hidden;
}
}
&__toolbar {
&-page-scale {
cursor: default;
width: 79px;
height: 24px;
font-size: var(--theme-body-1-font-size);
border: 1px solid var(--theme-border-color);
text-align: center;
line-height: 24px;
margin-left: 4px;
margin-right: 4px;
}
}
.adf-thumbnails-template {
&__container {
display: flex;

View File

@@ -80,9 +80,6 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
@Output()
error = new EventEmitter<any>();
@Output()
close = new EventEmitter<any>();
page: number;
displayPage: number;
totalPages: number;
@@ -514,8 +511,6 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
.afterClosed().subscribe((password) => {
if (password) {
callback(password);
} else {
this.close.emit();
}
});
}

View File

@@ -0,0 +1,95 @@
<div *ngIf="isLoading"
class="adf-viewer-main">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container">
<ng-container *ngIf="isLoading">
<div class="adf-viewer-render__loading-screen"
fxFlex="1 1 auto">
<h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div>
<mat-spinner></mat-spinner>
</div>
</div>
</ng-container>
</div>
</div>
</div>
<div *ngIf="!isLoading"
class="adf-viewer-main">
<div class="adf-viewer-render-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-render-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'external'">
<adf-preview-extension
*ngIf="!!externalViewer"
[id]="externalViewer.component"
[url]="urlFile"
[extension]="externalViewer.fileExtension"
[attr.data-automation-id]="externalViewer.component">
</adf-preview-extension>
</ng-container>
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer [thumbnailsTemplate]="thumbnailsTemplate"
[allowThumbnails]="allowThumbnails"
[blobFile]="blobFile"
[urlFile]="urlFile"
[fileName]="fileName"
[cacheType]="cacheTypeForContent"
(error)="onUnsupportedFile()">
</adf-pdf-viewer>
</ng-container>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer [urlFile]="urlFile"
[fileName]="fileName"
[blobFile]="blobFile"
(error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)"
></adf-img-viewer>
</ng-container>
<ng-container *ngSwitchCase="'media'">
<adf-media-player id="adf-mdedia-player"
[urlFile]="urlFile"
[mimeType]="mimeType"
[blobFile]="blobFile"
[fileName]="fileName"
(error)="onUnsupportedFile()">
</adf-media-player>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFile"
[blobFile]="blobFile">
</adf-txt-viewer>
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<ng-container *ngFor="let ext of viewerExtensions">
<adf-preview-extension *ngIf="checkExtensions(ext.fileExtension)"
[id]="ext.component"
[url]="urlFile"
[extension]="extension"
[attr.data-automation-id]="ext.component">
</adf-preview-extension>
</ng-container>
<span class="adf-viewer-render-custom-content"
*ngFor="let extensionTemplate of extensionTemplates">
<ng-template *ngIf="extensionTemplate.isVisible"
[ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFile: urlFile, extension:extension }">
</ng-template>
</span>
</ng-container>
<ng-container *ngSwitchDefault>
<adf-viewer-unknown-format></adf-viewer-unknown-format>
</ng-container>
</div>
</div>
</div>

View File

@@ -0,0 +1,72 @@
/* stylelint-disable scss/at-extend-no-missing-placeholder */
.adf-full-screen {
width: 100%;
height: 100%;
background-color: var(--theme-card-bg-color);
}
.adf-viewer-render {
&-main {
width: 0;
}
&-content-container {
display: flex;
justify-content: center;
}
.adf-viewer-render-layout-content {
@extend .adf-full-screen;
position: relative;
overflow-y: hidden;
overflow-x: hidden;
z-index: 1;
background-color: var(--theme-background-color);
display: flex;
flex-direction: row;
flex-wrap: wrap;
flex: 1;
& > div {
display: flex;
flex-flow: row wrap;
margin: 0 auto;
align-items: stretch;
height: 93vh;
width: 100%;
}
}
&-overlay-container {
.adf-viewer-render-content {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
}
&__loading-screen {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 85vh;
.mat-spinner {
margin: 0 auto;
}
}
&-custom-content {
width: 100vw;
}
&-unknown-content {
align-items: center;
display: flex;
}
}

View File

@@ -24,7 +24,7 @@ import { AlfrescoApiService, RenditionsService } from '../../services';
import { throwError } from 'rxjs';
import { EventMock } from '../../mock/event.mock';
import { RenderingQueueServices } from '../services/rendering-queue.services';
import { ViewerComponent } from './viewer.component';
import { ViewerRenderComponent } from './viewer-render.component.ts';
import { setupTestBed } from '../../testing/setup-test-bed';
import { NodeEntry, VersionEntry } from '@alfresco/js-api';
import { CoreTestingModule } from '../../testing/core.testing.module';
@@ -142,17 +142,17 @@ class ViewerWithCustomMoreActionsComponent {
})
class DoubleViewerComponent {
@ViewChild('viewer1')
viewer1: ViewerComponent;
viewer1: ViewerRenderComponent;
@ViewChild('viewer2')
viewer2: ViewerComponent;
viewer2: ViewerRenderComponent;
}
describe('ViewerComponent', () => {
let component: ViewerComponent;
let fixture: ComponentFixture<ViewerComponent>;
let component: ViewerRenderComponent;
let fixture: ComponentFixture<ViewerRenderComponent>;
let alfrescoApiService: AlfrescoApiService;
let element: HTMLElement;
let dialog: MatDialog;
@@ -188,7 +188,7 @@ describe('ViewerComponent', () => {
});
beforeEach(() => {
fixture = TestBed.createComponent(ViewerComponent);
fixture = TestBed.createComponent(ViewerRenderComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
@@ -241,7 +241,7 @@ describe('ViewerComponent', () => {
};
spyOn(extensionService, 'getViewerExtensions').and.returnValue([extension]);
fixture = TestBed.createComponent(ViewerComponent);
fixture = TestBed.createComponent(ViewerRenderComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
@@ -272,7 +272,7 @@ describe('ViewerComponent', () => {
];
spyOn(extensionService, 'getViewerExtensions').and.returnValue(extensions);
fixture = TestBed.createComponent(ViewerComponent);
fixture = TestBed.createComponent(ViewerRenderComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
@@ -294,7 +294,7 @@ describe('ViewerComponent', () => {
};
spyOn(extensionService, 'getViewerExtensions').and.returnValue([extension]);
fixture = TestBed.createComponent(ViewerComponent);
fixture = TestBed.createComponent(ViewerRenderComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
@@ -318,7 +318,7 @@ describe('ViewerComponent', () => {
};
spyOn(extensionService, 'getViewerExtensions').and.returnValue([extension]);
fixture = TestBed.createComponent(ViewerComponent);
fixture = TestBed.createComponent(ViewerRenderComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;
@@ -1350,7 +1350,7 @@ describe('ViewerComponent', () => {
describe('Viewer component - Full Screen Mode - Mocking fixture element', () => {
beforeEach(() => {
fixture = TestBed.createComponent(ViewerComponent);
fixture = TestBed.createComponent(ViewerRenderComponent);
element = fixture.nativeElement;
component = fixture.componentInstance;

View File

@@ -20,47 +20,33 @@
//TODO TO UNDERSTAND THE LEFT AND RIGHT SIDEBAR
//TODO uncomment media load subtitle
//TODO rename allowGoBack allow close button
//TODO prevent momentanus unkown format
//TODO prevent momentous unknown format
//TODO null propagation
//TODO viewer widget specialization in process service cloud
//TODO Test close dialog password scenario
//TODO Remove unused CSS
//TODO FIX documentation
//TODO Fix core viewer widget
import {
Component, ContentChild, EventEmitter, HostListener, ElementRef,
Component, EventEmitter,
Input, OnChanges, Output, TemplateRef,
ViewEncapsulation, OnInit, OnDestroy
} from '@angular/core';
import { BaseEvent } from '../../events';
import { ViewerMoreActionsComponent } from './viewer-more-actions.component';
import { ViewerOpenWithComponent } from './viewer-open-with.component';
import { ViewerSidebarComponent } from './viewer-sidebar.component';
import { ViewerToolbarComponent } from './viewer-toolbar.component';
import { fromEvent, Subject } from 'rxjs';
import { Subject } from 'rxjs';
import { ViewUtilService } from '../services/view-util.service';
import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
import { filter, skipWhile, takeUntil } from 'rxjs/operators';
import { MatDialog } from '@angular/material/dialog';
@Component({
selector: 'adf-viewer',
templateUrl: './viewer.component.html',
styleUrls: ['./viewer.component.scss'],
host: {class: 'adf-viewer'},
selector: 'adf-viewer-render',
templateUrl: './viewer-render.component.html',
styleUrls: ['./viewer-render.component.scss'],
host: {class: 'adf-viewer-render'},
encapsulation: ViewEncapsulation.None,
providers: [ViewUtilService]
})
export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@ContentChild(ViewerToolbarComponent)
toolbar: ViewerToolbarComponent;
@ContentChild(ViewerSidebarComponent)
sidebar: ViewerSidebarComponent;
@ContentChild(ViewerOpenWithComponent)
mnuOpenWith: ViewerOpenWithComponent;
@ContentChild(ViewerMoreActionsComponent)
mnuMoreActions: ViewerMoreActionsComponent;
export class ViewerRenderComponent implements OnChanges, OnInit, OnDestroy {
/** 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.
@@ -72,24 +58,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@Input()
blobFile: Blob;
/** If `true` then show the Viewer as a full page over the current content.
* Otherwise fit inside the parent div.
*/
@Input()
overlayMode = false;
/** Hide or show the viewer */
@Input()
showViewer = true;
/** Toggles downloading. */
@Input()
allowDownload = true;
/** Toggles printing. */
@Input()
allowPrint = false;
/** Toggles the 'Full Screen' feature. */
@Input()
allowFullScreen = true;
@@ -131,18 +99,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
@Input()
isLoading = false;
/** Emitted when user clicks the 'Back' button. */
@Output()
goBack = new EventEmitter<BaseEvent<any>>();
/** Emitted when user clicks the 'Print' button. */
@Output()
print = new EventEmitter<BaseEvent<any>>();
/** Emitted when the viewer is shown or hidden. */
@Output()
showViewerChange = new EventEmitter<boolean>();
/** Emitted when the filename extension changes. */
@Output()
extensionChange = new EventEmitter<string>();
@@ -152,12 +108,7 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
submitFile = new EventEmitter<Blob>();
extensionTemplates: { template: TemplateRef<any>; isVisible: boolean }[] = [];
urlFileContent: string;
otherMenu: any;
extension: string;
// sidebarRightTemplateContext: { node: Node } = { node: null };
// sidebarLeftTemplateContext: { node: Node } = { node: null };
fileTitle: string;
/**
* Returns a list of the active Viewer content extensions.
@@ -182,22 +133,16 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
return this._externalViewer;
}
// readOnly = true;
cacheTypeForContent = '';
private onDestroy$ = new Subject<boolean>();
private shouldCloseViewer = true;
private keyDown$ = fromEvent<KeyboardEvent>(document, 'keydown');
constructor(private viewUtilService: ViewUtilService,
private extensionService: AppExtensionService,
private el: ElementRef,
public dialog: MatDialog) {
}
ngOnInit() {
this.closeOverlayManager();
this.cacheTypeForContent = '';
}
@@ -207,16 +152,14 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
}
ngOnChanges() {
if (this.showViewer) {
this.isLoading = true;
this.isLoading = true;
if (this.blobFile) {
this.setUpBlobData();
this.isLoading = false;
} else if (this.urlFile) {
this.setUpUrlFile();
this.isLoading = false;
}
if (this.blobFile) {
this.setUpBlobData();
this.isLoading = false;
} else if (this.urlFile) {
this.setUpUrlFile();
this.isLoading = false;
}
}
@@ -224,91 +167,23 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.mimeType = this.blobFile.type;
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(this.mimeType);
this.allowDownload = false;
// TODO: wrap blob into the data url and allow downloading
this.extensionChange.emit(this.mimeType);
this.scrollTop();
}
private setUpUrlFile() {
this.fileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
this.extension = this.viewUtilService.getFileExtension(this.fileTitle);
this.urlFileContent = this.urlFile;
this.extension = this.viewUtilService.getFileExtension(this.fileName);
this.viewerType = this.viewerType === 'unknown' ? this.viewUtilService.getViewerType(this.extension, this.mimeType) : this.viewerType;
this.extensionChange.emit(this.extension);
this.scrollTop();
}
scrollTop() {
window.scrollTo(0, 1);
}
onBackButtonClick() {
this.close();
}
/**
* close the viewer
*/
close() {
if (this.otherMenu) {
this.otherMenu.hidden = false;
}
this.showViewer = false;
this.showViewerChange.emit(this.showViewer);
}
/**
* Keyboard event listener
*
* @param event
*/
@HostListener('document:keyup', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
if (event && event.defaultPrevented) {
return;
}
const key = event.keyCode;
// Ctrl+F
if (key === 70 && event.ctrlKey) {
event.preventDefault();
this.enterFullScreen();
}
}
printContent() {
if (this.allowPrint) {
this.print.next(new BaseEvent());
}
}
/**
* Triggers full screen mode with a main content area displayed.
*/
enterFullScreen(): void {
if (this.allowFullScreen) {
const container = this.el.nativeElement.querySelector('.adf-viewer__fullscreen-container');
if (container) {
if (container.requestFullscreen) {
container.requestFullscreen();
} else if (container.webkitRequestFullscreen) {
container.webkitRequestFullscreen();
} else if (container.mozRequestFullScreen) {
container.mozRequestFullScreen();
} else if (container.msRequestFullscreen) {
container.msRequestFullscreen();
}
}
}
}
checkExtensions(extensionAllowed) {
if (typeof extensionAllowed === 'string') {
return this.extension.toLowerCase() === extensionAllowed.toLowerCase();
@@ -325,28 +200,4 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
this.viewerType = 'unknown';
}
private closeOverlayManager() {
this.dialog.afterOpened.pipe(
skipWhile(() => !this.overlayMode),
takeUntil(this.onDestroy$)
).subscribe(() => this.shouldCloseViewer = false);
this.dialog.afterAllClosed.pipe(
skipWhile(() => !this.overlayMode),
takeUntil(this.onDestroy$)
).subscribe(() => this.shouldCloseViewer = true);
this.keyDown$.pipe(
skipWhile(() => !this.overlayMode),
filter((e: KeyboardEvent) => e.keyCode === 27),
takeUntil(this.onDestroy$)
).subscribe((event: KeyboardEvent) => {
event.preventDefault();
if (this.shouldCloseViewer) {
this.close();
}
});
}
}

View File

@@ -1,142 +0,0 @@
<div *ngIf="showViewer"
class="adf-viewer-container"
[class.adf-viewer-overlay-container]="overlayMode"
[class.adf-viewer-inline-container]="!overlayMode">
<div class="adf-viewer-content"
fxLayout="column"
[cdkTrapFocus]="overlayMode"
cdkTrapFocusAutoCapture>
<ng-content select="adf-viewer-toolbar"></ng-content>
<div fxLayout="row"
fxFlex="1 1 auto">
<ng-container *ngIf="allowRightSidebar && showRightSidebar">
<div class="adf-viewer__sidebar"
[ngClass]="'adf-viewer__sidebar__right'"
fxFlexOrder="4"
id="adf-right-sidebar">
<!-- <ng-container *ngIf="sidebarRightTemplate">-->
<!-- <ng-container *ngTemplateOutlet="sidebarRightTemplate;context:sidebarRightTemplateContext">-->
<!-- </ng-container>-->
<!-- </ng-container>-->
<ng-content *ngIf="!sidebarRightTemplate"
select="adf-viewer-sidebar"></ng-content>
</div>
</ng-container>
<ng-container *ngIf="allowLeftSidebar && showLeftSidebar">
<div class="adf-viewer__sidebar"
[ngClass]="'adf-viewer__sidebar__left'"
fxFlexOrder="1"
id="adf-left-sidebar">
<!-- <ng-container *ngIf="sidebarLeftTemplate">-->
<!-- <ng-container *ngTemplateOutlet="sidebarLeftTemplate;context:sidebarLeftTemplateContext">-->
<!-- </ng-container>-->
<!-- </ng-container>-->
<ng-content *ngIf="!sidebarLeftTemplate"
select="adf-viewer-sidebar"></ng-content>
</div>
</ng-container>
<div *ngIf="isLoading"
class="adf-viewer-main"
fxFlexOrder="1"
fxFlex="1 1 auto">
<div class="adf-viewer-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-content-container">
<ng-container *ngIf="isLoading">
<div class="adf-viewer__loading-screen"
fxFlex="1 1 auto">
<h2>{{ 'ADF_VIEWER.LOADING' | translate }}</h2>
<div>
<mat-spinner></mat-spinner>
</div>
</div>
</ng-container>
</div>
</div>
</div>
<div *ngIf="!isLoading"
class="adf-viewer-main"
fxFlexOrder="1"
fxFlex="1 1 auto">
<div class="adf-viewer-layout-content adf-viewer__fullscreen-container">
<div class="adf-viewer-content-container" [ngSwitch]="viewerType">
<ng-container *ngSwitchCase="'external'">
<!-- <adf-preview-extension-->
<!-- *ngIf="!!externalViewer"-->
<!-- [id]="externalViewer.component"-->
<!-- [node]="nodeEntry?.entry"-->
<!-- [url]="urlFileContent"-->
<!-- [extension]="externalViewer.fileExtension"-->
<!-- [attr.data-automation-id]="externalViewer.component">-->
<!-- </adf-preview-extension>-->
</ng-container>
<ng-container *ngSwitchCase="'pdf'">
<adf-pdf-viewer (close)="onBackButtonClick()"
[thumbnailsTemplate]="thumbnailsTemplate"
[allowThumbnails]="allowThumbnails"
[blobFile]="blobFile"
[urlFile]="urlFileContent"
[fileName]="fileName"
[cacheType]="cacheTypeForContent"
(error)="onUnsupportedFile()"></adf-pdf-viewer>
</ng-container>
<ng-container *ngSwitchCase="'image'">
<adf-img-viewer [urlFile]="urlFileContent"
[fileName]="fileName"
[blobFile]="blobFile"
(error)="onUnsupportedFile()"
(submit)="onSubmitFile($event)"
></adf-img-viewer>
</ng-container>
<ng-container *ngSwitchCase="'media'">
<adf-media-player id="adf-mdedia-player"
[urlFile]="urlFileContent"
[mimeType]="mimeType"
[blobFile]="blobFile"
[fileName]="fileName"
(error)="onUnsupportedFile()"></adf-media-player>
</ng-container>
<ng-container *ngSwitchCase="'text'">
<adf-txt-viewer [urlFile]="urlFileContent"
[blobFile]="blobFile"></adf-txt-viewer>
</ng-container>
<ng-container *ngSwitchCase="'custom'">
<ng-container *ngFor="let ext of viewerExtensions">
<!-- <adf-preview-extension *ngIf="checkExtensions(ext.fileExtension)"-->
<!-- [id]="ext.component"-->
<!-- [node]="nodeEntry.entry"-->
<!-- [url]="urlFileContent"-->
<!-- [extension]="extension"-->
<!-- [attr.data-automation-id]="ext.component">-->
<!-- </adf-preview-extension>-->
</ng-container>
<span class="adf-viewer-custom-content"
*ngFor="let extensionTemplate of extensionTemplates">
<ng-template *ngIf="extensionTemplate.isVisible"
[ngTemplateOutlet]="extensionTemplate.template"
[ngTemplateOutletContext]="{ urlFileContent: urlFileContent, extension:extension }">
</ng-template>
</span>
</ng-container>
<ng-container *ngSwitchDefault>
<adf-viewer-unknown-format></adf-viewer-unknown-format>
</ng-container>
</div>
</div>
</div>
</div>
</div>
</div>

View File

@@ -1,205 +0,0 @@
/* stylelint-disable scss/at-extend-no-missing-placeholder */
.adf-full-screen {
width: 100%;
height: 100%;
background-color: var(--theme-card-bg-color);
}
.adf-viewer {
position: absolute;
width: 100%;
height: 100%;
.mat-toolbar {
color: var(--theme-text-color);
.adf-toolbar-title {
width: auto;
}
}
&-main {
width: 0;
}
&__mimeicon {
vertical-align: middle;
height: 18px;
width: 18px;
}
&-toolbar {
.mat-toolbar {
background-color: var(--theme-card-bg-bold-color);
}
}
&__file-title {
text-align: center;
}
&__display-name {
font-size: var(--theme-subheading-2-font-size);
opacity: 0.87;
line-height: 1.5;
letter-spacing: -0.4px;
font-weight: normal;
font-style: normal;
font-stretch: normal;
max-width: 400px;
text-overflow: ellipsis;
overflow: hidden;
display: inline-block;
vertical-align: middle;
color: var(--theme-text-fg-color);
}
&-container {
.adf-viewer-layout-content {
@extend .adf-full-screen;
position: relative;
overflow-y: hidden;
overflow-x: hidden;
z-index: 1;
background-color: var(--theme-background-color);
display: flex;
flex-direction: row;
/* stylelint-disable-next-line declaration-block-no-redundant-longhand-properties */
flex-wrap: wrap;
flex: 1;
& > div {
display: flex;
flex-flow: row wrap;
margin: 0 auto;
align-items: stretch;
height: 100%;
}
}
.adf-viewer-layout {
@extend .adf-full-screen;
display: flex;
flex-direction: row;
overflow-y: auto;
overflow-x: hidden;
position: relative;
}
.adf-viewer-content {
@extend .adf-full-screen;
flex: 1;
& > div {
height: 0; // Firefox
}
}
}
&-overlay-container {
.adf-viewer-content {
position: fixed;
top: 0;
left: 0;
z-index: 1000;
}
}
&-inline-container {
@extend .adf-full-screen;
}
&-content-container {
display: flex;
justify-content: center;
}
&-custom-content {
width: 100vw;
}
&-unknown-content {
align-items: center;
display: flex;
}
&__loading-screen {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
height: 85vh;
.mat-spinner {
margin: 0 auto;
}
}
&__sidebar {
width: 350px;
display: block;
padding: 0;
background-color: var(--theme-background-color);
box-shadow: 0 2px 4px 0 var(--theme-text-fg-shadow-color);
overflow: auto;
&__right {
border-left: 1px solid var(--theme-border-color);
}
&__left {
border-right: 1px solid var(--theme-border-color);
}
}
&__thumbnails {
width: 180px;
display: flex;
flex-direction: column;
padding: 0;
background: #e6e6e6;
.adf-info-drawer-layout {
display: flex;
flex-direction: column;
flex: 1;
background: #e6e6e6;
}
.adf-info-drawer-layout-header {
margin-bottom: 0;
}
.adf-info-drawer-layout-content {
padding: 0;
height: 100%;
overflow: hidden;
}
.adf-info-drawer-content {
height: 100%;
}
.adf-info-drawer-layout-content > *:last-child {
height: 100%;
overflow: hidden;
}
}
&__toolbar {
&-page-scale {
cursor: default;
width: 79px;
height: 24px;
font-size: var(--theme-body-1-font-size);
border: 1px solid var(--theme-border-color);
text-align: center;
line-height: 24px;
margin-left: 4px;
margin-right: 4px;
}
}
}

View File

@@ -19,7 +19,7 @@ import { Location } from '@angular/common';
import { SpyLocation } from '@angular/common/testing';
import { ChangeDetectorRef, ElementRef } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { ViewerComponent } from '../components/viewer.component';
import { ViewerRenderComponent } from '../components/viewer-render.component.ts';
import { ViewerExtensionDirective } from './viewer-extension.directive';
import { setupTestBed } from '../../testing/setup-test-bed';
import { CoreTestingModule } from '../../testing/core.testing.module';
@@ -43,7 +43,7 @@ describe('ExtensionViewerDirective', () => {
{ provide: Location, useClass: SpyLocation },
ViewerExtensionDirective,
{provide: ElementRef, useClass: MockElementRef},
ViewerComponent,
ViewerRenderComponent,
{ provide: ChangeDetectorRef, useValue: { detectChanges: () => {} } }
]
});

View File

@@ -16,7 +16,7 @@
*/
import { AfterContentInit, ContentChild, Directive, Input, TemplateRef, OnDestroy } from '@angular/core';
import { ViewerComponent } from '../components/viewer.component';
import { ViewerRenderComponent } from '../components/viewer-render.component';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
@@ -41,7 +41,7 @@ export class ViewerExtensionDirective implements AfterContentInit, OnDestroy {
private onDestroy$ = new Subject<boolean>();
constructor(private viewerComponent: ViewerComponent) {
constructor(private viewerComponent: ViewerRenderComponent) {
}
ngAfterContentInit() {

View File

@@ -16,7 +16,6 @@
*/
export * from './services/view-util.service';
export * from './components/viewer.component';
export * from './components/img-viewer.component';
export * from './components/media-player.component';
export * from './components/pdf-viewer-password-dialog';
@@ -31,7 +30,7 @@ export * from './components/viewer-open-with.component';
export * from './components/viewer-sidebar.component';
export * from './components/viewer-toolbar.component';
export * from './components/viewer-toolbar-actions.component';
export * from './components/viewer.component';
export * from './components/viewer-render.component';
export * from './directives/viewer-extension.directive';

View File

@@ -1,288 +0,0 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Injectable } from '@angular/core';
import { ContentApi, RenditionEntry, RenditionPaging, RenditionsApi, VersionsApi } from '@alfresco/js-api';
import { AlfrescoApiService } from '../../services/alfresco-api.service';
import { LogService } from '../../services/log.service';
import { Track } from '../models/viewer.model';
import { TranslationService } from '../../services/translation.service';
@Injectable({
providedIn: 'root'
})
export class RenditionViewerService {
/**
* Content groups based on categorization of files that can be viewed in the web browser. This
* implementation or grouping is tied to the definition the ng component: ViewerComponent
*/
static ContentGroup = {
IMAGE: 'image',
MEDIA: 'media',
PDF: 'pdf',
TEXT: 'text'
};
/**
* The name of the rendition with the media subtitles in the supported format
*/
static SUBTITLES_RENDITION_NAME = 'webvtt';
/**
* Based on ViewerComponent Implementation, this value is used to determine how many times we try
* to get the rendition of a file for preview, or printing.
*/
maxRetries = 5;
/**
* Mime-type grouping based on the ViewerComponent.
*/
private mimeTypes = {
text: ['text/plain', 'text/csv', 'text/xml', 'text/html', 'application/x-javascript'],
pdf: ['application/pdf'],
image: ['image/png', 'image/jpeg', 'image/gif', 'image/bmp', 'image/svg+xml'],
media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/ogg', 'audio/wav']
};
/**
* Timeout used for setInterval.
*/
private TRY_TIMEOUT: number = 10000;
_renditionsApi: RenditionsApi;
get renditionsApi(): RenditionsApi {
this._renditionsApi = this._renditionsApi ?? new RenditionsApi(this.apiService.getInstance());
return this._renditionsApi;
}
_contentApi: ContentApi;
get contentApi(): ContentApi {
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
return this._contentApi;
}
_versionsApi: VersionsApi;
private DEFAULT_RENDITION: string = 'imgpreview';
get versionsApi(): VersionsApi {
this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance());
return this._versionsApi;
}
constructor(private apiService: AlfrescoApiService,
private logService: LogService,
private translateService: TranslationService) {
}
getRenditionUrl(nodeId: string, type: string, renditionExists: boolean): string {
return (renditionExists && type !== RenditionViewerService.ContentGroup.IMAGE) ?
this.contentApi.getRenditionUrl(nodeId, RenditionViewerService.ContentGroup.PDF) :
this.contentApi.getContentUrl(nodeId, false);
}
private async waitRendition(nodeId: string, renditionId: string, retries: number): Promise<RenditionEntry> {
const rendition = await this.renditionsApi.getRendition(nodeId, renditionId);
if (this.maxRetries < retries) {
const status = rendition.entry.status.toString();
if (status === 'CREATED') {
return rendition;
} else {
retries += 1;
await this.wait(1000);
return this.waitRendition(nodeId, renditionId, retries);
}
}
return Promise.resolve(null);
}
getViewerTypeByMimeType(mimeType: string): string {
if (mimeType) {
mimeType = mimeType.toLowerCase();
const editorTypes = Object.keys(this.mimeTypes);
for (const type of editorTypes) {
if (this.mimeTypes[type].indexOf(mimeType) >= 0) {
return type;
}
}
}
return 'unknown';
}
private wait(ms: number): Promise<any> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async getRendition(nodeId: string, renditionId: string): Promise<RenditionEntry> {
const renditionPaging: RenditionPaging = await this.renditionsApi.listRenditions(nodeId);
let rendition: RenditionEntry = renditionPaging.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
if (rendition) {
const status = rendition.entry.status.toString();
if (status === 'NOT_CREATED') {
try {
await this.renditionsApi.createRendition(nodeId, {id: renditionId});
rendition = await this.waitRendition(nodeId, renditionId, 0);
} catch (err) {
this.logService.error(err);
}
}
}
return new Promise<RenditionEntry>((resolve) => resolve(rendition));
}
async getNodeRendition(nodeId: string, versionId?: string): Promise<{ url: string, viewerType: string }> {
try {
return versionId ? await this.resolveNodeRendition(nodeId, 'pdf', versionId) :
await this.resolveNodeRendition(nodeId, 'pdf');
} catch (err) {
this.logService.error(err);
return null;
}
}
private async resolveNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<{ url: string, viewerType: string }> {
renditionId = renditionId.toLowerCase();
const supportedRendition: RenditionPaging = versionId ? await this.versionsApi.listVersionRenditions(nodeId, versionId) :
await this.renditionsApi.listRenditions(nodeId);
let rendition = this.findRenditionById(supportedRendition, renditionId);
if (!rendition) {
renditionId = this.DEFAULT_RENDITION;
rendition = this.findRenditionById(supportedRendition, this.DEFAULT_RENDITION);
}
if (rendition) {
const status: string = rendition.entry.status.toString();
if (status === 'NOT_CREATED') {
return this.requestCreateRendition(nodeId, renditionId, versionId);
} else {
return this.handleNodeRendition(nodeId, renditionId, versionId);
}
}
return null;
}
private async requestCreateRendition(nodeId: string, renditionId: string, versionId: string) {
try {
if (versionId) {
await this.versionsApi.createVersionRendition(nodeId, versionId, {id: renditionId})
} else {
await this.renditionsApi.createRendition(nodeId, {id: renditionId});
}
try {
return versionId ? await this.waitNodeRendition(nodeId, renditionId, versionId) : await this.waitNodeRendition(nodeId, renditionId);
} catch (e) {
return null;
}
} catch (err) {
this.logService.error(err);
return null;
}
}
private findRenditionById(supportedRendition: RenditionPaging, renditionId: string) {
let rendition: RenditionEntry = supportedRendition.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
return rendition;
}
private async waitNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<{ url: string, viewerType: string }> {
let currentRetry: number = 0;
return new Promise<{ url: string, viewerType: string }>((resolve, reject) => {
const intervalId = setInterval(() => {
currentRetry++;
if (this.maxRetries >= currentRetry) {
if (versionId) {
this.versionsApi.getVersionRendition(nodeId, versionId, renditionId).then((rendition: RenditionEntry) => {
const status: string = rendition.entry.status.toString();
if (status === 'CREATED') {
clearInterval(intervalId);
return resolve(this.handleNodeRendition(nodeId, renditionId, versionId));
}
}, () => reject());
} else {
this.renditionsApi.getRendition(nodeId, renditionId).then((rendition: RenditionEntry) => {
const status: string = rendition.entry.status.toString();
if (status === 'CREATED') {
clearInterval(intervalId);
return resolve(this.handleNodeRendition(nodeId, renditionId, versionId));
}
}, () => reject());
}
} else {
clearInterval(intervalId);
return reject();
}
}, this.TRY_TIMEOUT);
});
}
private async handleNodeRendition(nodeId: string, renditionId: string, versionId?: string): Promise<{ url: string, viewerType: string }> {
let viewerType = '';
if (renditionId === 'pdf') {
viewerType = 'pdf';
} else if (renditionId === 'imgpreview') {
viewerType = 'image';
}
const url = versionId ? this.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
this.contentApi.getRenditionUrl(nodeId, renditionId);
return {url, viewerType};
}
async generateMediaTracksRendition(nodeId: string): Promise<Track[]> {
return this.isRenditionAvailable(nodeId, RenditionViewerService.SUBTITLES_RENDITION_NAME)
.then((value) => {
const tracks = [];
if (value) {
tracks.push({
kind: 'subtitles',
src: this.contentApi.getRenditionUrl(nodeId, RenditionViewerService.SUBTITLES_RENDITION_NAME),
label: this.translateService.instant('ADF_VIEWER.SUBTITLES')
});
}
return tracks;
})
.catch((err) => {
this.logService.error('Error while retrieving ' + RenditionViewerService.SUBTITLES_RENDITION_NAME + ' rendition');
this.logService.error(err);
return [];
});
}
private async isRenditionAvailable(nodeId: string, renditionId: string): Promise<boolean> {
const renditionPaging: RenditionPaging = await this.renditionsApi.listRenditions(nodeId);
const rendition: RenditionEntry = renditionPaging.list.entries.find((renditionEntry: RenditionEntry) => renditionEntry.entry.id.toLowerCase() === renditionId);
return rendition?.entry?.status?.toString() === 'CREATED' || false;
}
}

View File

@@ -17,7 +17,7 @@
import { Injectable } from '@angular/core';
import { LogService } from '../../services/log.service';
import { RenditionViewerService } from "./rendition-viewer.service";
import { RenditionViewerService } from "../../../../../content-services/src/lib/viewer/services/rendition-viewer.service";
import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
@Injectable({

View File

@@ -38,12 +38,11 @@ import { ViewerMoreActionsComponent } from './components/viewer-more-actions.com
import { ViewerOpenWithComponent } from './components/viewer-open-with.component';
import { ViewerSidebarComponent } from './components/viewer-sidebar.component';
import { ViewerToolbarComponent } from './components/viewer-toolbar.component';
import { ViewerComponent } from './components/viewer.component';
import { ViewerRenderComponent } from './components/viewer-render.component';
import { ViewerExtensionDirective } from './directives/viewer-extension.directive';
import { ViewerToolbarActionsComponent } from './components/viewer-toolbar-actions.component';
import { DirectiveModule } from '../directives/directive.module';
import { A11yModule } from '@angular/cdk/a11y';
import { AlfrescoViewerComponent } from "./components/alfresco-viewer.component";
@NgModule({
imports: [
@@ -61,8 +60,7 @@ import { AlfrescoViewerComponent } from "./components/alfresco-viewer.component"
],
declarations: [
PdfPasswordDialogComponent,
ViewerComponent,
AlfrescoViewerComponent,
ViewerRenderComponent,
ImgViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,
@@ -78,8 +76,7 @@ import { AlfrescoViewerComponent } from "./components/alfresco-viewer.component"
ViewerToolbarActionsComponent
],
exports: [
ViewerComponent,
AlfrescoViewerComponent,
ViewerRenderComponent,
ImgViewerComponent,
TxtViewerComponent,
MediaPlayerComponent,