mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2025-07-31 17:38:48 +00:00
refactor version 1 many todo
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
<adf-viewer
|
||||
[overlayMode]="true"
|
||||
<adf-alfresco-viewer
|
||||
[nodeId]="nodeId">
|
||||
</adf-viewer>
|
||||
</adf-alfresco-viewer>
|
||||
|
@@ -320,102 +320,106 @@
|
||||
</adf-info-drawer>
|
||||
</ng-template>
|
||||
|
||||
<adf-viewer
|
||||
[blobFile]="content"
|
||||
<adf-alfresco-viewer
|
||||
[nodeId]="nodeId"
|
||||
[versionId]="versionId"
|
||||
[showRightSidebar]="showRightSidebar"
|
||||
[showLeftSidebar]="showLeftSidebar"
|
||||
[allowGoBack]="allowGoBack"
|
||||
[displayName]="displayName"
|
||||
[showToolbar]="showToolbar"
|
||||
[allowPrint]="allowPrint"
|
||||
[allowDownload]="allowDownload"
|
||||
[allowRightSidebar]="allowRightSidebar"
|
||||
[allowLeftSidebar]="allowLeftSidebar"
|
||||
[urlFile]="urlFile"
|
||||
(showViewerChange)="onViewerVisibilityChanged()"
|
||||
[sidebarLeftTemplate]="sidebarLeftTemplate"
|
||||
[sidebarRightTemplate]="sidebarRightTemplate">
|
||||
></adf-alfresco-viewer>
|
||||
|
||||
<adf-viewer-toolbar *ngIf="customToolbar" data-automation-id="adf-viewer-custom-toolbar">
|
||||
<h1>My custom toolbar</h1>
|
||||
</adf-viewer-toolbar>
|
||||
<!-- <adf-viewer-->
|
||||
<!-- [blobFile]="content"-->
|
||||
<!-- [nodeId]="nodeId"-->
|
||||
<!-- [versionId]="versionId"-->
|
||||
<!-- [showRightSidebar]="showRightSidebar"-->
|
||||
<!-- [showLeftSidebar]="showLeftSidebar"-->
|
||||
<!-- [allowGoBack]="allowGoBack"-->
|
||||
<!-- [displayName]="displayName"-->
|
||||
<!-- [showToolbar]="showToolbar"-->
|
||||
<!-- [allowPrint]="allowPrint"-->
|
||||
<!-- [allowDownload]="allowDownload"-->
|
||||
<!-- [allowRightSidebar]="allowRightSidebar"-->
|
||||
<!-- [allowLeftSidebar]="allowLeftSidebar"-->
|
||||
<!-- [urlFile]="urlFile"-->
|
||||
<!-- (showViewerChange)="onViewerVisibilityChanged()"-->
|
||||
<!-- [sidebarLeftTemplate]="sidebarLeftTemplate"-->
|
||||
<!-- [sidebarRightTemplate]="sidebarRightTemplate">-->
|
||||
|
||||
<adf-viewer-toolbar-actions *ngIf="moreActions">
|
||||
<button mat-icon-button id="adf-viewer-time">
|
||||
<mat-icon>alarm</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button id="adf-viewer-upload">
|
||||
<mat-icon>backup</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button id="adf-viewer-bug">
|
||||
<mat-icon>bug_report</mat-icon>
|
||||
</button>
|
||||
</adf-viewer-toolbar-actions>
|
||||
<!-- <adf-viewer-toolbar *ngIf="customToolbar" data-automation-id="adf-viewer-custom-toolbar">-->
|
||||
<!-- <h1>My custom toolbar</h1>-->
|
||||
<!-- </adf-viewer-toolbar>-->
|
||||
|
||||
<adf-viewer-more-actions *ngIf="moreActionsMenu">
|
||||
<button mat-menu-item id="adf-viewer-more-menu-alarm">
|
||||
<mat-icon>alarm</mat-icon>
|
||||
<span>Alarm</span>
|
||||
</button>
|
||||
<button mat-menu-item id="adf-viewer-more-menu-backup">
|
||||
<mat-icon>backup</mat-icon>
|
||||
<span>Backup</span>
|
||||
</button>
|
||||
<button mat-menu-item id="adf-viewer-more-menu-bug">
|
||||
<mat-icon>bug_report</mat-icon>
|
||||
<span>Bug report</span>
|
||||
</button>
|
||||
</adf-viewer-more-actions>
|
||||
<!-- <adf-viewer-toolbar-actions *ngIf="moreActions">-->
|
||||
<!-- <button mat-icon-button id="adf-viewer-time">-->
|
||||
<!-- <mat-icon>alarm</mat-icon>-->
|
||||
<!-- </button>-->
|
||||
<!-- <button mat-icon-button id="adf-viewer-upload">-->
|
||||
<!-- <mat-icon>backup</mat-icon>-->
|
||||
<!-- </button>-->
|
||||
<!-- <button mat-icon-button id="adf-viewer-bug">-->
|
||||
<!-- <mat-icon>bug_report</mat-icon>-->
|
||||
<!-- </button>-->
|
||||
<!-- </adf-viewer-toolbar-actions>-->
|
||||
|
||||
<ng-container *ngIf="openWith">
|
||||
<adf-viewer-open-with>
|
||||
<button mat-menu-item>
|
||||
<mat-icon>dialpad</mat-icon>
|
||||
<span>Option 1</span>
|
||||
</button>
|
||||
<button mat-menu-item disabled>
|
||||
<mat-icon>voicemail</mat-icon>
|
||||
<span>Option 2</span>
|
||||
</button>
|
||||
<button mat-menu-item>
|
||||
<mat-icon>notifications_off</mat-icon>
|
||||
<span>Option 3</span>
|
||||
</button>
|
||||
</adf-viewer-open-with>
|
||||
</ng-container>
|
||||
<!-- <adf-viewer-more-actions *ngIf="moreActionsMenu">-->
|
||||
<!-- <button mat-menu-item id="adf-viewer-more-menu-alarm">-->
|
||||
<!-- <mat-icon>alarm</mat-icon>-->
|
||||
<!-- <span>Alarm</span>-->
|
||||
<!-- </button>-->
|
||||
<!-- <button mat-menu-item id="adf-viewer-more-menu-backup">-->
|
||||
<!-- <mat-icon>backup</mat-icon>-->
|
||||
<!-- <span>Backup</span>-->
|
||||
<!-- </button>-->
|
||||
<!-- <button mat-menu-item id="adf-viewer-more-menu-bug">-->
|
||||
<!-- <mat-icon>bug_report</mat-icon>-->
|
||||
<!-- <span>Bug report</span>-->
|
||||
<!-- </button>-->
|
||||
<!-- </adf-viewer-more-actions>-->
|
||||
|
||||
<!--
|
||||
<adf-viewer-extension [supportedExtensions]="['json']">
|
||||
<ng-template let-urlFileContent="urlFileContent" let-extension="extension">
|
||||
<h1>JSON VIEWER</h1>
|
||||
</ng-template>
|
||||
</adf-viewer-extension>
|
||||
-->
|
||||
<!-- <ng-container *ngIf="openWith">-->
|
||||
<!-- <adf-viewer-open-with>-->
|
||||
<!-- <button mat-menu-item>-->
|
||||
<!-- <mat-icon>dialpad</mat-icon>-->
|
||||
<!-- <span>Option 1</span>-->
|
||||
<!-- </button>-->
|
||||
<!-- <button mat-menu-item disabled>-->
|
||||
<!-- <mat-icon>voicemail</mat-icon>-->
|
||||
<!-- <span>Option 2</span>-->
|
||||
<!-- </button>-->
|
||||
<!-- <button mat-menu-item>-->
|
||||
<!-- <mat-icon>notifications_off</mat-icon>-->
|
||||
<!-- <span>Option 3</span>-->
|
||||
<!-- </button>-->
|
||||
<!-- </adf-viewer-open-with>-->
|
||||
<!-- </ng-container>-->
|
||||
|
||||
<!--
|
||||
<adf-viewer-extension [supportedExtensions]="['png']">
|
||||
<ng-template>
|
||||
<h1>PNG Viewer</h1>
|
||||
</ng-template>
|
||||
</adf-viewer-extension>
|
||||
-->
|
||||
<!-- <!–-->
|
||||
<!-- <adf-viewer-extension [supportedExtensions]="['json']">-->
|
||||
<!-- <ng-template let-urlFileContent="urlFileContent" let-extension="extension">-->
|
||||
<!-- <h1>JSON VIEWER</h1>-->
|
||||
<!-- </ng-template>-->
|
||||
<!-- </adf-viewer-extension>-->
|
||||
<!-- –>-->
|
||||
|
||||
<!--
|
||||
<adf-viewer-extension [supportedExtensions]="['pdf']">
|
||||
<ng-template>
|
||||
<h1>PDF Viewer</h1>
|
||||
</ng-template>
|
||||
</adf-viewer-extension>
|
||||
-->
|
||||
<!-- <!–-->
|
||||
<!-- <adf-viewer-extension [supportedExtensions]="['png']">-->
|
||||
<!-- <ng-template>-->
|
||||
<!-- <h1>PNG Viewer</h1>-->
|
||||
<!-- </ng-template>-->
|
||||
<!-- </adf-viewer-extension>-->
|
||||
<!-- –>-->
|
||||
|
||||
<!--
|
||||
<extension-viewer [supportedExtensions]="['obj','3DS']" #extension>
|
||||
<ng-template let-urlFileContent="urlFileContent" let-extension="extension" >
|
||||
<threed-viewer [urlFile]="urlFileContent" [extension]="extension" ></threed-viewer>
|
||||
</ng-template>
|
||||
</extension-viewer>
|
||||
-->
|
||||
</adf-viewer>
|
||||
<!-- <!–-->
|
||||
<!-- <adf-viewer-extension [supportedExtensions]="['pdf']">-->
|
||||
<!-- <ng-template>-->
|
||||
<!-- <h1>PDF Viewer</h1>-->
|
||||
<!-- </ng-template>-->
|
||||
<!-- </adf-viewer-extension>-->
|
||||
<!-- –>-->
|
||||
|
||||
<!-- <!–-->
|
||||
<!-- <extension-viewer [supportedExtensions]="['obj','3DS']" #extension>-->
|
||||
<!-- <ng-template let-urlFileContent="urlFileContent" let-extension="extension" >-->
|
||||
<!-- <threed-viewer [urlFile]="urlFileContent" [extension]="extension" ></threed-viewer>-->
|
||||
<!-- </ng-template>-->
|
||||
<!-- </extension-viewer>-->
|
||||
<!-- –>-->
|
||||
<!-- </adf-viewer>-->
|
||||
</ng-container>
|
||||
|
@@ -15,8 +15,8 @@
|
||||
(preview)="showPreview($event)">
|
||||
</adf-document-list>
|
||||
|
||||
<adf-viewer
|
||||
[(showViewer)]="showViewer"
|
||||
[overlayMode]="isOverlay"
|
||||
[nodeId]="nodeId">
|
||||
</adf-viewer>
|
||||
<!--<adf-viewer-->
|
||||
<!-- [(showViewer)]="showViewer"-->
|
||||
<!-- [overlayMode]="isOverlay"-->
|
||||
<!-- [nodeId]="nodeId">-->
|
||||
<!--</adf-viewer>-->
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<ng-container *ngIf="sharedLinkId">
|
||||
<adf-viewer
|
||||
<adf-alfresco-viewer
|
||||
(invalidSharedLink)="redirectTo404()"
|
||||
[sharedLinkId]="sharedLinkId"
|
||||
[allowGoBack]="false">
|
||||
</adf-viewer>
|
||||
</adf-alfresco-viewer>
|
||||
</ng-container>
|
||||
|
@@ -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" [nodeId]="field.value" [showViewer]="field.value" [allowGoBack]="false"></adf-viewer>
|
||||
<adf-viewer [overlayMode]="false" [urlFile]="field.value" [showViewer]="field.value" [allowGoBack]="false"></adf-viewer>
|
||||
<error-widget [error]="field.validationSummary"></error-widget>
|
||||
</div>
|
||||
|
42
lib/core/src/lib/services/url.service.ts
Normal file
42
lib/core/src/lib/services/url.service.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
/*!
|
||||
* @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 { DomSanitizer } from '@angular/platform-browser';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class UrlService {
|
||||
|
||||
constructor(private sanitizer: DomSanitizer) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a trusted object URL from the Blob.
|
||||
* WARNING: calling this method with untrusted user data exposes your application to XSS security risks!
|
||||
*
|
||||
* @param blob Data to wrap into object URL
|
||||
* @returns URL string
|
||||
*/
|
||||
createTrustedUrl(blob: Blob): string {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
return this.sanitizer.bypassSecurityTrustUrl(url) as string;
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -0,0 +1,149 @@
|
||||
<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>
|
409
lib/core/src/lib/viewer/components/alfresco-viewer.component.ts
Normal file
409
lib/core/src/lib/viewer/components/alfresco-viewer.component.ts
Normal file
@@ -0,0 +1,409 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@@ -1,5 +1,5 @@
|
||||
<div id="adf-image-container" (keydown)="onKeyDown($event)" class="adf-image-container" tabindex="0" role="img" [attr.aria-label]="nameFile" data-automation-id="adf-image-container">
|
||||
<img #image id="viewer-image" [src]="urlFile" [alt]="nameFile" (error)="onImageError()" />
|
||||
<div id="adf-image-container" (keydown)="onKeyDown($event)" class="adf-image-container" tabindex="0" role="img" [attr.aria-label]="fileName" data-automation-id="adf-image-container">
|
||||
<img #image id="viewer-image" [src]="urlFile" [alt]="fileName" (error)="onImageError()" />
|
||||
</div>
|
||||
|
||||
<div class="adf-image-viewer__toolbar" *ngIf="showToolbar">
|
||||
|
@@ -25,8 +25,8 @@ import {
|
||||
Output,
|
||||
EventEmitter, AfterViewInit, ViewChild, HostListener, OnDestroy
|
||||
} from '@angular/core';
|
||||
import { ContentService } from '../../services/content.service';
|
||||
import { AppConfigService } from '../../app-config/app-config.service';
|
||||
import { UrlService } from "../../services/url.service";
|
||||
import Cropper from 'cropperjs';
|
||||
|
||||
@Component({
|
||||
@@ -51,7 +51,7 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||
blobFile: Blob;
|
||||
|
||||
@Input()
|
||||
nameFile: string;
|
||||
fileName: string;
|
||||
|
||||
// eslint-disable-next-line @angular-eslint/no-output-native
|
||||
@Output()
|
||||
@@ -74,7 +74,7 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||
|
||||
constructor(
|
||||
private appConfigService: AppConfigService,
|
||||
private contentService: ContentService) {
|
||||
private urlService: UrlService) {
|
||||
this.initializeScaling();
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ export class ImgViewerComponent implements AfterViewInit, OnChanges, OnDestroy {
|
||||
ngOnChanges(changes: SimpleChanges) {
|
||||
const blobFile = changes['blobFile'];
|
||||
if (blobFile && blobFile.currentValue) {
|
||||
this.urlFile = this.contentService.createTrustedUrl(this.blobFile);
|
||||
this.urlFile = this.urlService.createTrustedUrl(this.blobFile);
|
||||
return;
|
||||
}
|
||||
if (!this.urlFile && !this.blobFile) {
|
||||
|
@@ -18,7 +18,6 @@
|
||||
import { Component, Input, OnChanges, SimpleChanges, ViewEncapsulation, Output, EventEmitter } from '@angular/core';
|
||||
import { ContentService } from '../../services/content.service';
|
||||
import { Track } from '../models/viewer.model';
|
||||
import { ViewUtilService } from '../services/view-util.service';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-media-player',
|
||||
@@ -39,10 +38,10 @@ export class MediaPlayerComponent implements OnChanges {
|
||||
mimeType: string;
|
||||
|
||||
@Input()
|
||||
nameFile: string;
|
||||
|
||||
@Input()
|
||||
nodeId: string;
|
||||
fileName: string;
|
||||
//
|
||||
// @Input()
|
||||
// nodeId: string;
|
||||
|
||||
@Input()
|
||||
tracks: Track[] = [];
|
||||
@@ -50,21 +49,21 @@ export class MediaPlayerComponent implements OnChanges {
|
||||
@Output()
|
||||
error = new EventEmitter<any>();
|
||||
|
||||
constructor(private contentService: ContentService, private viewUtils: ViewUtilService) {
|
||||
constructor(private contentService: ContentService) {
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
if (nodeId && nodeId.currentValue) {
|
||||
this.viewUtils.generateMediaTracks(this.nodeId).then((tracks) => this.tracks = tracks);
|
||||
}
|
||||
// if (nodeId && nodeId.currentValue) {
|
||||
// this.viewUtils.generateMediaTracksRendition(this.nodeId).then((tracks) => this.tracks = tracks);
|
||||
// }
|
||||
|
||||
if (!this.urlFile && !this.blobFile) {
|
||||
throw new Error('Attribute urlFile or blobFile is required');
|
||||
|
@@ -60,7 +60,7 @@ export class PdfViewerComponent implements OnChanges, OnDestroy {
|
||||
blobFile: Blob;
|
||||
|
||||
@Input()
|
||||
nameFile: string;
|
||||
fileName: string;
|
||||
|
||||
@Input()
|
||||
showToolbar: boolean = true;
|
||||
|
@@ -8,141 +8,6 @@
|
||||
[cdkTrapFocus]="overlayMode"
|
||||
cdkTrapFocusAutoCapture>
|
||||
<ng-content select="adf-viewer-toolbar"></ng-content>
|
||||
<ng-container *ngIf="showToolbar && !toolbar">
|
||||
<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">{{ fileTitle }}</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>
|
||||
|
||||
<div fxLayout="row"
|
||||
fxFlex="1 1 auto">
|
||||
@@ -151,10 +16,10 @@
|
||||
[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-container *ngIf="sidebarRightTemplate">-->
|
||||
<!-- <ng-container *ngTemplateOutlet="sidebarRightTemplate;context:sidebarRightTemplateContext">-->
|
||||
<!-- </ng-container>-->
|
||||
<!-- </ng-container>-->
|
||||
<ng-content *ngIf="!sidebarRightTemplate"
|
||||
select="adf-viewer-sidebar"></ng-content>
|
||||
</div>
|
||||
@@ -165,10 +30,10 @@
|
||||
[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-container *ngIf="sidebarLeftTemplate">-->
|
||||
<!-- <ng-container *ngTemplateOutlet="sidebarLeftTemplate;context:sidebarLeftTemplateContext">-->
|
||||
<!-- </ng-container>-->
|
||||
<!-- </ng-container>-->
|
||||
<ng-content *ngIf="!sidebarLeftTemplate"
|
||||
select="adf-viewer-sidebar"></ng-content>
|
||||
</div>
|
||||
@@ -201,14 +66,14 @@
|
||||
<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>
|
||||
<!-- <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'">
|
||||
@@ -217,16 +82,15 @@
|
||||
[allowThumbnails]="allowThumbnails"
|
||||
[blobFile]="blobFile"
|
||||
[urlFile]="urlFileContent"
|
||||
[nameFile]="displayName"
|
||||
[fileName]="fileName"
|
||||
[cacheType]="cacheTypeForContent"
|
||||
(error)="onUnsupportedFile()"></adf-pdf-viewer>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'image'">
|
||||
<adf-img-viewer [urlFile]="urlFileContent"
|
||||
[nameFile]="displayName || fileName"
|
||||
[fileName]="fileName"
|
||||
[blobFile]="blobFile"
|
||||
[readOnly]="readOnly"
|
||||
(error)="onUnsupportedFile()"
|
||||
(submit)="onSubmitFile($event)"
|
||||
></adf-img-viewer>
|
||||
@@ -235,10 +99,9 @@
|
||||
<ng-container *ngSwitchCase="'media'">
|
||||
<adf-media-player id="adf-mdedia-player"
|
||||
[urlFile]="urlFileContent"
|
||||
[nodeId]="nodeEntry?.entry?.id"
|
||||
[mimeType]="mimeType"
|
||||
[blobFile]="blobFile"
|
||||
[nameFile]="displayName"
|
||||
[fileName]="fileName"
|
||||
(error)="onUnsupportedFile()"></adf-media-player>
|
||||
</ng-container>
|
||||
|
||||
@@ -247,25 +110,15 @@
|
||||
[blobFile]="blobFile"></adf-txt-viewer>
|
||||
</ng-container>
|
||||
|
||||
<ng-container *ngSwitchCase="'in_creation'">
|
||||
<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>
|
||||
|
||||
<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>
|
||||
<!-- <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"
|
||||
|
@@ -15,23 +15,21 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
//TODO BETTER APPROACH FOR IMG EXTENSION submit
|
||||
//TODO uncomment readOnly
|
||||
//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 null propagation
|
||||
//TODO viewer widget specialization in process service cloud
|
||||
|
||||
import {
|
||||
Component, ContentChild, EventEmitter, HostListener, ElementRef,
|
||||
Input, OnChanges, Output, TemplateRef,
|
||||
ViewEncapsulation, OnInit, OnDestroy, ChangeDetectorRef
|
||||
ViewEncapsulation, OnInit, OnDestroy
|
||||
} from '@angular/core';
|
||||
import {
|
||||
SharedLinkEntry,
|
||||
Node,
|
||||
Version,
|
||||
RenditionEntry,
|
||||
NodeEntry,
|
||||
VersionEntry,
|
||||
SharedlinksApi, VersionsApi, NodesApi, ContentApi
|
||||
} from '@alfresco/js-api';
|
||||
import { BaseEvent } from '../../events';
|
||||
import { AlfrescoApiService } from '../../services/alfresco-api.service';
|
||||
import { LogService } from '../../services/log.service';
|
||||
import { ViewerMoreActionsComponent } from './viewer-more-actions.component';
|
||||
import { ViewerOpenWithComponent } from './viewer-open-with.component';
|
||||
import { ViewerSidebarComponent } from './viewer-sidebar.component';
|
||||
@@ -41,15 +39,12 @@ 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';
|
||||
import { ContentService } from '../../services/content.service';
|
||||
import { UploadService } from '../../services/upload.service';
|
||||
import { FileModel } from '../../models';
|
||||
|
||||
@Component({
|
||||
selector: 'adf-viewer',
|
||||
templateUrl: './viewer.component.html',
|
||||
styleUrls: ['./viewer.component.scss'],
|
||||
host: { class: 'adf-viewer' },
|
||||
host: {class: 'adf-viewer'},
|
||||
encapsulation: ViewEncapsulation.None,
|
||||
providers: [ViewUtilService]
|
||||
})
|
||||
@@ -73,28 +68,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input()
|
||||
urlFile = '';
|
||||
|
||||
/** Viewer to use with the `urlFile` address (`pdf`, `image`, `media`, `text`).
|
||||
* Used when `urlFile` has no filename and extension.
|
||||
*/
|
||||
@Input()
|
||||
urlFileViewer: string = null;
|
||||
|
||||
/** Loads a Blob File */
|
||||
@Input()
|
||||
blobFile: Blob;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** If `true` then show the Viewer as a full page over the current content.
|
||||
* Otherwise fit inside the parent div.
|
||||
*/
|
||||
@@ -105,19 +82,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input()
|
||||
showViewer = true;
|
||||
|
||||
/** Hide or show the toolbar */
|
||||
@Input()
|
||||
showToolbar = true;
|
||||
|
||||
/** Specifies the name of the file when it is not available from the URL. */
|
||||
@Input()
|
||||
displayName: string;
|
||||
|
||||
/** @deprecated 3.2.0 */
|
||||
/** Allows `back` navigation */
|
||||
@Input()
|
||||
allowGoBack = true;
|
||||
|
||||
/** Toggles downloading. */
|
||||
@Input()
|
||||
allowDownload = true;
|
||||
@@ -130,40 +94,10 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input()
|
||||
allowFullScreen = true;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** Toggles PDF thumbnails. */
|
||||
@Input()
|
||||
allowThumbnails = true;
|
||||
|
||||
/** 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;
|
||||
|
||||
/** The template for the right sidebar. The template context contains the loaded node data. */
|
||||
@Input()
|
||||
sidebarRightTemplate: TemplateRef<any> = null;
|
||||
@@ -180,15 +114,22 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Input()
|
||||
mimeType: string;
|
||||
|
||||
/** Content filename. */
|
||||
/** Override Content filename. */
|
||||
@Input()
|
||||
fileName: string;
|
||||
|
||||
/** Number of times the Viewer will retry fetching content Rendition.
|
||||
* There is a delay of at least one second between attempts.
|
||||
*/
|
||||
/** Override Content view type.
|
||||
Viewer to use with the `urlFile` address (`pdf`, `image`, `media`, `text`).*/
|
||||
@Input()
|
||||
maxRetries = 30;
|
||||
viewerType: string = 'unknown';
|
||||
|
||||
/** Allows `back` navigation */
|
||||
@Input()
|
||||
allowGoBack = true;
|
||||
|
||||
/** Override loading status */
|
||||
@Input()
|
||||
isLoading = false;
|
||||
|
||||
/** Emitted when user clicks the 'Back' button. */
|
||||
@Output()
|
||||
@@ -206,29 +147,16 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
@Output()
|
||||
extensionChange = new EventEmitter<string>();
|
||||
|
||||
/** Emitted when user clicks 'Navigate Before' ("<") button. */
|
||||
/** Emitted when the img is submitted in the img viewer. */
|
||||
@Output()
|
||||
navigateBefore = new EventEmitter<MouseEvent | KeyboardEvent>();
|
||||
|
||||
/** Emitted when user clicks 'Navigate Next' (">") button. */
|
||||
@Output()
|
||||
navigateNext = new EventEmitter<MouseEvent | KeyboardEvent>();
|
||||
|
||||
/** Emitted when the shared link used is not valid. */
|
||||
@Output()
|
||||
invalidSharedLink = new EventEmitter();
|
||||
|
||||
viewerType = 'unknown';
|
||||
isLoading = false;
|
||||
nodeEntry: NodeEntry;
|
||||
versionEntry: VersionEntry;
|
||||
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 };
|
||||
// sidebarRightTemplateContext: { node: Node } = { node: null };
|
||||
// sidebarLeftTemplateContext: { node: Node } = { node: null };
|
||||
fileTitle: string;
|
||||
|
||||
/**
|
||||
@@ -254,116 +182,32 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
return this._externalViewer;
|
||||
}
|
||||
|
||||
readOnly = true;
|
||||
// readOnly = true;
|
||||
|
||||
private cacheBusterNumber: number;
|
||||
cacheTypeForContent = '';
|
||||
|
||||
// Extensions that are supported by the Viewer without conversion
|
||||
private extensions = {
|
||||
image: ['png', 'jpg', 'jpeg', 'gif', 'bpm', 'svg'],
|
||||
media: ['wav', 'mp4', 'mp3', 'webm', 'ogg'],
|
||||
text: ['txt', 'xml', 'html', 'json', 'ts', 'css', 'md'],
|
||||
pdf: ['pdf']
|
||||
};
|
||||
|
||||
// Mime types that are supported by the Viewer without conversion
|
||||
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/mp3', 'audio/ogg', 'audio/wav']
|
||||
};
|
||||
|
||||
private onDestroy$ = new Subject<boolean>();
|
||||
private shouldCloseViewer = true;
|
||||
private keyDown$ = fromEvent<KeyboardEvent>(document, 'keydown');
|
||||
|
||||
_sharedLinksApi: SharedlinksApi;
|
||||
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 viewUtilService: ViewUtilService,
|
||||
private logService: LogService,
|
||||
constructor(private viewUtilService: ViewUtilService,
|
||||
private extensionService: AppExtensionService,
|
||||
private contentService: ContentService,
|
||||
private uploadService: UploadService,
|
||||
private el: ElementRef,
|
||||
public dialog: MatDialog,
|
||||
private cdr: ChangeDetectorRef) {
|
||||
viewUtilService.maxRetries = this.maxRetries;
|
||||
}
|
||||
|
||||
isSourceDefined(): boolean {
|
||||
return !!(this.urlFile || this.blobFile || this.nodeId || this.sharedLinkId);
|
||||
public dialog: MatDialog) {
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
this.viewUtilService.viewerTypeChange.pipe(takeUntil(this.onDestroy$)).subscribe((type: string) => {
|
||||
this.viewerType = type;
|
||||
});
|
||||
|
||||
this.viewUtilService.urlFileContentChange.pipe(takeUntil(this.onDestroy$)).subscribe((content: string) => {
|
||||
this.urlFileContent = content;
|
||||
});
|
||||
|
||||
this.closeOverlayManager();
|
||||
this.cacheTypeForContent = '';
|
||||
}
|
||||
|
||||
private getNodeVersionProperty(node: Node): string {
|
||||
return node?.properties['cm:versionLabel'] ?? '';
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.onDestroy$.next(true);
|
||||
this.onDestroy$.complete();
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
ngOnChanges() {
|
||||
if (this.showViewer) {
|
||||
if (!this.isSourceDefined()) {
|
||||
throw new Error('A content source attribute value is missing.');
|
||||
}
|
||||
this.isLoading = true;
|
||||
|
||||
if (this.blobFile) {
|
||||
@@ -372,60 +216,13 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
} else if (this.urlFile) {
|
||||
this.setUpUrlFile();
|
||||
this.isLoading = false;
|
||||
} else if (this.nodeId) {
|
||||
this.setupNode();
|
||||
} else if (this.sharedLinkId) {
|
||||
this.setupSharedLink();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 setUpBlobData() {
|
||||
this.fileTitle = this.getDisplayName('Unknown');
|
||||
this.mimeType = this.blobFile.type;
|
||||
this.viewerType = this.getViewerTypeByMimeType(this.mimeType);
|
||||
this.viewerType = this.viewUtilService.getViewerTypeByMimeType(this.mimeType);
|
||||
|
||||
this.allowDownload = false;
|
||||
// TODO: wrap blob into the data url and allow downloading
|
||||
@@ -435,164 +232,24 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private setUpUrlFile() {
|
||||
const filenameFromUrl = this.getFilenameFromUrl(this.urlFile);
|
||||
this.fileTitle = this.getDisplayName(filenameFromUrl);
|
||||
this.extension = this.getFileExtension(filenameFromUrl);
|
||||
this.fileName = this.fileName ? this.fileName : this.viewUtilService.getFilenameFromUrl(this.urlFile);
|
||||
this.extension = this.viewUtilService.getFileExtension(this.fileTitle);
|
||||
this.urlFileContent = this.urlFile;
|
||||
this.fileName = this.displayName;
|
||||
this.viewerType = this.urlFileViewer || this.getViewerType(this.extension, this.mimeType);
|
||||
this.viewerType = this.viewerType === 'unknown' ? this.viewUtilService.getViewerType(this.extension, this.mimeType) : this.viewerType;
|
||||
|
||||
this.extensionChange.emit(this.extension);
|
||||
this.scrollTop();
|
||||
}
|
||||
|
||||
private async setUpNodeFile(nodeData: Node, versionData?: Version): Promise<void> {
|
||||
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;
|
||||
}
|
||||
|
||||
this.fileTitle = this.getDisplayName(versionData ? versionData.name : nodeData.name);
|
||||
|
||||
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;
|
||||
|
||||
this.extension = this.getFileExtension(versionData ? versionData.name : nodeData.name);
|
||||
this.fileName = versionData ? versionData.name : nodeData.name;
|
||||
this.viewerType = this.getViewerType(this.extension, this.mimeType);
|
||||
|
||||
if (this.viewerType === 'unknown') {
|
||||
if (versionData) {
|
||||
await this.viewUtilService.displayNodeRendition(nodeData.id, versionData.id);
|
||||
} else {
|
||||
await this.viewUtilService.displayNodeRendition(nodeData.id);
|
||||
}
|
||||
}
|
||||
|
||||
this.extensionChange.emit(this.extension);
|
||||
this.sidebarRightTemplateContext.node = nodeData;
|
||||
this.sidebarLeftTemplateContext.node = nodeData;
|
||||
this.scrollTop();
|
||||
}
|
||||
|
||||
private getViewerType(extension: string, mimeType: string): string {
|
||||
let viewerType = this.getViewerTypeByExtension(extension);
|
||||
|
||||
if (viewerType === 'unknown') {
|
||||
viewerType = this.getViewerTypeByMimeType(mimeType);
|
||||
}
|
||||
|
||||
return viewerType;
|
||||
}
|
||||
|
||||
private setUpSharedLinkFile(details: any) {
|
||||
this.mimeType = details.entry.content.mimeType;
|
||||
this.fileTitle = this.getDisplayName(details.entry.name);
|
||||
this.extension = this.getFileExtension(details.entry.name);
|
||||
this.fileName = details.entry.name;
|
||||
this.urlFileContent = this.contentApi.getSharedLinkContentUrl(this.sharedLinkId, false);
|
||||
this.viewerType = this.getViewerType(this.extension, this.mimeType);
|
||||
|
||||
if (this.viewerType === 'unknown') {
|
||||
this.displaySharedLinkRendition(this.sharedLinkId);
|
||||
}
|
||||
|
||||
this.extensionChange.emit(this.extension);
|
||||
}
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private getDisplayName(name) {
|
||||
return this.displayName || name;
|
||||
}
|
||||
|
||||
scrollTop() {
|
||||
window.scrollTo(0, 1);
|
||||
}
|
||||
|
||||
getViewerTypeByMimeType(mimeType: 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';
|
||||
}
|
||||
|
||||
getViewerTypeByExtension(extension: string): string {
|
||||
if (extension) {
|
||||
extension = extension.toLowerCase();
|
||||
}
|
||||
|
||||
if (this.isExternalViewer()) {
|
||||
return 'external';
|
||||
}
|
||||
|
||||
if (this.isCustomViewerExtension(extension)) {
|
||||
return 'custom';
|
||||
}
|
||||
|
||||
if (this.extensions.image.indexOf(extension) >= 0) {
|
||||
return 'image';
|
||||
}
|
||||
|
||||
if (this.extensions.media.indexOf(extension) >= 0) {
|
||||
return 'media';
|
||||
}
|
||||
|
||||
if (this.extensions.text.indexOf(extension) >= 0) {
|
||||
return 'text';
|
||||
}
|
||||
|
||||
if (this.extensions.pdf.indexOf(extension) >= 0) {
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
onBackButtonClick() {
|
||||
this.close();
|
||||
}
|
||||
|
||||
onNavigateBeforeClick(event: MouseEvent | KeyboardEvent) {
|
||||
this.navigateBefore.next(event);
|
||||
}
|
||||
|
||||
onNavigateNextClick(event: MouseEvent | KeyboardEvent) {
|
||||
this.navigateNext.next(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* close the viewer
|
||||
@@ -605,50 +262,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
this.showViewerChange.emit(this.showViewer);
|
||||
}
|
||||
|
||||
/**
|
||||
* get File name from url
|
||||
*
|
||||
* @param url - url file
|
||||
*/
|
||||
getFilenameFromUrl(url: string): string {
|
||||
const anchor = url.indexOf('#');
|
||||
const query = url.indexOf('?');
|
||||
const end = Math.min(
|
||||
anchor > 0 ? anchor : url.length,
|
||||
query > 0 ? query : url.length);
|
||||
return url.substring(url.lastIndexOf('/', end) + 1, end);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file extension from the string.
|
||||
* Supports the URL formats like:
|
||||
* http://localhost/test.jpg?cache=1000
|
||||
* http://localhost/test.jpg#cache=1000
|
||||
*
|
||||
* @param fileName - file name
|
||||
*/
|
||||
getFileExtension(fileName: string): string {
|
||||
if (fileName) {
|
||||
const match = fileName.match(/\.([^\./\?\#]+)($|\?|\#)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private isExternalViewer(): boolean {
|
||||
return !!this.viewerExtensions.find(ext => ext.fileExtension === '*');
|
||||
}
|
||||
|
||||
isCustomViewerExtension(extension: string): boolean {
|
||||
const extensions = this.externalExtensions || [];
|
||||
|
||||
if (extension && extensions.length > 0) {
|
||||
extension = extension.toLowerCase();
|
||||
return extensions.flat().indexOf(extension) >= 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyboard event listener
|
||||
@@ -663,18 +276,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// Ctrl+F
|
||||
if (key === 70 && event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
@@ -684,12 +285,7 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
|
||||
printContent() {
|
||||
if (this.allowPrint) {
|
||||
const args = new BaseEvent();
|
||||
this.print.next(args);
|
||||
|
||||
if (!args.defaultPrevented) {
|
||||
this.viewUtilService.printFileGeneric(this.nodeId, this.mimeType);
|
||||
}
|
||||
this.print.next(new BaseEvent());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -713,27 +309,6 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
|
||||
private async displaySharedLinkRendition(sharedId: string) {
|
||||
try {
|
||||
const rendition: RenditionEntry = await this.sharedLinksApi.getSharedLinkRendition(sharedId, 'pdf');
|
||||
if (rendition.entry.status.toString() === 'CREATED') {
|
||||
this.viewerType = 'pdf';
|
||||
this.urlFileContent = this.contentApi.getSharedLinkRenditionUrl(sharedId, 'pdf');
|
||||
}
|
||||
} catch (error) {
|
||||
this.logService.error(error);
|
||||
try {
|
||||
const rendition: RenditionEntry = await this.sharedLinksApi.getSharedLinkRendition(sharedId, 'imgpreview');
|
||||
if (rendition.entry.status.toString() === 'CREATED') {
|
||||
this.viewerType = 'image';
|
||||
this.urlFileContent = this.contentApi.getSharedLinkRenditionUrl(sharedId, 'imgpreview');
|
||||
}
|
||||
} catch (renditionError) {
|
||||
this.logService.error(renditionError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
checkExtensions(extensionAllowed) {
|
||||
if (typeof extensionAllowed === 'string') {
|
||||
return this.extension.toLowerCase() === extensionAllowed.toLowerCase();
|
||||
@@ -743,21 +318,7 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
this.submitFile.next(newImageBlob);
|
||||
}
|
||||
|
||||
onUnsupportedFile() {
|
||||
@@ -788,7 +349,4 @@ export class ViewerComponent implements OnChanges, OnInit, OnDestroy {
|
||||
});
|
||||
}
|
||||
|
||||
private generateCacheBusterNumber() {
|
||||
this.cacheBusterNumber = Date.now();
|
||||
}
|
||||
}
|
||||
|
288
lib/core/src/lib/viewer/services/rendition-viewer.service.ts
Normal file
288
lib/core/src/lib/viewer/services/rendition-viewer.service.ts
Normal file
@@ -0,0 +1,288 @@
|
||||
/*!
|
||||
* @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;
|
||||
}
|
||||
}
|
@@ -16,12 +16,9 @@
|
||||
*/
|
||||
|
||||
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 { Subject } from 'rxjs';
|
||||
import { Track } from '../models/viewer.model';
|
||||
import { TranslationService } from '../../services/translation.service';
|
||||
import { RenditionViewerService } from "./rendition-viewer.service";
|
||||
import { AppExtensionService, ViewerExtensionRef } from '@alfresco/adf-extensions';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@@ -29,70 +26,69 @@ import { TranslationService } from '../../services/translation.service';
|
||||
export class ViewUtilService {
|
||||
static TARGET = '_new';
|
||||
|
||||
/**
|
||||
* 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'
|
||||
// Extensions that are supported by the Viewer without conversion
|
||||
private extensions = {
|
||||
image: ['png', 'jpg', 'jpeg', 'gif', 'bpm', 'svg'],
|
||||
media: ['wav', 'mp4', 'mp3', 'webm', 'ogg'],
|
||||
text: ['txt', 'xml', 'html', 'json', 'ts', 'css', 'md'],
|
||||
pdf: ['pdf']
|
||||
};
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
// Mime types that are supported by the Viewer without conversion
|
||||
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']
|
||||
media: ['video/mp4', 'video/webm', 'video/ogg', 'audio/mpeg', 'audio/mp3', 'audio/ogg', 'audio/wav']
|
||||
};
|
||||
|
||||
/**
|
||||
* Timeout used for setInterval.
|
||||
* Returns a list of the active Viewer content extensions.
|
||||
*/
|
||||
TRY_TIMEOUT: number = 10000;
|
||||
get viewerExtensions(): ViewerExtensionRef[] {
|
||||
return this.extensionService.getViewerExtensions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribers needed for ViewerComponent to update the viewerType and urlFileContent.
|
||||
* Provides a list of file extensions supported by external plugins.
|
||||
*/
|
||||
viewerTypeChange: Subject<string> = new Subject<string>();
|
||||
urlFileContentChange: Subject<string> = new Subject<string>();
|
||||
|
||||
_renditionsApi: RenditionsApi;
|
||||
get renditionsApi(): RenditionsApi {
|
||||
this._renditionsApi = this._renditionsApi ?? new RenditionsApi(this.apiService.getInstance());
|
||||
return this._renditionsApi;
|
||||
get externalExtensions(): string[] {
|
||||
return this.viewerExtensions.map(ext => ext.fileExtension);
|
||||
}
|
||||
|
||||
_contentApi: ContentApi;
|
||||
get contentApi(): ContentApi {
|
||||
this._contentApi = this._contentApi ?? new ContentApi(this.apiService.getInstance());
|
||||
return this._contentApi;
|
||||
constructor(private renditionViewerService: RenditionViewerService,
|
||||
private extensionService: AppExtensionService,
|
||||
private logService: LogService) {
|
||||
}
|
||||
|
||||
_versionsApi: VersionsApi;
|
||||
get versionsApi(): VersionsApi {
|
||||
this._versionsApi = this._versionsApi ?? new VersionsApi(this.apiService.getInstance());
|
||||
return this._versionsApi;
|
||||
/**
|
||||
* get File name from url
|
||||
*
|
||||
* @param url - url file
|
||||
*/
|
||||
getFilenameFromUrl(url: string): string {
|
||||
const anchor = url.indexOf('#');
|
||||
const query = url.indexOf('?');
|
||||
const end = Math.min(
|
||||
anchor > 0 ? anchor : url.length,
|
||||
query > 0 ? query : url.length);
|
||||
return url.substring(url.lastIndexOf('/', end) + 1, end);
|
||||
}
|
||||
|
||||
constructor(private apiService: AlfrescoApiService,
|
||||
private logService: LogService,
|
||||
private translateService: TranslationService) {
|
||||
/**
|
||||
* Get file extension from the string.
|
||||
* Supports the URL formats like:
|
||||
* http://localhost/test.jpg?cache=1000
|
||||
* http://localhost/test.jpg#cache=1000
|
||||
*
|
||||
* @param fileName - file name
|
||||
*/
|
||||
getFileExtension(fileName: string): string {
|
||||
if (fileName) {
|
||||
const match = fileName.match(/\.([^\./\?\#]+)($|\?|\#)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -104,7 +100,7 @@ export class ViewUtilService {
|
||||
const pwa = window.open(url, ViewUtilService.TARGET);
|
||||
if (pwa) {
|
||||
// Because of the way chrome focus and close image window vs. pdf preview window
|
||||
if (type === ViewUtilService.ContentGroup.IMAGE) {
|
||||
if (type === RenditionViewerService.ContentGroup.IMAGE) {
|
||||
pwa.onfocus = () => {
|
||||
setTimeout(() => {
|
||||
pwa.close();
|
||||
@@ -127,14 +123,14 @@ export class ViewUtilService {
|
||||
*/
|
||||
printFileGeneric(objectId: string, mimeType: string): void {
|
||||
const nodeId = objectId;
|
||||
const type: string = this.getViewerTypeByMimeType(mimeType);
|
||||
const type: string = this.renditionViewerService.getViewerTypeByMimeType(mimeType);
|
||||
|
||||
this.getRendition(nodeId, ViewUtilService.ContentGroup.PDF)
|
||||
this.renditionViewerService.getRendition(nodeId, RenditionViewerService.ContentGroup.PDF)
|
||||
.then((value) => {
|
||||
const url: string = this.getRenditionUrl(nodeId, type, (!!value));
|
||||
const printType = (type === ViewUtilService.ContentGroup.PDF
|
||||
|| type === ViewUtilService.ContentGroup.TEXT)
|
||||
? ViewUtilService.ContentGroup.PDF : type;
|
||||
const url: string = this.renditionViewerService.getRenditionUrl(nodeId, type, (!!value));
|
||||
const printType = (type === RenditionViewerService.ContentGroup.PDF
|
||||
|| type === RenditionViewerService.ContentGroup.TEXT)
|
||||
? RenditionViewerService.ContentGroup.PDF : type;
|
||||
this.printFile(url, printType);
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -143,31 +139,18 @@ export class ViewUtilService {
|
||||
});
|
||||
}
|
||||
|
||||
getRenditionUrl(nodeId: string, type: string, renditionExists: boolean): string {
|
||||
return (renditionExists && type !== ViewUtilService.ContentGroup.IMAGE) ?
|
||||
this.contentApi.getRenditionUrl(nodeId, ViewUtilService.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);
|
||||
getViewerType(extension: string, mimeType: string): string {
|
||||
let viewerType = this.getViewerTypeByExtension(extension);
|
||||
|
||||
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);
|
||||
}
|
||||
if (viewerType === 'unknown') {
|
||||
viewerType = this.getViewerTypeByMimeType(mimeType);
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
return viewerType;
|
||||
}
|
||||
|
||||
getViewerTypeByMimeType(mimeType: string): string {
|
||||
getViewerTypeByMimeType(mimeType: string) {
|
||||
if (mimeType) {
|
||||
mimeType = mimeType.toLowerCase();
|
||||
|
||||
@@ -181,164 +164,53 @@ export class ViewUtilService {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
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 displayNodeRendition(nodeId: string, versionId?: string) {
|
||||
try {
|
||||
const rendition = versionId ? await this.resolveNodeRendition(nodeId, 'pdf', versionId) :
|
||||
await this.resolveNodeRendition(nodeId, 'pdf');
|
||||
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.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
|
||||
this.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.versionsApi.listVersionRenditions(nodeId, versionId) :
|
||||
await this.renditionsApi.listRenditions(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);
|
||||
private getViewerTypeByExtension(extension: string): string {
|
||||
if (extension) {
|
||||
extension = extension.toLowerCase();
|
||||
}
|
||||
|
||||
if (rendition) {
|
||||
const status: string = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'NOT_CREATED') {
|
||||
try {
|
||||
if (versionId) {
|
||||
await this.versionsApi.createVersionRendition(nodeId, versionId, { id: renditionId }).then(() => {
|
||||
this.viewerTypeChange.next('in_creation');
|
||||
});
|
||||
} else {
|
||||
await this.renditionsApi.createRendition(nodeId, { id: renditionId }).then(() => {
|
||||
this.viewerTypeChange.next('in_creation');
|
||||
});
|
||||
}
|
||||
try {
|
||||
rendition = versionId ? await this.waitNodeRendition(nodeId, renditionId, versionId) : await this.waitNodeRendition(nodeId, renditionId);
|
||||
} catch (e) {
|
||||
this.viewerTypeChange.next('error_in_creation');
|
||||
rendition = null;
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
this.logService.error(err);
|
||||
}
|
||||
}
|
||||
if (this.isExternalViewer()) {
|
||||
return 'external';
|
||||
}
|
||||
|
||||
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.versionsApi.getVersionRendition(nodeId, versionId, renditionId).then((rendition: RenditionEntry) => {
|
||||
const status: string = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'CREATED') {
|
||||
this.handleNodeRendition(nodeId, renditionId, versionId);
|
||||
clearInterval(intervalId);
|
||||
return resolve(rendition);
|
||||
}
|
||||
}, () => reject());
|
||||
} else {
|
||||
this.renditionsApi.getRendition(nodeId, renditionId).then((rendition: RenditionEntry) => {
|
||||
const status: string = rendition.entry.status.toString();
|
||||
|
||||
if (status === 'CREATED') {
|
||||
this.handleNodeRendition(nodeId, renditionId);
|
||||
clearInterval(intervalId);
|
||||
return resolve(rendition);
|
||||
}
|
||||
}, () => reject());
|
||||
}
|
||||
} else {
|
||||
clearInterval(intervalId);
|
||||
return reject();
|
||||
}
|
||||
}, this.TRY_TIMEOUT);
|
||||
});
|
||||
}
|
||||
|
||||
private async handleNodeRendition(nodeId: string, renditionId: string, versionId?: string) {
|
||||
if (renditionId === 'pdf') {
|
||||
this.viewerTypeChange.next('pdf');
|
||||
} else if (renditionId === 'imgpreview') {
|
||||
this.viewerTypeChange.next('image');
|
||||
if (this.isCustomViewerExtension(extension)) {
|
||||
return 'custom';
|
||||
}
|
||||
|
||||
const urlFileContent = versionId ? this.contentApi.getVersionRenditionUrl(nodeId, versionId, renditionId) :
|
||||
this.contentApi.getRenditionUrl(nodeId, renditionId);
|
||||
this.urlFileContentChange.next(urlFileContent);
|
||||
if (this.extensions.image.indexOf(extension) >= 0) {
|
||||
return 'image';
|
||||
}
|
||||
|
||||
if (this.extensions.media.indexOf(extension) >= 0) {
|
||||
return 'media';
|
||||
}
|
||||
|
||||
if (this.extensions.text.indexOf(extension) >= 0) {
|
||||
return 'text';
|
||||
}
|
||||
|
||||
if (this.extensions.pdf.indexOf(extension) >= 0) {
|
||||
return 'pdf';
|
||||
}
|
||||
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
async generateMediaTracks(nodeId: string): Promise<Track[]> {
|
||||
return this.isRenditionAvailable(nodeId, ViewUtilService.SUBTITLES_RENDITION_NAME)
|
||||
.then((value) => {
|
||||
const tracks = [];
|
||||
if (value) {
|
||||
tracks.push({
|
||||
kind: 'subtitles',
|
||||
src: this.contentApi.getRenditionUrl(nodeId, ViewUtilService.SUBTITLES_RENDITION_NAME),
|
||||
label: this.translateService.instant('ADF_VIEWER.SUBTITLES')
|
||||
});
|
||||
}
|
||||
return tracks;
|
||||
})
|
||||
.catch((err) => {
|
||||
this.logService.error('Error while retrieving ' + ViewUtilService.SUBTITLES_RENDITION_NAME + ' rendition');
|
||||
this.logService.error(err);
|
||||
return [];
|
||||
});
|
||||
|
||||
private isExternalViewer(): boolean {
|
||||
return !!this.viewerExtensions.find(ext => ext.fileExtension === '*');
|
||||
}
|
||||
|
||||
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);
|
||||
isCustomViewerExtension(extension: string): boolean {
|
||||
const extensions = this.externalExtensions || [];
|
||||
|
||||
return rendition?.entry?.status?.toString() === 'CREATED' || false;
|
||||
if (extension && extensions.length > 0) {
|
||||
extension = extension.toLowerCase();
|
||||
return extensions.flat().indexOf(extension) >= 0;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ import { ViewerExtensionDirective } from './directives/viewer-extension.directiv
|
||||
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,6 +62,7 @@ import { A11yModule } from '@angular/cdk/a11y';
|
||||
declarations: [
|
||||
PdfPasswordDialogComponent,
|
||||
ViewerComponent,
|
||||
AlfrescoViewerComponent,
|
||||
ImgViewerComponent,
|
||||
TxtViewerComponent,
|
||||
MediaPlayerComponent,
|
||||
@@ -77,6 +79,7 @@ import { A11yModule } from '@angular/cdk/a11y';
|
||||
],
|
||||
exports: [
|
||||
ViewerComponent,
|
||||
AlfrescoViewerComponent,
|
||||
ImgViewerComponent,
|
||||
TxtViewerComponent,
|
||||
MediaPlayerComponent,
|
||||
|
Reference in New Issue
Block a user