UNPKG

ionic-image-loader-v7

Version:
1 lines 63.7 kB
{"version":3,"file":"ionic-image-loader-v7.mjs","sources":["../../../projects/ionic-image-loader-v7/src/lib/services/image-loader-config.service.ts","../../../projects/ionic-image-loader-v7/src/lib/services/image-loader.service.ts","../../../projects/ionic-image-loader-v7/src/lib/ionic-image-loader.component.ts","../../../projects/ionic-image-loader-v7/src/lib/ionic-image-loader.module.ts","../../../projects/ionic-image-loader-v7/src/public-api.ts","../../../projects/ionic-image-loader-v7/src/ionic-image-loader-v7.ts"],"sourcesContent":["import {HttpHeaders} from '@angular/common/http';\r\nimport {Injectable} from '@angular/core';\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class ImageLoaderConfigService {\r\n debugMode = false;\r\n\r\n spinnerEnabled = true;\r\n\r\n fallbackAsPlaceholder = false;\r\n\r\n backgroundSize = 'contain';\r\n\r\n backgroundRepeat = 'no-repeat';\r\n\r\n display = 'block';\r\n\r\n width = '100%';\r\n\r\n height = '100%';\r\n\r\n useImg = false;\r\n\r\n fallbackUrl;\r\n\r\n concurrency = 5;\r\n\r\n maxCacheSize = -1;\r\n\r\n maxCacheAge = -1;\r\n\r\n imageReturnType: 'base64' | 'uri' = 'uri';\r\n\r\n spinnerName;\r\n\r\n spinnerColor;\r\n\r\n httpHeaders: HttpHeaders;\r\n\r\n // Must be default 'true' for the new WebView to show images\r\n fileNameCachedWithExtension = true;\r\n\r\n fallbackFileNameCachedExtension = '.jpg';\r\n\r\n cacheDirectoryType: 'cache' | 'data' | 'external' = 'cache';\r\n\r\n private _cacheDirectoryName = 'image-loader-cache';\r\n\r\n\r\n get cacheDirectoryName() {\r\n return this._cacheDirectoryName;\r\n }\r\n\r\n set cacheDirectoryName(name) {\r\n name.replace(/\\W/g, '');\r\n this._cacheDirectoryName = name;\r\n }\r\n\r\n /**\r\n * Enables debug mode to receive console logs, errors, warnings\r\n */\r\n enableDebugMode() {\r\n this.debugMode = true;\r\n }\r\n\r\n /**\r\n * Enable/Disable the spinner by default. Defaults to true.\r\n * @param enable set to true to enable\r\n */\r\n enableSpinner(enable: boolean) {\r\n this.spinnerEnabled = enable;\r\n }\r\n\r\n /**\r\n * Enable/Disable the fallback image as placeholder instead of the spinner. Defaults to false.\r\n * @param enable set to true to enable\r\n */\r\n enableFallbackAsPlaceholder(enable: boolean) {\r\n this.fallbackAsPlaceholder = enable;\r\n }\r\n\r\n /**\r\n * Sets the cache directory name. Defaults to 'image-loader-cache'\r\n * @param name name of directory\r\n */\r\n setCacheDirectoryName(name: string) {\r\n this.cacheDirectoryName = name;\r\n }\r\n\r\n /**\r\n * Set default height for images that are not using <img> tag\r\n * @param height height\r\n */\r\n setHeight(height: string) {\r\n this.height = height;\r\n }\r\n\r\n /**\r\n * Set default width for images that are not using <img> tag\r\n * @param width Width\r\n */\r\n setWidth(width: string) {\r\n this.width = width;\r\n }\r\n\r\n /**\r\n * Enable display mode for images that are not using <img> tag\r\n * @param display Display mode\r\n */\r\n setDisplay(display: string) {\r\n this.display = display;\r\n }\r\n\r\n /**\r\n * Use <img> tag by default\r\n * @param use set to true to use <img> tag by default\r\n */\r\n useImageTag(use: boolean) {\r\n this.useImg = use;\r\n }\r\n\r\n /**\r\n * Set default background size for images that are not using <img> tag\r\n * @param backgroundSize Background size\r\n */\r\n setBackgroundSize(backgroundSize: string) {\r\n this.backgroundSize = backgroundSize;\r\n }\r\n\r\n /**\r\n * Set background repeat for images that are not using <img> tag\r\n * @param backgroundRepeat Background repeat\r\n */\r\n setBackgroundRepeat(backgroundRepeat: string) {\r\n this.backgroundRepeat = backgroundRepeat;\r\n }\r\n\r\n /**\r\n * Set fallback URL to use when image src is undefined or did not resolve.\r\n * This image will not be cached. This should ideally be a locally saved image.\r\n * @param fallbackUrl The remote or local URL of the image\r\n */\r\n setFallbackUrl(fallbackUrl: string) {\r\n this.fallbackUrl = fallbackUrl;\r\n }\r\n\r\n /**\r\n * Set the maximum number of allowed connections at the same time.\r\n * @param concurrency\r\n */\r\n setConcurrency(concurrency: number) {\r\n this.concurrency = concurrency;\r\n }\r\n\r\n /**\r\n * Sets the maximum allowed cache size\r\n * @param cacheSize Cache size in bytes\r\n */\r\n setMaximumCacheSize(cacheSize: number) {\r\n this.maxCacheSize = cacheSize;\r\n }\r\n\r\n /**\r\n * Sets the maximum allowed cache age\r\n * @param cacheAge Maximum cache age in milliseconds\r\n */\r\n setMaximumCacheAge(cacheAge: number) {\r\n this.maxCacheAge = cacheAge;\r\n }\r\n\r\n /**\r\n * Set the return type of cached images\r\n * @param imageReturnType The return type; either 'base64' or 'uri'\r\n */\r\n setImageReturnType(imageReturnType: 'base64' | 'uri') {\r\n this.imageReturnType = imageReturnType;\r\n }\r\n\r\n /**\r\n * Set the default spinner name\r\n * @param name\r\n */\r\n setSpinnerName(name: string) {\r\n this.spinnerName = name;\r\n }\r\n\r\n /**\r\n * Set the default spinner color\r\n * @param color\r\n */\r\n setSpinnerColor(color: string) {\r\n this.spinnerColor = color;\r\n }\r\n\r\n /**\r\n * Set headers options for the HttpClient transfers.\r\n * @param headers\r\n */\r\n setHttpHeaders(headers: HttpHeaders) {\r\n this.httpHeaders = headers;\r\n }\r\n\r\n /**\r\n * Set options for the FileTransfer plugin\r\n * @param options\r\n * @deprecated FileTransfer plugin removed.\r\n */\r\n setFileTransferOptions(options: {\r\n trustAllHosts: boolean;\r\n [key: string]: any;\r\n }) {\r\n // do nothing, plugin deprecated.\r\n }\r\n\r\n /**\r\n * Enable/Disable the save filename of cached images with extension. Defaults to false.\r\n * @param enable set to true to enable\r\n */\r\n setFileNameCachedWithExtension(enable: boolean) {\r\n this.fileNameCachedWithExtension = enable;\r\n }\r\n\r\n /**\r\n * Set fallback extension filename of cached images. Defaults to '.jpg'.\r\n * @param extension fallback extension (e.x .jpg)\r\n */\r\n setFallbackFileNameCachedExtension(extension: string) {\r\n this.fallbackFileNameCachedExtension = extension;\r\n }\r\n}\r\n","import {HttpClient} from '@angular/common/http';\r\nimport {Injectable} from '@angular/core';\r\nimport {File, FileEntry} from '@awesome-cordova-plugins/file/ngx';\r\nimport {WebView} from '@awesome-cordova-plugins/ionic-webview/ngx';\r\nimport {Platform} from '@ionic/angular';\r\nimport {fromEvent, Subject} from 'rxjs';\r\nimport {filter, first, take} from 'rxjs/operators';\r\nimport {ImageLoaderConfigService} from './image-loader-config.service';\r\n\r\ninterface IndexItem {\r\n name: string;\r\n modificationTime: Date;\r\n size: number;\r\n}\r\n\r\ninterface QueueItem {\r\n imageUrl: string;\r\n resolve: Function;\r\n reject: Function;\r\n}\r\n\r\ndeclare const Ionic: any;\r\n\r\nconst EXTENSIONS = ['jpg', 'png', 'jpeg', 'gif', 'svg', 'tiff'];\r\n\r\n@Injectable({\r\n providedIn: 'root',\r\n})\r\nexport class ImageLoaderService {\r\n\r\n /**\r\n * Indicates if the cache service is ready.\r\n * When the cache service isn't ready, images are loaded via browser instead.\r\n */\r\n private isCacheReady = false;\r\n /**\r\n * Indicates if this service is initialized.\r\n * This service is initialized once all the setup is done.\r\n */\r\n private isInit = false;\r\n private initPromiseResolve: Function;\r\n private initPromise = new Promise<void>(resolve => this.initPromiseResolve = resolve);\r\n private lockSubject = new Subject<boolean>();\r\n private lock$ = this.lockSubject.asObservable();\r\n /**\r\n * Number of concurrent requests allowed\r\n */\r\n private concurrency = 5;\r\n /**\r\n * Queue items\r\n */\r\n private queue: QueueItem[] = [];\r\n private processing = 0;\r\n /**\r\n * Fast accessible Object for currently processing items\r\n */\r\n private currentlyProcessing: { [index: string]: Promise<any> } = {};\r\n private cacheIndex: IndexItem[] = [];\r\n private currentCacheSize = 0;\r\n private indexed = false;\r\n private lockedCallsQueue: Function[] = [];\r\n\r\n constructor(\r\n private config: ImageLoaderConfigService,\r\n private file: File,\r\n private http: HttpClient,\r\n private platform: Platform,\r\n private webview: WebView,\r\n ) {\r\n if (!platform.is('cordova')) {\r\n // we are running on a browser, or using livereload\r\n // plugin will not function in this case\r\n this.isInit = true;\r\n this.throwWarning(\r\n 'You are running on a browser or using livereload, IonicImageLoader will not function, falling back to browser loading.',\r\n );\r\n this.initPromiseResolve();\r\n } else {\r\n fromEvent(document, 'deviceready')\r\n .pipe(first())\r\n .subscribe(res => {\r\n if (this.nativeAvailable) {\r\n this.initCache();\r\n } else {\r\n // we are running on a browser, or using livereload\r\n // plugin will not function in this case\r\n this.isInit = true;\r\n this.initPromiseResolve();\r\n this.throwWarning(\r\n 'You are running on a browser or using livereload, IonicImageLoader will not function, falling back to browser loading.',\r\n );\r\n }\r\n });\r\n }\r\n }\r\n\r\n get nativeAvailable(): boolean {\r\n return File.installed();\r\n }\r\n\r\n private get isCacheSpaceExceeded(): boolean {\r\n return (\r\n this.config.maxCacheSize > -1 &&\r\n this.currentCacheSize > this.config.maxCacheSize\r\n );\r\n }\r\n\r\n private get isWKWebView(): boolean {\r\n return (\r\n this.platform.is('ios') &&\r\n (<any> window).webkit &&\r\n (<any> window).webkit.messageHandlers\r\n );\r\n }\r\n\r\n private get isIonicWKWebView(): boolean {\r\n return (\r\n // Important: isWKWebview && isIonicWKWebview must be mutually excluse.\r\n // Otherwise the logic for copying to tmp under IOS will fail.\r\n (this.platform.is('android') && this.webview) ||\r\n (this.platform.is('android')) && (location.host === 'localhost:8080') ||\r\n (<any> window).LiveReload);\r\n }\r\n\r\n private get isDevServer(): boolean {\r\n return window['IonicDevServer'] !== undefined;\r\n }\r\n\r\n /**\r\n * Check if we can process more items in the queue\r\n */\r\n private get canProcess(): boolean {\r\n return this.queue.length > 0 && this.processing < this.concurrency;\r\n }\r\n\r\n ready(): Promise<void> {\r\n return this.initPromise;\r\n }\r\n\r\n /**\r\n * Preload an image\r\n * @param imageUrl Image URL\r\n * @returns returns a promise that resolves with the cached image URL\r\n */\r\n preload(imageUrl: string): Promise<string> {\r\n return this.getImagePath(imageUrl);\r\n }\r\n\r\n getFileCacheDirectory() {\r\n if (this.config.cacheDirectoryType === 'data') {\r\n return this.file.dataDirectory;\r\n } else if (this.config.cacheDirectoryType === 'external') {\r\n return this.platform.is('android') ? this.file.externalDataDirectory : this.file.documentsDirectory;\r\n }\r\n return this.file.cacheDirectory;\r\n }\r\n\r\n /**\r\n * Clears cache of a single image\r\n * @param imageUrl Image URL\r\n */\r\n async clearImageCache(imageUrl: string) {\r\n if (!this.platform.is('cordova')) {\r\n return;\r\n }\r\n\r\n await this.ready();\r\n\r\n this.runLocked(async () => {\r\n const fileName = this.createFileName(imageUrl);\r\n const route = this.getFileCacheDirectory() + this.config.cacheDirectoryName;\r\n // pause any operations\r\n this.isInit = false;\r\n\r\n try {\r\n await this.file.removeFile(route, fileName);\r\n\r\n if (this.isWKWebView && !this.isIonicWKWebView) {\r\n await this.file.removeFile(this.file.tempDirectory + this.config.cacheDirectoryName, fileName);\r\n }\r\n } catch (err) {\r\n this.throwError(err);\r\n }\r\n\r\n return this.initCache(true);\r\n });\r\n }\r\n\r\n /**\r\n * Clears the cache\r\n */\r\n async clearCache() {\r\n if (!this.platform.is('cordova')) {\r\n return;\r\n }\r\n\r\n await this.ready();\r\n\r\n this.runLocked(async () => {\r\n try {\r\n await this.file.removeRecursively(this.getFileCacheDirectory(), this.config.cacheDirectoryName);\r\n\r\n if (this.isWKWebView && !this.isIonicWKWebView) {\r\n // also clear the temp files\r\n try {\r\n this.file.removeRecursively(this.file.tempDirectory, this.config.cacheDirectoryName);\r\n } catch (err) {\r\n // Noop catch. Removing the tempDirectory might fail,\r\n // as it is not persistent.\r\n }\r\n }\r\n } catch (err) {\r\n this.throwError(err);\r\n }\r\n\r\n return this.initCache(true);\r\n });\r\n }\r\n\r\n /**\r\n * Gets the filesystem path of an image.\r\n * This will return the remote path if anything goes wrong or if the cache service isn't ready yet.\r\n * @param imageUrl The remote URL of the image\r\n * @returns Returns a promise that will always resolve with an image URL\r\n */\r\n async getImagePath(imageUrl: string): Promise<string> {\r\n if (typeof imageUrl !== 'string' || imageUrl.length <= 0) {\r\n throw new Error('The image url provided was empty or invalid.');\r\n }\r\n\r\n await this.ready();\r\n\r\n if (!this.isCacheReady) {\r\n this.throwWarning('The cache system is not running. Images will be loaded by your browser instead.');\r\n return imageUrl;\r\n }\r\n\r\n if (this.isImageUrlRelative(imageUrl)) {\r\n return imageUrl;\r\n }\r\n\r\n try {\r\n return await this.getCachedImagePath(imageUrl);\r\n } catch (err) {\r\n // image doesn't exist in cache, lets fetch it and save it\r\n return this.addItemToQueue(imageUrl);\r\n }\r\n }\r\n\r\n private async processLockedQueue() {\r\n if (await this.getLockedState()) {\r\n return;\r\n }\r\n\r\n if (this.lockedCallsQueue.length > 0) {\r\n await this.setLockedState(true);\r\n\r\n try {\r\n await this.lockedCallsQueue.slice(0, 1)[0]();\r\n } catch (err) {\r\n console.log('Error running locked function: ', err);\r\n }\r\n\r\n await this.setLockedState(false);\r\n return this.processLockedQueue();\r\n }\r\n }\r\n\r\n private getLockedState(): Promise<boolean> {\r\n return this.lock$\r\n .pipe(take(1))\r\n .toPromise();\r\n }\r\n\r\n private awaitUnlocked(): Promise<boolean> {\r\n return this.lock$\r\n .pipe(\r\n filter(locked => !!locked),\r\n take(1),\r\n )\r\n .toPromise();\r\n }\r\n\r\n private async setLockedState(locked: boolean) {\r\n this.lockSubject.next(locked);\r\n }\r\n\r\n private runLocked(fn: Function) {\r\n this.lockedCallsQueue.push(fn);\r\n this.processLockedQueue();\r\n }\r\n\r\n /**\r\n * Returns if an imageUrl is an relative path\r\n * @param imageUrl\r\n */\r\n private isImageUrlRelative(imageUrl: string) {\r\n return !/^(https?|file):\\/\\/\\/?/i.test(imageUrl);\r\n }\r\n\r\n /**\r\n * Add an item to the queue\r\n * @param imageUrl\r\n * @param resolve\r\n * @param reject\r\n */\r\n private addItemToQueue(imageUrl: string, resolve?, reject?): void | Promise<any> {\r\n let p: void | Promise<any>;\r\n\r\n if (!resolve && !reject) {\r\n p = new Promise<any>((res, rej) => {\r\n resolve = res;\r\n reject = rej;\r\n });\r\n } else {\r\n resolve = resolve || (() => {\r\n });\r\n reject = reject || (() => {\r\n });\r\n }\r\n\r\n this.queue.push({\r\n imageUrl,\r\n resolve,\r\n reject,\r\n });\r\n\r\n this.processQueue();\r\n\r\n return p;\r\n }\r\n\r\n /**\r\n * Processes one item from the queue\r\n */\r\n private async processQueue() {\r\n // make sure we can process items first\r\n if (!this.canProcess) {\r\n return;\r\n }\r\n\r\n // increase the processing number\r\n this.processing++;\r\n\r\n // take the first item from queue\r\n const currentItem: QueueItem = this.queue.splice(0, 1)[0];\r\n\r\n // function to call when done processing this item\r\n // this will reduce the processing number\r\n // then will execute this function again to process any remaining items\r\n const done = () => {\r\n this.processing--;\r\n this.processQueue();\r\n\r\n // only delete if it's the last/unique occurrence in the queue\r\n if (this.currentlyProcessing[currentItem.imageUrl] !== undefined && !this.currentlyInQueue(currentItem.imageUrl)) {\r\n delete this.currentlyProcessing[currentItem.imageUrl];\r\n }\r\n };\r\n\r\n const error = (e) => {\r\n currentItem.reject();\r\n this.throwError(e);\r\n done();\r\n };\r\n\r\n if (this.currentlyProcessing[currentItem.imageUrl] !== undefined) {\r\n try {\r\n // Prevented same Image from loading at the same time\r\n await this.currentlyProcessing[currentItem.imageUrl];\r\n const localUrl = await this.getCachedImagePath(currentItem.imageUrl);\r\n currentItem.resolve(localUrl);\r\n done();\r\n } catch (err) {\r\n error(err);\r\n }\r\n return;\r\n }\r\n\r\n this.currentlyProcessing[currentItem.imageUrl] = (async () => {\r\n // process more items concurrently if we can\r\n if (this.canProcess) {\r\n this.processQueue();\r\n }\r\n\r\n const localDir = this.getFileCacheDirectory() + this.config.cacheDirectoryName + '/';\r\n const fileName = this.createFileName(currentItem.imageUrl);\r\n\r\n try {\r\n const data: Blob = await this.http.get(currentItem.imageUrl, {\r\n responseType: 'blob',\r\n headers: this.config.httpHeaders,\r\n }).toPromise();\r\n\r\n const file = await this.file.writeFile(localDir, fileName, data, {replace: true}) as FileEntry;\r\n\r\n if (this.isCacheSpaceExceeded) {\r\n this.maintainCacheSize();\r\n }\r\n\r\n await this.addFileToIndex(file);\r\n const localUrl = await this.getCachedImagePath(currentItem.imageUrl);\r\n currentItem.resolve(localUrl);\r\n done();\r\n this.maintainCacheSize();\r\n } catch (err) {\r\n error(err);\r\n throw err;\r\n }\r\n })();\r\n\r\n }\r\n\r\n /**\r\n * Search if the url is currently in the queue\r\n * @param imageUrl Image url to search\r\n */\r\n private currentlyInQueue(imageUrl: string) {\r\n return this.queue.some(item => item.imageUrl === imageUrl);\r\n }\r\n\r\n /**\r\n * Initialize the cache service\r\n * @param [replace] Whether to replace the cache directory if it already exists\r\n */\r\n private async initCache(replace?: boolean) {\r\n this.concurrency = this.config.concurrency;\r\n\r\n // create cache directories if they do not exist\r\n try {\r\n await this.createCacheDirectory(replace);\r\n await this.indexCache();\r\n this.isCacheReady = true;\r\n } catch (err) {\r\n this.throwError(err);\r\n }\r\n\r\n this.isInit = true;\r\n this.initPromiseResolve();\r\n }\r\n\r\n /**\r\n * Adds a file to index.\r\n * Also deletes any files if they are older than the set maximum cache age.\r\n * @param file FileEntry to index\r\n */\r\n private async addFileToIndex(file: FileEntry): Promise<any> {\r\n const metadata = await new Promise<any>((resolve, reject) => file.getMetadata(resolve, reject));\r\n\r\n if (\r\n this.config.maxCacheAge > -1 &&\r\n Date.now() - metadata.modificationTime.getTime() >\r\n this.config.maxCacheAge\r\n ) {\r\n // file age exceeds maximum cache age\r\n return this.removeFile(file.name);\r\n } else {\r\n // file age doesn't exceed maximum cache age, or maximum cache age isn't set\r\n this.currentCacheSize += metadata.size;\r\n\r\n // add item to index\r\n this.cacheIndex.push({\r\n name: file.name,\r\n modificationTime: metadata.modificationTime,\r\n size: metadata.size,\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Indexes the cache if necessary\r\n */\r\n private async indexCache(): Promise<void> {\r\n this.cacheIndex = [];\r\n\r\n try {\r\n const files = await this.file.listDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName);\r\n await Promise.all(files.map(this.addFileToIndex.bind(this)));\r\n // Sort items by date. Most recent to oldest.\r\n this.cacheIndex = this.cacheIndex.sort(\r\n (a: IndexItem, b: IndexItem): number => (a > b ? -1 : a < b ? 1 : 0),\r\n );\r\n this.indexed = true;\r\n } catch (err) {\r\n this.throwError(err);\r\n }\r\n }\r\n\r\n /**\r\n * This method runs every time a new file is added.\r\n * It checks the cache size and ensures that it doesn't exceed the maximum cache size set in the config.\r\n * If the limit is reached, it will delete old images to create free space.\r\n */\r\n private async maintainCacheSize() {\r\n if (this.config.maxCacheSize > -1 && this.indexed) {\r\n const maintain = async () => {\r\n if (this.currentCacheSize > this.config.maxCacheSize) {\r\n // grab the first item in index since it's the oldest one\r\n const file: IndexItem = this.cacheIndex.splice(0, 1)[0];\r\n\r\n if (typeof file === 'undefined') {\r\n return maintain();\r\n }\r\n\r\n // delete the file then process next file if necessary\r\n try {\r\n await this.removeFile(file.name);\r\n } catch (err) {\r\n // ignore errors, nothing we can do about it\r\n }\r\n\r\n this.currentCacheSize -= file.size;\r\n return maintain();\r\n }\r\n };\r\n\r\n return maintain();\r\n }\r\n }\r\n\r\n /**\r\n * Remove a file\r\n * @param file The name of the file to remove\r\n */\r\n private async removeFile(file: string): Promise<any> {\r\n await this.file.removeFile(this.getFileCacheDirectory() + this.config.cacheDirectoryName, file);\r\n\r\n if (this.isWKWebView && !this.isIonicWKWebView) {\r\n try {\r\n return this.file.removeFile(this.file.tempDirectory + this.config.cacheDirectoryName, file);\r\n } catch (err) {\r\n // Noop catch. Removing the files from tempDirectory might fail, as it is not persistent.\r\n }\r\n }\r\n }\r\n\r\n /**\r\n * Get the local path of a previously cached image if exists\r\n * @param url The remote URL of the image\r\n * @returns Returns a promise that resolves with the local path if exists, or rejects if doesn't exist\r\n */\r\n private async getCachedImagePath(url: string): Promise<string> {\r\n await this.ready();\r\n\r\n if (!this.isCacheReady) {\r\n throw new Error('Cache is not ready');\r\n }\r\n\r\n // if we're running with livereload, ignore cache and call the resource from it's URL\r\n if (this.isDevServer) {\r\n return url;\r\n }\r\n\r\n // get file name\r\n const fileName = this.createFileName(url);\r\n\r\n // get full path\r\n const dirPath = this.getFileCacheDirectory() + this.config.cacheDirectoryName,\r\n tempDirPath = this.file.tempDirectory + this.config.cacheDirectoryName;\r\n\r\n try {\r\n // check if exists\r\n const fileEntry = await this.file.resolveLocalFilesystemUrl(dirPath + '/' + fileName) as FileEntry;\r\n\r\n // file exists in cache\r\n if (this.config.imageReturnType === 'base64') {\r\n // read the file as data url and return the base64 string.\r\n // should always be successful as the existence of the file\r\n // is already ensured\r\n const base64: string = await this.file.readAsDataURL(dirPath, fileName);\r\n return base64.replace('data:null', 'data:*/*');\r\n } else if (this.config.imageReturnType !== 'uri') {\r\n return;\r\n }\r\n\r\n // now check if iOS device & using WKWebView Engine.\r\n // in this case only the tempDirectory is accessible,\r\n // therefore the file needs to be copied into that directory first!\r\n if (this.isIonicWKWebView) {\r\n return this.normalizeUrl(fileEntry);\r\n }\r\n\r\n if (!this.isWKWebView) {\r\n // return native path\r\n return fileEntry.nativeURL;\r\n }\r\n\r\n // check if file already exists in temp directory\r\n try {\r\n const tempFileEntry = await this.file.resolveLocalFilesystemUrl(tempDirPath + '/' + fileName) as FileEntry;\r\n // file exists in temp directory\r\n // return native path\r\n return this.normalizeUrl(tempFileEntry);\r\n } catch (err) {\r\n // file does not yet exist in the temp directory.\r\n // copy it!\r\n const tempFileEntry = await this.file\r\n .copyFile(dirPath, fileName, tempDirPath, fileName) as FileEntry;\r\n\r\n // now the file exists in the temp directory\r\n // return native path\r\n return this.normalizeUrl(tempFileEntry);\r\n }\r\n } catch (err) {\r\n throw new Error('File does not exist');\r\n }\r\n }\r\n\r\n /**\r\n * Normalizes the image uri to a version that can be loaded in the webview\r\n * @param fileEntry the FileEntry of the image file\r\n * @returns the normalized Url\r\n */\r\n\r\n private normalizeUrl(fileEntry: FileEntry): string {\r\n // Use Ionic normalizeUrl to generate the right URL for Ionic WKWebView\r\n if (Ionic && typeof Ionic.normalizeURL === 'function') {\r\n return Ionic.normalizeURL(fileEntry.nativeURL);\r\n }\r\n // use new webview function to do the trick\r\n if (this.webview) {\r\n return this.webview.convertFileSrc(fileEntry.nativeURL);\r\n }\r\n return fileEntry.nativeURL;\r\n }\r\n\r\n /**\r\n * Throws a console error if debug mode is enabled\r\n * @param args Error message\r\n */\r\n private throwError(...args: any[]) {\r\n if (this.config.debugMode) {\r\n args.unshift('ImageLoader Error: ');\r\n console.error.apply(console, args);\r\n }\r\n }\r\n\r\n /**\r\n * Throws a console warning if debug mode is enabled\r\n * @param args Error message\r\n */\r\n private throwWarning(...args: any[]) {\r\n if (this.config.debugMode) {\r\n args.unshift('ImageLoader Warning: ');\r\n console.warn.apply(console, args);\r\n }\r\n }\r\n\r\n /**\r\n * Check if the cache directory exists\r\n * @param directory The directory to check. Either this.file.tempDirectory or this.getFileCacheDirectory()\r\n * @returns Returns a promise that resolves if exists, and rejects if it doesn't\r\n */\r\n private cacheDirectoryExists(directory: string): Promise<boolean> {\r\n return this.file.checkDir(directory, this.config.cacheDirectoryName);\r\n }\r\n\r\n /**\r\n * Create the cache directories\r\n * @param replace override directory if exists\r\n * @returns Returns a promise that resolves if the directories were created, and rejects on error\r\n */\r\n private createCacheDirectory(replace: boolean = false): Promise<any> {\r\n let cacheDirectoryPromise: Promise<any>, tempDirectoryPromise: Promise<any>;\r\n\r\n if (replace) {\r\n // create or replace the cache directory\r\n cacheDirectoryPromise = this.file.createDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName, replace);\r\n } else {\r\n // check if the cache directory exists.\r\n // if it does not exist create it!\r\n cacheDirectoryPromise = this.cacheDirectoryExists(this.getFileCacheDirectory())\r\n .catch(() => this.file.createDir(this.getFileCacheDirectory(), this.config.cacheDirectoryName, false));\r\n }\r\n\r\n if (this.isWKWebView && !this.isIonicWKWebView) {\r\n if (replace) {\r\n // create or replace the temp directory\r\n tempDirectoryPromise = this.file.createDir(\r\n this.file.tempDirectory,\r\n this.config.cacheDirectoryName,\r\n replace,\r\n );\r\n } else {\r\n // check if the temp directory exists.\r\n // if it does not exist create it!\r\n tempDirectoryPromise = this.cacheDirectoryExists(\r\n this.file.tempDirectory,\r\n ).catch(() =>\r\n this.file.createDir(\r\n this.file.tempDirectory,\r\n this.config.cacheDirectoryName,\r\n false,\r\n ),\r\n );\r\n }\r\n } else {\r\n tempDirectoryPromise = Promise.resolve();\r\n }\r\n\r\n return Promise.all([cacheDirectoryPromise, tempDirectoryPromise]);\r\n }\r\n\r\n /**\r\n * Creates a unique file name out of the URL\r\n * @param url URL of the file\r\n * @returns Unique file name\r\n */\r\n private createFileName(url: string): string {\r\n // hash the url to get a unique file name\r\n return (\r\n this.hashString(url).toString() +\r\n (this.config.fileNameCachedWithExtension\r\n ? this.getExtensionFromUrl(url)\r\n : '')\r\n );\r\n }\r\n\r\n /**\r\n * Converts a string to a unique 32-bit int\r\n * @param string string to hash\r\n * @returns 32-bit int\r\n */\r\n private hashString(string: string): number {\r\n let hash = 0,\r\n char;\r\n if (string.length === 0) {\r\n return hash;\r\n }\r\n for (let i = 0; i < string.length; i++) {\r\n char = string.charCodeAt(i);\r\n // tslint:disable-next-line\r\n hash = (hash << 5) - hash + char;\r\n // tslint:disable-next-line\r\n hash = hash & hash;\r\n }\r\n return hash;\r\n }\r\n\r\n /**\r\n * Extract extension from filename or url\r\n *\r\n * @param url\r\n * @returns\r\n *\r\n * Not always will url's contain a valid image extention. We'll check if any valid extention is supplied.\r\n * If not, we will use the default.\r\n */\r\n private getExtensionFromUrl(url: string): string {\r\n const urlWitoutParams = url.split(/\\#|\\?/)[0];\r\n const ext: string = (urlWitoutParams.substr((~-urlWitoutParams.lastIndexOf('.') >>> 0) + 1) || '').toLowerCase();\r\n\r\n return (\r\n EXTENSIONS.indexOf(ext) >= 0 ? ext : this.config.fallbackFileNameCachedExtension\r\n );\r\n }\r\n}\r\n","import { Component, ElementRef, EventEmitter, Input, OnInit, Output, Renderer2 } from '@angular/core';\r\nimport { ImageLoaderConfigService } from './services/image-loader-config.service';\r\nimport { ImageLoaderService } from './services/image-loader.service';\r\n\r\nconst propMap: any = {\r\n display: 'display',\r\n height: 'height',\r\n width: 'width',\r\n backgroundSize: 'background-size',\r\n backgroundRepeat: 'background-repeat',\r\n};\r\n\r\nexport interface ImageAttribute {\r\n element: string;\r\n value: string;\r\n}\r\n\r\n@Component({\r\n selector: 'img-loader',\r\n template: `\r\n <ion-spinner\r\n *ngIf=\"spinner && isLoading && !fallbackAsPlaceholder\"\r\n [name]=\"spinnerName\"\r\n [color]=\"spinnerColor\"\r\n ></ion-spinner>\r\n <ng-content></ng-content>\r\n `,\r\n styles: [\r\n 'ion-spinner { float: none; margin-left: auto; margin-right: auto; display: block; }',\r\n ],\r\n})\r\nexport class IonicImageLoaderComponent implements OnInit {\r\n /**\r\n * Fallback URL to load when the image url fails to load or does not exist.\r\n */\r\n @Input() fallbackUrl: string = this.config.fallbackUrl;\r\n /**\r\n * Whether to show a spinner while the image loads\r\n */\r\n @Input() spinner: boolean = this.config.spinnerEnabled;\r\n /**\r\n * Whether to show the fallback image instead of a spinner while the image loads\r\n */\r\n\r\n @Input() fallbackAsPlaceholder: boolean = this.config.fallbackAsPlaceholder;\r\n /**\r\n * Attributes to pass through to img tag if _useImg == true\r\n */\r\n @Input() imgAttributes: ImageAttribute[] = [];\r\n /**\r\n * Enable/Disable caching\r\n */\r\n @Input() cache = true;\r\n /**\r\n * Width of the image. This will be ignored if using useImg.\r\n */\r\n @Input() width: string = this.config.width;\r\n /**\r\n * Height of the image. This will be ignored if using useImg.\r\n */\r\n @Input() height: string = this.config.height;\r\n /**\r\n * Display type of the image. This will be ignored if using useImg.\r\n */\r\n @Input() display: string = this.config.display;\r\n /**\r\n * Background size. This will be ignored if using useImg.\r\n */\r\n @Input() backgroundSize: string = this.config.backgroundSize;\r\n /**\r\n * Background repeat. This will be ignored if using useImg.\r\n */\r\n @Input() backgroundRepeat: string = this.config.backgroundRepeat;\r\n /**\r\n * Name of the spinner\r\n */\r\n @Input() spinnerName: string = this.config.spinnerName;\r\n /**\r\n * Color of the spinner\r\n */\r\n @Input() spinnerColor: string = this.config.spinnerColor;\r\n /**\r\n * Notify on image load..\r\n */\r\n @Output()\r\n load: EventEmitter<IonicImageLoaderComponent> = new EventEmitter<IonicImageLoaderComponent>();\r\n /**\r\n * Indicates if the image is still loading\r\n */\r\n isLoading = true;\r\n element: HTMLElement;\r\n\r\n constructor(\r\n private _element: ElementRef,\r\n private renderer: Renderer2,\r\n private imageLoader: ImageLoaderService,\r\n private config: ImageLoaderConfigService,\r\n ) {\r\n }\r\n\r\n private _useImg: boolean = this.config.useImg;\r\n\r\n /**\r\n * Use <img> tag\r\n */\r\n @Input()\r\n set useImg(val: boolean) {\r\n this._useImg = val !== false;\r\n }\r\n\r\n /**\r\n * Convenience attribute to disable caching\r\n */\r\n @Input()\r\n set noCache(val: boolean) {\r\n this.cache = val !== false;\r\n }\r\n\r\n private _src: string;\r\n\r\n get src(): string {\r\n return this._src;\r\n }\r\n\r\n /**\r\n * The URL of the image to load.\r\n */\r\n @Input()\r\n set src(imageUrl: string) {\r\n this._src = this.processImageUrl(imageUrl);\r\n this.updateImage(this._src);\r\n }\r\n\r\n ngOnInit(): void {\r\n if (this.fallbackAsPlaceholder && this.fallbackUrl) {\r\n this.setImage(this.fallbackUrl, false);\r\n }\r\n\r\n if (!this.src) {\r\n // image url was not passed\r\n // this can happen when [src] is set to a variable that turned out to be undefined\r\n // one example could be a list of users with their profile pictures\r\n // in this case, it would be useful to use the fallback image instead\r\n // if fallbackUrl was used as placeholder we do not need to set it again\r\n if (!this.fallbackAsPlaceholder && this.fallbackUrl) {\r\n // we're not going to cache the fallback image since it should be locally saved\r\n this.setImage(this.fallbackUrl);\r\n } else {\r\n this.isLoading = false;\r\n }\r\n }\r\n }\r\n\r\n private updateImage(imageUrl: string) {\r\n this.imageLoader\r\n .getImagePath(imageUrl)\r\n .then((url: string) => this.setImage(url))\r\n .catch((error: any) => this.setImage(this.fallbackUrl || imageUrl));\r\n }\r\n\r\n /**\r\n * Gets the image URL to be loaded and disables caching if necessary\r\n */\r\n private processImageUrl(imageUrl: string): string {\r\n if (this.cache === false) {\r\n // need to disable caching\r\n\r\n if (imageUrl.indexOf('?') < 0) {\r\n // add ? if doesn't exists\r\n imageUrl += '?';\r\n } else {\r\n imageUrl += '&';\r\n }\r\n // append timestamp at the end to make URL unique\r\n imageUrl += 'cache_buster=' + Date.now();\r\n }\r\n\r\n return imageUrl;\r\n }\r\n\r\n /**\r\n * Set the image to be displayed\r\n * @param imageUrl image src\r\n * @param stopLoading set to true to mark the image as loaded\r\n */\r\n private setImage(imageUrl: string, stopLoading: boolean = true): void {\r\n this.isLoading = !stopLoading;\r\n\r\n if (this._useImg) {\r\n // Using <img> tag\r\n if (!this.element) {\r\n // create img element if we dont have one\r\n this.element = this.renderer.createElement('img');\r\n this.renderer.appendChild(this._element.nativeElement, this.element);\r\n }\r\n\r\n // set it's src\r\n this.renderer.setAttribute(this.element, 'src', imageUrl);\r\n\r\n // if imgAttributes are defined, add them to our img element\r\n this.imgAttributes.forEach((attribute) => {\r\n this.renderer.setAttribute(this.element, attribute.element, attribute.value);\r\n });\r\n if (this.fallbackUrl && !this.imageLoader.nativeAvailable) {\r\n this.renderer.listen(this.element, 'error', () =>\r\n this.renderer.setAttribute(this.element, 'src', this.fallbackUrl),\r\n );\r\n }\r\n } else {\r\n // Not using <img> tag\r\n\r\n this.element = this._element.nativeElement;\r\n\r\n for (const prop in propMap) {\r\n if (this[prop]) {\r\n this.renderer.setStyle(this.element, propMap[prop], this[prop]);\r\n }\r\n }\r\n this.renderer.setStyle(\r\n this.element,\r\n 'background-image',\r\n `url(\"${imageUrl || this.fallbackUrl}\")`,\r\n );\r\n }\r\n if (stopLoading) {\r\n this.load.emit(this);\r\n }\r\n }\r\n}\r\n","import {NgModule} from '@angular/core';\r\nimport {CommonModule} from '@angular/common';\r\nimport {HttpClientModule} from '@angular/common/http';\r\nimport {IonicModule} from '@ionic/angular';\r\nimport {File} from '@awesome-cordova-plugins/file/ngx';\r\n\r\nimport {IonicImageLoaderComponent} from './ionic-image-loader.component';\r\nimport {ImageLoaderConfigService} from './services/image-loader-config.service';\r\nimport {ImageLoaderService} from './services/image-loader.service';\r\n\r\n\r\n@NgModule({\r\n imports: [\r\n IonicModule,\r\n HttpClientModule,\r\n CommonModule\r\n ],\r\n declarations: [\r\n IonicImageLoaderComponent\r\n ],\r\n exports: [\r\n IonicImageLoaderComponent\r\n ],\r\n providers: [\r\n File,\r\n ImageLoaderConfigService,\r\n ImageLoaderService\r\n ]\r\n})\r\nexport class IonicImageLoaderModule {\r\n}\r\n","/*\n * Public API Surface of ionic-image-loader-v7\n */\n\nexport * from './lib/services/image-loader.service';\nexport * from './lib/services/image-loader-config.service';\nexport * from './lib/ionic-image-loader.component';\nexport * from './lib/ionic-image-loader.module';\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './public-api';\n"],"names":[],"mappings":"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