@limetech/lime-elements
Version:
530 lines (529 loc) • 21.9 kB
JavaScript
import { h, } from "@stencil/core";
import translate from "../../global/translations";
import { detectExtension } from "./extension-mapping";
import { Fullscreen } from "./fullscreen";
import { loadEmail } from "../email-viewer/email-loader";
/**
* This is a smart component that automatically detects
* the most common file types such as image, audio, video, and text,
* and properly displays them in the browser.
* The component is also capable to render the most common office files.
*
* :::note
* Image files will always be contained in their containers, which means
* they automatically increase or decrease in size to fill their containing box
* whilst preserving their aspect-ratio.
*
* Text and PDF files will also always respect the width and height of the
* container in which the `limel-file-viewer` is loaded.
* :::
*
* For some file types such as text and images, the component will display a
* download button and a button to open the file in a new browser tab.
* This will allow users to preview the file in a fullscreen mode with the
* browser and take advantage of for example native zooming and panning
* functionalities.
*
* @exampleComponent limel-example-file-viewer-basic
* @exampleComponent limel-example-file-viewer-office
* @exampleComponent limel-example-file-viewer-eml
* @exampleComponent limel-example-file-viewer-filename
* @exampleComponent limel-example-file-viewer-inbuilt-actions
* @exampleComponent limel-example-file-viewer-custom-actions
* @exampleComponent limel-example-file-viewer-with-picker
*
* @beta
*/
export class FileViewer {
constructor() {
/**
* Displays a button that allows the user to view the file
* in fullscreen mode.
* Not displayed for office files!
*/
this.allowFullscreen = false;
/**
* Displays a button that allows the user to open the file
* in a new browser tab.
* Not displayed for office files!
*/
this.allowOpenInNewTab = false;
/**
* Displays a button that allows the user to download the file.
* Note that due to the browser's security policies,
* the file should be hosted on the same domain
* for the download button to work properly.
* Not displayed for office files!
*/
this.allowDownload = false;
/**
* Defines the localization for translations.
*/
this.language = 'en';
/**
* Defines the third-party viewer that should be used to render
* the content of office files, such as word processing documents,
* presentations, or spreadsheets.
*/
this.officeViewer = 'microsoft-office';
this.isFullscreen = false;
/**
* True while the file is being loaded.
*/
this.loading = true;
this.fileUrl = '';
this.renderPdf = () => {
return [
h("div", { class: "action-menu-for-pdf-files" }, this.renderActionMenu()),
h("iframe", { src: this.sanitizeUrl(this.fileUrl), loading: "lazy" }),
];
};
this.renderImage = () => {
return [
this.renderButtons(),
h("img", { src: this.sanitizeUrl(this.fileUrl), alt: this.alt, loading: "lazy" }),
];
};
this.renderVideo = () => {
return (h("video", { controls: true }, h("source", { src: this.sanitizeUrl(this.fileUrl) })));
};
this.renderAudio = () => {
return (h("audio", { controls: true }, h("source", { src: this.sanitizeUrl(this.fileUrl) })));
};
this.renderText = () => {
const fallbackContent = [this.renderNoFileSupportMessage()];
return [
this.renderButtons(),
h("object", { data: this.sanitizeUrl(this.fileUrl), type: "text/plain" }, fallbackContent),
];
};
this.renderEmail = () => {
if (!this.email) {
return this.renderFileNotFoundMessage();
}
return [
this.renderButtons(),
h("limel-email-viewer", { email: this.email, fallbackUrl: this.sanitizeUrl(this.fileUrl), language: this.language }, h("div", { slot: "fallback" }, this.renderNoFileSupportMessage())),
];
};
this.renderOffice = () => {
return [
h("div", { class: "action-menu-for-office-files" }, this.renderActionMenu()),
h("iframe", { src: this.getOfficeViewerUrl() +
encodeURIComponent(this.sanitizeUrl(this.fileUrl)) +
'&embedded=true', loading: "lazy" }),
];
};
this.isOfficeFileAccessibleViaURL = () => {
return (this.fileType === 'office' &&
!(this.fileUrl.startsWith('http://') ||
this.fileUrl.startsWith('https://')));
};
this.getOfficeViewerUrl = () => {
const officeViewers = {
'microsoft-office': 'https://view.officeapps.live.com/op/embed.aspx?src=',
'google-drive': 'https://docs.google.com/gview?url=',
};
return officeViewers[this.officeViewer];
};
this.renderNoFileSupportMessage = () => {
return (h("div", { class: "no-support", role: "alert" }, h("h1", null, "\u26A0\uFE0F"), h("p", null, this.getTranslation('file-viewer.message.unsupported-filetype')), h("p", { style: { textAlign: 'center', margin: '0 auto' } }, this.getTranslation('file-viewer.message.try-other-options')), this.renderDownloadButton()));
};
this.renderFileNotFoundMessage = () => {
return (h("div", { class: "no-support", role: "alert" }, h("h1", null, "\u26A0\uFE0F"), h("p", null, this.getTranslation('file-viewer.message.file-not-found'))));
};
this.renderButtons = () => {
return (h("div", { class: "buttons" }, this.renderActionMenu(), this.renderToggleFullscreenButton(), this.renderDownloadButton(), this.renderOpenInNewTabButton()));
};
this.renderToggleFullscreenButton = () => {
if (!this.allowFullscreen || !this.fullscreen.isSupported()) {
return;
}
const icon = this.isFullscreen ? 'multiply' : 'fit_to_width';
const label = this.isFullscreen
? this.getTranslation('file-viewer.exit-fullscreen')
: this.getTranslation('file-viewer.open-in-fullscreen');
return [
h("button", { class: "button--toggle-fullscreen", id: "tooltip-toggle-fullscreen", role: "button", onClick: this.handleToggleFullscreen }, h("limel-icon", { name: icon }), h("limel-tooltip", { label: label, elementId: "tooltip-toggle-fullscreen", openDirection: "left" })),
];
};
this.renderDownloadButton = () => {
var _a;
if (!this.allowDownload || this.isFullscreen) {
return;
}
return (h("a", { class: "button--download", id: "tooltip-download", role: "button", download: (_a = this.filename) !== null && _a !== void 0 ? _a : '', href: this.sanitizeUrl(this.fileUrl), target: "_blank" }, h("limel-icon", { name: "download_2" }), h("limel-tooltip", { label: this.getTranslation('file-viewer.download'), elementId: "tooltip-download", openDirection: "left" })));
};
this.renderOpenInNewTabButton = () => {
if (!this.allowOpenInNewTab || this.isFullscreen) {
return;
}
return (h("a", { class: "button--new-tab", id: "tooltip-new-tab", role: "button", href: this.sanitizeUrl(this.fileUrl), target: "_blank", rel: "noopener noreferrer" }, h("limel-icon", { name: "external_link" }), h("limel-tooltip", { label: this.getTranslation('file-viewer.open-in-new-tab'), elementId: "tooltip-new-tab", openDirection: "left" })));
};
this.renderActionMenu = () => {
if (!this.actions || this.isFullscreen) {
return;
}
return (h("limel-menu", { class: "action-menu", items: this.actions, onSelect: this.emitOnAction, "open-direction": "left" }, h("button", { class: "button--action", id: "tooltip-more", role: "button", slot: "trigger" }, h("limel-icon", { name: "menu_2" }), h("limel-tooltip", { label: this.getTranslation('file-viewer.more-actions'), elementId: "tooltip-more", openDirection: "left" }))));
};
this.createURL = async (fileType) => {
this.revokePdfBlobUrl();
const loaders = {
pdf: this.loadPdf,
email: this.loadEmail,
};
const loader = loaders[fileType] || this.loadDefault;
try {
await loader();
}
finally {
this.loading = false;
}
};
this.loadPdf = async () => {
const response = await fetch(this.url);
if (!response.ok) {
throw new Error(`Failed to load PDF (${response.status} ${response.statusText})`);
}
const blob = await response.blob();
this.fileUrl = URL.createObjectURL(blob);
this.pdfBlobUrl = this.fileUrl;
};
this.loadEmail = async () => {
try {
this.email = await loadEmail(this.url);
this.fileUrl = this.url;
}
catch (_a) {
this.email = undefined;
this.fileUrl = '';
}
};
this.loadDefault = async () => {
this.fileUrl = this.url;
};
this.handleToggleFullscreen = () => {
if (this.fullscreen.isSupported()) {
this.fullscreen.toggle();
this.isFullscreen = !this.isFullscreen;
}
};
this.emitOnAction = (event) => {
event.stopPropagation();
this.action.emit(event.detail);
};
this.fullscreen = new Fullscreen(this.HostElement);
}
disconnectedCallback() {
this.revokePdfBlobUrl();
}
async componentWillLoad() {
this.fileType = detectExtension(this.filename, this.url);
await this.createURL(this.fileType);
}
render() {
if (!this.isOfficeFileAccessibleViaURL) {
return this.renderNoFileSupportMessage();
}
if (this.loading) {
return h("limel-spinner", { size: "x-small", limeBranded: false });
}
return this.renderFileViewer();
}
async watchUrl(newUrl, oldUrl) {
if (newUrl === oldUrl) {
return;
}
this.loading = true;
this.fileType = detectExtension(this.filename, this.fileUrl);
await this.createURL(this.fileType);
}
renderFileViewer() {
const fileViewerFunctions = {
pdf: this.renderPdf,
image: this.renderImage,
video: this.renderVideo,
audio: this.renderAudio,
text: this.renderText,
email: this.renderEmail,
office: this.renderOffice,
};
const fileViewerFunction = fileViewerFunctions[this.fileType] ||
this.renderNoFileSupportMessage;
return fileViewerFunction();
}
revokePdfBlobUrl() {
if (this.pdfBlobUrl) {
URL.revokeObjectURL(this.pdfBlobUrl);
this.pdfBlobUrl = undefined;
}
}
sanitizeUrl(url) {
if (!(url === null || url === void 0 ? void 0 : url.trim())) {
return '';
}
try {
const u = new URL(url, window.location.href);
const allowed = ['http:', 'https:', 'blob:'];
return allowed.includes(u.protocol) ? u.href : '';
}
catch (_a) {
return '';
}
}
getTranslation(key) {
return translate.get(key, this.language);
}
static get is() { return "limel-file-viewer"; }
static get encapsulation() { return "shadow"; }
static get originalStyleUrls() {
return {
"$": ["file-viewer.scss"]
};
}
static get styleUrls() {
return {
"$": ["file-viewer.css"]
};
}
static get properties() {
return {
"url": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Link to the file"
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "url"
},
"filename": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "The name of the file that must also contains its extension.\nThis overrides the filename that the `url` ends with.\nUseful when the `url` does not contain the filename.\nWhen specified, the `filename` will be used as filename of\nthe downloaded file."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "filename"
},
"alt": {
"type": "string",
"mutable": false,
"complexType": {
"original": "string",
"resolved": "string",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "An optional alternative text, mainly for assistive technologies and screen readers.\nIt is used for only image files, as an `alt` attribute.\nShould optimally hold a description of the image,\nwhich is also displayed on the page if the image can't be loaded for some reason."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "alt"
},
"allowFullscreen": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Displays a button that allows the user to view the file\nin fullscreen mode.\nNot displayed for office files!"
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "allow-fullscreen",
"defaultValue": "false"
},
"allowOpenInNewTab": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Displays a button that allows the user to open the file\nin a new browser tab.\nNot displayed for office files!"
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "allow-open-in-new-tab",
"defaultValue": "false"
},
"allowDownload": {
"type": "boolean",
"mutable": false,
"complexType": {
"original": "boolean",
"resolved": "boolean",
"references": {}
},
"required": false,
"optional": true,
"docs": {
"tags": [],
"text": "Displays a button that allows the user to download the file.\nNote that due to the browser's security policies,\nthe file should be hosted on the same domain\nfor the download button to work properly.\nNot displayed for office files!"
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "allow-download",
"defaultValue": "false"
},
"language": {
"type": "string",
"mutable": false,
"complexType": {
"original": "Languages",
"resolved": "\"da\" | \"de\" | \"en\" | \"fi\" | \"fr\" | \"nb\" | \"nl\" | \"no\" | \"sv\"",
"references": {
"Languages": {
"location": "import",
"path": "../date-picker/date.types",
"id": "src/components/date-picker/date.types.ts::Languages",
"referenceLocation": "Languages"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines the localization for translations."
},
"getter": false,
"setter": false,
"reflect": false,
"attribute": "language",
"defaultValue": "'en'"
},
"officeViewer": {
"type": "string",
"mutable": false,
"complexType": {
"original": "OfficeViewer",
"resolved": "\"google-drive\" | \"microsoft-office\"",
"references": {
"OfficeViewer": {
"location": "import",
"path": "./file-viewer.types",
"id": "src/components/file-viewer/file-viewer.types.ts::OfficeViewer",
"referenceLocation": "OfficeViewer"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "Defines the third-party viewer that should be used to render\nthe content of office files, such as word processing documents,\npresentations, or spreadsheets."
},
"getter": false,
"setter": false,
"reflect": true,
"attribute": "office-viewer",
"defaultValue": "'microsoft-office'"
},
"actions": {
"type": "unknown",
"mutable": false,
"complexType": {
"original": "ListItem[]",
"resolved": "ListItem<any>[]",
"references": {
"ListItem": {
"location": "import",
"path": "../list-item/list-item.types",
"id": "src/components/list-item/list-item.types.ts::ListItem",
"referenceLocation": "ListItem"
}
}
},
"required": false,
"optional": false,
"docs": {
"tags": [],
"text": "An array of custom actions that can be displayed\nas an action menu on the file which is being displayed."
},
"getter": false,
"setter": false
}
};
}
static get states() {
return {
"isFullscreen": {},
"fileType": {},
"loading": {},
"fileUrl": {},
"email": {}
};
}
static get events() {
return [{
"method": "action",
"name": "action",
"bubbles": true,
"cancelable": true,
"composed": true,
"docs": {
"tags": [],
"text": "Emitted when a custom action is selected from the action menu."
},
"complexType": {
"original": "ListItem",
"resolved": "ListItem<any>",
"references": {
"ListItem": {
"location": "import",
"path": "../list-item/list-item.types",
"id": "src/components/list-item/list-item.types.ts::ListItem",
"referenceLocation": "ListItem"
}
}
}
}];
}
static get elementRef() { return "HostElement"; }
static get watchers() {
return [{
"propName": "url",
"methodName": "watchUrl"
}];
}
}