From 6740dec5006d4d1341816cb4a4992ee50059a6c5 Mon Sep 17 00:00:00 2001 From: chiu Date: Wed, 25 Sep 2019 14:53:46 +0800 Subject: [PATCH] add new feature:1.recreate thumb, 2.crop iamges --- app/assets/images/jquery.minicolors.png | Bin 0 -> 68627 bytes app/assets/javascripts/cropper.js | 3566 +++++++++++++++++ app/assets/javascripts/gallery.js | 106 +- app/assets/javascripts/jquery-cropper.js | 75 + app/assets/javascripts/jquery.minicolors.js | 1127 ++++++ app/assets/javascripts/theater.js | 1 + app/assets/stylesheets/cropper.css | 304 ++ app/assets/stylesheets/gallery.css | 7 + app/assets/stylesheets/jquery.minicolors.css | 432 ++ app/controllers/admin/galleries_controller.rb | 206 +- app/controllers/admin/images_controller.rb | 13 + app/controllers/galleries_controller.rb | 19 +- app/models/album.rb | 2 + app/models/album_color.rb | 6 + app/models/album_crop.rb | 8 + app/models/album_image.rb | 3 +- app/uploaders/gallery_uploader.rb | 133 +- app/views/admin/galleries/_album.html.erb | 3 + app/views/admin/galleries/_form.html.erb | 159 +- app/views/admin/galleries/_image.html.erb | 3 + .../admin/galleries/_recreate_thumb.html.erb | 106 + app/views/admin/galleries/index.html.erb | 18 +- .../admin/galleries/recreate_image.html.erb | 39 + app/views/admin/galleries/show.html.erb | 44 +- .../admin/galleries/upload_panel.html.erb | 6 +- .../admin/galleries/upload_process.html.erb | 39 + app/views/admin/images/batch_crop.html.erb | 102 + app/views/admin/images/crop_process.html.erb | 39 + app/views/admin/images/edit_image.html.erb | 59 + app/views/galleries/show.html.erb | 3 + config/locales/en.yml | 31 + config/locales/zh_tw.yml | 39 +- config/routes.rb | 12 +- 33 files changed, 6559 insertions(+), 151 deletions(-) create mode 100644 app/assets/images/jquery.minicolors.png create mode 100644 app/assets/javascripts/cropper.js create mode 100644 app/assets/javascripts/jquery-cropper.js create mode 100644 app/assets/javascripts/jquery.minicolors.js create mode 100644 app/assets/stylesheets/cropper.css create mode 100644 app/assets/stylesheets/jquery.minicolors.css create mode 100644 app/models/album_color.rb create mode 100644 app/models/album_crop.rb create mode 100644 app/views/admin/galleries/_recreate_thumb.html.erb create mode 100644 app/views/admin/galleries/recreate_image.html.erb create mode 100644 app/views/admin/galleries/upload_process.html.erb create mode 100644 app/views/admin/images/batch_crop.html.erb create mode 100644 app/views/admin/images/crop_process.html.erb create mode 100644 app/views/admin/images/edit_image.html.erb diff --git a/app/assets/images/jquery.minicolors.png b/app/assets/images/jquery.minicolors.png new file mode 100644 index 0000000000000000000000000000000000000000..bccc2012af78a0358e893f004e0207cac8f9e642 GIT binary patch literal 68627 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' + '
' + '' + '
' + '
' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '' + '
' + ''; + + /** + * Check if the given value is not a number. + */ + + var isNaN = Number.isNaN || WINDOW.isNaN; + /** + * Check if the given value is a number. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a number, else `false`. + */ + + function isNumber(value) { + return typeof value === 'number' && !isNaN(value); + } + /** + * Check if the given value is a positive number. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a positive number, else `false`. + */ + + var isPositiveNumber = function isPositiveNumber(value) { + return value > 0 && value < Infinity; + }; + /** + * Check if the given value is undefined. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is undefined, else `false`. + */ + + function isUndefined(value) { + return typeof value === 'undefined'; + } + /** + * Check if the given value is an object. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is an object, else `false`. + */ + + function isObject(value) { + return _typeof(value) === 'object' && value !== null; + } + var hasOwnProperty = Object.prototype.hasOwnProperty; + /** + * Check if the given value is a plain object. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a plain object, else `false`. + */ + + function isPlainObject(value) { + if (!isObject(value)) { + return false; + } + + try { + var _constructor = value.constructor; + var prototype = _constructor.prototype; + return _constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf'); + } catch (error) { + return false; + } + } + /** + * Check if the given value is a function. + * @param {*} value - The value to check. + * @returns {boolean} Returns `true` if the given value is a function, else `false`. + */ + + function isFunction(value) { + return typeof value === 'function'; + } + var slice = Array.prototype.slice; + /** + * Convert array-like or iterable object to an array. + * @param {*} value - The value to convert. + * @returns {Array} Returns a new array. + */ + + function toArray(value) { + return Array.from ? Array.from(value) : slice.call(value); + } + /** + * Iterate the given data. + * @param {*} data - The data to iterate. + * @param {Function} callback - The process function for each element. + * @returns {*} The original data. + */ + + function forEach(data, callback) { + if (data && isFunction(callback)) { + if (Array.isArray(data) || isNumber(data.length) + /* array-like */ + ) { + toArray(data).forEach(function (value, key) { + callback.call(data, value, key, data); + }); + } else if (isObject(data)) { + Object.keys(data).forEach(function (key) { + callback.call(data, data[key], key, data); + }); + } + } + + return data; + } + /** + * Extend the given object. + * @param {*} target - The target object to extend. + * @param {*} args - The rest objects for merging to the target object. + * @returns {Object} The extended object. + */ + + var assign = Object.assign || function assign(target) { + for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + if (isObject(target) && args.length > 0) { + args.forEach(function (arg) { + if (isObject(arg)) { + Object.keys(arg).forEach(function (key) { + target[key] = arg[key]; + }); + } + }); + } + + return target; + }; + var REGEXP_DECIMALS = /\.\d*(?:0|9){12}\d*$/; + /** + * Normalize decimal number. + * Check out {@link http://0.30000000000000004.com/} + * @param {number} value - The value to normalize. + * @param {number} [times=100000000000] - The times for normalizing. + * @returns {number} Returns the normalized number. + */ + + function normalizeDecimalNumber(value) { + var times = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 100000000000; + return REGEXP_DECIMALS.test(value) ? Math.round(value * times) / times : value; + } + var REGEXP_SUFFIX = /^width|height|left|top|marginLeft|marginTop$/; + /** + * Apply styles to the given element. + * @param {Element} element - The target element. + * @param {Object} styles - The styles for applying. + */ + + function setStyle(element, styles) { + var style = element.style; + forEach(styles, function (value, property) { + if (REGEXP_SUFFIX.test(property) && isNumber(value)) { + value = "".concat(value, "px"); + } + + style[property] = value; + }); + } + /** + * Check if the given element has a special class. + * @param {Element} element - The element to check. + * @param {string} value - The class to search. + * @returns {boolean} Returns `true` if the special class was found. + */ + + function hasClass(element, value) { + return element.classList ? element.classList.contains(value) : element.className.indexOf(value) > -1; + } + /** + * Add classes to the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be added. + */ + + function addClass(element, value) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + addClass(elem, value); + }); + return; + } + + if (element.classList) { + element.classList.add(value); + return; + } + + var className = element.className.trim(); + + if (!className) { + element.className = value; + } else if (className.indexOf(value) < 0) { + element.className = "".concat(className, " ").concat(value); + } + } + /** + * Remove classes from the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be removed. + */ + + function removeClass(element, value) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + removeClass(elem, value); + }); + return; + } + + if (element.classList) { + element.classList.remove(value); + return; + } + + if (element.className.indexOf(value) >= 0) { + element.className = element.className.replace(value, ''); + } + } + /** + * Add or remove classes from the given element. + * @param {Element} element - The target element. + * @param {string} value - The classes to be toggled. + * @param {boolean} added - Add only. + */ + + function toggleClass(element, value, added) { + if (!value) { + return; + } + + if (isNumber(element.length)) { + forEach(element, function (elem) { + toggleClass(elem, value, added); + }); + return; + } // IE10-11 doesn't support the second parameter of `classList.toggle` + + + if (added) { + addClass(element, value); + } else { + removeClass(element, value); + } + } + var REGEXP_CAMEL_CASE = /([a-z\d])([A-Z])/g; + /** + * Transform the given string from camelCase to kebab-case + * @param {string} value - The value to transform. + * @returns {string} The transformed value. + */ + + function toParamCase(value) { + return value.replace(REGEXP_CAMEL_CASE, '$1-$2').toLowerCase(); + } + /** + * Get data from the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to get. + * @returns {string} The data value. + */ + + function getData(element, name) { + if (isObject(element[name])) { + return element[name]; + } + + if (element.dataset) { + return element.dataset[name]; + } + + return element.getAttribute("data-".concat(toParamCase(name))); + } + /** + * Set data to the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to set. + * @param {string} data - The data value. + */ + + function setData(element, name, data) { + if (isObject(data)) { + element[name] = data; + } else if (element.dataset) { + element.dataset[name] = data; + } else { + element.setAttribute("data-".concat(toParamCase(name)), data); + } + } + /** + * Remove data from the given element. + * @param {Element} element - The target element. + * @param {string} name - The data key to remove. + */ + + function removeData(element, name) { + if (isObject(element[name])) { + try { + delete element[name]; + } catch (error) { + element[name] = undefined; + } + } else if (element.dataset) { + // #128 Safari not allows to delete dataset property + try { + delete element.dataset[name]; + } catch (error) { + element.dataset[name] = undefined; + } + } else { + element.removeAttribute("data-".concat(toParamCase(name))); + } + } + var REGEXP_SPACES = /\s\s*/; + + var onceSupported = function () { + var supported = false; + + if (IS_BROWSER) { + var once = false; + + var listener = function listener() {}; + + var options = Object.defineProperty({}, 'once', { + get: function get() { + supported = true; + return once; + }, + + /** + * This setter can fix a `TypeError` in strict mode + * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only} + * @param {boolean} value - The value to set + */ + set: function set(value) { + once = value; + } + }); + WINDOW.addEventListener('test', listener, options); + WINDOW.removeEventListener('test', listener, options); + } + + return supported; + }(); + /** + * Remove event listener from the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Function} listener - The event listener. + * @param {Object} options - The event options. + */ + + + function removeListener(element, type, listener) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var handler = listener; + type.trim().split(REGEXP_SPACES).forEach(function (event) { + if (!onceSupported) { + var listeners = element.listeners; + + if (listeners && listeners[event] && listeners[event][listener]) { + handler = listeners[event][listener]; + delete listeners[event][listener]; + + if (Object.keys(listeners[event]).length === 0) { + delete listeners[event]; + } + + if (Object.keys(listeners).length === 0) { + delete element.listeners; + } + } + } + + element.removeEventListener(event, handler, options); + }); + } + /** + * Add event listener to the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Function} listener - The event listener. + * @param {Object} options - The event options. + */ + + function addListener(element, type, listener) { + var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {}; + var _handler = listener; + type.trim().split(REGEXP_SPACES).forEach(function (event) { + if (options.once && !onceSupported) { + var _element$listeners = element.listeners, + listeners = _element$listeners === void 0 ? {} : _element$listeners; + + _handler = function handler() { + delete listeners[event][listener]; + element.removeEventListener(event, _handler, options); + + for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) { + args[_key2] = arguments[_key2]; + } + + listener.apply(element, args); + }; + + if (!listeners[event]) { + listeners[event] = {}; + } + + if (listeners[event][listener]) { + element.removeEventListener(event, listeners[event][listener], options); + } + + listeners[event][listener] = _handler; + element.listeners = listeners; + } + + element.addEventListener(event, _handler, options); + }); + } + /** + * Dispatch event on the target element. + * @param {Element} element - The event target. + * @param {string} type - The event type(s). + * @param {Object} data - The additional event data. + * @returns {boolean} Indicate if the event is default prevented or not. + */ + + function dispatchEvent(element, type, data) { + var event; // Event and CustomEvent on IE9-11 are global objects, not constructors + + if (isFunction(Event) && isFunction(CustomEvent)) { + event = new CustomEvent(type, { + detail: data, + bubbles: true, + cancelable: true + }); + } else { + event = document.createEvent('CustomEvent'); + event.initCustomEvent(type, true, true, data); + } + + return element.dispatchEvent(event); + } + /** + * Get the offset base on the document. + * @param {Element} element - The target element. + * @returns {Object} The offset data. + */ + + function getOffset(element) { + var box = element.getBoundingClientRect(); + return { + left: box.left + (window.pageXOffset - document.documentElement.clientLeft), + top: box.top + (window.pageYOffset - document.documentElement.clientTop) + }; + } + var location = WINDOW.location; + var REGEXP_ORIGINS = /^(\w+:)\/\/([^:/?#]*):?(\d*)/i; + /** + * Check if the given URL is a cross origin URL. + * @param {string} url - The target URL. + * @returns {boolean} Returns `true` if the given URL is a cross origin URL, else `false`. + */ + + function isCrossOriginURL(url) { + var parts = url.match(REGEXP_ORIGINS); + return parts !== null && (parts[1] !== location.protocol || parts[2] !== location.hostname || parts[3] !== location.port); + } + /** + * Add timestamp to the given URL. + * @param {string} url - The target URL. + * @returns {string} The result URL. + */ + + function addTimestamp(url) { + var timestamp = "timestamp=".concat(new Date().getTime()); + return url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp; + } + /** + * Get transforms base on the given object. + * @param {Object} obj - The target object. + * @returns {string} A string contains transform values. + */ + + function getTransforms(_ref) { + var rotate = _ref.rotate, + scaleX = _ref.scaleX, + scaleY = _ref.scaleY, + translateX = _ref.translateX, + translateY = _ref.translateY; + var values = []; + + if (isNumber(translateX) && translateX !== 0) { + values.push("translateX(".concat(translateX, "px)")); + } + + if (isNumber(translateY) && translateY !== 0) { + values.push("translateY(".concat(translateY, "px)")); + } // Rotate should come first before scale to match orientation transform + + + if (isNumber(rotate) && rotate !== 0) { + values.push("rotate(".concat(rotate, "deg)")); + } + + if (isNumber(scaleX) && scaleX !== 1) { + values.push("scaleX(".concat(scaleX, ")")); + } + + if (isNumber(scaleY) && scaleY !== 1) { + values.push("scaleY(".concat(scaleY, ")")); + } + + var transform = values.length ? values.join(' ') : 'none'; + return { + WebkitTransform: transform, + msTransform: transform, + transform: transform + }; + } + /** + * Get the max ratio of a group of pointers. + * @param {string} pointers - The target pointers. + * @returns {number} The result ratio. + */ + + function getMaxZoomRatio(pointers) { + var pointers2 = assign({}, pointers); + var ratios = []; + forEach(pointers, function (pointer, pointerId) { + delete pointers2[pointerId]; + forEach(pointers2, function (pointer2) { + var x1 = Math.abs(pointer.startX - pointer2.startX); + var y1 = Math.abs(pointer.startY - pointer2.startY); + var x2 = Math.abs(pointer.endX - pointer2.endX); + var y2 = Math.abs(pointer.endY - pointer2.endY); + var z1 = Math.sqrt(x1 * x1 + y1 * y1); + var z2 = Math.sqrt(x2 * x2 + y2 * y2); + var ratio = (z2 - z1) / z1; + ratios.push(ratio); + }); + }); + ratios.sort(function (a, b) { + return Math.abs(a) < Math.abs(b); + }); + return ratios[0]; + } + /** + * Get a pointer from an event object. + * @param {Object} event - The target event object. + * @param {boolean} endOnly - Indicates if only returns the end point coordinate or not. + * @returns {Object} The result pointer contains start and/or end point coordinates. + */ + + function getPointer(_ref2, endOnly) { + var pageX = _ref2.pageX, + pageY = _ref2.pageY; + var end = { + endX: pageX, + endY: pageY + }; + return endOnly ? end : assign({ + startX: pageX, + startY: pageY + }, end); + } + /** + * Get the center point coordinate of a group of pointers. + * @param {Object} pointers - The target pointers. + * @returns {Object} The center point coordinate. + */ + + function getPointersCenter(pointers) { + var pageX = 0; + var pageY = 0; + var count = 0; + forEach(pointers, function (_ref3) { + var startX = _ref3.startX, + startY = _ref3.startY; + pageX += startX; + pageY += startY; + count += 1; + }); + pageX /= count; + pageY /= count; + return { + pageX: pageX, + pageY: pageY + }; + } + /** + * Get the max sizes in a rectangle under the given aspect ratio. + * @param {Object} data - The original sizes. + * @param {string} [type='contain'] - The adjust type. + * @returns {Object} The result sizes. + */ + + function getAdjustedSizes(_ref4) // or 'cover' + { + var aspectRatio = _ref4.aspectRatio, + height = _ref4.height, + width = _ref4.width; + var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'contain'; + var isValidWidth = isPositiveNumber(width); + var isValidHeight = isPositiveNumber(height); + + if (isValidWidth && isValidHeight) { + var adjustedWidth = height * aspectRatio; + + if (type === 'contain' && adjustedWidth > width || type === 'cover' && adjustedWidth < width) { + height = width / aspectRatio; + } else { + width = height * aspectRatio; + } + } else if (isValidWidth) { + height = width / aspectRatio; + } else if (isValidHeight) { + width = height * aspectRatio; + } + + return { + width: width, + height: height + }; + } + /** + * Get the new sizes of a rectangle after rotated. + * @param {Object} data - The original sizes. + * @returns {Object} The result sizes. + */ + + function getRotatedSizes(_ref5) { + var width = _ref5.width, + height = _ref5.height, + degree = _ref5.degree; + degree = Math.abs(degree) % 180; + + if (degree === 90) { + return { + width: height, + height: width + }; + } + + var arc = degree % 90 * Math.PI / 180; + var sinArc = Math.sin(arc); + var cosArc = Math.cos(arc); + var newWidth = width * cosArc + height * sinArc; + var newHeight = width * sinArc + height * cosArc; + return degree > 90 ? { + width: newHeight, + height: newWidth + } : { + width: newWidth, + height: newHeight + }; + } + /** + * Get a canvas which drew the given image. + * @param {HTMLImageElement} image - The image for drawing. + * @param {Object} imageData - The image data. + * @param {Object} canvasData - The canvas data. + * @param {Object} options - The options. + * @returns {HTMLCanvasElement} The result canvas. + */ + + function getSourceCanvas(image, _ref6, _ref7, _ref8) { + var imageAspectRatio = _ref6.aspectRatio, + imageNaturalWidth = _ref6.naturalWidth, + imageNaturalHeight = _ref6.naturalHeight, + _ref6$rotate = _ref6.rotate, + rotate = _ref6$rotate === void 0 ? 0 : _ref6$rotate, + _ref6$scaleX = _ref6.scaleX, + scaleX = _ref6$scaleX === void 0 ? 1 : _ref6$scaleX, + _ref6$scaleY = _ref6.scaleY, + scaleY = _ref6$scaleY === void 0 ? 1 : _ref6$scaleY; + var aspectRatio = _ref7.aspectRatio, + naturalWidth = _ref7.naturalWidth, + naturalHeight = _ref7.naturalHeight; + var _ref8$fillColor = _ref8.fillColor, + fillColor = _ref8$fillColor === void 0 ? 'transparent' : _ref8$fillColor, + _ref8$imageSmoothingE = _ref8.imageSmoothingEnabled, + imageSmoothingEnabled = _ref8$imageSmoothingE === void 0 ? true : _ref8$imageSmoothingE, + _ref8$imageSmoothingQ = _ref8.imageSmoothingQuality, + imageSmoothingQuality = _ref8$imageSmoothingQ === void 0 ? 'low' : _ref8$imageSmoothingQ, + _ref8$maxWidth = _ref8.maxWidth, + maxWidth = _ref8$maxWidth === void 0 ? Infinity : _ref8$maxWidth, + _ref8$maxHeight = _ref8.maxHeight, + maxHeight = _ref8$maxHeight === void 0 ? Infinity : _ref8$maxHeight, + _ref8$minWidth = _ref8.minWidth, + minWidth = _ref8$minWidth === void 0 ? 0 : _ref8$minWidth, + _ref8$minHeight = _ref8.minHeight, + minHeight = _ref8$minHeight === void 0 ? 0 : _ref8$minHeight; + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + var maxSizes = getAdjustedSizes({ + aspectRatio: aspectRatio, + width: maxWidth, + height: maxHeight + }); + var minSizes = getAdjustedSizes({ + aspectRatio: aspectRatio, + width: minWidth, + height: minHeight + }, 'cover'); + var width = Math.min(maxSizes.width, Math.max(minSizes.width, naturalWidth)); + var height = Math.min(maxSizes.height, Math.max(minSizes.height, naturalHeight)); // Note: should always use image's natural sizes for drawing as + // imageData.naturalWidth === canvasData.naturalHeight when rotate % 180 === 90 + + var destMaxSizes = getAdjustedSizes({ + aspectRatio: imageAspectRatio, + width: maxWidth, + height: maxHeight + }); + var destMinSizes = getAdjustedSizes({ + aspectRatio: imageAspectRatio, + width: minWidth, + height: minHeight + }, 'cover'); + var destWidth = Math.min(destMaxSizes.width, Math.max(destMinSizes.width, imageNaturalWidth)); + var destHeight = Math.min(destMaxSizes.height, Math.max(destMinSizes.height, imageNaturalHeight)); + var params = [-destWidth / 2, -destHeight / 2, destWidth, destHeight]; + canvas.width = normalizeDecimalNumber(width); + canvas.height = normalizeDecimalNumber(height); + context.fillStyle = fillColor; + context.fillRect(0, 0, width, height); + context.save(); + context.translate(width / 2, height / 2); + context.rotate(rotate * Math.PI / 180); + context.scale(scaleX, scaleY); + context.imageSmoothingEnabled = imageSmoothingEnabled; + context.imageSmoothingQuality = imageSmoothingQuality; + context.drawImage.apply(context, [image].concat(_toConsumableArray(params.map(function (param) { + return Math.floor(normalizeDecimalNumber(param)); + })))); + context.restore(); + return canvas; + } + var fromCharCode = String.fromCharCode; + /** + * Get string from char code in data view. + * @param {DataView} dataView - The data view for read. + * @param {number} start - The start index. + * @param {number} length - The read length. + * @returns {string} The read result. + */ + + function getStringFromCharCode(dataView, start, length) { + var str = ''; + length += start; + + for (var i = start; i < length; i += 1) { + str += fromCharCode(dataView.getUint8(i)); + } + + return str; + } + var REGEXP_DATA_URL_HEAD = /^data:.*,/; + /** + * Transform Data URL to array buffer. + * @param {string} dataURL - The Data URL to transform. + * @returns {ArrayBuffer} The result array buffer. + */ + + function dataURLToArrayBuffer(dataURL) { + var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, ''); + var binary = atob(base64); + var arrayBuffer = new ArrayBuffer(binary.length); + var uint8 = new Uint8Array(arrayBuffer); + forEach(uint8, function (value, i) { + uint8[i] = binary.charCodeAt(i); + }); + return arrayBuffer; + } + /** + * Transform array buffer to Data URL. + * @param {ArrayBuffer} arrayBuffer - The array buffer to transform. + * @param {string} mimeType - The mime type of the Data URL. + * @returns {string} The result Data URL. + */ + + function arrayBufferToDataURL(arrayBuffer, mimeType) { + var chunks = []; // Chunk Typed Array for better performance (#435) + + var chunkSize = 8192; + var uint8 = new Uint8Array(arrayBuffer); + + while (uint8.length > 0) { + // XXX: Babel's `toConsumableArray` helper will throw error in IE or Safari 9 + // eslint-disable-next-line prefer-spread + chunks.push(fromCharCode.apply(null, toArray(uint8.subarray(0, chunkSize)))); + uint8 = uint8.subarray(chunkSize); + } + + return "data:".concat(mimeType, ";base64,").concat(btoa(chunks.join(''))); + } + /** + * Get orientation value from given array buffer. + * @param {ArrayBuffer} arrayBuffer - The array buffer to read. + * @returns {number} The read orientation value. + */ + + function resetAndGetOrientation(arrayBuffer) { + var dataView = new DataView(arrayBuffer); + var orientation; // Ignores range error when the image does not have correct Exif information + + try { + var littleEndian; + var app1Start; + var ifdStart; // Only handle JPEG image (start by 0xFFD8) + + if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) { + var length = dataView.byteLength; + var offset = 2; + + while (offset + 1 < length) { + if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) { + app1Start = offset; + break; + } + + offset += 1; + } + } + + if (app1Start) { + var exifIDCode = app1Start + 4; + var tiffOffset = app1Start + 10; + + if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') { + var endianness = dataView.getUint16(tiffOffset); + littleEndian = endianness === 0x4949; + + if (littleEndian || endianness === 0x4D4D + /* bigEndian */ + ) { + if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) { + var firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian); + + if (firstIFDOffset >= 0x00000008) { + ifdStart = tiffOffset + firstIFDOffset; + } + } + } + } + } + + if (ifdStart) { + var _length = dataView.getUint16(ifdStart, littleEndian); + + var _offset; + + var i; + + for (i = 0; i < _length; i += 1) { + _offset = ifdStart + i * 12 + 2; + + if (dataView.getUint16(_offset, littleEndian) === 0x0112 + /* Orientation */ + ) { + // 8 is the offset of the current tag's value + _offset += 8; // Get the original orientation value + + orientation = dataView.getUint16(_offset, littleEndian); // Override the orientation with its default value + + dataView.setUint16(_offset, 1, littleEndian); + break; + } + } + } + } catch (error) { + orientation = 1; + } + + return orientation; + } + /** + * Parse Exif Orientation value. + * @param {number} orientation - The orientation to parse. + * @returns {Object} The parsed result. + */ + + function parseOrientation(orientation) { + var rotate = 0; + var scaleX = 1; + var scaleY = 1; + + switch (orientation) { + // Flip horizontal + case 2: + scaleX = -1; + break; + // Rotate left 180° + + case 3: + rotate = -180; + break; + // Flip vertical + + case 4: + scaleY = -1; + break; + // Flip vertical and rotate right 90° + + case 5: + rotate = 90; + scaleY = -1; + break; + // Rotate right 90° + + case 6: + rotate = 90; + break; + // Flip horizontal and rotate right 90° + + case 7: + rotate = 90; + scaleX = -1; + break; + // Rotate left 90° + + case 8: + rotate = -90; + break; + + default: + } + + return { + rotate: rotate, + scaleX: scaleX, + scaleY: scaleY + }; + } + + var render = { + render: function render() { + this.initContainer(); + this.initCanvas(); + this.initCropBox(); + this.renderCanvas(); + + if (this.cropped) { + this.renderCropBox(); + } + }, + initContainer: function initContainer() { + var element = this.element, + options = this.options, + container = this.container, + cropper = this.cropper; + addClass(cropper, CLASS_HIDDEN); + removeClass(element, CLASS_HIDDEN); + var containerData = { + width: Math.max(container.offsetWidth, Number(options.minContainerWidth) || 200), + height: Math.max(container.offsetHeight, Number(options.minContainerHeight) || 100) + }; + this.containerData = containerData; + setStyle(cropper, { + width: containerData.width, + height: containerData.height + }); + addClass(element, CLASS_HIDDEN); + removeClass(cropper, CLASS_HIDDEN); + }, + // Canvas (image wrapper) + initCanvas: function initCanvas() { + var containerData = this.containerData, + imageData = this.imageData; + var viewMode = this.options.viewMode; + var rotated = Math.abs(imageData.rotate) % 180 === 90; + var naturalWidth = rotated ? imageData.naturalHeight : imageData.naturalWidth; + var naturalHeight = rotated ? imageData.naturalWidth : imageData.naturalHeight; + var aspectRatio = naturalWidth / naturalHeight; + var canvasWidth = containerData.width; + var canvasHeight = containerData.height; + + if (containerData.height * aspectRatio > containerData.width) { + if (viewMode === 3) { + canvasWidth = containerData.height * aspectRatio; + } else { + canvasHeight = containerData.width / aspectRatio; + } + } else if (viewMode === 3) { + canvasHeight = containerData.width / aspectRatio; + } else { + canvasWidth = containerData.height * aspectRatio; + } + + var canvasData = { + aspectRatio: aspectRatio, + naturalWidth: naturalWidth, + naturalHeight: naturalHeight, + width: canvasWidth, + height: canvasHeight + }; + canvasData.left = (containerData.width - canvasWidth) / 2; + canvasData.top = (containerData.height - canvasHeight) / 2; + canvasData.oldLeft = canvasData.left; + canvasData.oldTop = canvasData.top; + this.canvasData = canvasData; + this.limited = viewMode === 1 || viewMode === 2; + this.limitCanvas(true, true); + this.initialImageData = assign({}, imageData); + this.initialCanvasData = assign({}, canvasData); + }, + limitCanvas: function limitCanvas(sizeLimited, positionLimited) { + var options = this.options, + containerData = this.containerData, + canvasData = this.canvasData, + cropBoxData = this.cropBoxData; + var viewMode = options.viewMode; + var aspectRatio = canvasData.aspectRatio; + var cropped = this.cropped && cropBoxData; + + if (sizeLimited) { + var minCanvasWidth = Number(options.minCanvasWidth) || 0; + var minCanvasHeight = Number(options.minCanvasHeight) || 0; + + if (viewMode > 1) { + minCanvasWidth = Math.max(minCanvasWidth, containerData.width); + minCanvasHeight = Math.max(minCanvasHeight, containerData.height); + + if (viewMode === 3) { + if (minCanvasHeight * aspectRatio > minCanvasWidth) { + minCanvasWidth = minCanvasHeight * aspectRatio; + } else { + minCanvasHeight = minCanvasWidth / aspectRatio; + } + } + } else if (viewMode > 0) { + if (minCanvasWidth) { + minCanvasWidth = Math.max(minCanvasWidth, cropped ? cropBoxData.width : 0); + } else if (minCanvasHeight) { + minCanvasHeight = Math.max(minCanvasHeight, cropped ? cropBoxData.height : 0); + } else if (cropped) { + minCanvasWidth = cropBoxData.width; + minCanvasHeight = cropBoxData.height; + + if (minCanvasHeight * aspectRatio > minCanvasWidth) { + minCanvasWidth = minCanvasHeight * aspectRatio; + } else { + minCanvasHeight = minCanvasWidth / aspectRatio; + } + } + } + + var _getAdjustedSizes = getAdjustedSizes({ + aspectRatio: aspectRatio, + width: minCanvasWidth, + height: minCanvasHeight + }); + + minCanvasWidth = _getAdjustedSizes.width; + minCanvasHeight = _getAdjustedSizes.height; + canvasData.minWidth = minCanvasWidth; + canvasData.minHeight = minCanvasHeight; + canvasData.maxWidth = Infinity; + canvasData.maxHeight = Infinity; + } + + if (positionLimited) { + if (viewMode > (cropped ? 0 : 1)) { + var newCanvasLeft = containerData.width - canvasData.width; + var newCanvasTop = containerData.height - canvasData.height; + canvasData.minLeft = Math.min(0, newCanvasLeft); + canvasData.minTop = Math.min(0, newCanvasTop); + canvasData.maxLeft = Math.max(0, newCanvasLeft); + canvasData.maxTop = Math.max(0, newCanvasTop); + + if (cropped && this.limited) { + canvasData.minLeft = Math.min(cropBoxData.left, cropBoxData.left + (cropBoxData.width - canvasData.width)); + canvasData.minTop = Math.min(cropBoxData.top, cropBoxData.top + (cropBoxData.height - canvasData.height)); + canvasData.maxLeft = cropBoxData.left; + canvasData.maxTop = cropBoxData.top; + + if (viewMode === 2) { + if (canvasData.width >= containerData.width) { + canvasData.minLeft = Math.min(0, newCanvasLeft); + canvasData.maxLeft = Math.max(0, newCanvasLeft); + } + + if (canvasData.height >= containerData.height) { + canvasData.minTop = Math.min(0, newCanvasTop); + canvasData.maxTop = Math.max(0, newCanvasTop); + } + } + } + } else { + canvasData.minLeft = -canvasData.width; + canvasData.minTop = -canvasData.height; + canvasData.maxLeft = containerData.width; + canvasData.maxTop = containerData.height; + } + } + }, + renderCanvas: function renderCanvas(changed, transformed) { + var canvasData = this.canvasData, + imageData = this.imageData; + + if (transformed) { + var _getRotatedSizes = getRotatedSizes({ + width: imageData.naturalWidth * Math.abs(imageData.scaleX || 1), + height: imageData.naturalHeight * Math.abs(imageData.scaleY || 1), + degree: imageData.rotate || 0 + }), + naturalWidth = _getRotatedSizes.width, + naturalHeight = _getRotatedSizes.height; + + var width = canvasData.width * (naturalWidth / canvasData.naturalWidth); + var height = canvasData.height * (naturalHeight / canvasData.naturalHeight); + canvasData.left -= (width - canvasData.width) / 2; + canvasData.top -= (height - canvasData.height) / 2; + canvasData.width = width; + canvasData.height = height; + canvasData.aspectRatio = naturalWidth / naturalHeight; + canvasData.naturalWidth = naturalWidth; + canvasData.naturalHeight = naturalHeight; + this.limitCanvas(true, false); + } + + if (canvasData.width > canvasData.maxWidth || canvasData.width < canvasData.minWidth) { + canvasData.left = canvasData.oldLeft; + } + + if (canvasData.height > canvasData.maxHeight || canvasData.height < canvasData.minHeight) { + canvasData.top = canvasData.oldTop; + } + + canvasData.width = Math.min(Math.max(canvasData.width, canvasData.minWidth), canvasData.maxWidth); + canvasData.height = Math.min(Math.max(canvasData.height, canvasData.minHeight), canvasData.maxHeight); + this.limitCanvas(false, true); + canvasData.left = Math.min(Math.max(canvasData.left, canvasData.minLeft), canvasData.maxLeft); + canvasData.top = Math.min(Math.max(canvasData.top, canvasData.minTop), canvasData.maxTop); + canvasData.oldLeft = canvasData.left; + canvasData.oldTop = canvasData.top; + setStyle(this.canvas, assign({ + width: canvasData.width, + height: canvasData.height + }, getTransforms({ + translateX: canvasData.left, + translateY: canvasData.top + }))); + this.renderImage(changed); + + if (this.cropped && this.limited) { + this.limitCropBox(true, true); + } + }, + renderImage: function renderImage(changed) { + var canvasData = this.canvasData, + imageData = this.imageData; + var width = imageData.naturalWidth * (canvasData.width / canvasData.naturalWidth); + var height = imageData.naturalHeight * (canvasData.height / canvasData.naturalHeight); + assign(imageData, { + width: width, + height: height, + left: (canvasData.width - width) / 2, + top: (canvasData.height - height) / 2 + }); + setStyle(this.image, assign({ + width: imageData.width, + height: imageData.height + }, getTransforms(assign({ + translateX: imageData.left, + translateY: imageData.top + }, imageData)))); + + if (changed) { + this.output(); + } + }, + initCropBox: function initCropBox() { + var options = this.options, + canvasData = this.canvasData; + var aspectRatio = options.aspectRatio || options.initialAspectRatio; + var autoCropArea = Number(options.autoCropArea) || 0.8; + var cropBoxData = { + width: canvasData.width, + height: canvasData.height + }; + + if (aspectRatio) { + if (canvasData.height * aspectRatio > canvasData.width) { + cropBoxData.height = cropBoxData.width / aspectRatio; + } else { + cropBoxData.width = cropBoxData.height * aspectRatio; + } + } + + this.cropBoxData = cropBoxData; + this.limitCropBox(true, true); // Initialize auto crop area + + cropBoxData.width = Math.min(Math.max(cropBoxData.width, cropBoxData.minWidth), cropBoxData.maxWidth); + cropBoxData.height = Math.min(Math.max(cropBoxData.height, cropBoxData.minHeight), cropBoxData.maxHeight); // The width/height of auto crop area must large than "minWidth/Height" + + cropBoxData.width = Math.max(cropBoxData.minWidth, cropBoxData.width * autoCropArea); + cropBoxData.height = Math.max(cropBoxData.minHeight, cropBoxData.height * autoCropArea); + cropBoxData.left = canvasData.left + (canvasData.width - cropBoxData.width) / 2; + cropBoxData.top = canvasData.top + (canvasData.height - cropBoxData.height) / 2; + cropBoxData.oldLeft = cropBoxData.left; + cropBoxData.oldTop = cropBoxData.top; + this.initialCropBoxData = assign({}, cropBoxData); + }, + limitCropBox: function limitCropBox(sizeLimited, positionLimited) { + var options = this.options, + containerData = this.containerData, + canvasData = this.canvasData, + cropBoxData = this.cropBoxData, + limited = this.limited; + var aspectRatio = options.aspectRatio; + + if (sizeLimited) { + var minCropBoxWidth = Number(options.minCropBoxWidth) || 0; + var minCropBoxHeight = Number(options.minCropBoxHeight) || 0; + var maxCropBoxWidth = limited ? Math.min(containerData.width, canvasData.width, canvasData.width + canvasData.left, containerData.width - canvasData.left) : containerData.width; + var maxCropBoxHeight = limited ? Math.min(containerData.height, canvasData.height, canvasData.height + canvasData.top, containerData.height - canvasData.top) : containerData.height; // The min/maxCropBoxWidth/Height must be less than container's width/height + + minCropBoxWidth = Math.min(minCropBoxWidth, containerData.width); + minCropBoxHeight = Math.min(minCropBoxHeight, containerData.height); + + if (aspectRatio) { + if (minCropBoxWidth && minCropBoxHeight) { + if (minCropBoxHeight * aspectRatio > minCropBoxWidth) { + minCropBoxHeight = minCropBoxWidth / aspectRatio; + } else { + minCropBoxWidth = minCropBoxHeight * aspectRatio; + } + } else if (minCropBoxWidth) { + minCropBoxHeight = minCropBoxWidth / aspectRatio; + } else if (minCropBoxHeight) { + minCropBoxWidth = minCropBoxHeight * aspectRatio; + } + + if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) { + maxCropBoxHeight = maxCropBoxWidth / aspectRatio; + } else { + maxCropBoxWidth = maxCropBoxHeight * aspectRatio; + } + } // The minWidth/Height must be less than maxWidth/Height + + + cropBoxData.minWidth = Math.min(minCropBoxWidth, maxCropBoxWidth); + cropBoxData.minHeight = Math.min(minCropBoxHeight, maxCropBoxHeight); + cropBoxData.maxWidth = maxCropBoxWidth; + cropBoxData.maxHeight = maxCropBoxHeight; + } + + if (positionLimited) { + if (limited) { + cropBoxData.minLeft = Math.max(0, canvasData.left); + cropBoxData.minTop = Math.max(0, canvasData.top); + cropBoxData.maxLeft = Math.min(containerData.width, canvasData.left + canvasData.width) - cropBoxData.width; + cropBoxData.maxTop = Math.min(containerData.height, canvasData.top + canvasData.height) - cropBoxData.height; + } else { + cropBoxData.minLeft = 0; + cropBoxData.minTop = 0; + cropBoxData.maxLeft = containerData.width - cropBoxData.width; + cropBoxData.maxTop = containerData.height - cropBoxData.height; + } + } + }, + renderCropBox: function renderCropBox() { + var options = this.options, + containerData = this.containerData, + cropBoxData = this.cropBoxData; + + if (cropBoxData.width > cropBoxData.maxWidth || cropBoxData.width < cropBoxData.minWidth) { + cropBoxData.left = cropBoxData.oldLeft; + } + + if (cropBoxData.height > cropBoxData.maxHeight || cropBoxData.height < cropBoxData.minHeight) { + cropBoxData.top = cropBoxData.oldTop; + } + + cropBoxData.width = Math.min(Math.max(cropBoxData.width, cropBoxData.minWidth), cropBoxData.maxWidth); + cropBoxData.height = Math.min(Math.max(cropBoxData.height, cropBoxData.minHeight), cropBoxData.maxHeight); + this.limitCropBox(false, true); + cropBoxData.left = Math.min(Math.max(cropBoxData.left, cropBoxData.minLeft), cropBoxData.maxLeft); + cropBoxData.top = Math.min(Math.max(cropBoxData.top, cropBoxData.minTop), cropBoxData.maxTop); + cropBoxData.oldLeft = cropBoxData.left; + cropBoxData.oldTop = cropBoxData.top; + + if (options.movable && options.cropBoxMovable) { + // Turn to move the canvas when the crop box is equal to the container + setData(this.face, DATA_ACTION, cropBoxData.width >= containerData.width && cropBoxData.height >= containerData.height ? ACTION_MOVE : ACTION_ALL); + } + + setStyle(this.cropBox, assign({ + width: cropBoxData.width, + height: cropBoxData.height + }, getTransforms({ + translateX: cropBoxData.left, + translateY: cropBoxData.top + }))); + + if (this.cropped && this.limited) { + this.limitCanvas(true, true); + } + + if (!this.disabled) { + this.output(); + } + }, + output: function output() { + this.preview(); + dispatchEvent(this.element, EVENT_CROP, this.getData()); + } + }; + + var preview = { + initPreview: function initPreview() { + var element = this.element, + crossOrigin = this.crossOrigin; + var preview = this.options.preview; + var url = crossOrigin ? this.crossOriginUrl : this.url; + var alt = element.alt || 'The image to preview'; + var image = document.createElement('img'); + + if (crossOrigin) { + image.crossOrigin = crossOrigin; + } + + image.src = url; + image.alt = alt; + this.viewBox.appendChild(image); + this.viewBoxImage = image; + + if (!preview) { + return; + } + + var previews = preview; + + if (typeof preview === 'string') { + previews = element.ownerDocument.querySelectorAll(preview); + } else if (preview.querySelector) { + previews = [preview]; + } + + this.previews = previews; + forEach(previews, function (el) { + var img = document.createElement('img'); // Save the original size for recover + + setData(el, DATA_PREVIEW, { + width: el.offsetWidth, + height: el.offsetHeight, + html: el.innerHTML + }); + + if (crossOrigin) { + img.crossOrigin = crossOrigin; + } + + img.src = url; + img.alt = alt; + /** + * Override img element styles + * Add `display:block` to avoid margin top issue + * Add `height:auto` to override `height` attribute on IE8 + * (Occur only when margin-top <= -height) + */ + + img.style.cssText = 'display:block;' + 'width:100%;' + 'height:auto;' + 'min-width:0!important;' + 'min-height:0!important;' + 'max-width:none!important;' + 'max-height:none!important;' + 'image-orientation:0deg!important;"'; + el.innerHTML = ''; + el.appendChild(img); + }); + }, + resetPreview: function resetPreview() { + forEach(this.previews, function (element) { + var data = getData(element, DATA_PREVIEW); + setStyle(element, { + width: data.width, + height: data.height + }); + element.innerHTML = data.html; + removeData(element, DATA_PREVIEW); + }); + }, + preview: function preview() { + var imageData = this.imageData, + canvasData = this.canvasData, + cropBoxData = this.cropBoxData; + var cropBoxWidth = cropBoxData.width, + cropBoxHeight = cropBoxData.height; + var width = imageData.width, + height = imageData.height; + var left = cropBoxData.left - canvasData.left - imageData.left; + var top = cropBoxData.top - canvasData.top - imageData.top; + + if (!this.cropped || this.disabled) { + return; + } + + setStyle(this.viewBoxImage, assign({ + width: width, + height: height + }, getTransforms(assign({ + translateX: -left, + translateY: -top + }, imageData)))); + forEach(this.previews, function (element) { + var data = getData(element, DATA_PREVIEW); + var originalWidth = data.width; + var originalHeight = data.height; + var newWidth = originalWidth; + var newHeight = originalHeight; + var ratio = 1; + + if (cropBoxWidth) { + ratio = originalWidth / cropBoxWidth; + newHeight = cropBoxHeight * ratio; + } + + if (cropBoxHeight && newHeight > originalHeight) { + ratio = originalHeight / cropBoxHeight; + newWidth = cropBoxWidth * ratio; + newHeight = originalHeight; + } + + setStyle(element, { + width: newWidth, + height: newHeight + }); + setStyle(element.getElementsByTagName('img')[0], assign({ + width: width * ratio, + height: height * ratio + }, getTransforms(assign({ + translateX: -left * ratio, + translateY: -top * ratio + }, imageData)))); + }); + } + }; + + var events = { + bind: function bind() { + var element = this.element, + options = this.options, + cropper = this.cropper; + + if (isFunction(options.cropstart)) { + addListener(element, EVENT_CROP_START, options.cropstart); + } + + if (isFunction(options.cropmove)) { + addListener(element, EVENT_CROP_MOVE, options.cropmove); + } + + if (isFunction(options.cropend)) { + addListener(element, EVENT_CROP_END, options.cropend); + } + + if (isFunction(options.crop)) { + addListener(element, EVENT_CROP, options.crop); + } + + if (isFunction(options.zoom)) { + addListener(element, EVENT_ZOOM, options.zoom); + } + + addListener(cropper, EVENT_POINTER_DOWN, this.onCropStart = this.cropStart.bind(this)); + + if (options.zoomable && options.zoomOnWheel) { + addListener(cropper, EVENT_WHEEL, this.onWheel = this.wheel.bind(this), { + passive: false, + capture: true + }); + } + + if (options.toggleDragModeOnDblclick) { + addListener(cropper, EVENT_DBLCLICK, this.onDblclick = this.dblclick.bind(this)); + } + + addListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove = this.cropMove.bind(this)); + addListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd = this.cropEnd.bind(this)); + + if (options.responsive) { + addListener(window, EVENT_RESIZE, this.onResize = this.resize.bind(this)); + } + }, + unbind: function unbind() { + var element = this.element, + options = this.options, + cropper = this.cropper; + + if (isFunction(options.cropstart)) { + removeListener(element, EVENT_CROP_START, options.cropstart); + } + + if (isFunction(options.cropmove)) { + removeListener(element, EVENT_CROP_MOVE, options.cropmove); + } + + if (isFunction(options.cropend)) { + removeListener(element, EVENT_CROP_END, options.cropend); + } + + if (isFunction(options.crop)) { + removeListener(element, EVENT_CROP, options.crop); + } + + if (isFunction(options.zoom)) { + removeListener(element, EVENT_ZOOM, options.zoom); + } + + removeListener(cropper, EVENT_POINTER_DOWN, this.onCropStart); + + if (options.zoomable && options.zoomOnWheel) { + removeListener(cropper, EVENT_WHEEL, this.onWheel, { + passive: false, + capture: true + }); + } + + if (options.toggleDragModeOnDblclick) { + removeListener(cropper, EVENT_DBLCLICK, this.onDblclick); + } + + removeListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove); + removeListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd); + + if (options.responsive) { + removeListener(window, EVENT_RESIZE, this.onResize); + } + } + }; + + var handlers = { + resize: function resize() { + var options = this.options, + container = this.container, + containerData = this.containerData; + var minContainerWidth = Number(options.minContainerWidth) || MIN_CONTAINER_WIDTH; + var minContainerHeight = Number(options.minContainerHeight) || MIN_CONTAINER_HEIGHT; + + if (this.disabled || containerData.width <= minContainerWidth || containerData.height <= minContainerHeight) { + return; + } + + var ratio = container.offsetWidth / containerData.width; // Resize when width changed or height changed + + if (ratio !== 1 || container.offsetHeight !== containerData.height) { + var canvasData; + var cropBoxData; + + if (options.restore) { + canvasData = this.getCanvasData(); + cropBoxData = this.getCropBoxData(); + } + + this.render(); + + if (options.restore) { + this.setCanvasData(forEach(canvasData, function (n, i) { + canvasData[i] = n * ratio; + })); + this.setCropBoxData(forEach(cropBoxData, function (n, i) { + cropBoxData[i] = n * ratio; + })); + } + } + }, + dblclick: function dblclick() { + if (this.disabled || this.options.dragMode === DRAG_MODE_NONE) { + return; + } + + this.setDragMode(hasClass(this.dragBox, CLASS_CROP) ? DRAG_MODE_MOVE : DRAG_MODE_CROP); + }, + wheel: function wheel(event) { + var _this = this; + + var ratio = Number(this.options.wheelZoomRatio) || 0.1; + var delta = 1; + + if (this.disabled) { + return; + } + + event.preventDefault(); // Limit wheel speed to prevent zoom too fast (#21) + + if (this.wheeling) { + return; + } + + this.wheeling = true; + setTimeout(function () { + _this.wheeling = false; + }, 50); + + if (event.deltaY) { + delta = event.deltaY > 0 ? 1 : -1; + } else if (event.wheelDelta) { + delta = -event.wheelDelta / 120; + } else if (event.detail) { + delta = event.detail > 0 ? 1 : -1; + } + + this.zoom(-delta * ratio, event); + }, + cropStart: function cropStart(event) { + var buttons = event.buttons, + button = event.button; + + if (this.disabled // No primary button (Usually the left button) + // Note that touch events have no `buttons` or `button` property + || isNumber(buttons) && buttons !== 1 || isNumber(button) && button !== 0 // Open context menu + || event.ctrlKey) { + return; + } + + var options = this.options, + pointers = this.pointers; + var action; + + if (event.changedTouches) { + // Handle touch event + forEach(event.changedTouches, function (touch) { + pointers[touch.identifier] = getPointer(touch); + }); + } else { + // Handle mouse event and pointer event + pointers[event.pointerId || 0] = getPointer(event); + } + + if (Object.keys(pointers).length > 1 && options.zoomable && options.zoomOnTouch) { + action = ACTION_ZOOM; + } else { + action = getData(event.target, DATA_ACTION); + } + + if (!REGEXP_ACTIONS.test(action)) { + return; + } + + if (dispatchEvent(this.element, EVENT_CROP_START, { + originalEvent: event, + action: action + }) === false) { + return; + } // This line is required for preventing page zooming in iOS browsers + + + event.preventDefault(); + this.action = action; + this.cropping = false; + + if (action === ACTION_CROP) { + this.cropping = true; + addClass(this.dragBox, CLASS_MODAL); + } + }, + cropMove: function cropMove(event) { + var action = this.action; + + if (this.disabled || !action) { + return; + } + + var pointers = this.pointers; + event.preventDefault(); + + if (dispatchEvent(this.element, EVENT_CROP_MOVE, { + originalEvent: event, + action: action + }) === false) { + return; + } + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + // The first parameter should not be undefined (#432) + assign(pointers[touch.identifier] || {}, getPointer(touch, true)); + }); + } else { + assign(pointers[event.pointerId || 0] || {}, getPointer(event, true)); + } + + this.change(event); + }, + cropEnd: function cropEnd(event) { + if (this.disabled) { + return; + } + + var action = this.action, + pointers = this.pointers; + + if (event.changedTouches) { + forEach(event.changedTouches, function (touch) { + delete pointers[touch.identifier]; + }); + } else { + delete pointers[event.pointerId || 0]; + } + + if (!action) { + return; + } + + event.preventDefault(); + + if (!Object.keys(pointers).length) { + this.action = ''; + } + + if (this.cropping) { + this.cropping = false; + toggleClass(this.dragBox, CLASS_MODAL, this.cropped && this.options.modal); + } + + dispatchEvent(this.element, EVENT_CROP_END, { + originalEvent: event, + action: action + }); + } + }; + + var change = { + change: function change(event) { + var options = this.options, + canvasData = this.canvasData, + containerData = this.containerData, + cropBoxData = this.cropBoxData, + pointers = this.pointers; + var action = this.action; + var aspectRatio = options.aspectRatio; + var left = cropBoxData.left, + top = cropBoxData.top, + width = cropBoxData.width, + height = cropBoxData.height; + var right = left + width; + var bottom = top + height; + var minLeft = 0; + var minTop = 0; + var maxWidth = containerData.width; + var maxHeight = containerData.height; + var renderable = true; + var offset; // Locking aspect ratio in "free mode" by holding shift key + + if (!aspectRatio && event.shiftKey) { + aspectRatio = width && height ? width / height : 1; + } + + if (this.limited) { + minLeft = cropBoxData.minLeft; + minTop = cropBoxData.minTop; + maxWidth = minLeft + Math.min(containerData.width, canvasData.width, canvasData.left + canvasData.width); + maxHeight = minTop + Math.min(containerData.height, canvasData.height, canvasData.top + canvasData.height); + } + + var pointer = pointers[Object.keys(pointers)[0]]; + var range = { + x: pointer.endX - pointer.startX, + y: pointer.endY - pointer.startY + }; + + var check = function check(side) { + switch (side) { + case ACTION_EAST: + if (right + range.x > maxWidth) { + range.x = maxWidth - right; + } + + break; + + case ACTION_WEST: + if (left + range.x < minLeft) { + range.x = minLeft - left; + } + + break; + + case ACTION_NORTH: + if (top + range.y < minTop) { + range.y = minTop - top; + } + + break; + + case ACTION_SOUTH: + if (bottom + range.y > maxHeight) { + range.y = maxHeight - bottom; + } + + break; + + default: + } + }; + + switch (action) { + // Move crop box + case ACTION_ALL: + left += range.x; + top += range.y; + break; + // Resize crop box + + case ACTION_EAST: + if (range.x >= 0 && (right >= maxWidth || aspectRatio && (top <= minTop || bottom >= maxHeight))) { + renderable = false; + break; + } + + check(ACTION_EAST); + width += range.x; + + if (width < 0) { + action = ACTION_WEST; + width = -width; + left -= width; + } + + if (aspectRatio) { + height = width / aspectRatio; + top += (cropBoxData.height - height) / 2; + } + + break; + + case ACTION_NORTH: + if (range.y <= 0 && (top <= minTop || aspectRatio && (left <= minLeft || right >= maxWidth))) { + renderable = false; + break; + } + + check(ACTION_NORTH); + height -= range.y; + top += range.y; + + if (height < 0) { + action = ACTION_SOUTH; + height = -height; + top -= height; + } + + if (aspectRatio) { + width = height * aspectRatio; + left += (cropBoxData.width - width) / 2; + } + + break; + + case ACTION_WEST: + if (range.x <= 0 && (left <= minLeft || aspectRatio && (top <= minTop || bottom >= maxHeight))) { + renderable = false; + break; + } + + check(ACTION_WEST); + width -= range.x; + left += range.x; + + if (width < 0) { + action = ACTION_EAST; + width = -width; + left -= width; + } + + if (aspectRatio) { + height = width / aspectRatio; + top += (cropBoxData.height - height) / 2; + } + + break; + + case ACTION_SOUTH: + if (range.y >= 0 && (bottom >= maxHeight || aspectRatio && (left <= minLeft || right >= maxWidth))) { + renderable = false; + break; + } + + check(ACTION_SOUTH); + height += range.y; + + if (height < 0) { + action = ACTION_NORTH; + height = -height; + top -= height; + } + + if (aspectRatio) { + width = height * aspectRatio; + left += (cropBoxData.width - width) / 2; + } + + break; + + case ACTION_NORTH_EAST: + if (aspectRatio) { + if (range.y <= 0 && (top <= minTop || right >= maxWidth)) { + renderable = false; + break; + } + + check(ACTION_NORTH); + height -= range.y; + top += range.y; + width = height * aspectRatio; + } else { + check(ACTION_NORTH); + check(ACTION_EAST); + + if (range.x >= 0) { + if (right < maxWidth) { + width += range.x; + } else if (range.y <= 0 && top <= minTop) { + renderable = false; + } + } else { + width += range.x; + } + + if (range.y <= 0) { + if (top > minTop) { + height -= range.y; + top += range.y; + } + } else { + height -= range.y; + top += range.y; + } + } + + if (width < 0 && height < 0) { + action = ACTION_SOUTH_WEST; + height = -height; + width = -width; + top -= height; + left -= width; + } else if (width < 0) { + action = ACTION_NORTH_WEST; + width = -width; + left -= width; + } else if (height < 0) { + action = ACTION_SOUTH_EAST; + height = -height; + top -= height; + } + + break; + + case ACTION_NORTH_WEST: + if (aspectRatio) { + if (range.y <= 0 && (top <= minTop || left <= minLeft)) { + renderable = false; + break; + } + + check(ACTION_NORTH); + height -= range.y; + top += range.y; + width = height * aspectRatio; + left += cropBoxData.width - width; + } else { + check(ACTION_NORTH); + check(ACTION_WEST); + + if (range.x <= 0) { + if (left > minLeft) { + width -= range.x; + left += range.x; + } else if (range.y <= 0 && top <= minTop) { + renderable = false; + } + } else { + width -= range.x; + left += range.x; + } + + if (range.y <= 0) { + if (top > minTop) { + height -= range.y; + top += range.y; + } + } else { + height -= range.y; + top += range.y; + } + } + + if (width < 0 && height < 0) { + action = ACTION_SOUTH_EAST; + height = -height; + width = -width; + top -= height; + left -= width; + } else if (width < 0) { + action = ACTION_NORTH_EAST; + width = -width; + left -= width; + } else if (height < 0) { + action = ACTION_SOUTH_WEST; + height = -height; + top -= height; + } + + break; + + case ACTION_SOUTH_WEST: + if (aspectRatio) { + if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) { + renderable = false; + break; + } + + check(ACTION_WEST); + width -= range.x; + left += range.x; + height = width / aspectRatio; + } else { + check(ACTION_SOUTH); + check(ACTION_WEST); + + if (range.x <= 0) { + if (left > minLeft) { + width -= range.x; + left += range.x; + } else if (range.y >= 0 && bottom >= maxHeight) { + renderable = false; + } + } else { + width -= range.x; + left += range.x; + } + + if (range.y >= 0) { + if (bottom < maxHeight) { + height += range.y; + } + } else { + height += range.y; + } + } + + if (width < 0 && height < 0) { + action = ACTION_NORTH_EAST; + height = -height; + width = -width; + top -= height; + left -= width; + } else if (width < 0) { + action = ACTION_SOUTH_EAST; + width = -width; + left -= width; + } else if (height < 0) { + action = ACTION_NORTH_WEST; + height = -height; + top -= height; + } + + break; + + case ACTION_SOUTH_EAST: + if (aspectRatio) { + if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) { + renderable = false; + break; + } + + check(ACTION_EAST); + width += range.x; + height = width / aspectRatio; + } else { + check(ACTION_SOUTH); + check(ACTION_EAST); + + if (range.x >= 0) { + if (right < maxWidth) { + width += range.x; + } else if (range.y >= 0 && bottom >= maxHeight) { + renderable = false; + } + } else { + width += range.x; + } + + if (range.y >= 0) { + if (bottom < maxHeight) { + height += range.y; + } + } else { + height += range.y; + } + } + + if (width < 0 && height < 0) { + action = ACTION_NORTH_WEST; + height = -height; + width = -width; + top -= height; + left -= width; + } else if (width < 0) { + action = ACTION_SOUTH_WEST; + width = -width; + left -= width; + } else if (height < 0) { + action = ACTION_NORTH_EAST; + height = -height; + top -= height; + } + + break; + // Move canvas + + case ACTION_MOVE: + this.move(range.x, range.y); + renderable = false; + break; + // Zoom canvas + + case ACTION_ZOOM: + this.zoom(getMaxZoomRatio(pointers), event); + renderable = false; + break; + // Create crop box + + case ACTION_CROP: + if (!range.x || !range.y) { + renderable = false; + break; + } + + offset = getOffset(this.cropper); + left = pointer.startX - offset.left; + top = pointer.startY - offset.top; + width = cropBoxData.minWidth; + height = cropBoxData.minHeight; + + if (range.x > 0) { + action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST; + } else if (range.x < 0) { + left -= width; + action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST; + } + + if (range.y < 0) { + top -= height; + } // Show the crop box if is hidden + + + if (!this.cropped) { + removeClass(this.cropBox, CLASS_HIDDEN); + this.cropped = true; + + if (this.limited) { + this.limitCropBox(true, true); + } + } + + break; + + default: + } + + if (renderable) { + cropBoxData.width = width; + cropBoxData.height = height; + cropBoxData.left = left; + cropBoxData.top = top; + this.action = action; + this.renderCropBox(); + } // Override + + + forEach(pointers, function (p) { + p.startX = p.endX; + p.startY = p.endY; + }); + } + }; + + var methods = { + // Show the crop box manually + crop: function crop() { + if (this.ready && !this.cropped && !this.disabled) { + this.cropped = true; + this.limitCropBox(true, true); + + if (this.options.modal) { + addClass(this.dragBox, CLASS_MODAL); + } + + removeClass(this.cropBox, CLASS_HIDDEN); + this.setCropBoxData(this.initialCropBoxData); + } + + return this; + }, + // Reset the image and crop box to their initial states + reset: function reset() { + if (this.ready && !this.disabled) { + this.imageData = assign({}, this.initialImageData); + this.canvasData = assign({}, this.initialCanvasData); + this.cropBoxData = assign({}, this.initialCropBoxData); + this.renderCanvas(); + + if (this.cropped) { + this.renderCropBox(); + } + } + + return this; + }, + // Clear the crop box + clear: function clear() { + if (this.cropped && !this.disabled) { + assign(this.cropBoxData, { + left: 0, + top: 0, + width: 0, + height: 0 + }); + this.cropped = false; + this.renderCropBox(); + this.limitCanvas(true, true); // Render canvas after crop box rendered + + this.renderCanvas(); + removeClass(this.dragBox, CLASS_MODAL); + addClass(this.cropBox, CLASS_HIDDEN); + } + + return this; + }, + + /** + * Replace the image's src and rebuild the cropper + * @param {string} url - The new URL. + * @param {boolean} [hasSameSize] - Indicate if the new image has the same size as the old one. + * @returns {Cropper} this + */ + replace: function replace(url) { + var hasSameSize = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false; + + if (!this.disabled && url) { + if (this.isImg) { + this.element.src = url; + } + + if (hasSameSize) { + this.url = url; + this.image.src = url; + + if (this.ready) { + this.viewBoxImage.src = url; + forEach(this.previews, function (element) { + element.getElementsByTagName('img')[0].src = url; + }); + } + } else { + if (this.isImg) { + this.replaced = true; + } + + this.options.data = null; + this.uncreate(); + this.load(url); + } + } + + return this; + }, + // Enable (unfreeze) the cropper + enable: function enable() { + if (this.ready && this.disabled) { + this.disabled = false; + removeClass(this.cropper, CLASS_DISABLED); + } + + return this; + }, + // Disable (freeze) the cropper + disable: function disable() { + if (this.ready && !this.disabled) { + this.disabled = true; + addClass(this.cropper, CLASS_DISABLED); + } + + return this; + }, + + /** + * Destroy the cropper and remove the instance from the image + * @returns {Cropper} this + */ + destroy: function destroy() { + var element = this.element; + + if (!element[NAMESPACE]) { + return this; + } + + element[NAMESPACE] = undefined; + + if (this.isImg && this.replaced) { + element.src = this.originalUrl; + } + + this.uncreate(); + return this; + }, + + /** + * Move the canvas with relative offsets + * @param {number} offsetX - The relative offset distance on the x-axis. + * @param {number} [offsetY=offsetX] - The relative offset distance on the y-axis. + * @returns {Cropper} this + */ + move: function move(offsetX) { + var offsetY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : offsetX; + var _this$canvasData = this.canvasData, + left = _this$canvasData.left, + top = _this$canvasData.top; + return this.moveTo(isUndefined(offsetX) ? offsetX : left + Number(offsetX), isUndefined(offsetY) ? offsetY : top + Number(offsetY)); + }, + + /** + * Move the canvas to an absolute point + * @param {number} x - The x-axis coordinate. + * @param {number} [y=x] - The y-axis coordinate. + * @returns {Cropper} this + */ + moveTo: function moveTo(x) { + var y = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : x; + var canvasData = this.canvasData; + var changed = false; + x = Number(x); + y = Number(y); + + if (this.ready && !this.disabled && this.options.movable) { + if (isNumber(x)) { + canvasData.left = x; + changed = true; + } + + if (isNumber(y)) { + canvasData.top = y; + changed = true; + } + + if (changed) { + this.renderCanvas(true); + } + } + + return this; + }, + + /** + * Zoom the canvas with a relative ratio + * @param {number} ratio - The target ratio. + * @param {Event} _originalEvent - The original event if any. + * @returns {Cropper} this + */ + zoom: function zoom(ratio, _originalEvent) { + var canvasData = this.canvasData; + ratio = Number(ratio); + + if (ratio < 0) { + ratio = 1 / (1 - ratio); + } else { + ratio = 1 + ratio; + } + + return this.zoomTo(canvasData.width * ratio / canvasData.naturalWidth, null, _originalEvent); + }, + + /** + * Zoom the canvas to an absolute ratio + * @param {number} ratio - The target ratio. + * @param {Object} pivot - The zoom pivot point coordinate. + * @param {Event} _originalEvent - The original event if any. + * @returns {Cropper} this + */ + zoomTo: function zoomTo(ratio, pivot, _originalEvent) { + var options = this.options, + canvasData = this.canvasData; + var width = canvasData.width, + height = canvasData.height, + naturalWidth = canvasData.naturalWidth, + naturalHeight = canvasData.naturalHeight; + ratio = Number(ratio); + + if (ratio >= 0 && this.ready && !this.disabled && options.zoomable) { + var newWidth = naturalWidth * ratio; + var newHeight = naturalHeight * ratio; + + if (dispatchEvent(this.element, EVENT_ZOOM, { + ratio: ratio, + oldRatio: width / naturalWidth, + originalEvent: _originalEvent + }) === false) { + return this; + } + + if (_originalEvent) { + var pointers = this.pointers; + var offset = getOffset(this.cropper); + var center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : { + pageX: _originalEvent.pageX, + pageY: _originalEvent.pageY + }; // Zoom from the triggering point of the event + + canvasData.left -= (newWidth - width) * ((center.pageX - offset.left - canvasData.left) / width); + canvasData.top -= (newHeight - height) * ((center.pageY - offset.top - canvasData.top) / height); + } else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) { + canvasData.left -= (newWidth - width) * ((pivot.x - canvasData.left) / width); + canvasData.top -= (newHeight - height) * ((pivot.y - canvasData.top) / height); + } else { + // Zoom from the center of the canvas + canvasData.left -= (newWidth - width) / 2; + canvasData.top -= (newHeight - height) / 2; + } + + canvasData.width = newWidth; + canvasData.height = newHeight; + this.renderCanvas(true); + } + + return this; + }, + + /** + * Rotate the canvas with a relative degree + * @param {number} degree - The rotate degree. + * @returns {Cropper} this + */ + rotate: function rotate(degree) { + return this.rotateTo((this.imageData.rotate || 0) + Number(degree)); + }, + + /** + * Rotate the canvas to an absolute degree + * @param {number} degree - The rotate degree. + * @returns {Cropper} this + */ + rotateTo: function rotateTo(degree) { + degree = Number(degree); + + if (isNumber(degree) && this.ready && !this.disabled && this.options.rotatable) { + this.imageData.rotate = degree % 360; + this.renderCanvas(true, true); + } + + return this; + }, + + /** + * Scale the image on the x-axis. + * @param {number} scaleX - The scale ratio on the x-axis. + * @returns {Cropper} this + */ + scaleX: function scaleX(_scaleX) { + var scaleY = this.imageData.scaleY; + return this.scale(_scaleX, isNumber(scaleY) ? scaleY : 1); + }, + + /** + * Scale the image on the y-axis. + * @param {number} scaleY - The scale ratio on the y-axis. + * @returns {Cropper} this + */ + scaleY: function scaleY(_scaleY) { + var scaleX = this.imageData.scaleX; + return this.scale(isNumber(scaleX) ? scaleX : 1, _scaleY); + }, + + /** + * Scale the image + * @param {number} scaleX - The scale ratio on the x-axis. + * @param {number} [scaleY=scaleX] - The scale ratio on the y-axis. + * @returns {Cropper} this + */ + scale: function scale(scaleX) { + var scaleY = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : scaleX; + var imageData = this.imageData; + var transformed = false; + scaleX = Number(scaleX); + scaleY = Number(scaleY); + + if (this.ready && !this.disabled && this.options.scalable) { + if (isNumber(scaleX)) { + imageData.scaleX = scaleX; + transformed = true; + } + + if (isNumber(scaleY)) { + imageData.scaleY = scaleY; + transformed = true; + } + + if (transformed) { + this.renderCanvas(true, true); + } + } + + return this; + }, + + /** + * Get the cropped area position and size data (base on the original image) + * @param {boolean} [rounded=false] - Indicate if round the data values or not. + * @returns {Object} The result cropped data. + */ + getData: function getData() { + var rounded = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false; + var options = this.options, + imageData = this.imageData, + canvasData = this.canvasData, + cropBoxData = this.cropBoxData; + var data; + + if (this.ready && this.cropped) { + data = { + x: cropBoxData.left - canvasData.left, + y: cropBoxData.top - canvasData.top, + width: cropBoxData.width, + height: cropBoxData.height + }; + var ratio = imageData.width / imageData.naturalWidth; + forEach(data, function (n, i) { + data[i] = n / ratio; + }); + + if (rounded) { + // In case rounding off leads to extra 1px in right or bottom border + // we should round the top-left corner and the dimension (#343). + var bottom = Math.round(data.y + data.height); + var right = Math.round(data.x + data.width); + data.x = Math.round(data.x); + data.y = Math.round(data.y); + data.width = right - data.x; + data.height = bottom - data.y; + } + } else { + data = { + x: 0, + y: 0, + width: 0, + height: 0 + }; + } + + if (options.rotatable) { + data.rotate = imageData.rotate || 0; + } + + if (options.scalable) { + data.scaleX = imageData.scaleX || 1; + data.scaleY = imageData.scaleY || 1; + } + + return data; + }, + + /** + * Set the cropped area position and size with new data + * @param {Object} data - The new data. + * @returns {Cropper} this + */ + setData: function setData(data) { + var options = this.options, + imageData = this.imageData, + canvasData = this.canvasData; + var cropBoxData = {}; + + if (this.ready && !this.disabled && isPlainObject(data)) { + var transformed = false; + + if (options.rotatable) { + if (isNumber(data.rotate) && data.rotate !== imageData.rotate) { + imageData.rotate = data.rotate; + transformed = true; + } + } + + if (options.scalable) { + if (isNumber(data.scaleX) && data.scaleX !== imageData.scaleX) { + imageData.scaleX = data.scaleX; + transformed = true; + } + + if (isNumber(data.scaleY) && data.scaleY !== imageData.scaleY) { + imageData.scaleY = data.scaleY; + transformed = true; + } + } + + if (transformed) { + this.renderCanvas(true, true); + } + + var ratio = imageData.width / imageData.naturalWidth; + + if (isNumber(data.x)) { + cropBoxData.left = data.x * ratio + canvasData.left; + } + + if (isNumber(data.y)) { + cropBoxData.top = data.y * ratio + canvasData.top; + } + + if (isNumber(data.width)) { + cropBoxData.width = data.width * ratio; + } + + if (isNumber(data.height)) { + cropBoxData.height = data.height * ratio; + } + + this.setCropBoxData(cropBoxData); + } + + return this; + }, + + /** + * Get the container size data. + * @returns {Object} The result container data. + */ + getContainerData: function getContainerData() { + return this.ready ? assign({}, this.containerData) : {}; + }, + + /** + * Get the image position and size data. + * @returns {Object} The result image data. + */ + getImageData: function getImageData() { + return this.sized ? assign({}, this.imageData) : {}; + }, + + /** + * Get the canvas position and size data. + * @returns {Object} The result canvas data. + */ + getCanvasData: function getCanvasData() { + var canvasData = this.canvasData; + var data = {}; + + if (this.ready) { + forEach(['left', 'top', 'width', 'height', 'naturalWidth', 'naturalHeight'], function (n) { + data[n] = canvasData[n]; + }); + } + + return data; + }, + + /** + * Set the canvas position and size with new data. + * @param {Object} data - The new canvas data. + * @returns {Cropper} this + */ + setCanvasData: function setCanvasData(data) { + var canvasData = this.canvasData; + var aspectRatio = canvasData.aspectRatio; + + if (this.ready && !this.disabled && isPlainObject(data)) { + if (isNumber(data.left)) { + canvasData.left = data.left; + } + + if (isNumber(data.top)) { + canvasData.top = data.top; + } + + if (isNumber(data.width)) { + canvasData.width = data.width; + canvasData.height = data.width / aspectRatio; + } else if (isNumber(data.height)) { + canvasData.height = data.height; + canvasData.width = data.height * aspectRatio; + } + + this.renderCanvas(true); + } + + return this; + }, + + /** + * Get the crop box position and size data. + * @returns {Object} The result crop box data. + */ + getCropBoxData: function getCropBoxData() { + var cropBoxData = this.cropBoxData; + var data; + + if (this.ready && this.cropped) { + data = { + left: cropBoxData.left, + top: cropBoxData.top, + width: cropBoxData.width, + height: cropBoxData.height + }; + } + + return data || {}; + }, + + /** + * Set the crop box position and size with new data. + * @param {Object} data - The new crop box data. + * @returns {Cropper} this + */ + setCropBoxData: function setCropBoxData(data) { + var cropBoxData = this.cropBoxData; + var aspectRatio = this.options.aspectRatio; + var widthChanged; + var heightChanged; + + if (this.ready && this.cropped && !this.disabled && isPlainObject(data)) { + if (isNumber(data.left)) { + cropBoxData.left = data.left; + } + + if (isNumber(data.top)) { + cropBoxData.top = data.top; + } + + if (isNumber(data.width) && data.width !== cropBoxData.width) { + widthChanged = true; + cropBoxData.width = data.width; + } + + if (isNumber(data.height) && data.height !== cropBoxData.height) { + heightChanged = true; + cropBoxData.height = data.height; + } + + if (aspectRatio) { + if (widthChanged) { + cropBoxData.height = cropBoxData.width / aspectRatio; + } else if (heightChanged) { + cropBoxData.width = cropBoxData.height * aspectRatio; + } + } + + this.renderCropBox(); + } + + return this; + }, + + /** + * Get a canvas drawn the cropped image. + * @param {Object} [options={}] - The config options. + * @returns {HTMLCanvasElement} - The result canvas. + */ + getCroppedCanvas: function getCroppedCanvas() { + var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + + if (!this.ready || !window.HTMLCanvasElement) { + return null; + } + + var canvasData = this.canvasData; + var source = getSourceCanvas(this.image, this.imageData, canvasData, options); // Returns the source canvas if it is not cropped. + + if (!this.cropped) { + return source; + } + + var _this$getData = this.getData(), + initialX = _this$getData.x, + initialY = _this$getData.y, + initialWidth = _this$getData.width, + initialHeight = _this$getData.height; + + var ratio = source.width / Math.floor(canvasData.naturalWidth); + + if (ratio !== 1) { + initialX *= ratio; + initialY *= ratio; + initialWidth *= ratio; + initialHeight *= ratio; + } + + var aspectRatio = initialWidth / initialHeight; + var maxSizes = getAdjustedSizes({ + aspectRatio: aspectRatio, + width: options.maxWidth || Infinity, + height: options.maxHeight || Infinity + }); + var minSizes = getAdjustedSizes({ + aspectRatio: aspectRatio, + width: options.minWidth || 0, + height: options.minHeight || 0 + }, 'cover'); + + var _getAdjustedSizes = getAdjustedSizes({ + aspectRatio: aspectRatio, + width: options.width || (ratio !== 1 ? source.width : initialWidth), + height: options.height || (ratio !== 1 ? source.height : initialHeight) + }), + width = _getAdjustedSizes.width, + height = _getAdjustedSizes.height; + + width = Math.min(maxSizes.width, Math.max(minSizes.width, width)); + height = Math.min(maxSizes.height, Math.max(minSizes.height, height)); + var canvas = document.createElement('canvas'); + var context = canvas.getContext('2d'); + canvas.width = normalizeDecimalNumber(width); + canvas.height = normalizeDecimalNumber(height); + context.fillStyle = options.fillColor || 'transparent'; + context.fillRect(0, 0, width, height); + var _options$imageSmoothi = options.imageSmoothingEnabled, + imageSmoothingEnabled = _options$imageSmoothi === void 0 ? true : _options$imageSmoothi, + imageSmoothingQuality = options.imageSmoothingQuality; + context.imageSmoothingEnabled = imageSmoothingEnabled; + + if (imageSmoothingQuality) { + context.imageSmoothingQuality = imageSmoothingQuality; + } // https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage + + + var sourceWidth = source.width; + var sourceHeight = source.height; // Source canvas parameters + + var srcX = initialX; + var srcY = initialY; + var srcWidth; + var srcHeight; // Destination canvas parameters + + var dstX; + var dstY; + var dstWidth; + var dstHeight; + + if (srcX <= -initialWidth || srcX > sourceWidth) { + srcX = 0; + srcWidth = 0; + dstX = 0; + dstWidth = 0; + } else if (srcX <= 0) { + dstX = -srcX; + srcX = 0; + srcWidth = Math.min(sourceWidth, initialWidth + srcX); + dstWidth = srcWidth; + } else if (srcX <= sourceWidth) { + dstX = 0; + srcWidth = Math.min(initialWidth, sourceWidth - srcX); + dstWidth = srcWidth; + } + + if (srcWidth <= 0 || srcY <= -initialHeight || srcY > sourceHeight) { + srcY = 0; + srcHeight = 0; + dstY = 0; + dstHeight = 0; + } else if (srcY <= 0) { + dstY = -srcY; + srcY = 0; + srcHeight = Math.min(sourceHeight, initialHeight + srcY); + dstHeight = srcHeight; + } else if (srcY <= sourceHeight) { + dstY = 0; + srcHeight = Math.min(initialHeight, sourceHeight - srcY); + dstHeight = srcHeight; + } + + var params = [srcX, srcY, srcWidth, srcHeight]; // Avoid "IndexSizeError" + + if (dstWidth > 0 && dstHeight > 0) { + var scale = width / initialWidth; + params.push(dstX * scale, dstY * scale, dstWidth * scale, dstHeight * scale); + } // All the numerical parameters should be integer for `drawImage` + // https://github.com/fengyuanchen/cropper/issues/476 + + + context.drawImage.apply(context, [source].concat(_toConsumableArray(params.map(function (param) { + return Math.floor(normalizeDecimalNumber(param)); + })))); + return canvas; + }, + + /** + * Change the aspect ratio of the crop box. + * @param {number} aspectRatio - The new aspect ratio. + * @returns {Cropper} this + */ + setAspectRatio: function setAspectRatio(aspectRatio) { + var options = this.options; + + if (!this.disabled && !isUndefined(aspectRatio)) { + // 0 -> NaN + options.aspectRatio = Math.max(0, aspectRatio) || NaN; + + if (this.ready) { + this.initCropBox(); + + if (this.cropped) { + this.renderCropBox(); + } + } + } + + return this; + }, + + /** + * Change the drag mode. + * @param {string} mode - The new drag mode. + * @returns {Cropper} this + */ + setDragMode: function setDragMode(mode) { + var options = this.options, + dragBox = this.dragBox, + face = this.face; + + if (this.ready && !this.disabled) { + var croppable = mode === DRAG_MODE_CROP; + var movable = options.movable && mode === DRAG_MODE_MOVE; + mode = croppable || movable ? mode : DRAG_MODE_NONE; + options.dragMode = mode; + setData(dragBox, DATA_ACTION, mode); + toggleClass(dragBox, CLASS_CROP, croppable); + toggleClass(dragBox, CLASS_MOVE, movable); + + if (!options.cropBoxMovable) { + // Sync drag mode to crop box when it is not movable + setData(face, DATA_ACTION, mode); + toggleClass(face, CLASS_CROP, croppable); + toggleClass(face, CLASS_MOVE, movable); + } + } + + return this; + } + }; + + var AnotherCropper = WINDOW.Cropper; + + var Cropper = + /*#__PURE__*/ + function () { + /** + * Create a new Cropper. + * @param {Element} element - The target element for cropping. + * @param {Object} [options={}] - The configuration options. + */ + function Cropper(element) { + var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + + _classCallCheck(this, Cropper); + + if (!element || !REGEXP_TAG_NAME.test(element.tagName)) { + throw new Error('The first argument is required and must be an or element.'); + } + + this.element = element; + this.options = assign({}, DEFAULTS, isPlainObject(options) && options); + this.cropped = false; + this.disabled = false; + this.pointers = {}; + this.ready = false; + this.reloading = false; + this.replaced = false; + this.sized = false; + this.sizing = false; + this.init(); + } + + _createClass(Cropper, [{ + key: "init", + value: function init() { + var element = this.element; + var tagName = element.tagName.toLowerCase(); + var url; + + if (element[NAMESPACE]) { + return; + } + + element[NAMESPACE] = this; + + if (tagName === 'img') { + this.isImg = true; // e.g.: "img/picture.jpg" + + url = element.getAttribute('src') || ''; + this.originalUrl = url; // Stop when it's a blank image + + if (!url) { + return; + } // e.g.: "http://example.com/img/picture.jpg" + + + url = element.src; + } else if (tagName === 'canvas' && window.HTMLCanvasElement) { + url = element.toDataURL(); + } + + this.load(url); + } + }, { + key: "load", + value: function load(url) { + var _this = this; + + if (!url) { + return; + } + + this.url = url; + this.imageData = {}; + var element = this.element, + options = this.options; + + if (!options.rotatable && !options.scalable) { + options.checkOrientation = false; + } // Only IE10+ supports Typed Arrays + + + if (!options.checkOrientation || !window.ArrayBuffer) { + this.clone(); + return; + } // Detect the mime type of the image directly if it is a Data URL + + + if (REGEXP_DATA_URL.test(url)) { + // Read ArrayBuffer from Data URL of JPEG images directly for better performance + if (REGEXP_DATA_URL_JPEG.test(url)) { + this.read(dataURLToArrayBuffer(url)); + } else { + // Only a JPEG image may contains Exif Orientation information, + // the rest types of Data URLs are not necessary to check orientation at all. + this.clone(); + } + + return; + } // 1. Detect the mime type of the image by a XMLHttpRequest. + // 2. Load the image as ArrayBuffer for reading orientation if its a JPEG image. + + + var xhr = new XMLHttpRequest(); + var clone = this.clone.bind(this); + this.reloading = true; + this.xhr = xhr; // 1. Cross origin requests are only supported for protocol schemes: + // http, https, data, chrome, chrome-extension. + // 2. Access to XMLHttpRequest from a Data URL will be blocked by CORS policy + // in some browsers as IE11 and Safari. + + xhr.onabort = clone; + xhr.onerror = clone; + xhr.ontimeout = clone; + + xhr.onprogress = function () { + // Abort the request directly if it not a JPEG image for better performance + if (xhr.getResponseHeader('content-type') !== MIME_TYPE_JPEG) { + xhr.abort(); + } + }; + + xhr.onload = function () { + _this.read(xhr.response); + }; + + xhr.onloadend = function () { + _this.reloading = false; + _this.xhr = null; + }; // Bust cache when there is a "crossOrigin" property to avoid browser cache error + + + if (options.checkCrossOrigin && isCrossOriginURL(url) && element.crossOrigin) { + url = addTimestamp(url); + } + + xhr.open('GET', url); + xhr.responseType = 'arraybuffer'; + xhr.withCredentials = element.crossOrigin === 'use-credentials'; + xhr.send(); + } + }, { + key: "read", + value: function read(arrayBuffer) { + var options = this.options, + imageData = this.imageData; // Reset the orientation value to its default value 1 + // as some iOS browsers will render image with its orientation + + var orientation = resetAndGetOrientation(arrayBuffer); + var rotate = 0; + var scaleX = 1; + var scaleY = 1; + + if (orientation > 1) { + // Generate a new URL which has the default orientation value + this.url = arrayBufferToDataURL(arrayBuffer, MIME_TYPE_JPEG); + + var _parseOrientation = parseOrientation(orientation); + + rotate = _parseOrientation.rotate; + scaleX = _parseOrientation.scaleX; + scaleY = _parseOrientation.scaleY; + } + + if (options.rotatable) { + imageData.rotate = rotate; + } + + if (options.scalable) { + imageData.scaleX = scaleX; + imageData.scaleY = scaleY; + } + + this.clone(); + } + }, { + key: "clone", + value: function clone() { + var element = this.element, + url = this.url; + var crossOrigin = element.crossOrigin; + var crossOriginUrl = url; + + if (this.options.checkCrossOrigin && isCrossOriginURL(url)) { + if (!crossOrigin) { + crossOrigin = 'anonymous'; + } // Bust cache when there is not a "crossOrigin" property (#519) + + + crossOriginUrl = addTimestamp(url); + } + + this.crossOrigin = crossOrigin; + this.crossOriginUrl = crossOriginUrl; + var image = document.createElement('img'); + + if (crossOrigin) { + image.crossOrigin = crossOrigin; + } + + image.src = crossOriginUrl || url; + image.alt = element.alt || 'The image to crop'; + this.image = image; + image.onload = this.start.bind(this); + image.onerror = this.stop.bind(this); + addClass(image, CLASS_HIDE); + element.parentNode.insertBefore(image, element.nextSibling); + } + }, { + key: "start", + value: function start() { + var _this2 = this; + + var image = this.image; + image.onload = null; + image.onerror = null; + this.sizing = true; // Match all browsers that use WebKit as the layout engine in iOS devices, + // such as Safari for iOS, Chrome for iOS, and in-app browsers. + + var isIOSWebKit = WINDOW.navigator && /(?:iPad|iPhone|iPod).*?AppleWebKit/i.test(WINDOW.navigator.userAgent); + + var done = function done(naturalWidth, naturalHeight) { + assign(_this2.imageData, { + naturalWidth: naturalWidth, + naturalHeight: naturalHeight, + aspectRatio: naturalWidth / naturalHeight + }); + _this2.sizing = false; + _this2.sized = true; + + _this2.build(); + }; // Most modern browsers (excepts iOS WebKit) + + + if (image.naturalWidth && !isIOSWebKit) { + done(image.naturalWidth, image.naturalHeight); + return; + } + + var sizingImage = document.createElement('img'); + var body = document.body || document.documentElement; + this.sizingImage = sizingImage; + + sizingImage.onload = function () { + done(sizingImage.width, sizingImage.height); + + if (!isIOSWebKit) { + body.removeChild(sizingImage); + } + }; + + sizingImage.src = image.src; // iOS WebKit will convert the image automatically + // with its orientation once append it into DOM (#279) + + if (!isIOSWebKit) { + sizingImage.style.cssText = 'left:0;' + 'max-height:none!important;' + 'max-width:none!important;' + 'min-height:0!important;' + 'min-width:0!important;' + 'opacity:0;' + 'position:absolute;' + 'top:0;' + 'z-index:-1;'; + body.appendChild(sizingImage); + } + } + }, { + key: "stop", + value: function stop() { + var image = this.image; + image.onload = null; + image.onerror = null; + image.parentNode.removeChild(image); + this.image = null; + } + }, { + key: "build", + value: function build() { + if (!this.sized || this.ready) { + return; + } + + var element = this.element, + options = this.options, + image = this.image; // Create cropper elements + + var container = element.parentNode; + var template = document.createElement('div'); + template.innerHTML = TEMPLATE; + var cropper = template.querySelector(".".concat(NAMESPACE, "-container")); + var canvas = cropper.querySelector(".".concat(NAMESPACE, "-canvas")); + var dragBox = cropper.querySelector(".".concat(NAMESPACE, "-drag-box")); + var cropBox = cropper.querySelector(".".concat(NAMESPACE, "-crop-box")); + var face = cropBox.querySelector(".".concat(NAMESPACE, "-face")); + this.container = container; + this.cropper = cropper; + this.canvas = canvas; + this.dragBox = dragBox; + this.cropBox = cropBox; + this.viewBox = cropper.querySelector(".".concat(NAMESPACE, "-view-box")); + this.face = face; + canvas.appendChild(image); // Hide the original image + + addClass(element, CLASS_HIDDEN); // Inserts the cropper after to the current image + + container.insertBefore(cropper, element.nextSibling); // Show the image if is hidden + + if (!this.isImg) { + removeClass(image, CLASS_HIDE); + } + + this.initPreview(); + this.bind(); + options.initialAspectRatio = Math.max(0, options.initialAspectRatio) || NaN; + options.aspectRatio = Math.max(0, options.aspectRatio) || NaN; + options.viewMode = Math.max(0, Math.min(3, Math.round(options.viewMode))) || 0; + addClass(cropBox, CLASS_HIDDEN); + + if (!options.guides) { + addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-dashed")), CLASS_HIDDEN); + } + + if (!options.center) { + addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-center")), CLASS_HIDDEN); + } + + if (options.background) { + addClass(cropper, "".concat(NAMESPACE, "-bg")); + } + + if (!options.highlight) { + addClass(face, CLASS_INVISIBLE); + } + + if (options.cropBoxMovable) { + addClass(face, CLASS_MOVE); + setData(face, DATA_ACTION, ACTION_ALL); + } + + if (!options.cropBoxResizable) { + addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-line")), CLASS_HIDDEN); + addClass(cropBox.getElementsByClassName("".concat(NAMESPACE, "-point")), CLASS_HIDDEN); + } + + this.render(); + this.ready = true; + this.setDragMode(options.dragMode); + + if (options.autoCrop) { + this.crop(); + } + + this.setData(options.data); + + if (isFunction(options.ready)) { + addListener(element, EVENT_READY, options.ready, { + once: true + }); + } + + dispatchEvent(element, EVENT_READY); + } + }, { + key: "unbuild", + value: function unbuild() { + if (!this.ready) { + return; + } + + this.ready = false; + this.unbind(); + this.resetPreview(); + this.cropper.parentNode.removeChild(this.cropper); + removeClass(this.element, CLASS_HIDDEN); + } + }, { + key: "uncreate", + value: function uncreate() { + if (this.ready) { + this.unbuild(); + this.ready = false; + this.cropped = false; + } else if (this.sizing) { + this.sizingImage.onload = null; + this.sizing = false; + this.sized = false; + } else if (this.reloading) { + this.xhr.onabort = null; + this.xhr.abort(); + } else if (this.image) { + this.stop(); + } + } + /** + * Get the no conflict cropper class. + * @returns {Cropper} The cropper class. + */ + + }], [{ + key: "noConflict", + value: function noConflict() { + window.Cropper = AnotherCropper; + return Cropper; + } + /** + * Change the default options. + * @param {Object} options - The new default options. + */ + + }, { + key: "setDefaults", + value: function setDefaults(options) { + assign(DEFAULTS, isPlainObject(options) && options); + } + }]); + + return Cropper; + }(); + + assign(Cropper.prototype, render, preview, events, handlers, change, methods); + + return Cropper; + +})); diff --git a/app/assets/javascripts/gallery.js b/app/assets/javascripts/gallery.js index 0dabb60..7321357 100644 --- a/app/assets/javascripts/gallery.js +++ b/app/assets/javascripts/gallery.js @@ -7,13 +7,13 @@ $this = this; $li = this.children('li'); $dropzone = $('#dropzone'); - if(($li.length - _set.onlyOne) == 0) { + if(($li.length - _set.onlyOne) === 0) { $('#dropzone').fadeIn(300); } else { $('#dropzone').fadeOut(300); - }; + } $('#file-list').nanoScroller({ scrollTop: 0, iOSNativeScrolling: true }); - } + }; }(window.jQuery); $(function () { @@ -21,7 +21,7 @@ $(function () { // Initialize the jQuery File Upload widget: if($('#fileupload').length){ $('#fileupload').fileupload({ - maxFileSize: 5000000, + //maxFileSize: 5000000,= acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i, dropZone: $('#dropzone'), headers:{ @@ -30,8 +30,96 @@ $(function () { }); } }); +function batch_crop(){ + var check_li = $('#imgholder').find("input[type='checkbox']:checked").parents('li'); + var image_ids =[]; + if (check_li.length>0){ + check_li.each(function(){ + image_ids.push($(this).data('image-id')); + }); + if (navigator.onLine) { + window.location.href = '/admin/galleries/batch_crop?image_ids=' + image_ids.join(',') + } else { + alert('Please connect the network and try again later!') + } + }else{ + alert('Please select at least one') + } +} +function select_all() { + $('#imgholder').find("input[type='checkbox']:not(:checked)").trigger('click') +} +function translate(ele,pretext,text){ + if (navigator.onLine) { + $.ajax({ + url : "/admin/galleries/translate", + dataType : "json", + type : "post", + data:{text:text}, + success:function(data){ + ele.html(pretext + data.translate) + }, + error:function(){ + var back = text.split('.')[1].split('_') + var result = [] + for (i=0;i
Close panel'); + translate($('.add-imgs'),' ','gallery.close_panel') $('.rgbody').stop(true, false).animate({'padding-bottom': 280}, 300); $("#edit-order-btn").hide(); $.ajax({ @@ -261,7 +349,7 @@ $(function() { last_image_id = d.last_image_id; }) } else { - $('.add-imgs').html(' Add Image'); + translate($('.add-imgs'),' ','gallery.add_image') $('.files').empty() $('#file-list').checkListLength(); $('.rgbody').stop(true, false).animate({'padding-bottom': 0}, 300); diff --git a/app/assets/javascripts/jquery-cropper.js b/app/assets/javascripts/jquery-cropper.js new file mode 100644 index 0000000..8cbe437 --- /dev/null +++ b/app/assets/javascripts/jquery-cropper.js @@ -0,0 +1,75 @@ +/*! + * jQuery Cropper v1.0.0 + * https://github.com/fengyuanchen/jquery-cropper + * + * Copyright (c) 2018 Chen Fengyuan + * Released under the MIT license + * + * Date: 2018-04-01T06:20:13.168Z + */ + +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? factory(require('jquery'), require('cropperjs')) : + typeof define === 'function' && define.amd ? define(['jquery', 'cropperjs'], factory) : + (factory(global.jQuery,global.Cropper)); +}(this, (function ($,Cropper) { 'use strict'; + + $ = $ && $.hasOwnProperty('default') ? $['default'] : $; + Cropper = Cropper && Cropper.hasOwnProperty('default') ? Cropper['default'] : Cropper; + + if ($.fn) { + var AnotherCropper = $.fn.cropper; + var NAMESPACE = 'cropper'; + + $.fn.cropper = function jQueryCropper(option) { + for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + args[_key - 1] = arguments[_key]; + } + + var result = void 0; + + this.each(function (i, element) { + var $element = $(element); + var isDestroy = option === 'destroy'; + var cropper = $element.data(NAMESPACE); + + if (!cropper) { + if (isDestroy) { + return; + } + + var options = $.extend({}, $element.data(), $.isPlainObject(option) && option); + + cropper = new Cropper(element, options); + $element.data(NAMESPACE, cropper); + } + + if (typeof option === 'string') { + var fn = cropper[option]; + + if ($.isFunction(fn)) { + result = fn.apply(cropper, args); + + if (result === cropper) { + result = undefined; + } + + if (isDestroy) { + $element.removeData(NAMESPACE); + } + } + } + }); + + return result !== undefined ? result : this; + }; + + $.fn.cropper.Constructor = Cropper; + $.fn.cropper.setDefaults = Cropper.setDefaults; + $.fn.cropper.noConflict = function noConflict() { + $.fn.cropper = AnotherCropper; + return this; + }; + } + +}))); \ No newline at end of file diff --git a/app/assets/javascripts/jquery.minicolors.js b/app/assets/javascripts/jquery.minicolors.js new file mode 100644 index 0000000..6c5284a --- /dev/null +++ b/app/assets/javascripts/jquery.minicolors.js @@ -0,0 +1,1127 @@ +// +// jQuery MiniColors: A tiny color picker built on jQuery +// +// Developed by Cory LaViska for A Beautiful Site, LLC +// +// Licensed under the MIT license: http://opensource.org/licenses/MIT +// +(function (factory) { + if(typeof define === 'function' && define.amd) { + // AMD. Register as an anonymous module. + define(['jquery'], factory); + } else if(typeof exports === 'object') { + // Node/CommonJS + module.exports = factory(require('jquery')); + } else { + // Browser globals + factory(jQuery); + } +}(function ($) { + 'use strict'; + + // Defaults + $.minicolors = { + defaults: { + animationSpeed: 50, + animationEasing: 'swing', + change: null, + changeDelay: 0, + control: 'hue', + defaultValue: '', + format: 'hex', + hide: null, + hideSpeed: 100, + inline: false, + keywords: '', + letterCase: 'lowercase', + opacity: false, + position: 'bottom', + show: null, + showSpeed: 100, + theme: 'default', + swatches: [] + } + }; + + // Public methods + $.extend($.fn, { + minicolors: function(method, data) { + + switch(method) { + // Destroy the control + case 'destroy': + $(this).each(function() { + destroy($(this)); + }); + return $(this); + + // Hide the color picker + case 'hide': + hide(); + return $(this); + + // Get/set opacity + case 'opacity': + // Getter + if(data === undefined) { + // Getter + return $(this).attr('data-opacity'); + } else { + // Setter + $(this).each(function() { + updateFromInput($(this).attr('data-opacity', data)); + }); + } + return $(this); + + // Get an RGB(A) object based on the current color/opacity + case 'rgbObject': + return rgbObject($(this), method === 'rgbaObject'); + + // Get an RGB(A) string based on the current color/opacity + case 'rgbString': + case 'rgbaString': + return rgbString($(this), method === 'rgbaString'); + + // Get/set settings on the fly + case 'settings': + if(data === undefined) { + return $(this).data('minicolors-settings'); + } else { + // Setter + $(this).each(function() { + var settings = $(this).data('minicolors-settings') || {}; + destroy($(this)); + $(this).minicolors($.extend(true, settings, data)); + }); + } + return $(this); + + // Show the color picker + case 'show': + show($(this).eq(0)); + return $(this); + + // Get/set the hex color value + case 'value': + if(data === undefined) { + // Getter + return $(this).val(); + } else { + // Setter + $(this).each(function() { + if(typeof(data) === 'object' && data !== null) { + if(data.opacity !== undefined) { + $(this).attr('data-opacity', keepWithin(data.opacity, 0, 1)); + } + if(data.color) { + $(this).val(data.color); + } + } else { + $(this).val(data); + } + updateFromInput($(this)); + }); + } + return $(this); + + // Initializes the control + default: + if(method !== 'create') data = method; + $(this).each(function() { + init($(this), data); + }); + return $(this); + + } + + } + }); + + // Initialize input elements + function init(input, settings) { + var minicolors = $('
'); + var defaults = $.minicolors.defaults; + var name; + var size; + var swatches; + var swatch; + var swatchString; + var panel; + var i; + + // Do nothing if already initialized + if(input.data('minicolors-initialized')) return; + + // Handle settings + settings = $.extend(true, {}, defaults, settings); + + // The wrapper + minicolors + .addClass('minicolors-theme-' + settings.theme) + .toggleClass('minicolors-with-opacity', settings.opacity); + + // Custom positioning + if(settings.position !== undefined) { + $.each(settings.position.split(' '), function() { + minicolors.addClass('minicolors-position-' + this); + }); + } + + // Input size + if(settings.format === 'rgb') { + size = settings.opacity ? '25' : '20'; + } else { + size = settings.keywords ? '11' : '7'; + } + + // The input + input + .addClass('minicolors-input') + .data('minicolors-initialized', false) + .data('minicolors-settings', settings) + .prop('size', size) + .wrap(minicolors) + .after( + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + + '
' + ); + + // The swatch + if(!settings.inline) { + input.after(''); + input.next('.minicolors-input-swatch').on('click', function(event) { + event.preventDefault(); + input.focus(); + }); + } + + // Prevent text selection in IE + panel = input.parent().find('.minicolors-panel'); + panel.on('selectstart', function() { return false; }).end(); + + // Swatches + if(settings.swatches && settings.swatches.length !== 0) { + panel.addClass('minicolors-with-swatches'); + swatches = $('
    ') + .appendTo(panel); + for(i = 0; i < settings.swatches.length; ++i) { + // allow for custom objects as swatches + if($.type(settings.swatches[i]) === 'object') { + name = settings.swatches[i].name; + swatch = settings.swatches[i].color; + } else { + name = ''; + swatch = settings.swatches[i]; + } + swatchString = swatch; + swatch = isRgb(swatch) ? parseRgb(swatch, true) : hex2rgb(parseHex(swatch, true)); + $('
  • ') + .appendTo(swatches) + .data('swatch-color', swatchString) + .find('.minicolors-swatch-color') + .css({ + backgroundColor: rgb2hex(swatch), + opacity: swatch.a + }); + settings.swatches[i] = swatch; + } + } + + // Inline controls + if(settings.inline) input.parent().addClass('minicolors-inline'); + + updateFromInput(input, false); + + input.data('minicolors-initialized', true); + } + + // Returns the input back to its original state + function destroy(input) { + var minicolors = input.parent(); + + // Revert the input element + input + .removeData('minicolors-initialized') + .removeData('minicolors-settings') + .removeProp('size') + .removeClass('minicolors-input'); + + // Remove the wrap and destroy whatever remains + minicolors.before(input).remove(); + } + + // Shows the specified dropdown panel + function show(input) { + var minicolors = input.parent(); + var panel = minicolors.find('.minicolors-panel'); + var settings = input.data('minicolors-settings'); + + // Do nothing if uninitialized, disabled, inline, or already open + if( + !input.data('minicolors-initialized') || + input.prop('disabled') || + minicolors.hasClass('minicolors-inline') || + minicolors.hasClass('minicolors-focus') + ) return; + + hide(); + + minicolors.addClass('minicolors-focus'); + if (panel.animate) { + panel + .stop(true, true) + .fadeIn(settings.showSpeed, function () { + if (settings.show) settings.show.call(input.get(0)); + }); + } else { + panel.show(); + if (settings.show) settings.show.call(input.get(0)); + } + } + + // Hides all dropdown panels + function hide() { + $('.minicolors-focus').each(function() { + var minicolors = $(this); + var input = minicolors.find('.minicolors-input'); + var panel = minicolors.find('.minicolors-panel'); + var settings = input.data('minicolors-settings'); + + if (panel.animate) { + panel.fadeOut(settings.hideSpeed, function () { + if (settings.hide) settings.hide.call(input.get(0)); + minicolors.removeClass('minicolors-focus'); + }); + } else { + panel.hide(); + if (settings.hide) settings.hide.call(input.get(0)); + minicolors.removeClass('minicolors-focus'); + } + }); + } + + // Moves the selected picker + function move(target, event, animate) { + var input = target.parents('.minicolors').find('.minicolors-input'); + var settings = input.data('minicolors-settings'); + var picker = target.find('[class$=-picker]'); + var offsetX = target.offset().left; + var offsetY = target.offset().top; + var x = Math.round(event.pageX - offsetX); + var y = Math.round(event.pageY - offsetY); + var duration = animate ? settings.animationSpeed : 0; + var wx, wy, r, phi, styles; + + // Touch support + if(event.originalEvent.changedTouches) { + x = event.originalEvent.changedTouches[0].pageX - offsetX; + y = event.originalEvent.changedTouches[0].pageY - offsetY; + } + + // Constrain picker to its container + if(x < 0) x = 0; + if(y < 0) y = 0; + if(x > target.width()) x = target.width(); + if(y > target.height()) y = target.height(); + + // Constrain color wheel values to the wheel + if(target.parent().is('.minicolors-slider-wheel') && picker.parent().is('.minicolors-grid')) { + wx = 75 - x; + wy = 75 - y; + r = Math.sqrt(wx * wx + wy * wy); + phi = Math.atan2(wy, wx); + if(phi < 0) phi += Math.PI * 2; + if(r > 75) { + r = 75; + x = 75 - (75 * Math.cos(phi)); + y = 75 - (75 * Math.sin(phi)); + } + x = Math.round(x); + y = Math.round(y); + } + + // Move the picker + styles = { + top: y + 'px' + }; + if(target.is('.minicolors-grid')) { + styles.left = x + 'px'; + } + if (picker.animate) { + picker + .stop(true) + .animate(styles, duration, settings.animationEasing, function() { + updateFromControl(input, target); + }); + } else { + picker + .css(styles); + updateFromControl(input, target); + } + } + + // Sets the input based on the color picker values + function updateFromControl(input, target) { + + function getCoords(picker, container) { + var left, top; + if(!picker.length || !container) return null; + left = picker.offset().left; + top = picker.offset().top; + + return { + x: left - container.offset().left + (picker.outerWidth() / 2), + y: top - container.offset().top + (picker.outerHeight() / 2) + }; + } + + var hue, saturation, brightness, x, y, r, phi; + var hex = input.val(); + var opacity = input.attr('data-opacity'); + + // Helpful references + var minicolors = input.parent(); + var settings = input.data('minicolors-settings'); + var swatch = minicolors.find('.minicolors-input-swatch'); + + // Panel objects + var grid = minicolors.find('.minicolors-grid'); + var slider = minicolors.find('.minicolors-slider'); + var opacitySlider = minicolors.find('.minicolors-opacity-slider'); + + // Picker objects + var gridPicker = grid.find('[class$=-picker]'); + var sliderPicker = slider.find('[class$=-picker]'); + var opacityPicker = opacitySlider.find('[class$=-picker]'); + + // Picker positions + var gridPos = getCoords(gridPicker, grid); + var sliderPos = getCoords(sliderPicker, slider); + var opacityPos = getCoords(opacityPicker, opacitySlider); + + // Handle colors + if(target.is('.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider')) { + + // Determine HSB values + switch(settings.control) { + case 'wheel': + // Calculate hue, saturation, and brightness + x = (grid.width() / 2) - gridPos.x; + y = (grid.height() / 2) - gridPos.y; + r = Math.sqrt(x * x + y * y); + phi = Math.atan2(y, x); + if(phi < 0) phi += Math.PI * 2; + if(r > 75) { + r = 75; + gridPos.x = 69 - (75 * Math.cos(phi)); + gridPos.y = 69 - (75 * Math.sin(phi)); + } + saturation = keepWithin(r / 0.75, 0, 100); + hue = keepWithin(phi * 180 / Math.PI, 0, 360); + brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); + hex = hsb2hex({ + h: hue, + s: saturation, + b: brightness + }); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 })); + break; + + case 'saturation': + // Calculate hue, saturation, and brightness + hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360); + saturation = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); + brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); + hex = hsb2hex({ + h: hue, + s: saturation, + b: brightness + }); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: brightness })); + minicolors.find('.minicolors-grid-inner').css('opacity', saturation / 100); + break; + + case 'brightness': + // Calculate hue, saturation, and brightness + hue = keepWithin(parseInt(gridPos.x * (360 / grid.width()), 10), 0, 360); + saturation = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); + brightness = keepWithin(100 - Math.floor(sliderPos.y * (100 / slider.height())), 0, 100); + hex = hsb2hex({ + h: hue, + s: saturation, + b: brightness + }); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hue, s: saturation, b: 100 })); + minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (brightness / 100)); + break; + + default: + // Calculate hue, saturation, and brightness + hue = keepWithin(360 - parseInt(sliderPos.y * (360 / slider.height()), 10), 0, 360); + saturation = keepWithin(Math.floor(gridPos.x * (100 / grid.width())), 0, 100); + brightness = keepWithin(100 - Math.floor(gridPos.y * (100 / grid.height())), 0, 100); + hex = hsb2hex({ + h: hue, + s: saturation, + b: brightness + }); + + // Update UI + grid.css('backgroundColor', hsb2hex({ h: hue, s: 100, b: 100 })); + break; + } + + // Handle opacity + if(settings.opacity) { + opacity = parseFloat(1 - (opacityPos.y / opacitySlider.height())).toFixed(2); + } else { + opacity = 1; + } + + updateInput(input, hex, opacity); + } + else { + // Set swatch color + swatch.find('span').css({ + backgroundColor: hex, + opacity: opacity + }); + + // Handle change event + doChange(input, hex, opacity); + } + } + + // Sets the value of the input and does the appropriate conversions + // to respect settings, also updates the swatch + function updateInput(input, value, opacity) { + var rgb; + + // Helpful references + var minicolors = input.parent(); + var settings = input.data('minicolors-settings'); + var swatch = minicolors.find('.minicolors-input-swatch'); + + if(settings.opacity) input.attr('data-opacity', opacity); + + // Set color string + if(settings.format === 'rgb') { + // Returns RGB(A) string + + // Checks for input format and does the conversion + if(isRgb(value)) { + rgb = parseRgb(value, true); + } + else { + rgb = hex2rgb(parseHex(value, true)); + } + + opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1); + if(isNaN(opacity) || !settings.opacity) opacity = 1; + + if(input.minicolors('rgbObject').a <= 1 && rgb && settings.opacity) { + // Set RGBA string if alpha + value = 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')'; + } else { + // Set RGB string (alpha = 1) + value = 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')'; + } + } else { + // Returns hex color + + // Checks for input format and does the conversion + if(isRgb(value)) { + value = rgbString2hex(value); + } + + value = convertCase(value, settings.letterCase); + } + + // Update value from picker + input.val(value); + + // Set swatch color + swatch.find('span').css({ + backgroundColor: value, + opacity: opacity + }); + + // Handle change event + doChange(input, value, opacity); + } + + // Sets the color picker values from the input + function updateFromInput(input, preserveInputValue) { + var hex, hsb, opacity, keywords, alpha, value, x, y, r, phi; + + // Helpful references + var minicolors = input.parent(); + var settings = input.data('minicolors-settings'); + var swatch = minicolors.find('.minicolors-input-swatch'); + + // Panel objects + var grid = minicolors.find('.minicolors-grid'); + var slider = minicolors.find('.minicolors-slider'); + var opacitySlider = minicolors.find('.minicolors-opacity-slider'); + + // Picker objects + var gridPicker = grid.find('[class$=-picker]'); + var sliderPicker = slider.find('[class$=-picker]'); + var opacityPicker = opacitySlider.find('[class$=-picker]'); + + // Determine hex/HSB values + if(isRgb(input.val())) { + // If input value is a rgb(a) string, convert it to hex color and update opacity + hex = rgbString2hex(input.val()); + alpha = keepWithin(parseFloat(getAlpha(input.val())).toFixed(2), 0, 1); + if(alpha) { + input.attr('data-opacity', alpha); + } + } else { + hex = convertCase(parseHex(input.val(), true), settings.letterCase); + } + + if(!hex){ + hex = convertCase(parseInput(settings.defaultValue, true), settings.letterCase); + } + hsb = hex2hsb(hex); + + // Get array of lowercase keywords + keywords = !settings.keywords ? [] : $.map(settings.keywords.split(','), function(a) { + return $.trim(a.toLowerCase()); + }); + + // Set color string + if(input.val() !== '' && $.inArray(input.val().toLowerCase(), keywords) > -1) { + value = convertCase(input.val()); + } else { + value = isRgb(input.val()) ? parseRgb(input.val()) : hex; + } + + // Update input value + if(!preserveInputValue) input.val(value); + + // Determine opacity value + if(settings.opacity) { + // Get from data-opacity attribute and keep within 0-1 range + opacity = input.attr('data-opacity') === '' ? 1 : keepWithin(parseFloat(input.attr('data-opacity')).toFixed(2), 0, 1); + if(isNaN(opacity)) opacity = 1; + input.attr('data-opacity', opacity); + swatch.find('span').css('opacity', opacity); + + // Set opacity picker position + y = keepWithin(opacitySlider.height() - (opacitySlider.height() * opacity), 0, opacitySlider.height()); + opacityPicker.css('top', y + 'px'); + } + + // Set opacity to zero if input value is transparent + if(input.val().toLowerCase() === 'transparent') { + swatch.find('span').css('opacity', 0); + } + + // Update swatch + swatch.find('span').css('backgroundColor', hex); + + // Determine picker locations + switch(settings.control) { + case 'wheel': + // Set grid position + r = keepWithin(Math.ceil(hsb.s * 0.75), 0, grid.height() / 2); + phi = hsb.h * Math.PI / 180; + x = keepWithin(75 - Math.cos(phi) * r, 0, grid.width()); + y = keepWithin(75 - Math.sin(phi) * r, 0, grid.height()); + gridPicker.css({ + top: y + 'px', + left: x + 'px' + }); + + // Set slider position + y = 150 - (hsb.b / (100 / grid.height())); + if(hex === '') y = 0; + sliderPicker.css('top', y + 'px'); + + // Update panel color + slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 })); + break; + + case 'saturation': + // Set grid position + x = keepWithin((5 * hsb.h) / 12, 0, 150); + y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height()); + gridPicker.css({ + top: y + 'px', + left: x + 'px' + }); + + // Set slider position + y = keepWithin(slider.height() - (hsb.s * (slider.height() / 100)), 0, slider.height()); + sliderPicker.css('top', y + 'px'); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: hsb.b })); + minicolors.find('.minicolors-grid-inner').css('opacity', hsb.s / 100); + break; + + case 'brightness': + // Set grid position + x = keepWithin((5 * hsb.h) / 12, 0, 150); + y = keepWithin(grid.height() - Math.ceil(hsb.s / (100 / grid.height())), 0, grid.height()); + gridPicker.css({ + top: y + 'px', + left: x + 'px' + }); + + // Set slider position + y = keepWithin(slider.height() - (hsb.b * (slider.height() / 100)), 0, slider.height()); + sliderPicker.css('top', y + 'px'); + + // Update UI + slider.css('backgroundColor', hsb2hex({ h: hsb.h, s: hsb.s, b: 100 })); + minicolors.find('.minicolors-grid-inner').css('opacity', 1 - (hsb.b / 100)); + break; + + default: + // Set grid position + x = keepWithin(Math.ceil(hsb.s / (100 / grid.width())), 0, grid.width()); + y = keepWithin(grid.height() - Math.ceil(hsb.b / (100 / grid.height())), 0, grid.height()); + gridPicker.css({ + top: y + 'px', + left: x + 'px' + }); + + // Set slider position + y = keepWithin(slider.height() - (hsb.h / (360 / slider.height())), 0, slider.height()); + sliderPicker.css('top', y + 'px'); + + // Update panel color + grid.css('backgroundColor', hsb2hex({ h: hsb.h, s: 100, b: 100 })); + break; + } + + // Fire change event, but only if minicolors is fully initialized + if(input.data('minicolors-initialized')) { + doChange(input, value, opacity); + } + } + + // Runs the change and changeDelay callbacks + function doChange(input, value, opacity) { + var settings = input.data('minicolors-settings'); + var lastChange = input.data('minicolors-lastChange'); + var obj, sel, i; + + // Only run if it actually changed + if(!lastChange || lastChange.value !== value || lastChange.opacity !== opacity) { + + // Remember last-changed value + input.data('minicolors-lastChange', { + value: value, + opacity: opacity + }); + + // Check and select applicable swatch + if(settings.swatches && settings.swatches.length !== 0) { + if(!isRgb(value)) { + obj = hex2rgb(value); + } + else { + obj = parseRgb(value, true); + } + sel = -1; + for(i = 0; i < settings.swatches.length; ++i) { + if(obj.r === settings.swatches[i].r && obj.g === settings.swatches[i].g && obj.b === settings.swatches[i].b && obj.a === settings.swatches[i].a) { + sel = i; + break; + } + } + + input.parent().find('.minicolors-swatches .minicolors-swatch').removeClass('selected'); + if(sel !== -1) { + input.parent().find('.minicolors-swatches .minicolors-swatch').eq(i).addClass('selected'); + } + } + + // Fire change event + if(settings.change) { + if(settings.changeDelay) { + // Call after a delay + clearTimeout(input.data('minicolors-changeTimeout')); + input.data('minicolors-changeTimeout', setTimeout(function() { + settings.change.call(input.get(0), value, opacity); + }, settings.changeDelay)); + } else { + // Call immediately + settings.change.call(input.get(0), value, opacity); + } + } + input.trigger('change').trigger('input'); + } + } + + // Generates an RGB(A) object based on the input's value + function rgbObject(input) { + var rgb, + opacity = $(input).attr('data-opacity'); + if( isRgb($(input).val()) ) { + rgb = parseRgb($(input).val(), true); + } else { + var hex = parseHex($(input).val(), true); + rgb = hex2rgb(hex); + } + if( !rgb ) return null; + if( opacity !== undefined ) $.extend(rgb, { a: parseFloat(opacity) }); + return rgb; + } + + // Generates an RGB(A) string based on the input's value + function rgbString(input, alpha) { + var rgb, + opacity = $(input).attr('data-opacity'); + if( isRgb($(input).val()) ) { + rgb = parseRgb($(input).val(), true); + } else { + var hex = parseHex($(input).val(), true); + rgb = hex2rgb(hex); + } + if( !rgb ) return null; + if( opacity === undefined ) opacity = 1; + if( alpha ) { + return 'rgba(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ', ' + parseFloat(opacity) + ')'; + } else { + return 'rgb(' + rgb.r + ', ' + rgb.g + ', ' + rgb.b + ')'; + } + } + + // Converts to the letter case specified in settings + function convertCase(string, letterCase) { + return letterCase === 'uppercase' ? string.toUpperCase() : string.toLowerCase(); + } + + // Parses a string and returns a valid hex string when possible + function parseHex(string, expand) { + string = string.replace(/^#/g, ''); + if(!string.match(/^[A-F0-9]{3,6}/ig)) return ''; + if(string.length !== 3 && string.length !== 6) return ''; + if(string.length === 3 && expand) { + string = string[0] + string[0] + string[1] + string[1] + string[2] + string[2]; + } + return '#' + string; + } + + // Parses a string and returns a valid RGB(A) string when possible + function parseRgb(string, obj) { + var values = string.replace(/[^\d,.]/g, ''); + var rgba = values.split(','); + + rgba[0] = keepWithin(parseInt(rgba[0], 10), 0, 255); + rgba[1] = keepWithin(parseInt(rgba[1], 10), 0, 255); + rgba[2] = keepWithin(parseInt(rgba[2], 10), 0, 255); + if(rgba[3] !== undefined) { + rgba[3] = keepWithin(parseFloat(rgba[3], 10), 0, 1); + } + + // Return RGBA object + if( obj ) { + if (rgba[3] !== undefined) { + return { + r: rgba[0], + g: rgba[1], + b: rgba[2], + a: rgba[3] + }; + } else { + return { + r: rgba[0], + g: rgba[1], + b: rgba[2] + }; + } + } + + // Return RGBA string + if(typeof(rgba[3]) !== 'undefined' && rgba[3] <= 1) { + return 'rgba(' + rgba[0] + ', ' + rgba[1] + ', ' + rgba[2] + ', ' + rgba[3] + ')'; + } else { + return 'rgb(' + rgba[0] + ', ' + rgba[1] + ', ' + rgba[2] + ')'; + } + + } + + // Parses a string and returns a valid color string when possible + function parseInput(string, expand) { + if(isRgb(string)) { + // Returns a valid rgb(a) string + return parseRgb(string); + } else { + return parseHex(string, expand); + } + } + + // Keeps value within min and max + function keepWithin(value, min, max) { + if(value < min) value = min; + if(value > max) value = max; + return value; + } + + // Checks if a string is a valid RGB(A) string + function isRgb(string) { + var rgb = string.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); + return (rgb && rgb.length === 4) ? true : false; + } + + // Function to get alpha from a RGB(A) string + function getAlpha(rgba) { + rgba = rgba.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+(\.\d{1,2})?|\.\d{1,2})[\s+]?/i); + return (rgba && rgba.length === 6) ? rgba[4] : '1'; + } + + // Converts an HSB object to an RGB object + function hsb2rgb(hsb) { + var rgb = {}; + var h = Math.round(hsb.h); + var s = Math.round(hsb.s * 255 / 100); + var v = Math.round(hsb.b * 255 / 100); + if(s === 0) { + rgb.r = rgb.g = rgb.b = v; + } else { + var t1 = v; + var t2 = (255 - s) * v / 255; + var t3 = (t1 - t2) * (h % 60) / 60; + if(h === 360) h = 0; + if(h < 60) { rgb.r = t1; rgb.b = t2; rgb.g = t2 + t3; } + else if(h < 120) {rgb.g = t1; rgb.b = t2; rgb.r = t1 - t3; } + else if(h < 180) {rgb.g = t1; rgb.r = t2; rgb.b = t2 + t3; } + else if(h < 240) {rgb.b = t1; rgb.r = t2; rgb.g = t1 - t3; } + else if(h < 300) {rgb.b = t1; rgb.g = t2; rgb.r = t2 + t3; } + else if(h < 360) {rgb.r = t1; rgb.g = t2; rgb.b = t1 - t3; } + else { rgb.r = 0; rgb.g = 0; rgb.b = 0; } + } + return { + r: Math.round(rgb.r), + g: Math.round(rgb.g), + b: Math.round(rgb.b) + }; + } + + // Converts an RGB string to a hex string + function rgbString2hex(rgb){ + rgb = rgb.match(/^rgba?[\s+]?\([\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?,[\s+]?(\d+)[\s+]?/i); + return (rgb && rgb.length === 4) ? '#' + + ('0' + parseInt(rgb[1],10).toString(16)).slice(-2) + + ('0' + parseInt(rgb[2],10).toString(16)).slice(-2) + + ('0' + parseInt(rgb[3],10).toString(16)).slice(-2) : ''; + } + + // Converts an RGB object to a hex string + function rgb2hex(rgb) { + var hex = [ + rgb.r.toString(16), + rgb.g.toString(16), + rgb.b.toString(16) + ]; + $.each(hex, function(nr, val) { + if(val.length === 1) hex[nr] = '0' + val; + }); + return '#' + hex.join(''); + } + + // Converts an HSB object to a hex string + function hsb2hex(hsb) { + return rgb2hex(hsb2rgb(hsb)); + } + + // Converts a hex string to an HSB object + function hex2hsb(hex) { + var hsb = rgb2hsb(hex2rgb(hex)); + if(hsb.s === 0) hsb.h = 360; + return hsb; + } + + // Converts an RGB object to an HSB object + function rgb2hsb(rgb) { + var hsb = { h: 0, s: 0, b: 0 }; + var min = Math.min(rgb.r, rgb.g, rgb.b); + var max = Math.max(rgb.r, rgb.g, rgb.b); + var delta = max - min; + hsb.b = max; + hsb.s = max !== 0 ? 255 * delta / max : 0; + if(hsb.s !== 0) { + if(rgb.r === max) { + hsb.h = (rgb.g - rgb.b) / delta; + } else if(rgb.g === max) { + hsb.h = 2 + (rgb.b - rgb.r) / delta; + } else { + hsb.h = 4 + (rgb.r - rgb.g) / delta; + } + } else { + hsb.h = -1; + } + hsb.h *= 60; + if(hsb.h < 0) { + hsb.h += 360; + } + hsb.s *= 100/255; + hsb.b *= 100/255; + return hsb; + } + + // Converts a hex string to an RGB object + function hex2rgb(hex) { + hex = parseInt(((hex.indexOf('#') > -1) ? hex.substring(1) : hex), 16); + return { + r: hex >> 16, + g: (hex & 0x00FF00) >> 8, + b: (hex & 0x0000FF) + }; + } + + // Handle events + $([document]) + // Hide on clicks outside of the control + .on('mousedown.minicolors touchstart.minicolors', function(event) { + if(!$(event.target).parents().add(event.target).hasClass('minicolors')) { + hide(); + } + }) + // Start moving + .on('mousedown.minicolors touchstart.minicolors', '.minicolors-grid, .minicolors-slider, .minicolors-opacity-slider', function(event) { + var target = $(this); + event.preventDefault(); + $(event.delegateTarget).data('minicolors-target', target); + move(target, event, true); + }) + // Move pickers + .on('mousemove.minicolors touchmove.minicolors', function(event) { + var target = $(event.delegateTarget).data('minicolors-target'); + if(target) move(target, event); + }) + // Stop moving + .on('mouseup.minicolors touchend.minicolors', function() { + $(this).removeData('minicolors-target'); + }) + // Selected a swatch + .on('click.minicolors', '.minicolors-swatches li', function(event) { + event.preventDefault(); + var target = $(this), input = target.parents('.minicolors').find('.minicolors-input'), color = target.data('swatch-color'); + updateInput(input, color, getAlpha(color)); + updateFromInput(input); + }) + // Show panel when swatch is clicked + .on('mousedown.minicolors touchstart.minicolors', '.minicolors-input-swatch', function(event) { + var input = $(this).parent().find('.minicolors-input'); + event.preventDefault(); + show(input); + }) + // Show on focus + .on('focus.minicolors', '.minicolors-input', function() { + var input = $(this); + if(!input.data('minicolors-initialized')) return; + show(input); + }) + // Update value on blur + .on('blur.minicolors', '.minicolors-input', function() { + var input = $(this); + var settings = input.data('minicolors-settings'); + var keywords; + var hex; + var rgba; + var swatchOpacity; + var value; + + if(!input.data('minicolors-initialized')) return; + + // Get array of lowercase keywords + keywords = !settings.keywords ? [] : $.map(settings.keywords.split(','), function(a) { + return $.trim(a.toLowerCase()); + }); + + // Set color string + if(input.val() !== '' && $.inArray(input.val().toLowerCase(), keywords) > -1) { + value = input.val(); + } else { + // Get RGBA values for easy conversion + if(isRgb(input.val())) { + rgba = parseRgb(input.val(), true); + } else { + hex = parseHex(input.val(), true); + rgba = hex ? hex2rgb(hex) : null; + } + + // Convert to format + if(rgba === null) { + value = settings.defaultValue; + } else if(settings.format === 'rgb') { + value = settings.opacity ? + parseRgb('rgba(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ',' + input.attr('data-opacity') + ')') : + parseRgb('rgb(' + rgba.r + ',' + rgba.g + ',' + rgba.b + ')'); + } else { + value = rgb2hex(rgba); + } + } + + // Update swatch opacity + swatchOpacity = settings.opacity ? input.attr('data-opacity') : 1; + if(value.toLowerCase() === 'transparent') swatchOpacity = 0; + input + .closest('.minicolors') + .find('.minicolors-input-swatch > span') + .css('opacity', swatchOpacity); + + // Set input value + input.val(value); + + // Is it blank? + if(input.val() === '') input.val(parseInput(settings.defaultValue, true)); + + // Adjust case + input.val(convertCase(input.val(), settings.letterCase)); + + }) + // Handle keypresses + .on('keydown.minicolors', '.minicolors-input', function(event) { + var input = $(this); + if(!input.data('minicolors-initialized')) return; + switch(event.which) { + case 9: // tab + hide(); + break; + case 13: // enter + case 27: // esc + hide(); + input.blur(); + break; + } + }) + // Update on keyup + .on('keyup.minicolors', '.minicolors-input', function() { + var input = $(this); + if(!input.data('minicolors-initialized')) return; + updateFromInput(input, true); + }) + // Update on paste + .on('paste.minicolors', '.minicolors-input', function() { + var input = $(this); + if(!input.data('minicolors-initialized')) return; + setTimeout(function() { + updateFromInput(input, true); + }, 1); + }); +})); diff --git a/app/assets/javascripts/theater.js b/app/assets/javascripts/theater.js index cf8f3a9..6ae0264 100644 --- a/app/assets/javascripts/theater.js +++ b/app/assets/javascripts/theater.js @@ -332,6 +332,7 @@ var GalleryTheater = function(){ var setMainPic = function(direction,selectedFromStrip){ var img = null; + $('div.gallery-show-original a').eq(0).attr('href',currentPic.image.url) if(direction == null){ img = $(""); img.hide(); diff --git a/app/assets/stylesheets/cropper.css b/app/assets/stylesheets/cropper.css new file mode 100644 index 0000000..6c5dee5 --- /dev/null +++ b/app/assets/stylesheets/cropper.css @@ -0,0 +1,304 @@ +/*! + * Cropper.js v1.5.5 + * https://fengyuanchen.github.io/cropperjs + * + * Copyright 2015-present Chen Fengyuan + * Released under the MIT license + * + * Date: 2019-08-04T02:26:27.232Z + */ + +.cropper-container { + direction: ltr; + font-size: 0; + line-height: 0; + position: relative; + -ms-touch-action: none; + touch-action: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.cropper-container img { + display: block; + height: 100%; + image-orientation: 0deg; + max-height: none !important; + max-width: none !important; + min-height: 0 !important; + min-width: 0 !important; + width: 100%; +} + +.cropper-wrap-box, +.cropper-canvas, +.cropper-drag-box, +.cropper-crop-box, +.cropper-modal { + bottom: 0; + left: 0; + position: absolute; + right: 0; + top: 0; +} + +.cropper-wrap-box, +.cropper-canvas { + overflow: hidden; +} + +.cropper-drag-box { + background-color: #fff; + opacity: 0; +} + +.cropper-modal { + background-color: #000; + opacity: 0.5; +} + +.cropper-view-box { + display: block; + height: 100%; + outline: 1px solid #39f; + outline-color: rgba(51, 153, 255, 0.75); + overflow: hidden; + width: 100%; +} + +.cropper-dashed { + border: 0 dashed #eee; + display: block; + opacity: 0.5; + position: absolute; +} + +.cropper-dashed.dashed-h { + border-bottom-width: 1px; + border-top-width: 1px; + height: calc(100% / 3); + left: 0; + top: calc(100% / 3); + width: 100%; +} + +.cropper-dashed.dashed-v { + border-left-width: 1px; + border-right-width: 1px; + height: 100%; + left: calc(100% / 3); + top: 0; + width: calc(100% / 3); +} + +.cropper-center { + display: block; + height: 0; + left: 50%; + opacity: 0.75; + position: absolute; + top: 50%; + width: 0; +} + +.cropper-center::before, +.cropper-center::after { + background-color: #eee; + content: ' '; + display: block; + position: absolute; +} + +.cropper-center::before { + height: 1px; + left: -3px; + top: 0; + width: 7px; +} + +.cropper-center::after { + height: 7px; + left: 0; + top: -3px; + width: 1px; +} + +.cropper-face, +.cropper-line, +.cropper-point { + display: block; + height: 100%; + opacity: 0.1; + position: absolute; + width: 100%; +} + +.cropper-face { + background-color: #fff; + left: 0; + top: 0; +} + +.cropper-line { + background-color: #39f; +} + +.cropper-line.line-e { + cursor: ew-resize; + right: -3px; + top: 0; + width: 5px; +} + +.cropper-line.line-n { + cursor: ns-resize; + height: 5px; + left: 0; + top: -3px; +} + +.cropper-line.line-w { + cursor: ew-resize; + left: -3px; + top: 0; + width: 5px; +} + +.cropper-line.line-s { + bottom: -3px; + cursor: ns-resize; + height: 5px; + left: 0; +} + +.cropper-point { + background-color: #39f; + height: 5px; + opacity: 0.75; + width: 5px; +} + +.cropper-point.point-e { + cursor: ew-resize; + margin-top: -3px; + right: -3px; + top: 50%; +} + +.cropper-point.point-n { + cursor: ns-resize; + left: 50%; + margin-left: -3px; + top: -3px; +} + +.cropper-point.point-w { + cursor: ew-resize; + left: -3px; + margin-top: -3px; + top: 50%; +} + +.cropper-point.point-s { + bottom: -3px; + cursor: s-resize; + left: 50%; + margin-left: -3px; +} + +.cropper-point.point-ne { + cursor: nesw-resize; + right: -3px; + top: -3px; +} + +.cropper-point.point-nw { + cursor: nwse-resize; + left: -3px; + top: -3px; +} + +.cropper-point.point-sw { + bottom: -3px; + cursor: nesw-resize; + left: -3px; +} + +.cropper-point.point-se { + bottom: -3px; + cursor: nwse-resize; + height: 20px; + opacity: 1; + right: -3px; + width: 20px; +} + +@media (min-width: 768px) { + .cropper-point.point-se { + height: 15px; + width: 15px; + } +} + +@media (min-width: 992px) { + .cropper-point.point-se { + height: 10px; + width: 10px; + } +} + +@media (min-width: 1200px) { + .cropper-point.point-se { + height: 5px; + opacity: 0.75; + width: 5px; + } +} + +.cropper-point.point-se::before { + background-color: #39f; + bottom: -50%; + content: ' '; + display: block; + height: 200%; + opacity: 0; + position: absolute; + right: -50%; + width: 200%; +} + +.cropper-invisible { + opacity: 0; +} + +.cropper-bg { + background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC'); +} + +.cropper-hide { + display: block; + height: 0; + position: absolute; + width: 0; +} + +.cropper-hidden { + display: none !important; +} + +.cropper-move { + cursor: move; +} + +.cropper-crop { + cursor: crosshair; +} + +.cropper-disabled .cropper-drag-box, +.cropper-disabled .cropper-face, +.cropper-disabled .cropper-line, +.cropper-disabled .cropper-point { + cursor: not-allowed; +} diff --git a/app/assets/stylesheets/gallery.css b/app/assets/stylesheets/gallery.css index 920a349..c6dd415 100644 --- a/app/assets/stylesheets/gallery.css +++ b/app/assets/stylesheets/gallery.css @@ -161,6 +161,13 @@ padding: 0 0 10px; list-style: none; } +.rgalbum .photo_edit{ + right: 100%; + transition-duration: 0.5s; +} +.rgalbum:hover .photo_edit{ + transform: translate(100%); +} #imgholder .rgalbum { position: relative; float: left; diff --git a/app/assets/stylesheets/jquery.minicolors.css b/app/assets/stylesheets/jquery.minicolors.css new file mode 100644 index 0000000..a281101 --- /dev/null +++ b/app/assets/stylesheets/jquery.minicolors.css @@ -0,0 +1,432 @@ +.minicolors { + position: relative; +} + +.minicolors-sprite { + background-image: url(jquery.minicolors.png); +} + +.minicolors-swatch { + position: absolute; + vertical-align: middle; + background-position: -80px 0; + border: solid 1px #ccc; + cursor: text; + padding: 0; + margin: 0; + display: inline-block; +} + +.minicolors-swatch-color { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +.minicolors input[type=hidden] + .minicolors-swatch { + width: 28px; + position: static; + cursor: pointer; +} + +.minicolors input[type=hidden][disabled] + .minicolors-swatch { + cursor: default; +} + +/* Panel */ +.minicolors-panel { + position: relative; + width: 173px; + background: white; + border: solid 1px #CCC; + box-shadow: 0 0 20px rgba(0, 0, 0, .2); + z-index: 99999; + box-sizing: content-box; + display: none; +} + +.minicolors-panel.minicolors-visible { + display: block; +} + +/* Panel positioning */ +.minicolors-position-top .minicolors-panel { + top: -154px; +} + +.minicolors-position-right .minicolors-panel { + right: 0; +} + +.minicolors-position-bottom .minicolors-panel { + top: auto; +} + +.minicolors-position-left .minicolors-panel { + left: 0; +} + +.minicolors-with-opacity .minicolors-panel { + width: 194px; +} + +.minicolors .minicolors-grid { + position: relative; + top: 1px; + left: 1px; /* LTR */ + width: 150px; + height: 150px; + margin-bottom: 2px; + background-position: -120px 0; + cursor: crosshair; +} +[dir=rtl] .minicolors .minicolors-grid { + right: 1px; +} + +.minicolors .minicolors-grid-inner { + position: absolute; + top: 0; + left: 0; + width: 150px; + height: 150px; +} + +.minicolors-slider-saturation .minicolors-grid { + background-position: -420px 0; +} + +.minicolors-slider-saturation .minicolors-grid-inner { + background-position: -270px 0; + background-image: inherit; +} + +.minicolors-slider-brightness .minicolors-grid { + background-position: -570px 0; +} + +.minicolors-slider-brightness .minicolors-grid-inner { + background-color: black; +} + +.minicolors-slider-wheel .minicolors-grid { + background-position: -720px 0; +} + +.minicolors-slider, +.minicolors-opacity-slider { + position: absolute; + top: 1px; + left: 152px; /* LTR */ + width: 20px; + height: 150px; + background-color: white; + background-position: 0 0; + cursor: row-resize; +} +[dir=rtl] .minicolors-slider, +[dir=rtl] .minicolors-opacity-slider { + right: 152px; +} + +.minicolors-slider-saturation .minicolors-slider { + background-position: -60px 0; +} + +.minicolors-slider-brightness .minicolors-slider { + background-position: -20px 0; +} + +.minicolors-slider-wheel .minicolors-slider { + background-position: -20px 0; +} + +.minicolors-opacity-slider { + left: 173px; /* LTR */ + background-position: -40px 0; + display: none; +} +[dir=rtl] .minicolors-opacity-slider { + right: 173px; +} + +.minicolors-with-opacity .minicolors-opacity-slider { + display: block; +} + +/* Pickers */ +.minicolors-grid .minicolors-picker { + position: absolute; + top: 70px; + left: 70px; + width: 12px; + height: 12px; + border: solid 1px black; + border-radius: 10px; + margin-top: -6px; + margin-left: -6px; + background: none; +} + +.minicolors-grid .minicolors-picker > div { + position: absolute; + top: 0; + left: 0; + width: 8px; + height: 8px; + border-radius: 8px; + border: solid 2px white; + box-sizing: content-box; +} + +.minicolors-picker { + position: absolute; + top: 0; + left: 0; + width: 18px; + height: 2px; + background: white; + border: solid 1px black; + margin-top: -2px; + box-sizing: content-box; +} + +/* Swatches */ +.minicolors-swatches, +.minicolors-swatches li { + margin: 5px 0 3px 5px; /* LTR */ + padding: 0; + list-style: none; + overflow: hidden; +} +[dir=rtl] .minicolors-swatches, +[dir=rtl] .minicolors-swatches li { + margin: 5px 5px 3px 0; +} + +.minicolors-swatches .minicolors-swatch { + position: relative; + float: left; /* LTR */ + cursor: pointer; + margin:0 4px 0 0; /* LTR */ +} +[dir=rtl] .minicolors-swatches .minicolors-swatch { + float: right; + margin:0 0 0 4px; +} + +.minicolors-with-opacity .minicolors-swatches .minicolors-swatch { + margin-right: 7px; /* LTR */ +} +[dir=rtl] .minicolors-with-opacity .minicolors-swatches .minicolors-swatch { + margin-right: 0; + margin-left: 7px; +} + +.minicolors-swatch.selected { + border-color: #000; +} + +/* Inline controls */ +.minicolors-inline { + display: inline-block; +} + +.minicolors-inline .minicolors-input { + display: none !important; +} + +.minicolors-inline .minicolors-panel { + position: relative; + top: auto; + left: auto; /* LTR */ + box-shadow: none; + z-index: auto; + display: inline-block; +} +[dir=rtl] .minicolors-inline .minicolors-panel { + right: auto; +} + +/* Default theme */ +.minicolors-theme-default .minicolors-swatch { + top: 5px; + left: 5px; /* LTR */ + width: 18px; + height: 18px; +} +[dir=rtl] .minicolors-theme-default .minicolors-swatch { + right: 5px; +} +.minicolors-theme-default .minicolors-swatches .minicolors-swatch { + margin-bottom: 2px; + top: 0; + left: 0; /* LTR */ + width: 18px; + height: 18px; +} +[dir=rtl] .minicolors-theme-default .minicolors-swatches .minicolors-swatch { + right: 0; +} +.minicolors-theme-default.minicolors-position-right .minicolors-swatch { + left: auto; /* LTR */ + right: 5px; /* LTR */ +} +[dir=rtl] .minicolors-theme-default.minicolors-position-left .minicolors-swatch { + right: auto; + left: 5px; +} +.minicolors-theme-default.minicolors { + width: auto; + display: inline-block; +} +.minicolors-theme-default .minicolors-input { + height: 20px; + width: auto; + display: inline-block; + padding-left: 26px; /* LTR */ +} +[dir=rtl] .minicolors-theme-default .minicolors-input { + text-align: right; + unicode-bidi: plaintext; + padding-left: 1px; + padding-right: 26px; +} +.minicolors-theme-default.minicolors-position-right .minicolors-input { + padding-right: 26px; /* LTR */ + padding-left: inherit; /* LTR */ +} +[dir=rtl] .minicolors-theme-default.minicolors-position-left .minicolors-input { + padding-right: inherit; + padding-left: 26px; +} + +/* Bootstrap theme */ +.minicolors-theme-bootstrap .minicolors-swatch { + z-index: 2; + top: 3px; + left: 3px; /* LTR */ + width: 28px; + height: 28px; + border-radius: 3px; +} +[dir=rtl] .minicolors-theme-bootstrap .minicolors-swatch { + right: 3px; +} +.minicolors-theme-bootstrap .minicolors-swatches .minicolors-swatch { + margin-bottom: 2px; + top: 0; + left: 0; /* LTR */ + width: 20px; + height: 20px; +} +[dir=rtl] .minicolors-theme-bootstrap .minicolors-swatches .minicolors-swatch { + right: 0; +} +.minicolors-theme-bootstrap .minicolors-swatch-color { + border-radius: inherit; +} +.minicolors-theme-bootstrap.minicolors-position-right > .minicolors-swatch { + left: auto; /* LTR */ + right: 3px; /* LTR */ +} +[dir=rtl] .minicolors-theme-bootstrap.minicolors-position-left > .minicolors-swatch { + right: auto; + left: 3px; +} +.minicolors-theme-bootstrap .minicolors-input { + float: none; + padding-left: 44px; /* LTR */ +} +[dir=rtl] .minicolors-theme-bootstrap .minicolors-input { + text-align: right; + unicode-bidi: plaintext; + padding-left: 12px; + padding-right: 44px; +} +.minicolors-theme-bootstrap.minicolors-position-right .minicolors-input { + padding-right: 44px; /* LTR */ + padding-left: 12px; /* LTR */ +} +[dir=rtl] .minicolors-theme-bootstrap.minicolors-position-left .minicolors-input { + padding-right: 12px; + padding-left: 44px; +} +.minicolors-theme-bootstrap .minicolors-input.input-lg + .minicolors-swatch { + top: 4px; + left: 4px; /* LTR */ + width: 37px; + height: 37px; + border-radius: 5px; +} +[dir=rtl] .minicolors-theme-bootstrap .minicolors-input.input-lg + .minicolors-swatch { + right: 4px; +} +.minicolors-theme-bootstrap .minicolors-input.input-sm + .minicolors-swatch { + width: 24px; + height: 24px; +} +.minicolors-theme-bootstrap .minicolors-input.input-xs + .minicolors-swatch { + width: 18px; + height: 18px; +} +.input-group .minicolors-theme-bootstrap:not(:first-child) .minicolors-input { + border-top-left-radius: 0; /* LTR */ + border-bottom-left-radius: 0; /* LTR */ +} +[dir=rtl] .input-group .minicolors-theme-bootstrap .minicolors-input { + border-radius: 4px; +} +[dir=rtl] .input-group .minicolors-theme-bootstrap:not(:first-child) .minicolors-input { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +[dir=rtl] .input-group .minicolors-theme-bootstrap:not(:last-child) .minicolors-input { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +/* bootstrap input-group rtl override */ +[dir=rtl] .input-group .form-control, +[dir=rtl] .input-group-addon, +[dir=rtl] .input-group-btn > .btn, +[dir=rtl] .input-group-btn > .btn-group > .btn, +[dir=rtl] .input-group-btn > .dropdown-toggle { + border: 1px solid #ccc; + border-radius: 4px; +} +[dir=rtl] .input-group .form-control:first-child, +[dir=rtl] .input-group-addon:first-child, +[dir=rtl] .input-group-btn:first-child > .btn, +[dir=rtl] .input-group-btn:first-child > .btn-group > .btn, +[dir=rtl] .input-group-btn:first-child > .dropdown-toggle, +[dir=rtl] .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +[dir=rtl] .input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; + border-left: 0; +} +[dir=rtl] .input-group .form-control:last-child, +[dir=rtl] .input-group-addon:last-child, +[dir=rtl] .input-group-btn:last-child > .btn, +[dir=rtl] .input-group-btn:last-child > .btn-group > .btn, +[dir=rtl] .input-group-btn:last-child > .dropdown-toggle, +[dir=rtl] .input-group-btn:first-child > .btn:not(:first-child), +[dir=rtl] .input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} + +/* Semantic Ui theme */ +.minicolors-theme-semanticui .minicolors-swatch { + top: 0; + left: 0; /* LTR */ + padding: 18px; +} +[dir=rtl] .minicolors-theme-semanticui .minicolors-swatch { + right: 0; +} +.minicolors-theme-semanticui input { + text-indent: 30px; +} diff --git a/app/controllers/admin/galleries_controller.rb b/app/controllers/admin/galleries_controller.rb index adf9e5d..627acf3 100644 --- a/app/controllers/admin/galleries_controller.rb +++ b/app/controllers/admin/galleries_controller.rb @@ -1,10 +1,138 @@ require 'rubyXL' class Admin::GalleriesController < OrbitAdminController include Admin::GalleriesHelper - before_filter :setup_vars + before_filter :setup_vars before_action :authenticate_user, :except => "imgs" before_action :log_user_action + def save_crop + begin + images = AlbumImage.all.select{|value| params[:id].include? value.id.to_s} + id = images.first.album_id.to_s + x = params['x'] + y = params['y'] + w = params['w'] + h = params['h'] + cords = x.zip(y,w,h) + count = cords.length + images.each_with_index do |image,index| + cord = cords[index] + if image.album_crops.first.nil? + image.album_crops.create(crop_x: cord[0],crop_y: cord[1],crop_w: cord[2],crop_h: cord[3]) + else + image.album_crops.first.update_attributes(crop_x: cord[0],crop_y: cord[1],crop_w: cord[2],crop_h: cord[3]) + end + end + @@finish = false + @@start = false + Thread.new do + @@start = true + images.each_with_index do |image,index| + @@progress_percent = (index*100.0/count).floor.to_s+'%' + all_version = image.file.versions.map{|k,v| k} + begin + #org_fname = image.file.path + #org_extname = File.extname(org_fname) + #org_fname.slice! org_extname + #FileUtils.cp(org_fname + org_extname, org_fname + '_resized' + org_extname) + @@progress_filename = image[:file].to_s + all_version.each do |version| + if !(version.to_s == 'resized' && crop_but_no_backup(image)) + image.file.recreate_versions! version + image.save! + end + end + rescue => e + @@progress_filename = e.inspect.to_s + puts e.inspect + end + end + @@finish = true + end + rescue => e + puts e.inspect + end + redirect_url = "/admin/galleries/crop_process" + render :json => {'href' => redirect_url}.to_json + end + def call_translate + text = params['text'] + render :json => {'translate' => "#{t(text.to_s)}" }.to_json + end + def recreate_image + notalive = @@progress_percent.nil? rescue true + if notalive + if !params['album_id'].to_s.empty? + choice_ids = params['album_id'].split(',') + albums = Album.all.select { |value| (choice_ids.include? value.id.to_s)} + if !(params['use_default']=='true') + color = params['color_choice'].to_s.empty? ? 'transparent' : params['color_choice'] + albums.each do |album| + if album.album_colors.first.nil? + album.album_colors.create('color' => color) + else + album.album_colors.first.update_attributes('color' => color, 'updated_at' => Time.now) + end + end + end + @@start = false + count = 0 + albums.each{|album| count+=album.album_images.count } + @@finish = false + Thread.new do + @@start = true + i = 0 + albums.each do |album| + album.album_images.each do |image| + error = nil + all_version = image.file.versions.map{|k,v| k} + begin + all_version.each do |version| + if !(version.to_s == 'resized' && crop_but_no_backup(image)) + image.file.recreate_versions! version + image.save! + end + end + rescue => error + end + @@progress_percent = (i*100.0/count).floor.to_s+'%' + if !error.nil? + @@progress_filename = error.inspect.to_s.encode('UTF-8', invalid: :replace, undef: :replace, replace: '?') + sleep(1) + else + @@progress_filename = image[:file].to_s + end + i+=1 + end + end + @@finish = true + end + else + @@start = true + @@progress_filename = '' + @@finish = true + @@progress_percent = '100%' + end + end + end + def finish_recreate + @@progress_percent = nil + @@progress_filename = nil + @@start = false + render :text => '' + end + def recreate_progress + if @@start + render :json => {'percent' => (@@progress_percent rescue '0%'), 'filename' => (@@progress_filename rescue ''), 'finish' => (@@finish rescue false) }.to_json + else + render :json => {'percent' => '0%', 'filename' => '', 'finish' => false }.to_json + end + end def index + Album.each do |album| + if album.album_colors.first.nil? + album.album_colors.create('color' => '#000000') + end + end @tags = @module_app.tags categories = @module_app.categories.enabled @filter_fields = filter_fields(categories, @tags) @@ -14,7 +142,15 @@ class Admin::GalleriesController < OrbitAdminController albums = Album.where(:order.gt => -1).asc(:order).with_categories(filters("category")).with_tags(filters("tag")) albums = search_data(albums,[:name]) @albums = @albums.concat(albums) - + if AlbumColor.count!=0 + if AlbumColor.all.desc('updated_at').first[:color] == 'transparent' + @color_save = '' + else + @color_save = AlbumColor.desc('updated_at').first[:color] + end + else + @color_save = '#000000' + end if request.xhr? render :partial => "album", :collection => @albums end @@ -190,19 +326,53 @@ class Admin::GalleriesController < OrbitAdminController end - - def upload_image - @album = Album.find(params[:album_id]) - @files = params['files'] - a = Array.new - @files.each do |file| - @image = @album.album_images.new - @image.file = file - @image.tags = @album.tags rescue [] - @image.save! - a << {"thumbnail_url"=>@image.file.thumb.url,"url"=>admin_image_path(@image)} + def upload_process + if @@upload_success == true + count = @@image_unprocessed.length + Thread.new do + begin + @@start = true + @@image_unprocessed.each_with_index do |image,i| + @@progress_filename = @@file[i].original_filename + image.file = @@file[i] + image.save! + @@progress_percent = ((i+1)*100.0/count).floor.to_s + '%' + end + rescue => e + puts e.inspect + end + @@file = [] + @@image_unprocessed = [] + @upload_success = false + @@finish = true + end end - render :json=>{"files"=>a}.to_json + end + def start_upload_process + @@upload_success = true + render :json => {}.to_json + end + def init_upload + @@image_unprocessed = [] + @@start = false + @@finish = false + @@file = [] + @@progress_percent = '0%' + @@progress_filename = 'processing!!' + render :json => {}.to_json + end + def upload_image + if !(@@image_unprocessed.nil?) + album = Album.find(params[:album_id]) + files = params['files'] + files.each do |file| + image = album.album_images.new + @@file << file + image.tags = (album.tags rescue []) + @@image_unprocessed << image + end + end + render :json=>{"files"=>[{}]}.to_json end def last_image_id @@ -276,7 +446,13 @@ class Admin::GalleriesController < OrbitAdminController end private - + def crop_but_no_backup image + fname = image.file.path + extension = File.extname(fname) + base_name = fname.chomp(extension) + base_name += ('_resized'+ extension) + File.file?(base_name) + end def setup_vars @module_app = ModuleApp.where(:key=>"gallery").first end diff --git a/app/controllers/admin/images_controller.rb b/app/controllers/admin/images_controller.rb index 536d78b..ccf622b 100644 --- a/app/controllers/admin/images_controller.rb +++ b/app/controllers/admin/images_controller.rb @@ -1,5 +1,17 @@ class Admin::ImagesController < OrbitAdminController before_filter :setup_vars + def batch_crop + images = params['image_ids'].split(',') + @img = [] + images.each do |image| + @img << AlbumImage.find(image) + end + render 'batch_crop' + end + def edit + @image = AlbumImage.find(params[:id]) + render 'edit_image' + end def show @image = AlbumImage.find(params[:id]) @albumid = @image.album_id @@ -35,6 +47,7 @@ class Admin::ImagesController < OrbitAdminController images = params['images'] images.each do |image| img = AlbumImage.find(image) + FileUtils.rm_rf(File.dirname(img.file.path)) img.delete end if params['delete_cover'] == "true" diff --git a/app/controllers/galleries_controller.rb b/app/controllers/galleries_controller.rb index d68a7f5..6bdb0b5 100644 --- a/app/controllers/galleries_controller.rb +++ b/app/controllers/galleries_controller.rb @@ -67,6 +67,7 @@ class GalleriesController < ApplicationController { "link_to_show" => OrbitHelper.widget_more_url + "/" + a.album.to_param + "#" + a.id.to_s, "alt_title" => alt_text, + "src" => a.file.url, "thumb-src" => a.file.thumb.url, "thumb-large-src" => a.file.thumb_large.url, "image_description" => a.description, @@ -86,14 +87,15 @@ class GalleriesController < ApplicationController images = album.album_images.asc(:order) output = Array.new images.each do |values| - alt_text = (values.description.nil? || values.description == "" ? "gallery image" : values.description) - output << { _id: values.id.to_s, - description: values.description, - title: values.title, - alt_title: alt_text, - file: values.file.as_json[:file], - gallery_album_id: values.album_id, - tags: values.tags} + alt_text = (values.description.nil? || values.description == "" ? "gallery image" : values.description) + output << { _id: values.id.to_s, + description: values.description, + title: values.title, + alt_title: alt_text, + url: values.file.url, + file: values.file.as_json[:file], + gallery_album_id: values.album_id, + tags: values.tags} end return output end @@ -105,7 +107,6 @@ class GalleriesController < ApplicationController images = album.album_images.asc(:order) data = { "album" => album, - # "images" => images, "image" => image.id.to_s, "images" => imgs(albumid) } diff --git a/app/models/album.rb b/app/models/album.rb index b235329..8f738be 100644 --- a/app/models/album.rb +++ b/app/models/album.rb @@ -17,7 +17,9 @@ class Album # has_and_belongs_to_many :tags, :class_name => "GalleryTag" has_many :album_images, :autosave => true, :dependent => :destroy + has_many :album_colors, :autosave => true, :dependent => :destroy accepts_nested_attributes_for :album_images, :allow_destroy => true + accepts_nested_attributes_for :album_colors, :allow_destroy => true def self.find_by_param(input) self.find_by(uid: input) diff --git a/app/models/album_color.rb b/app/models/album_color.rb new file mode 100644 index 0000000..1295e00 --- /dev/null +++ b/app/models/album_color.rb @@ -0,0 +1,6 @@ +class AlbumColor + include Mongoid::Document + include Mongoid::Timestamps + field :color, type: String + belongs_to :album +end \ No newline at end of file diff --git a/app/models/album_crop.rb b/app/models/album_crop.rb new file mode 100644 index 0000000..889048c --- /dev/null +++ b/app/models/album_crop.rb @@ -0,0 +1,8 @@ +class AlbumCrop + include Mongoid::Document + field :crop_x, type: String + field :crop_y, type: String + field :crop_w, type: String + field :crop_h, type: String + belongs_to :album_image +end \ No newline at end of file diff --git a/app/models/album_image.rb b/app/models/album_image.rb index 447dd9d..b08ccc5 100644 --- a/app/models/album_image.rb +++ b/app/models/album_image.rb @@ -14,5 +14,6 @@ class AlbumImage # has_and_belongs_to_many :tags, :class_name => "GalleryTag" belongs_to :album - + has_many :album_crops, :autosave => true, :dependent => :destroy + accepts_nested_attributes_for :album_crops, :allow_destroy => true end \ No newline at end of file diff --git a/app/uploaders/gallery_uploader.rb b/app/uploaders/gallery_uploader.rb index e76a56d..25b0901 100644 --- a/app/uploaders/gallery_uploader.rb +++ b/app/uploaders/gallery_uploader.rb @@ -2,14 +2,8 @@ module CarrierWave module Uploader module Versions - def recreate_version!(version) - already_cached = cached? - cache_stored_file! if !already_cached - send(version).store! - if !already_cached && @cache_id - tmp_dir = File.expand_path(File.join(cache_dir, cache_id), CarrierWave.root) - FileUtils.rm_rf(tmp_dir) - end + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end end end @@ -21,7 +15,6 @@ class GalleryUploader < CarrierWave::Uploader::Base # include CarrierWave::RMagick # include CarrierWave::ImageScience include CarrierWave::MiniMagick - # Choose what kind of storage to use for this uploader: # storage :file # storage :s3 @@ -31,7 +24,13 @@ class GalleryUploader < CarrierWave::Uploader::Base def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end - + def get_org_url + if have_crop? + model.file.resized.url + else + model.file.url + end + end def fix_exif_rotation manipulate! do |img| img.tap(&:auto_orient) @@ -53,37 +52,45 @@ class GalleryUploader < CarrierWave::Uploader::Base # version :thumb do # process :scale => [50, 50] # end - + process :resizer + process :optimize + version :resized, :if => :have_crop? do #backup + def full_filename(for_file) + extension = File.extname(super(for_file)) + base_name = super(for_file).split('resized_').join('').chomp(extension) + base_name + '_resized'+ extension + end + end + version :crop_from_org, :if => :have_crop? do + process :crop_it + def full_filename(for_file) + super(for_file).split('crop_from_org_').join('') + end + end version :thumb do - process :fix_exif_rotation - process :resize_to_fill => [200, 200] + process :convert => 'png', :if => :transparent? + process :pad_process => [200,200] end - version :thumb_large do - process :fix_exif_rotation - process :resize_to_fill => [600, 600] + process :convert => 'png', :if => :transparent? + process :pad_process => [600,600] end - version :theater do - process :fix_exif_rotation - process :resize_to_limit => [1920, 1080] + process :limit_process => [1920, 1080] end - version :mobile do - process :fix_exif_rotation - process :resize_to_limit => [1152, 768] + process :limit_process => [1152, 768] end +# Add a white list of extensions which are allowed to be uploaded. +# For images you might use something like this: +# def extension_white_list +# %w(jpg jpeg gif png) +# end - # Add a white list of extensions which are allowed to be uploaded. - # For images you might use something like this: - # def extension_white_list - # %w(jpg jpeg gif png) - # end - - # Override the filename of the uploaded files: - # def filename - # "something.jpg" if original_filename - # end +# Override the filename of the uploaded files: +# def filename +# "something.jpg" if original_filename +# end # def manipulate! # raise current_path.inspect @@ -94,6 +101,66 @@ class GalleryUploader < CarrierWave::Uploader::Base # rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e # raise CarrierWave::ProcessingError.new("Failed to manipulate with MiniMagick, maybe it is not an image? Original Error: #{e}") # end - + def optimize (*arg) + manipulate! do |img| + return img unless img.mime_type.match /image\/jpeg/ + img.strip + img.combine_options do |c| + c.quality "90" + c.depth "24" + c.interlace "plane" + end + img + end + end + private + def resizer + size_of_file = size.to_f / (2**20) + if size_of_file > 5 + img = MiniMagick::Image.open(path) + img_width = img[:width] + img_height = img[:height] + multiple = [img_width/Math.sqrt(size_of_file/5)/1920,img_height/Math.sqrt(size_of_file/5)/1080].max + if (multiple - multiple.to_i)>0.5 + multiple = multiple.to_i + 0.5 + else + multiple = multiple.to_i + end + resize_to_limit(multiple*1920,multiple*1080) + else + manipulate! do |img| + img + end + end + end + def limit_process(w,h) + resize_to_limit(w,h) + end + def have_crop?(*arg) + !(model.album_crops.first.nil?) + end + def crop_it + crops = model.album_crops.first + x=(crops.crop_x).to_i.abs.to_s + y=(crops.crop_y).to_i.abs.to_s + w=crops.crop_w.to_i + h=crops.crop_h.to_i + crop_image("#{w}x#{h}+#{x}+#{y}") + end + def crop_image(geometry) + img = MiniMagick::Image.open(model.file.resized.path) + img.crop(geometry) + img.write(model.file.crop_from_org.path) + end + def transparent?(*arg) + now_id = model.album_id.to_s + now_album = Album.all.select { |value| (now_id==value.id.to_s)}[0] + now_album.album_colors.first['color']=='transparent' + end + def pad_process (w,h) + now_id = model.album_id.to_s + now_album = Album.all.select { |value| (now_id==value.id.to_s)}[0] + resize_and_pad(w, h, (now_album.album_colors.first['color']=='transparent' ? :transparent : now_album.album_colors.first['color']), 'Center') + end end diff --git a/app/views/admin/galleries/_album.html.erb b/app/views/admin/galleries/_album.html.erb index 5562c90..8e9645f 100644 --- a/app/views/admin/galleries/_album.html.erb +++ b/app/views/admin/galleries/_album.html.erb @@ -1,4 +1,7 @@
  • +
    + +
    <% if album.cover == "default" %> <%= image_tag "gallery/default.jpg" %> diff --git a/app/views/admin/galleries/_form.html.erb b/app/views/admin/galleries/_form.html.erb index 84c85fe..e48d91a 100644 --- a/app/views/admin/galleries/_form.html.erb +++ b/app/views/admin/galleries/_form.html.erb @@ -2,6 +2,7 @@ <%= stylesheet_link_tag "lib/main-forms" %> <%= stylesheet_link_tag "lib/fileupload" %> <%= stylesheet_link_tag "lib/main-list" %> + <%= stylesheet_link_tag "jquery.minicolors" %> <% end %> <% content_for :page_specific_javascript do %> <%= javascript_include_tag "lib/bootstrap-fileupload" %> @@ -9,7 +10,55 @@ <%= javascript_include_tag "lib/datetimepicker/datetimepicker.js" %> <%= javascript_include_tag "lib/modal-preview" %> <%= javascript_include_tag "lib/file-type" %> + <%= javascript_include_tag "jquery.minicolors" %> <% end %> + +
  • - +
    @@ -48,52 +100,69 @@ <%= select_tags(f, @module_app) %>
    - - + + <% if @album.album_colors.first.nil? %> + <% @album.album_colors.new('color' => '#000000') %> + <% end %> + <%= f.fields_for :album_colors do |album_color_form| %> +
    + +
    + + <%= album_color_form.text_field :color , :class => 'input-block-level minicolors-input' %> + + + <%= t('gallery.transparent') %> + +
    +
    + <% end %> - - -
    - - +
    +
    + + +
    + + + + <% @site_in_use_locales.each_with_index do |locale, i| %> + +
    +
    + <%= f.fields_for :name_translations do |name| %> + <%= label_tag(locale, t("gallery.album_name"),:class=>"control-label muted") %> +
    + <%= name.text_field locale, :class => "input-block-level", :placeholder=>"Title",:value => (@album.name_translations[locale] rescue nil) %> +
    + <% end %> +
    +
    + <%= f.fields_for :description_translations do |desc| %> + <%= label_tag(locale, t("gallery.album_desc"), :class=>"control-label muted") %> +
    +
    + <%= desc.text_area locale, :class => "ckeditor input-block-level", :value => (@album.description_translations[locale] rescue nil)%> +
    +
    + <% end %> +
    +
    + <% end %> + +
    - <% @site_in_use_locales.each_with_index do |locale, i| %> - -
    -
    - <%= f.fields_for :name_translations do |name| %> - <%= label_tag(locale, t("gallery.album_name"),:class=>"control-label muted") %> -
    - <%= name.text_field locale, :class => "input-block-level", :placeholder=>"Title",:value => (@album.name_translations[locale] rescue nil) %> -
    - <% end %> -
    -
    - <%= f.fields_for :description_translations do |desc| %> - <%= label_tag(locale, t("gallery.album_desc"), :class=>"control-label muted") %> -
    -
    - <%= desc.text_area locale, :class => "ckeditor input-block-level", :value => (@album.description_translations[locale] rescue nil)%> -
    -
    - <% end %> -
    -
    - <% end %> - -
    - + + +
    + <%= f.submit t("gallery.save"), :class=> "btn btn-primary bt-form-save" %> +
    - -
    - <%= f.submit t("gallery.save"), :class=> "btn btn-primary bt-form-save" %> -
    - \ No newline at end of file diff --git a/app/views/admin/galleries/_image.html.erb b/app/views/admin/galleries/_image.html.erb index 20a34f7..c1a7194 100644 --- a/app/views/admin/galleries/_image.html.erb +++ b/app/views/admin/galleries/_image.html.erb @@ -3,6 +3,9 @@ <% if can_edit_or_delete?(@album) %> + + + ">edit
    • ">
    • diff --git a/app/views/admin/galleries/_recreate_thumb.html.erb b/app/views/admin/galleries/_recreate_thumb.html.erb new file mode 100644 index 0000000..76aac23 --- /dev/null +++ b/app/views/admin/galleries/_recreate_thumb.html.erb @@ -0,0 +1,106 @@ +<%= javascript_include_tag "jquery.minicolors.js" %> +<%= stylesheet_link_tag "jquery.minicolors.css" %> + +
      + +<%= t('gallery.recreate_thumb')+':' %> + + + + +<%= t('gallery.transparent') %> + + + + + + + +
      + + + + + + +
      + \ No newline at end of file diff --git a/app/views/admin/galleries/index.html.erb b/app/views/admin/galleries/index.html.erb index 7b533e8..09a54a5 100644 --- a/app/views/admin/galleries/index.html.erb +++ b/app/views/admin/galleries/index.html.erb @@ -1,6 +1,5 @@ -<% content_for :page_specific_css do %> - <%= stylesheet_link_tag "gallery" %> -<% end %> +<%= stylesheet_link_tag "gallery" %> +<%= stylesheet_link_tag "lib/tags-groups" %> <% content_for :page_specific_javascript do %> <%= javascript_include_tag "lib/jquery-ui-1.10.0.custom.min" %> <%= javascript_include_tag "jquery.masonry.min.js" %> @@ -8,17 +7,18 @@ <%= javascript_include_tag "gallery" %> <% end %> <%= render_filter @filter_fields, "orbit_gallery" %> -
      Albums re-ordering enabled.
      - -
      - @@ -78,7 +78,7 @@
      - Drop files here + <%= t('gallery.drop_files_here') %>
      @@ -142,7 +142,7 @@ <% end %>
    @@ -160,7 +160,7 @@ <% end %> @@ -242,7 +242,7 @@
  • {%=file.name%}

    -

    Success File uploaded successfully!

    +

    <%= t('gallery.success') %><%= t('gallery.file_uploaded_successfully') %>!

    {%=o.formatFileSize(file.size)%}

  • {% } %} diff --git a/app/views/admin/galleries/upload_panel.html.erb b/app/views/admin/galleries/upload_panel.html.erb index d5f563f..1c3ec5e 100755 --- a/app/views/admin/galleries/upload_panel.html.erb +++ b/app/views/admin/galleries/upload_panel.html.erb @@ -12,12 +12,12 @@ <%= form_for @album, :url => panel_gallery_back_end_upload_image_path, :html => {:class => 'clear'} do |f| %>
    - - + +
    diff --git a/app/views/admin/galleries/upload_process.html.erb b/app/views/admin/galleries/upload_process.html.erb new file mode 100644 index 0000000..c6e12cd --- /dev/null +++ b/app/views/admin/galleries/upload_process.html.erb @@ -0,0 +1,39 @@ +<%= t('gallery.progressbar')+':' %> +
    +
    +
    0
    +
    +
    +
    + \ No newline at end of file diff --git a/app/views/admin/images/batch_crop.html.erb b/app/views/admin/images/batch_crop.html.erb new file mode 100644 index 0000000..fc7c9cf --- /dev/null +++ b/app/views/admin/images/batch_crop.html.erb @@ -0,0 +1,102 @@ + + + + +
    +
    + + + + + + +
    + +
    +
    + <% @img.each do |image| %> +
    + +
    + <% end %> +
    + + \ No newline at end of file diff --git a/app/views/admin/images/crop_process.html.erb b/app/views/admin/images/crop_process.html.erb new file mode 100644 index 0000000..c6e12cd --- /dev/null +++ b/app/views/admin/images/crop_process.html.erb @@ -0,0 +1,39 @@ +<%= t('gallery.progressbar')+':' %> +
    +
    +
    0
    +
    +
    +
    + \ No newline at end of file diff --git a/app/views/admin/images/edit_image.html.erb b/app/views/admin/images/edit_image.html.erb new file mode 100644 index 0000000..b21fed7 --- /dev/null +++ b/app/views/admin/images/edit_image.html.erb @@ -0,0 +1,59 @@ + + + + +
    + + \ No newline at end of file diff --git a/app/views/galleries/show.html.erb b/app/views/galleries/show.html.erb index 01300b6..d7273f4 100644 --- a/app/views/galleries/show.html.erb +++ b/app/views/galleries/show.html.erb @@ -15,6 +15,9 @@