import { getIconFromExt, SvgIcon } from './icons'; import utils from './utils'; import { RGBA, ImageThumbnail, VideoThumbnail } from './utils'; interface Dimensions { height: number; width: number; } interface Options { accept?: string; maxSize?: string; read: boolean; thumbnailSize?: number; averageColor?: boolean; withCredentials?: boolean; } interface ErrorText { common?: string; type?: string; size?: string; upload?: string; } interface ErrorFlags { common?: boolean; type?: boolean; size?: boolean; upload?: false | string; } interface RawFileRecord { url: string | undefined; urlResized: string | null; src: string; name: string; lastModified: number; sizeText: string; size: number; type: string; ext: string; color: string; file: File; progress: number; error?: false | ErrorFlags; dimensions: Dimensions; videoThumbnail: string; imageColor: RGBA; customName: string; upload: UploadData; } interface DummyFile { name: string; size: number; type: string; lastModified: number; lastModifiedDate: Date; } interface UploadData { data: any; error: string | false; } export type { Dimensions, ErrorText, Options, RawFileRecord }; class FileRecord { public static getFromRaw( rawFileRecord: RawFileRecord, options: Options, isSync: boolean, ): Promise | FileRecord { const fileRecord = new FileRecord(rawFileRecord, options); const promise = fileRecord.setUrl(rawFileRecord.url as string); if (isSync) { return fileRecord; } return promise; } public static fromRaw(rawFileRecord: RawFileRecord, options: Options): Promise { return FileRecord.getFromRaw(rawFileRecord, options, false) as Promise; } public static fromRawSync(rawFileRecord: RawFileRecord, options: Options): FileRecord { return FileRecord.getFromRaw(rawFileRecord, options, true) as FileRecord; } public static fromRawArray(rawFileRecords: RawFileRecord[], options: Options): Promise { const promises: Array> = []; for (const rawFileRecord of rawFileRecords) { promises.push(FileRecord.fromRaw(rawFileRecord, options)); } return Promise.all(promises); } public static fromRawArraySync(rawFileRecords: RawFileRecord[], options: Options): FileRecord[] { const fileRecords: Array = []; for (const rawFileRecord of rawFileRecords) { fileRecords.push(FileRecord.fromRawSync(rawFileRecord, options)); } return fileRecords; } public static toRawArray(fileRecords: FileRecord[]): RawFileRecord[] { const rawFileRecords: RawFileRecord[] = []; for (const fileRecord of fileRecords) { rawFileRecords.push(fileRecord.toRaw()); } return rawFileRecords; } public static readFile(fileRecord: FileRecord): Promise { return new Promise((resolve, reject) => { if (!fileRecord.read) { fileRecord.setUrl(null).then( () => { resolve(fileRecord); }, (err) => { // ignore error resolve(fileRecord); } ); return; } utils.getDataURL(fileRecord.file).then((dataUrl) => { fileRecord.setUrl(dataUrl).then(() => { resolve(fileRecord); }, reject); }, reject); }); } public static readFiles(fileRecords: FileRecord[]): Promise { const promises: Array> = []; for (const fileRecord of fileRecords) { promises.push(FileRecord.readFile(fileRecord)); } return Promise.all(promises); } public urlValue: null | string = null; public urlResized: null | string = null; public image: HTMLImageElement | {} = {}; public isPlayingAv: boolean = false; public oldFileName: string | null = null; public oldCustomName: string | null = null; public upload: UploadData = { data: null, error: false }; public raw: RawFileRecord; public progressInternal: number; public accept?: string; public dimensions: Dimensions; public error: false | ErrorFlags; public file: File; public height: undefined | number | string; public width: undefined | number | string; public id: string; public imageColor?: RGBA; public lastKnownSrc: null | string; public maxSize?: string; public options: Options; public read: boolean; public thumbnailSize: number; public videoThumbnail: any; public customName: any; public xhr?: XMLHttpRequest; public xhrQueue?: () => any; public stopAv?: (() => any) | null; public tusUpload?: any; public calculateAverageColor: boolean; public constructor(data: RawFileRecord, options: Options) { this.urlValue = null; this.urlResized = null; this.lastKnownSrc = null; this.image = {}; this.isPlayingAv = false; this.oldFileName = null; this.oldCustomName = null; this.raw = data; this.file = data.file instanceof File ? data.file : (this.createDummyFile(data) as any); this.progressInternal = !isNaN(data.progress as number) ? (data.progress as number) : 0; // this.width = FileRecord.defaultWidth; // this.height = FileRecord.defaultHeight; this.thumbnailSize = options.thumbnailSize || 360; this.read = !!options.read; this.dimensions = data.dimensions || { width: 0, height: 0 }; this.dimensions.width = this.dimensions.width || 0; this.dimensions.height = this.dimensions.height || 0; this.error = data.error || false; this.options = options; this.maxSize = options.maxSize; this.accept = options.accept; this.id = Math.random() + ':' + new Date().getTime(); this.videoThumbnail = data.videoThumbnail; this.imageColor = data.imageColor; this.customName = data.customName; this.calculateAverageColor = options.averageColor !== undefined ? options.averageColor : true; this.validate(); } public createDummyFile(data: RawFileRecord): DummyFile { const file: DummyFile = {} as DummyFile; file.lastModified = data.lastModified; const d = new Date(); if (file.lastModified) { d.setTime(file.lastModified); } file.lastModifiedDate = d; file.name = data.name; file.size = data.size; file.type = data.type; return file; } public hasProgress(): boolean { return !isNaN(this.progressInternal); // && this._progress <= 100; } progress(value: number): void; progress(): number; public progress(value?: number): number | void { if (value !== undefined) { this.progressInternal = value; return; } return this.progressInternal; } url(value: string): string | undefined | Promise; url(): string | undefined; public url(value?: string): string | undefined | Promise { if (value !== undefined) { return this.setUrl(value); } return this.urlValue || undefined; } public src(): string { if (this.isImage()) { return this.urlResized || this.urlValue || (this.file as any).url; } if (this.isPlayableVideo()) { return this.videoThumbnail || ''; } return ''; } public size(): string { if (!this.file) { return ''; } return utils.getSizeFormatted(this.file.size); } public ext(): string { if (this.file && this.file.name.indexOf('.') !== -1) { return (this.file.name as any).split('.').pop(); } return '?'; // return this.file.type.split('/').shift(); } public name(withoutExt?: boolean): string { const ext = this.ext(); if (this.customName) { return this.customName + (withoutExt ? '' : ext !== '?' ? '.' + ext : ''); } const name = this.file && this.file.name; if (withoutExt) { if (ext !== '?') { return name.substr(0, name.length - (ext.length + 1)); } } return name; } public isDarkColor(): boolean { if (this.imageColor) { const rgb = this.imageColor; const darkPoint = 20; return rgb[0] <= darkPoint && rgb[1] <= darkPoint && rgb[2] <= darkPoint; } return false; } public color(): string { if (this.imageColor) { const rgb = this.imageColor; return 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')'; } if (this.isImage()) { return 'transparent'; } const ext = this.ext(); const svgIcon = this.icon(); // var svgIcon = getIconFromExt(ext); if (svgIcon.color) { return svgIcon.color; } return utils.getColorForText(ext); } public isImage(): boolean { return this.file && this.file.type !== 'image/x-wmf' && !!this.file.type.match(/image((?!vnd).)*$/i); } public isVideo(): boolean { return this.file && this.file.type.indexOf('video') !== -1; } public isPlayableVideo(): boolean { return this.icon().category === 'video-playable'; } public isAudio(): boolean { return this.file && this.file.type.indexOf('audio') !== -1; } public isPlayableAudio(): boolean { return this.icon().category === 'audio-playable'; } public isText(): boolean { return this.file && this.file.type.indexOf('text') !== -1; } public setUrl(url: string | null): Promise { this.urlValue = url; return new Promise((resolve, reject) => { if (this.isImage()) { this.resizeImage().then( () => { resolve(this); }, (err) => { resolve(this); } ); return; } resolve(this); }); } public imageResized(resized: ImageThumbnail | null) { if (!resized) { return; } this.urlResized = resized.url; this.image = resized.image; if (resized.image && resized.image.width && resized.image.height) { this.dimensions.width = resized.image.width; this.dimensions.height = resized.image.height; } this.lastKnownSrc = this.urlResized; this.imageColor = resized.color; } public resizeImage(): Promise { return new Promise((resolve, reject) => { utils .resizeImage(this.thumbnailSize, this.file, this.urlValue as string, this.calculateAverageColor) .then((resized) => { this.imageResized(resized); resolve(this); }) .catch(reject); }); } public icon(): SvgIcon { const ext = this.ext(); const svgIcon = getIconFromExt(ext); return svgIcon; } public getErrorMessage(errorText?: ErrorText): string { const error = this.error; if (!error) { return ''; } errorText = errorText || {}; errorText = { common: errorText.common || 'Invalid file.', type: errorText.type || 'Invalid file type.', size: errorText.size || 'Files should not exceed ' + this.maxSize + ' in size', }; if (error.type) { return errorText.type as string; } else if (error.size) { return errorText.size as string; } else if (error.upload) { return this.upload.error ? this.upload.error : error.upload; } return errorText.common as string; } public toRaw(): RawFileRecord { const raw = this.raw || ({} as RawFileRecord); raw.url = this.url(); raw.urlResized = this.urlResized; raw.src = this.src(); raw.name = this.name(); raw.lastModified = this.file.lastModified; raw.sizeText = this.size(); raw.size = this.file.size; raw.type = this.file.type; raw.ext = this.ext(); raw.color = this.color(); raw.file = this.file; raw.progress = this.progress(); // pass it as a function raw.upload = this.upload; if (!('error' in raw)) { Object.defineProperty(raw, 'error', { get: () => { return this.error; }, }); } raw.dimensions = this.dimensions; return raw; } public validate(): void { const validType = utils.validateType(this.file, this.accept); const validSize = utils.validateSize(this.file, this.maxSize as string); if (!validType || !validSize) { this.error = { type: !validType, size: !validSize, }; } else { this.error = false; } } } export default FileRecord;