/** * Copyright Aquera Inc 2025 * * This source code is licensed under the BSD-3-Clause license found in the * LICENSE file in the root directory of this source tree. */ import {styles} from './nile-file-preview.css'; import NileElement from '../internal/nile-element'; import { customElement, property } from 'lit/decorators.js'; import { generatePreviewUrl, isRealImage } from './utils'; import { FilePreviewState, FilePreviewVariant, FilePreviewErrorMessages, FilePreviewEvent } from './types'; import { LitElement, html, CSSResultArray, TemplateResult, PropertyValues } from 'lit'; import { getHorizontalErrorState, getHorizontalNoPreviewState, getHorizontalPreviewState, getHorizontalUploadingState, getVerticalErrorState, getVerticalNoPreviewState, getVerticalPreviewState, getVerticalUploadingState } from './nile-file-preview.template'; /** * Nile preview component. * * @tag nile-preview * */ @customElement('nile-file-preview') export class NileFilePreview extends NileElement { /** * The styles for nile-preview * @remarks If you are extending this class you can extend the base styles with super. Eg `return [super(), myCustomStyles]` */ public static get styles(): CSSResultArray { return [styles]; } @property({ type: String }) errorMessage: string = ""; @property({ type: Object }) inputFile: File; @property({ type: String }) fileUrl: string = ""; @property({ type: Number }) uploadStatus: number = 0; @property({ type: String }) state: FilePreviewState = FilePreviewState.DEFAULT; @property({ type: String }) variant: FilePreviewVariant = FilePreviewVariant.HORIZONTAL; @property({ type: String }) inputFileName: string = ""; @property({ type: Boolean, reflect: true, attribute: true }) showFileType: boolean = false; private inputFileHtml: TemplateResult | null = null; private originalUrl: string = ""; public isStringTruncated: boolean = false; /** * Render method * @slot This is a slot test */ connectedCallback(): void { super.connectedCallback(); this.emit(FilePreviewEvent.NILE_INIT); } protected firstUpdated(_changedProperties: PropertyValues): void { super.firstUpdated(_changedProperties); } protected updated(changedProperties: PropertyValues): void { if(changedProperties.has('uploadStatus') && this.uploadStatus) { if(this.uploadStatus < 100) { this.createUploadState(this.inputFile, this.uploadStatus); } else { this.createState(this.inputFile); } } else if (changedProperties.has('inputFile') && this.inputFile) { this.createState(this.inputFile); this.emit(FilePreviewEvent.NILE_RECEIVE); } else if (changedProperties.has('fileUrl') && this.fileUrl) { this.handleFileUrl(this.fileUrl); this.emit(FilePreviewEvent.NILE_RECEIVE); } else if (changedProperties.has('errorMessage')) { this.createState(this.inputFile); } } private createUploadState( file: File, uploadStatus: number): void { const isHorizontal = this.variant === FilePreviewVariant.HORIZONTAL; this.inputFileHtml = isHorizontal ? getHorizontalUploadingState(file, uploadStatus, this) : getVerticalUploadingState(file, uploadStatus, this); this.requestUpdate(); } private async handleFileUrl(url: string): Promise { this.originalUrl = url; try { const response = await fetch(url); const blob = await response.blob(); const fileName = this.inputFileName || url.split('/').pop() || FilePreviewErrorMessages.UNKNOWN_FILE_NAME; const file = new File([blob], fileName, { type: blob.type }); this.createState(file); } catch (error) { const dummyFile = new File([], this.inputFileName || FilePreviewErrorMessages.UNKNOWN_FILE_NAME, { type: 'application/octet-stream' }); this.errorMessage = FilePreviewErrorMessages.FAILED_TO_LOAD; console.error(this.errorMessage); this.inputFileHtml = this.variant === FilePreviewVariant.HORIZONTAL ? getHorizontalErrorState(dummyFile, this.errorMessage, this.originalUrl, this) : getVerticalErrorState(dummyFile, this.errorMessage, this.originalUrl, this); this.requestUpdate(); } } public async createState(file: File): Promise { const isHorizontal = this.variant === FilePreviewVariant.HORIZONTAL; if (!this.errorMessage) { let isImage = false; try { isImage = file && await isRealImage(file); } catch (error) { isImage = false; } if (isImage) { try { const url = await generatePreviewUrl(file); this.inputFileHtml = isHorizontal ? getHorizontalPreviewState(url, file, this.originalUrl, this.showFileType) : getVerticalPreviewState(url, file, this.originalUrl, this.showFileType); } catch (error) { this.inputFileHtml = isHorizontal ? getHorizontalNoPreviewState(file, this.originalUrl, this.showFileType) : getVerticalNoPreviewState(file, this.originalUrl, this.showFileType); } } else { this.inputFileHtml = isHorizontal ? getHorizontalNoPreviewState(file, this.originalUrl, this.showFileType) : getVerticalNoPreviewState(file, this.originalUrl, this.showFileType); } } else { this.inputFileHtml = isHorizontal ? getHorizontalErrorState(file, this.errorMessage, this.originalUrl, this) : getVerticalErrorState(file, this.errorMessage, this.originalUrl, this); } this.requestUpdate(); } public render(): TemplateResult { return html`${this.inputFileHtml ?? html``}`; } disconnectedCallback(): void { super.disconnectedCallback(); this.emit(FilePreviewEvent.NILE_DESTROY); } /* #endregion */ } export default NileFilePreview; declare global { interface HTMLElementTagNameMap { 'nile-file-preview': NileFilePreview; } }