@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
194 lines (187 loc) • 14.1 kB
JavaScript
import * as i0 from '@angular/core';
import { InjectionToken, input, computed, Inject, Component, EventEmitter, NgModule } from '@angular/core';
import * as i3 from '@c8y/ngx-components';
import { IconDirective, ModalComponent, LoadingComponent, C8yTranslatePipe, BytesPipe, CoreModule, CommonModule } from '@c8y/ngx-components';
import * as i1 from 'ngx-bootstrap/modal';
import * as i2 from '@angular/platform-browser';
import { takeUntil, first } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { gettext } from '@c8y/ngx-components/gettext';
const PRODUCT_EXPERIENCE_FILE_PREVIEW = {
EVENTS: {
FILE_PREVIEW: 'filePreview'
},
COMPONENTS: {
FILE_PREVIEW_COMPONENT: 'file-preview'
},
ACTIONS: {
OPEN_PREVIEW: 'openPreview'
}
};
/**
* Provides InjectionToken used in FilePreviewModule. This token is used to inject EventEmitter instance, that is
* shared among all instances of FilePreviewComponent. These components communicate between each other using this
* emitter, which enables them to clear downloaded files from memory, if other instance of FilePreviewComponent
* started downloading different file. Value emitted by this EventEmitter is ID of downloaded file.
*/
const DOWNLOAD_EMITTER = new InjectionToken('downloadEmitter');
/**
* A component which shows a button that opens a modal with the preview of a binary managed object.
* This component requires CSP 'blob:' rule for img-src and media-src to be set.
*
* ```html
* <c8y-file-preview [mo]="managedObject">
* <button customButton>Preview</button>
* </c8y-file-preview>
* ```
* If no custom button provided, the component will use the default search icon button instead.
*
*/
class FilePreviewComponent {
constructor(downloadEmitter, modalRef, modalService, sanitizer, filesService, alertService, gainsightService) {
this.downloadEmitter = downloadEmitter;
this.modalRef = modalRef;
this.modalService = modalService;
this.sanitizer = sanitizer;
this.filesService = filesService;
this.alertService = alertService;
this.gainsightService = gainsightService;
this.mo = input.required(...(ngDevMode ? [{ debugName: "mo" }] : []));
this.downloadFn = input(...(ngDevMode ? [undefined, { debugName: "downloadFn" }] : []));
this.contentType = computed(() => this.resolveContentType(this.mo()), ...(ngDevMode ? [{ debugName: "contentType" }] : []));
this.progress = {
totalBytes: 0,
bufferedBytes: 0,
percentage: 0,
bytesPerSecond: 0
};
this.BUFFERING_STATUS_TEXT = gettext('{{speed}}/s - {{bufferedBytes}} of {{totalBytes}} buffered ({{percentage}}%)');
this.destroy$ = new Subject();
downloadEmitter.pipe(takeUntil(this.destroy$)).subscribe(id => {
if (this.dataUrl && this.mo().id !== id) {
this.removeDownloadedFile();
}
});
}
ngOnDestroy() {
this.removeDownloadedFile();
this.destroy$.next(true);
this.destroy$.complete();
}
async openModal(template) {
this.modalRef = this.modalService.show(template, {
ariaDescribedby: 'modal-body',
ariaLabelledBy: 'modal-title',
ignoreBackdropClick: true
});
this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_FILE_PREVIEW.EVENTS.FILE_PREVIEW, {
component: PRODUCT_EXPERIENCE_FILE_PREVIEW.COMPONENTS.FILE_PREVIEW_COMPONENT,
action: PRODUCT_EXPERIENCE_FILE_PREVIEW.ACTIONS.OPEN_PREVIEW
});
if (this.dataUrl) {
return;
}
this.downloadEmitter.emit(this.mo().id);
if (this.downloadFn()) {
await this.fetchWithDownloadFn();
return;
}
const subscription = this.filesService
.fetchFileWithProgress$(this.mo())
.pipe(takeUntil(this.destroy$))
.subscribe(progress => {
this.progress = progress;
if (this.progress.blob) {
this.dataUrl = URL.createObjectURL(progress.blob);
this.safeDataUrl = this.sanitizer.bypassSecurityTrustUrl(this.dataUrl);
}
}, e => {
this.modalRef.hide();
this.alertService.addServerFailure(e);
});
this.modalRef.onHide.pipe(first(), takeUntil(this.destroy$)).subscribe(() => {
subscription.unsubscribe();
});
}
async fetchWithDownloadFn() {
try {
const fn = this.downloadFn();
if (!fn) {
return;
}
const response = await fn();
const blob = await response.blob();
this.dataUrl = URL.createObjectURL(blob);
this.safeDataUrl = this.sanitizer.bypassSecurityTrustUrl(this.dataUrl);
}
catch (e) {
this.modalRef.hide();
this.alertService.addServerFailure(e);
}
}
removeDownloadedFile() {
URL.revokeObjectURL(this.dataUrl);
delete this.dataUrl;
delete this.safeDataUrl;
}
resolveContentType(mo) {
if (!mo || !mo.hasOwnProperty('c8y_IsBinary')) {
// eslint-disable-next-line no-console
console.warn('Provided Managed object is not binary');
return 'unsupported';
}
if (mo.contentType.startsWith('image/')) {
return 'image';
}
else if (mo.contentType.startsWith('video/')) {
return 'video';
}
else if (mo.contentType.startsWith('text/')) {
// @TODO: Implement in future
return 'unsupported';
}
else if (mo.contentType.startsWith('application/json')) {
// @TODO: Implement in future
return 'unsupported';
}
return 'unsupported';
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilePreviewComponent, deps: [{ token: DOWNLOAD_EMITTER }, { token: i1.BsModalRef }, { token: i1.BsModalService }, { token: i2.DomSanitizer }, { token: i3.FilesService }, { token: i3.AlertService }, { token: i3.GainsightService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.19", type: FilePreviewComponent, isStandalone: true, selector: "c8y-file-preview", inputs: { mo: { classPropertyName: "mo", publicName: "mo", isSignal: true, isRequired: true, transformFunction: null }, downloadFn: { classPropertyName: "downloadFn", publicName: "downloadFn", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@if (contentType() !== 'unsupported') {\n <div\n class=\"d-inline-block\"\n (click)=\"openModal(modalTemplate)\"\n >\n <div #customButtonRef>\n <ng-content select=\"[customButton]\"></ng-content>\n </div>\n\n @if (!customButtonRef.children.length) {\n <button\n class=\"btn btn-default btn-icon\"\n [title]=\"'Preview file' | translate\"\n type=\"button\"\n >\n <i c8yIcon=\"search\"></i>\n </button>\n }\n </div>\n}\n\n<ng-template #modalTemplate>\n <c8y-modal\n class=\"text-break-word\"\n [title]=\"mo().name\"\n [customFooter]=\"false\"\n (onClose)=\"modalRef.hide()\"\n (onDismiss)=\"modalRef.hide()\"\n [labels]=\"{ cancel: '', ok: 'Close' | translate }\"\n >\n @if (!dataUrl) {\n <div class=\"text-center d-block\">\n <c8y-loading\n layout=\"application\"\n [progress]=\"progress.percentage\"\n ></c8y-loading>\n {{\n BUFFERING_STATUS_TEXT\n | translate\n : {\n totalBytes: progress.totalBytes | bytes,\n bufferedBytes: progress.bufferedBytes | bytes,\n percentage: progress.percentage,\n speed: progress.bytesPerSecond | bytes\n }\n }}\n </div>\n }\n\n @if (dataUrl) {\n <div id=\"modal-body\">\n @switch (contentType()) {\n @case ('image') {\n <img\n class=\"fit-w\"\n alt=\"safeDataUrl\"\n [src]=\"safeDataUrl\"\n />\n }\n @case ('video') {\n <video\n class=\"fit-w\"\n controls\n autoplay\n >\n <source [src]=\"safeDataUrl\" />\n {{ 'Your browser does not support the video tag.' | translate }}\n </video>\n }\n @case ('text') {\n <!-- @TODO: Implement text viewer-->\n text\n }\n @case ('json') {\n <!-- @TODO: Implement json viewer-->\n json\n }\n }\n </div>\n }\n </c8y-modal>\n</ng-template>\n", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: BytesPipe, name: "bytes" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilePreviewComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-file-preview', imports: [IconDirective, ModalComponent, LoadingComponent, C8yTranslatePipe, BytesPipe], template: "@if (contentType() !== 'unsupported') {\n <div\n class=\"d-inline-block\"\n (click)=\"openModal(modalTemplate)\"\n >\n <div #customButtonRef>\n <ng-content select=\"[customButton]\"></ng-content>\n </div>\n\n @if (!customButtonRef.children.length) {\n <button\n class=\"btn btn-default btn-icon\"\n [title]=\"'Preview file' | translate\"\n type=\"button\"\n >\n <i c8yIcon=\"search\"></i>\n </button>\n }\n </div>\n}\n\n<ng-template #modalTemplate>\n <c8y-modal\n class=\"text-break-word\"\n [title]=\"mo().name\"\n [customFooter]=\"false\"\n (onClose)=\"modalRef.hide()\"\n (onDismiss)=\"modalRef.hide()\"\n [labels]=\"{ cancel: '', ok: 'Close' | translate }\"\n >\n @if (!dataUrl) {\n <div class=\"text-center d-block\">\n <c8y-loading\n layout=\"application\"\n [progress]=\"progress.percentage\"\n ></c8y-loading>\n {{\n BUFFERING_STATUS_TEXT\n | translate\n : {\n totalBytes: progress.totalBytes | bytes,\n bufferedBytes: progress.bufferedBytes | bytes,\n percentage: progress.percentage,\n speed: progress.bytesPerSecond | bytes\n }\n }}\n </div>\n }\n\n @if (dataUrl) {\n <div id=\"modal-body\">\n @switch (contentType()) {\n @case ('image') {\n <img\n class=\"fit-w\"\n alt=\"safeDataUrl\"\n [src]=\"safeDataUrl\"\n />\n }\n @case ('video') {\n <video\n class=\"fit-w\"\n controls\n autoplay\n >\n <source [src]=\"safeDataUrl\" />\n {{ 'Your browser does not support the video tag.' | translate }}\n </video>\n }\n @case ('text') {\n <!-- @TODO: Implement text viewer-->\n text\n }\n @case ('json') {\n <!-- @TODO: Implement json viewer-->\n json\n }\n }\n </div>\n }\n </c8y-modal>\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i0.EventEmitter, decorators: [{
type: Inject,
args: [DOWNLOAD_EMITTER]
}] }, { type: i1.BsModalRef }, { type: i1.BsModalService }, { type: i2.DomSanitizer }, { type: i3.FilesService }, { type: i3.AlertService }, { type: i3.GainsightService }], propDecorators: { mo: [{ type: i0.Input, args: [{ isSignal: true, alias: "mo", required: true }] }], downloadFn: [{ type: i0.Input, args: [{ isSignal: true, alias: "downloadFn", required: false }] }] } });
const downloadEmitter = new EventEmitter();
class FilePreviewModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilePreviewModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.19", ngImport: i0, type: FilePreviewModule, imports: [CoreModule, CommonModule, FilePreviewComponent], exports: [FilePreviewComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilePreviewModule, providers: [
{
provide: DOWNLOAD_EMITTER,
useValue: downloadEmitter
}
], imports: [CoreModule, CommonModule, FilePreviewComponent] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: FilePreviewModule, decorators: [{
type: NgModule,
args: [{
imports: [CoreModule, CommonModule, FilePreviewComponent],
exports: [FilePreviewComponent],
providers: [
{
provide: DOWNLOAD_EMITTER,
useValue: downloadEmitter
}
]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { FilePreviewComponent, FilePreviewModule };
//# sourceMappingURL=c8y-ngx-components-file-preview.mjs.map