UNPKG

img-cacher

Version:

Serve images from localStorage – great for offline apps.

847 lines (707 loc) 25.8 kB
/******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { /******/ configurable: false, /******/ enumerable: true, /******/ get: getter /******/ }); /******/ } /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 0); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { module.exports = __webpack_require__(1); /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { var Generator = __webpack_require__(2); var ImgCacher = Generator.generate(function ImgCacher(options) { var _ = this; _.defineProperties({ writable: true }, { logging: typeof options.logging === 'undefined' || typeof options.logging === 'string' ? options.logging : 'ImgCacher', prefix: options.prefix || 'img-' }); }); ImgCacher.definePrototype({ base64Img: __webpack_require__(3), getImg: __webpack_require__(4), getData: __webpack_require__(5), isValid: __webpack_require__(6), reset: __webpack_require__(7), save: __webpack_require__(8), src: __webpack_require__(9), srcFromLocalStorage: __webpack_require__(10), buildSrc: __webpack_require__(11), log: __webpack_require__(12) }); if (typeof window !== 'undefined') { window.ImgCacher = ImgCacher; } module.exports = ImgCacher; /***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_RESULT__;/** * @name generate.js * @author Michaelangelo Jong */ (function GeneratorScope() { /** * Assert Error function. * @param {Boolean} condition Whether or not to throw error. * @param {String} message Error message. */ function assertError(condition, message) { if (!condition) { throw new Error(message); } } /** * Assert TypeError function. * @param {Boolean} condition Whether or not to throw error. * @param {String} message Error message. */ function assertTypeError(test, type) { if (typeof test !== type) { throw new TypeError('Expected \'' + type + '\' but instead found \'' + typeof test + '\''); } } /** * Returns the name of function 'func'. * @param {Function} func Any function. * @return {String} Name of 'func'. */ function getFunctionName(func) { if (func.name !== void(0)) { return func.name; } // Else use IE Shim var funcNameMatch = func.toString() .match(/function\s*([^\s]*)\s*\(/); func.name = (funcNameMatch && funcNameMatch[1]) || ''; return func.name; } /** * Returns true if 'obj' is an object containing only get and set functions, false otherwise. * @param {Any} obj Value to be tested. * @return {Boolean} true or false. */ function isGetSet(obj) { var keys, length; if (obj && typeof obj === 'object') { keys = Object.getOwnPropertyNames(obj) .sort(); length = keys.length; if ((length === 1 && (keys[0] === 'get' && typeof obj.get === 'function' || keys[0] === 'set' && typeof obj.set === 'function' )) || (length === 2 && (keys[0] === 'get' && typeof obj.get === 'function' && keys[1] === 'set' && typeof obj.set === 'function' ))) { return true; } } return false; } /** * Defines properties on 'obj'. * @param {Object} obj An object that 'properties' will be attached to. * @param {Object} descriptor Optional object descriptor that will be applied to all attaching properties on 'properties'. * @param {Object} properties An object who's properties will be attached to 'obj'. * @return {Generator} 'obj'. */ function defineObjectProperties(obj, descriptor, properties) { var setProperties = {}, i, keys, length, p = properties || descriptor, d = properties && descriptor; properties = (p && typeof p === 'object') ? p : {}; descriptor = (d && typeof d === 'object') ? d : {}; keys = Object.getOwnPropertyNames(properties); length = keys.length; for (i = 0; i < length; i++) { if (isGetSet(properties[keys[i]])) { setProperties[keys[i]] = { configurable: !!descriptor.configurable, enumerable: !!descriptor.enumerable, get: properties[keys[i]].get, set: properties[keys[i]].set }; } else { setProperties[keys[i]] = { configurable: !!descriptor.configurable, enumerable: !!descriptor.enumerable, writable: !!descriptor.writable, value: properties[keys[i]] }; } } Object.defineProperties(obj, setProperties); return obj; } var Creation = { /** * Defines properties on this object. * @param {Object} descriptor Optional object descriptor that will be applied to all attaching properties. * @param {Object} properties An object who's properties will be attached to this object. * @return {Object} This object. */ defineProperties: function defineProperties(descriptor, properties) { defineObjectProperties(this, descriptor, properties); return this; }, /** * returns the prototype of `this` Creation. * @return {Object} Prototype of `this` Creation. */ getProto: function getProto() { return Object.getPrototypeOf(this); }, /** * returns the prototype of `this` super Creation. * @return {Object} Prototype of `this` super Creation. */ getSuper: function getSuper() { return Object.getPrototypeOf(this.constructor.prototype); } }; var Generation = { /** * Returns true if 'generator' was generated by this Generator. * @param {Generator} generator A Generator. * @return {Boolean} true or false. */ isGeneration: function isGeneration(generator) { assertTypeError(generator, 'function'); var _ = this; return _.prototype.isPrototypeOf(generator.prototype); }, /** * Returns true if 'object' was created by this Generator. * @param {Object} object An Object. * @return {Boolean} true or false. */ isCreation: function isCreation(object) { var _ = this; return object instanceof _; }, /** * Generates a new generator that inherits from `this` generator. * @param {Generator} ParentGenerator Generator to inherit from. * @param {Function} create Create method that gets called when creating a new instance of new generator. * @return {Generator} New Generator that inherits from 'ParentGenerator'. */ generate: function generate(construct) { assertTypeError(construct, 'function'); var _ = this; defineObjectProperties( construct, { configurable: false, enumerable: false, writable: false }, { prototype: Object.create(_.prototype) } ); defineObjectProperties( construct, { configurable: false, enumerable: false, writable: false }, Generation ); defineObjectProperties( construct.prototype, { configurable: false, enumerable: false, writable: false }, { constructor: construct, generator: construct, } ); return construct; }, /** * Defines shared properties for all objects created by this generator. * @param {Object} descriptor Optional object descriptor that will be applied to all attaching properties. * @param {Object} properties An object who's properties will be attached to this generator's prototype. * @return {Generator} This generator. */ definePrototype: function definePrototype(descriptor, properties) { defineObjectProperties(this.prototype, descriptor, properties); return this; } }; function Generator() {} defineObjectProperties( Generator, { configurable: false, enumerable: false, writable: false }, { prototype: Generator.prototype } ); defineObjectProperties( Generator.prototype, { configurable: false, enumerable: false, writable: false }, Creation ); defineObjectProperties( Generator, { configurable: false, enumerable: false, writable: false }, Generation ); defineObjectProperties( Generator, { configurable: false, enumerable: false, writable: false }, { /** * Returns true if 'generator' was generated by this Generator. * @param {Generator} generator A Generator. * @return {Boolean} true or false. */ isGenerator: function isGenerator(generator) { return this.isGeneration(generator); }, /** * Generates a new generator that inherits from `this` generator. * @param {Generator} extendFrom Constructor to inherit from. * @param {Function} create Create method that gets called when creating a new instance of new generator. * @return {Generator} New Generator that inherits from 'ParentGenerator'. */ toGenerator: function toGenerator(extendFrom, create) { console.warn( 'Generator.toGenerator is depreciated please use Generator.generateFrom' ); return this.generateFrom(extendFrom, create); }, /** * Generates a new generator that inherits from `this` generator. * @param {Constructor} extendFrom Constructor to inherit from. * @param {Function} create Create method that gets called when creating a new instance of new generator. * @return {Generator} New Generator that inherits from 'ParentGenerator'. */ generateFrom: function generateFrom(extendFrom, create) { assertTypeError(extendFrom, 'function'); assertTypeError(create, 'function'); defineObjectProperties( create, { configurable: false, enumerable: false, writable: false }, { prototype: Object.create(extendFrom.prototype), } ); defineObjectProperties( create, { configurable: false, enumerable: false, writable: false }, Generation ); defineObjectProperties( create.prototype, { configurable: false, enumerable: false, writable: false }, { constructor: create, generator: create, } ); defineObjectProperties( create.prototype, { configurable: false, enumerable: false, writable: false }, Creation ); return create; } } ); Object.freeze(Generator); Object.freeze(Generator.prototype); // Exports if (true) { // AMD !(__WEBPACK_AMD_DEFINE_RESULT__ = function () { return Generator; }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__)); } else if (typeof module === 'object' && typeof exports === 'object') { // Node/CommonJS module.exports = Generator; } else { // Browser global window.Generator = Generator; } }()); /***/ }), /* 3 */ /***/ (function(module, exports) { function getSize(img, options) { var ratio = 1, width = img.width, height = img.height; if (options.fillWidth || options.fillHeight) { var widthRatio = (typeof options.fillWidth !== 'undefined' ? options.fillWidth : 0) / width, heightRatio = (typeof options.fillHeight !== 'undefined' ? options.fillHeight : 0) / height; ratio = widthRatio > heightRatio ? widthRatio : heightRatio; height = img.height * ratio; width = img.width * ratio; } else if (options.maxWidth || options.maxHeight) { if (options.maxWidth && options.maxHeight) { ratio = Math.min(options.maxWidth / img.width, options.maxHeight / img.height); } else if (options.maxWidth) { ratio = options.maxWidth / img.width; } else if (options.maxHeight) { ratio = options.maxHeight / img.height; } height = img.height * ratio; width = img.width * ratio; } else if (options.width || options.height) { if (options.width && options.height) { width = options.width; height = options.height; } else if (options.width) { ratio = options.width / img.width; width = options.width; height = img.height * ratio; } else if (options.height) { ratio = options.height / img.height; width = img.width * ratio; height = options.height; } } return { width: Math.ceil(width), height: Math.ceil(height) }; } function getCrop(img, options, size) { var crop = { x: Math.ceil(typeof options.cropX !== 'undefined' ? options.cropX : (options.cropWidth ? (size.width - options.cropWidth) / -2 : 0)), y: Math.ceil(typeof options.cropY !== 'undefined' ? options.cropY : (options.cropHeight ? (size.height - options.cropHeight) / -2 : 0)), width: Math.ceil(size.width), height: Math.ceil(size.height) }; if (options.cropWidth) { if (Math.ceil(options.cropWidth) < crop.width) { crop.width = Math.ceil(options.cropWidth); } if (Math.ceil(options.cropHeight) < crop.height) { crop.height = Math.ceil(options.cropHeight); } } return { x: Math.ceil(typeof options.cropX !== 'undefined' ? options.cropX : (options.cropWidth ? (size.width - options.cropWidth) / -2 : 0)), y: Math.ceil(typeof options.cropY !== 'undefined' ? options.cropY : (options.cropHeight ? (size.height - options.cropHeight) / -2 : 0)), width: Math.ceil(options.cropWidth ? Math.floor(options.cropWidth, size.width) : size.width), height: Math.ceil(options.cropHeight ? Math.floor(options.cropHeight, size.height) : size.height) }; } // Derived from https://stackoverflow.com/questions/19262141/resize-image-with-javascript-canvas-smoothly function steppedScale(img, width, step) { var canvas = document.createElement('canvas'), ctx = canvas.getContext('2d'), oc = document.createElement('canvas'), octx = oc.getContext('2d'); canvas.width = width; canvas.height = Math.ceil(canvas.width * img.height / img.width); if (img.width * step > width) { var mul = 1 / step, cur = { width: Math.ceil(Math.floor(img.width * step)), height: Math.ceil(Math.floor(img.height * step)) }; oc.width = cur.width; oc.height = cur.height; octx.drawImage(img, 0, 0, cur.width, cur.height); while (cur.width * step > width) { cur = { width: Math.floor(cur.width * step), height: Math.floor(cur.height * step) }; octx.drawImage(oc, 0, 0, cur.width * mul, cur.height * mul, 0, 0, cur.width, cur.height); } ctx.drawImage(oc, 0, 0, cur.width, cur.height, 0, 0, canvas.width, canvas.height); } else { ctx.drawImage(img, 0, 0, canvas.width, canvas.height); } return canvas; } var cached = {}; module.exports = function base64Img(img, options, done) { if (typeof options === 'function') { done = options; options = {}; } var _ = this, srcWithOptions = _.buildSrc(img.src, options); if (cached[srcWithOptions]) { _.log('base64Img', 'fromMemory', img.src, options); return done( undefined, cached[srcWithOptions] ); } var croppedCanvas = document.createElement('canvas'), croppedContext = croppedCanvas.getContext('2d'), size = getSize(img, options), crop = getCrop(img, options, size), bufferCanvas = steppedScale(img, size.width, typeof options.sharpness === 'number' && options.sharpness < 1 ? options.sharpness : 0.5); console.log('HEREE', size, crop) croppedCanvas.width = crop.width; croppedCanvas.height = crop.height; if (options.bg) { croppedContext.fillStyle = options.bg; croppedContext.fillRect(0, 0, crop.width, crop.height); } croppedContext.drawImage(bufferCanvas, crop.x * -1, crop.y * -1, crop.width, crop.height, 0, 0, crop.width, crop.height); croppedContext.imageSmoothingEnabled = true; cached[srcWithOptions] = croppedCanvas.toDataURL(options.type, options.encoderOptions); _.log('base64Img', 'noMemory', img.src, options); done( undefined, cached[srcWithOptions], true ); }; /***/ }), /* 4 */ /***/ (function(module, exports) { var cache = {}; function resolveImg(cacheKey, err, data) { var img = cache[cacheKey], i = img.callbacks.length; while (i--) { img.callbacks[i].call(img.callbacks[i], err, data); delete img.callbacks[i]; } } module.exports = function getImg(cacheKey, options, done) { var _ = this, img = cache[cacheKey]; if (img && !img.error) { if (img.isLoaded) { _.log('getImg', 'fromMemory', cacheKey, options); return done(undefined, img); } _.log('getImg', 'fromMemoryLoading', cacheKey, options); return img.callbacks.push(done); } img = cache[cacheKey] = cache[cacheKey] || new Image(); img.setAttribute('crossOrigin', 'anonymous'); img.callbacks = [done]; img.onload = function() { img.isLoaded = true; resolveImg(cacheKey, undefined, img); }; img.onerror = function onError(err) { img.error = err; resolveImg(cacheKey, err); }; if (typeof options.src === 'function') { options.src.call(_, cacheKey, function(newSrc) { img.src = newSrc; }); } else { img.src = options.src || cacheKey; } _.log('getImg', 'noMemory', cacheKey, options); }; /***/ }), /* 5 */ /***/ (function(module, exports) { module.exports = function getData(cacheKey, options, done) { var _ = this; if (typeof options === 'function') { done = options; options = {}; } _.getImg(cacheKey, options, function(err, img) { if (err) { return done(err); } _.base64Img(img, options, function(err, data, isCreated) { if (isCreated) { _.save(cacheKey, options, data); } done(err, data); }); }); }; /***/ }), /* 6 */ /***/ (function(module, exports) { module.exports = function isValid(src) { var xhr = window.ActiveXObject ? new window.ActiveXObject('Microsoft.XMLHTTP') : new XMLHttpRequest('Microsoft.XMLHTTP'); xhr.open( 'HEAD', src, false ); try { xhr.send(); return xhr.status >= 200 && (xhr.status < 300 || xhr.status === 304); } catch (e) { return false; } }; /***/ }), /* 7 */ /***/ (function(module, exports) { module.exports = function reset() { var _ = this, localStorage = window.localStorage, key; for (var i = 0; i < localStorage.length; i++) { key = localStorage.key(i); if (key.indexOf(_.prefix) === 0) { localStorage.removeItem(localStorage.key(i)); } } _.log('reset'); }; /***/ }), /* 8 */ /***/ (function(module, exports) { module.exports = function saveSrc(src, options, data) { var _ = this; if (!data) { data = options; options = {}; } window.localStorage.setItem( _.buildSrc(src, options), data ); _.log('save', src, options); }; /***/ }), /* 9 */ /***/ (function(module, exports) { module.exports = function getSrc(cacheKey, options, done) { var _ = this; _.srcFromLocalStorage(_.buildSrc(cacheKey, options), function(err, data) { if (err) { return _.getData(cacheKey, options, function(err, data) { if (err) { _.log('getSrc', 'error', err, cacheKey, options); return done(err); } _.log('getSrc', 'success', 'noLocalStorage', cacheKey, options); done(undefined, data); }); } _.log('getSrc', 'success', 'fromLocalStorage', cacheKey, options); done(undefined, data); }); }; /***/ }), /* 10 */ /***/ (function(module, exports) { module.exports = function cacheFromSrc(cacheKey, done) { var _ = this; if (cacheKey.slice(0, 5) === 'data:') { done(undefined, cacheKey); } else { var exists = window.localStorage.getItem( cacheKey ); if (exists) _.log('cacheFromSrc', 'fromLocalStorage', cacheKey); done(exists ? undefined : new Error('Image does not exist.'), exists); } }; /***/ }), /* 11 */ /***/ (function(module, exports) { module.exports = function buildSrc(src, options) { var _ = this; options = typeof options === 'object' ? options : {}; return _.prefix + src + '-' + JSON.stringify(options); }; /***/ }), /* 12 */ /***/ (function(module, exports) { module.exports = function log() { var _ = this; if (_.logging) { var args = Array.prototype.slice.call(arguments); args.unshift(_.logging + ' ~>'); console.log.apply(console.debug, args); } }; /***/ }) /******/ ]);