bremen_short_url/BackEnd/assets/slim/slim.amd.js

8695 lines
266 KiB
JavaScript

/*
* Slim v4.19.0 - Image Cropping Made Easy
* Copyright (c) 2018 Rik Schennink - http://slimimagecropper.com
*/
define(function() {
// custom event polyfill for IE10
(function() {
if ( typeof window.CustomEvent === 'function' ) return false;
function CustomEvent ( event, params ) {
params = params || { bubbles: false, cancelable: false, detail: undefined };
var evt = document.createEvent( 'CustomEvent' );
evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
return evt;
}
CustomEvent.prototype = window.Event.prototype;
window.CustomEvent = CustomEvent;
})();
/*
* JavaScript Load Image
* https://github.com/blueimp/JavaScript-Load-Image
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*global define, module, window, document, URL, webkitURL, FileReader */
// Loads an image for a given File object.
// Invokes the callback with an img or optional canvas
// element (if supported by the browser) as parameter:
var loadImage = function (file, callback, options) {
var img = document.createElement('img')
var url
var oUrl
img.onerror = callback
img.onload = function () {
if (oUrl && !(options && options.noRevoke)) {
loadImage.revokeObjectURL(oUrl)
}
if (callback) {
callback(loadImage.scale(img, options))
}
}
if (loadImage.isInstanceOf('Blob', file) ||
// Files are also Blob instances, but some browsers
// (Firefox 3.6) support the File API but not Blobs:
loadImage.isInstanceOf('File', file)) {
url = oUrl = loadImage.createObjectURL(file)
// Store the file type for resize processing:
img._type = file.type
} else if (typeof file === 'string') {
url = file
if (options && options.crossOrigin) {
img.crossOrigin = options.crossOrigin
}
} else {
return false
}
if (url) {
img.src = url
return img
}
return loadImage.readFile(file, function (e) {
var target = e.target
if (target && target.result) {
img.src = target.result
} else {
if (callback) {
callback(e)
}
}
})
}
// The check for URL.revokeObjectURL fixes an issue with Opera 12,
// which provides URL.createObjectURL but doesn't properly implement it:
var urlAPI = (window.createObjectURL && window) ||
(window.URL && URL.revokeObjectURL && URL) ||
(window.webkitURL && webkitURL)
loadImage.isInstanceOf = function (type, obj) {
// Cross-frame instanceof check
return Object.prototype.toString.call(obj) === '[object ' + type + ']'
}
// Transform image coordinates, allows to override e.g.
// the canvas orientation based on the orientation option,
// gets canvas, options passed as arguments:
loadImage.transformCoordinates = function () {
return
}
// Returns transformed options, allows to override e.g.
// maxWidth, maxHeight and crop options based on the aspectRatio.
// gets img, options passed as arguments:
loadImage.getTransformedOptions = function (img, options) {
var aspectRatio = options.aspectRatio
var newOptions
var i
var width
var height
if (!aspectRatio) {
return options
}
newOptions = {}
for (i in options) {
if (options.hasOwnProperty(i)) {
newOptions[i] = options[i]
}
}
newOptions.crop = true
width = img.naturalWidth || img.width
height = img.naturalHeight || img.height
if (width / height > aspectRatio) {
newOptions.maxWidth = height * aspectRatio
newOptions.maxHeight = height
} else {
newOptions.maxWidth = width
newOptions.maxHeight = width / aspectRatio
}
return newOptions
}
// Canvas render method, allows to implement a different rendering algorithm:
loadImage.renderImageToCanvas = function (
canvas,
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
destX,
destY,
destWidth,
destHeight
) {
canvas.getContext('2d').drawImage(
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
destX,
destY,
destWidth,
destHeight
)
return canvas
}
// This method is used to determine if the target image
// should be a canvas element:
loadImage.hasCanvasOption = function (options) {
return options.canvas || options.crop || !!options.aspectRatio
}
// Scales and/or crops the given image (img or canvas HTML element)
// using the given options.
// Returns a canvas object if the browser supports canvas
// and the hasCanvasOption method returns true or a canvas
// object is passed as image, else the scaled image:
loadImage.scale = function (img, options) {
options = options || {}
var canvas = document.createElement('canvas')
var useCanvas = img.getContext ||
(loadImage.hasCanvasOption(options) && canvas.getContext)
var width = img.naturalWidth || img.width
var height = img.naturalHeight || img.height
var destWidth = width
var destHeight = height
var maxWidth
var maxHeight
var minWidth
var minHeight
var sourceWidth
var sourceHeight
var sourceX
var sourceY
var pixelRatio
var downsamplingRatio
var tmp
function scaleUp () {
var scale = Math.max(
(minWidth || destWidth) / destWidth,
(minHeight || destHeight) / destHeight
)
if (scale > 1) {
destWidth *= scale
destHeight *= scale
}
}
function scaleDown () {
var scale = Math.min(
(maxWidth || destWidth) / destWidth,
(maxHeight || destHeight) / destHeight
)
if (scale < 1) {
destWidth *= scale
destHeight *= scale
}
}
if (useCanvas) {
options = loadImage.getTransformedOptions(img, options)
sourceX = options.left || 0
sourceY = options.top || 0
if (options.sourceWidth) {
sourceWidth = options.sourceWidth
if (options.right !== undefined && options.left === undefined) {
sourceX = width - sourceWidth - options.right
}
} else {
sourceWidth = width - sourceX - (options.right || 0)
}
if (options.sourceHeight) {
sourceHeight = options.sourceHeight
if (options.bottom !== undefined && options.top === undefined) {
sourceY = height - sourceHeight - options.bottom
}
} else {
sourceHeight = height - sourceY - (options.bottom || 0)
}
destWidth = sourceWidth
destHeight = sourceHeight
}
maxWidth = options.maxWidth
maxHeight = options.maxHeight
minWidth = options.minWidth
minHeight = options.minHeight
if (useCanvas && maxWidth && maxHeight && options.crop) {
destWidth = maxWidth
destHeight = maxHeight
tmp = sourceWidth / sourceHeight - maxWidth / maxHeight
if (tmp < 0) {
sourceHeight = maxHeight * sourceWidth / maxWidth
if (options.top === undefined && options.bottom === undefined) {
sourceY = (height - sourceHeight) / 2
}
} else if (tmp > 0) {
sourceWidth = maxWidth * sourceHeight / maxHeight
if (options.left === undefined && options.right === undefined) {
sourceX = (width - sourceWidth) / 2
}
}
} else {
if (options.contain || options.cover) {
minWidth = maxWidth = maxWidth || minWidth
minHeight = maxHeight = maxHeight || minHeight
}
if (options.cover) {
scaleDown()
scaleUp()
} else {
scaleUp()
scaleDown()
}
}
if (useCanvas) {
pixelRatio = options.pixelRatio
if (pixelRatio > 1) {
canvas.style.width = destWidth + 'px'
canvas.style.height = destHeight + 'px'
destWidth *= pixelRatio
destHeight *= pixelRatio
canvas.getContext('2d').scale(pixelRatio, pixelRatio)
}
downsamplingRatio = options.downsamplingRatio
if (downsamplingRatio > 0 && downsamplingRatio < 1 &&
destWidth < sourceWidth && destHeight < sourceHeight) {
while (sourceWidth * downsamplingRatio > destWidth) {
canvas.width = sourceWidth * downsamplingRatio
canvas.height = sourceHeight * downsamplingRatio
loadImage.renderImageToCanvas(
canvas,
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
0,
0,
canvas.width,
canvas.height
)
sourceWidth = canvas.width
sourceHeight = canvas.height
img = document.createElement('canvas')
img.width = sourceWidth
img.height = sourceHeight
loadImage.renderImageToCanvas(
img,
canvas,
0,
0,
sourceWidth,
sourceHeight,
0,
0,
sourceWidth,
sourceHeight
)
}
}
canvas.width = destWidth
canvas.height = destHeight
loadImage.transformCoordinates(
canvas,
options
)
return loadImage.renderImageToCanvas(
canvas,
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
0,
0,
destWidth,
destHeight
)
}
img.width = destWidth
img.height = destHeight
return img
}
loadImage.createObjectURL = function (file) {
return urlAPI ? urlAPI.createObjectURL(file) : false
}
loadImage.revokeObjectURL = function (url) {
return urlAPI ? urlAPI.revokeObjectURL(url) : false
}
// Loads a given File object via FileReader interface,
// invokes the callback with the event object (load or error).
// The result can be read via event.target.result:
loadImage.readFile = function (file, callback, method) {
if (window.FileReader) {
var fileReader = new FileReader()
fileReader.onload = fileReader.onerror = callback
method = method || 'readAsDataURL'
if (fileReader[method]) {
fileReader[method](file)
return fileReader
}
}
return false
}
var originalHasCanvasOption = loadImage.hasCanvasOption
var originalTransformCoordinates = loadImage.transformCoordinates
var originalGetTransformedOptions = loadImage.getTransformedOptions
// This method is used to determine if the target image
// should be a canvas element:
loadImage.hasCanvasOption = function (options) {
return !!options.orientation ||
originalHasCanvasOption.call(loadImage, options)
}
// Transform image orientation based on
// the given EXIF orientation option:
loadImage.transformCoordinates = function (canvas, options) {
originalTransformCoordinates.call(loadImage, canvas, options)
var ctx = canvas.getContext('2d')
var width = canvas.width
var height = canvas.height
var styleWidth = canvas.style.width
var styleHeight = canvas.style.height
var orientation = options.orientation
if (!orientation || orientation > 8) {
return
}
if (orientation > 4) {
canvas.width = height
canvas.height = width
canvas.style.width = styleHeight
canvas.style.height = styleWidth
}
switch (orientation) {
case 2:
// horizontal flip
ctx.translate(width, 0)
ctx.scale(-1, 1)
break
case 3:
// 180° rotate left
ctx.translate(width, height)
ctx.rotate(Math.PI)
break
case 4:
// vertical flip
ctx.translate(0, height)
ctx.scale(1, -1)
break
case 5:
// vertical flip + 90 rotate right
ctx.rotate(0.5 * Math.PI)
ctx.scale(1, -1)
break
case 6:
// 90° rotate right
ctx.rotate(0.5 * Math.PI)
ctx.translate(0, -height)
break
case 7:
// horizontal flip + 90 rotate right
ctx.rotate(0.5 * Math.PI)
ctx.translate(width, -height)
ctx.scale(-1, 1)
break
case 8:
// 90° rotate left
ctx.rotate(-0.5 * Math.PI)
ctx.translate(-width, 0)
break
}
}
// Transforms coordinate and dimension options
// based on the given orientation option:
loadImage.getTransformedOptions = function (img, opts) {
var options = originalGetTransformedOptions.call(loadImage, img, opts)
var orientation = options.orientation
var newOptions
var i
if (!orientation || orientation > 8 || orientation === 1) {
return options
}
newOptions = {}
for (i in options) {
if (options.hasOwnProperty(i)) {
newOptions[i] = options[i]
}
}
switch (options.orientation) {
case 2:
// horizontal flip
newOptions.left = options.right
newOptions.right = options.left
break
case 3:
// 180° rotate left
newOptions.left = options.right
newOptions.top = options.bottom
newOptions.right = options.left
newOptions.bottom = options.top
break
case 4:
// vertical flip
newOptions.top = options.bottom
newOptions.bottom = options.top
break
case 5:
// vertical flip + 90 rotate right
newOptions.left = options.top
newOptions.top = options.left
newOptions.right = options.bottom
newOptions.bottom = options.right
break
case 6:
// 90° rotate right
newOptions.left = options.top
newOptions.top = options.right
newOptions.right = options.bottom
newOptions.bottom = options.left
break
case 7:
// horizontal flip + 90 rotate right
newOptions.left = options.bottom
newOptions.top = options.right
newOptions.right = options.top
newOptions.bottom = options.left
break
case 8:
// 90° rotate left
newOptions.left = options.bottom
newOptions.top = options.left
newOptions.right = options.top
newOptions.bottom = options.right
break
}
if (options.orientation > 4) {
newOptions.maxWidth = options.maxHeight
newOptions.maxHeight = options.maxWidth
newOptions.minWidth = options.minHeight
newOptions.minHeight = options.minWidth
newOptions.sourceWidth = options.sourceHeight
newOptions.sourceHeight = options.sourceWidth
}
return newOptions
}
var hasblobSlice = window.Blob && (Blob.prototype.slice ||
Blob.prototype.webkitSlice || Blob.prototype.mozSlice)
loadImage.blobSlice = hasblobSlice && function () {
var slice = this.slice || this.webkitSlice || this.mozSlice
return slice.apply(this, arguments)
}
loadImage.metaDataParsers = {
jpeg: {
0xffe1: [] // APP1 marker
}
}
// Parses image meta data and calls the callback with an object argument
// with the following properties:
// * imageHead: The complete image head as ArrayBuffer (Uint8Array for IE10)
// The options arguments accepts an object and supports the following properties:
// * maxMetaDataSize: Defines the maximum number of bytes to parse.
// * disableImageHead: Disables creating the imageHead property.
loadImage.parseMetaData = function (file, callback, options) {
options = options || {}
var that = this
// 256 KiB should contain all EXIF/ICC/IPTC segments:
var maxMetaDataSize = options.maxMetaDataSize || 262144
var data = {}
var noMetaData = !(window.DataView && file && file.size >= 12 &&
file.type === 'image/jpeg' && loadImage.blobSlice)
if (noMetaData || !loadImage.readFile(
loadImage.blobSlice.call(file, 0, maxMetaDataSize),
function (e) {
if (e.target.error) {
// FileReader error
//console.log(e.target.error)
callback(data)
return
}
// Note on endianness:
// Since the marker and length bytes in JPEG files are always
// stored in big endian order, we can leave the endian parameter
// of the DataView methods undefined, defaulting to big endian.
var buffer = e.target.result
var dataView = new DataView(buffer)
var offset = 2
var maxOffset = dataView.byteLength - 4
var headLength = offset
var markerBytes
var markerLength
var parsers
var i
// Check for the JPEG marker (0xffd8):
if (dataView.getUint16(0) === 0xffd8) {
while (offset < maxOffset) {
markerBytes = dataView.getUint16(offset)
// Search for APPn (0xffeN) and COM (0xfffe) markers,
// which contain application-specific meta-data like
// Exif, ICC and IPTC data and text comments:
if ((markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
markerBytes === 0xfffe) {
// The marker bytes (2) are always followed by
// the length bytes (2), indicating the length of the
// marker segment, which includes the length bytes,
// but not the marker bytes, so we add 2:
markerLength = dataView.getUint16(offset + 2) + 2
if (offset + markerLength > dataView.byteLength) {
//console.log('Invalid meta data: Invalid segment size.')
break
}
parsers = loadImage.metaDataParsers.jpeg[markerBytes]
if (parsers) {
for (i = 0; i < parsers.length; i += 1) {
parsers[i].call(
that,
dataView,
offset,
markerLength,
data,
options
)
}
}
offset += markerLength
headLength = offset
} else {
// Not an APPn or COM marker, probably safe to
// assume that this is the end of the meta data
break
}
}
// Meta length must be longer than JPEG marker (2)
// plus APPn marker (2), followed by length bytes (2):
if (!options.disableImageHead && headLength > 6) {
if (buffer.slice) {
data.imageHead = buffer.slice(0, headLength)
} else {
// Workaround for IE10, which does not yet
// support ArrayBuffer.slice:
data.imageHead = new Uint8Array(buffer)
.subarray(0, headLength)
}
}
} else {
//console.log('Invalid JPEG file: Missing JPEG marker.')
}
callback(data)
},
'readAsArrayBuffer'
)) {
callback(data)
}
}
loadImage.ExifMap = function () {
return this
}
loadImage.ExifMap.prototype.map = {
'Orientation': 0x0112
}
loadImage.ExifMap.prototype.get = function (id) {
return this[id] || this[this.map[id]]
}
loadImage.getExifThumbnail = function (dataView, offset, length) {
var hexData,
i,
b
if (!length || offset + length > dataView.byteLength) {
//console.log('Invalid Exif data: Invalid thumbnail data.')
return
}
hexData = []
for (i = 0; i < length; i += 1) {
b = dataView.getUint8(offset + i)
hexData.push((b < 16 ? '0' : '') + b.toString(16))
}
return 'data:image/jpeg,%' + hexData.join('%')
}
loadImage.exifTagTypes = {
// byte, 8-bit unsigned int:
1: {
getValue: function (dataView, dataOffset) {
return dataView.getUint8(dataOffset)
},
size: 1
},
// ascii, 8-bit byte:
2: {
getValue: function (dataView, dataOffset) {
return String.fromCharCode(dataView.getUint8(dataOffset))
},
size: 1,
ascii: true
},
// short, 16 bit int:
3: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getUint16(dataOffset, littleEndian)
},
size: 2
},
// long, 32 bit int:
4: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getUint32(dataOffset, littleEndian)
},
size: 4
},
// rational = two long values, first is numerator, second is denominator:
5: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getUint32(dataOffset, littleEndian) /
dataView.getUint32(dataOffset + 4, littleEndian)
},
size: 8
},
// slong, 32 bit signed int:
9: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getInt32(dataOffset, littleEndian)
},
size: 4
},
// srational, two slongs, first is numerator, second is denominator:
10: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getInt32(dataOffset, littleEndian) /
dataView.getInt32(dataOffset + 4, littleEndian)
},
size: 8
}
}
// undefined, 8-bit byte, value depending on field:
loadImage.exifTagTypes[7] = loadImage.exifTagTypes[1]
loadImage.getExifValue = function (dataView, tiffOffset, offset, type, length, littleEndian) {
var tagType = loadImage.exifTagTypes[type]
var tagSize
var dataOffset
var values
var i
var str
var c
if (!tagType) {
//console.log('Invalid Exif data: Invalid tag type.')
return
}
tagSize = tagType.size * length
// Determine if the value is contained in the dataOffset bytes,
// or if the value at the dataOffset is a pointer to the actual data:
dataOffset = tagSize > 4
? tiffOffset + dataView.getUint32(offset + 8, littleEndian)
: (offset + 8)
if (dataOffset + tagSize > dataView.byteLength) {
//console.log('Invalid Exif data: Invalid data offset.')
return
}
if (length === 1) {
return tagType.getValue(dataView, dataOffset, littleEndian)
}
values = []
for (i = 0; i < length; i += 1) {
values[i] = tagType.getValue(dataView, dataOffset + i * tagType.size, littleEndian)
}
if (tagType.ascii) {
str = ''
// Concatenate the chars:
for (i = 0; i < values.length; i += 1) {
c = values[i]
// Ignore the terminating NULL byte(s):
if (c === '\u0000') {
break
}
str += c
}
return str
}
return values
}
loadImage.parseExifTag = function (dataView, tiffOffset, offset, littleEndian, data) {
var tag = dataView.getUint16(offset, littleEndian)
data.exif[tag] = loadImage.getExifValue(
dataView,
tiffOffset,
offset,
dataView.getUint16(offset + 2, littleEndian), // tag type
dataView.getUint32(offset + 4, littleEndian), // tag length
littleEndian
)
}
loadImage.parseExifTags = function (dataView, tiffOffset, dirOffset, littleEndian, data) {
var tagsNumber,
dirEndOffset,
i
if (dirOffset + 6 > dataView.byteLength) {
//console.log('Invalid Exif data: Invalid directory offset.')
return
}
tagsNumber = dataView.getUint16(dirOffset, littleEndian)
dirEndOffset = dirOffset + 2 + 12 * tagsNumber
if (dirEndOffset + 4 > dataView.byteLength) {
//console.log('Invalid Exif data: Invalid directory size.')
return
}
for (i = 0; i < tagsNumber; i += 1) {
this.parseExifTag(
dataView,
tiffOffset,
dirOffset + 2 + 12 * i, // tag offset
littleEndian,
data
)
}
// Return the offset to the next directory:
return dataView.getUint32(dirEndOffset, littleEndian)
}
loadImage.parseExifData = function (dataView, offset, length, data, options) {
if (options.disableExif) {
return
}
var tiffOffset = offset + 10
var littleEndian
var dirOffset
var thumbnailData
// Check for the ASCII code for "Exif" (0x45786966):
if (dataView.getUint32(offset + 4) !== 0x45786966) {
// No Exif data, might be XMP data instead
return
}
if (tiffOffset + 8 > dataView.byteLength) {
//console.log('Invalid Exif data: Invalid segment size.')
return
}
// Check for the two null bytes:
if (dataView.getUint16(offset + 8) !== 0x0000) {
//console.log('Invalid Exif data: Missing byte alignment offset.')
return
}
// Check the byte alignment:
switch (dataView.getUint16(tiffOffset)) {
case 0x4949:
littleEndian = true
break
case 0x4D4D:
littleEndian = false
break
default:
//console.log('Invalid Exif data: Invalid byte alignment marker.')
return
}
// Check for the TIFF tag marker (0x002A):
if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002A) {
//console.log('Invalid Exif data: Missing TIFF marker.')
return
}
// Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian)
// Create the exif object to store the tags:
data.exif = new loadImage.ExifMap()
// Parse the tags of the main image directory and retrieve the
// offset to the next directory, usually the thumbnail directory:
dirOffset = loadImage.parseExifTags(
dataView,
tiffOffset,
tiffOffset + dirOffset,
littleEndian,
data
)
if (dirOffset && !options.disableExifThumbnail) {
thumbnailData = {exif: {}}
dirOffset = loadImage.parseExifTags(
dataView,
tiffOffset,
tiffOffset + dirOffset,
littleEndian,
thumbnailData
)
// Check for JPEG Thumbnail offset:
if (thumbnailData.exif[0x0201]) {
data.exif.Thumbnail = loadImage.getExifThumbnail(
dataView,
tiffOffset + thumbnailData.exif[0x0201],
thumbnailData.exif[0x0202] // Thumbnail data length
)
}
}
// Check for Exif Sub IFD Pointer:
if (data.exif[0x8769] && !options.disableExifSub) {
loadImage.parseExifTags(
dataView,
tiffOffset,
tiffOffset + data.exif[0x8769], // directory offset
littleEndian,
data
)
}
// Check for GPS Info IFD Pointer:
if (data.exif[0x8825] && !options.disableExifGps) {
loadImage.parseExifTags(
dataView,
tiffOffset,
tiffOffset + data.exif[0x8825], // directory offset
littleEndian,
data
)
}
}
// Registers the Exif parser for the APP1 JPEG meta data segment:
loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData)
var snabbt = (function() {
var tickRequests = [];
var runningAnimations = [];
var completedAnimations = [];
var transformProperty = 'transform';
// Find which vendor prefix to use
var styles = window.getComputedStyle(document.documentElement, '');
var vendorPrefix = (Array.prototype.slice
.call(styles)
.join('')
.match(/-(moz|webkit|ms)-/) || (styles.OLink === '' && ['', 'o'])
)[1];
if(vendorPrefix === 'webkit')
transformProperty = 'webkitTransform';
/* Entry point, only function to be called by user */
var snabbt = function(arg1, arg2, arg3) {
var elements = arg1;
// If argument is an Array or a NodeList or other list type that can be iterable.
// Loop through and start one animation for each element.
if(elements.length !== undefined) {
var aggregateChainer = {
chainers: [],
then: function(opts) {
return this.snabbt(opts);
},
snabbt: function(opts) {
var len = this.chainers.length;
this.chainers.forEach(function(chainer, index) {
chainer.snabbt(preprocessOptions(opts, index, len));
});
return aggregateChainer;
},
setValue: function(value) {
this.chainers.forEach(function(chainer) {
chainer.setValue(value);
});
return aggregateChainer;
},
finish: function() {
this.chainers.forEach(function(chainer) {
chainer.finish();
});
return aggregateChainer;
},
rollback: function() {
this.chainers.forEach(function(chainer) {
chainer.rollback();
});
return aggregateChainer;
}
};
for(var i=0, len=elements.length;i<len;++i) {
if(typeof arg2 == 'string')
aggregateChainer.chainers.push(snabbtSingleElement(elements[i], arg2, preprocessOptions(arg3, i, len)));
else
aggregateChainer.chainers.push(snabbtSingleElement(elements[i], preprocessOptions(arg2, i, len), arg3));
}
return aggregateChainer;
} else {
if(typeof arg2 == 'string')
return snabbtSingleElement(elements, arg2, preprocessOptions(arg3, 0, 1));
else
return snabbtSingleElement(elements, preprocessOptions(arg2, 0, 1), arg3);
}
};
var preprocessOptions = function(options, index, len) {
if(!options)
return options;
var clone = cloneObject(options);
if(isFunction(options.delay)) {
clone.delay = options.delay(index, len);
}
if(isFunction(options.callback)) {
clone.complete = function() {
options.callback.call(this, index, len);
};
}
var hasAllDoneCallback = isFunction(options.allDone);
var hasCompleteCallback = isFunction(options.complete);
if(hasCompleteCallback || hasAllDoneCallback) {
clone.complete = function() {
if(hasCompleteCallback) {
options.complete.call(this, index, len);
}
if(hasAllDoneCallback && (index == len - 1)) {
options.allDone();
}
};
}
if(isFunction(options.valueFeeder)) {
clone.valueFeeder = function(i, matrix) {
return options.valueFeeder(i, matrix, index, len);
};
}
if(isFunction(options.easing)) {
clone.easing = function(i) {
return options.easing(i, index, len);
};
}
var properties = [
'position',
'rotation',
'skew',
'rotationPost',
'scale',
'width',
'height',
'opacity',
'fromPosition',
'fromRotation',
'fromSkew',
'fromRotationPost',
'fromScale',
'fromWidth',
'fromHeight',
'fromOpacity',
'transformOrigin',
'duration',
'delay'
];
properties.forEach(function(property) {
if(isFunction(options[property])) {
clone[property] = options[property](index, len);
}
});
return clone;
};
var snabbtSingleElement = function(element, arg2, arg3) {
if(arg2 === 'attention') {
return setupAttentionAnimation(element, arg3);
}
if(arg2 === 'stop') {
return stopAnimation(element);
}
if(arg2 === 'detach') {
return detachChildren(element);
}
var options = arg2;
// Remove orphaned end states
clearOphanedEndStates();
// If there is a running or past completed animation with element, use that end state as start state
var currentState = currentAnimationState(element);
var start = currentState;
// from has precendance over current animation state
start = stateFromOptions(options, start, true);
var end = cloneObject(currentState);
end = stateFromOptions(options, end);
var animOptions = setupAnimationOptions(start, end, options);
var animation = createAnimation(animOptions);
runningAnimations.push([element, animation]);
animation.updateElement(element, true);
var queue = [];
var chainer = {
snabbt: function(opts) {
queue.unshift(preprocessOptions(opts, 0, 1));
return chainer;
},
then: function(opts) {
return this.snabbt(opts);
}
};
function tick(time) {
animation.tick(time);
animation.updateElement(element);
if(animation.isStopped())
return;
if(!animation.completed())
return queueTick(tick);
if(options.loop > 1 && !animation.isStopped()) {
// Loop current animation
options.loop -= 1;
animation.restart();
queueTick(tick);
} else {
if(options.complete) {
options.complete.call(element);
}
// Start next animation in queue
if(queue.length) {
options = queue.pop();
start = stateFromOptions(options, end, true);
end = stateFromOptions(options, cloneObject(end));
options = setupAnimationOptions(start, end, options);
animation = createAnimation(options);
runningAnimations.push([element, animation]);
animation.tick(time);
queueTick(tick);
}
}
}
queueTick(tick);
// Manual animations are not chainable, instead an animation controller object is returned
// with setValue, finish and rollback methods
if(options.manual)
return animation;
return chainer;
};
var setupAttentionAnimation = function(element, options) {
var movement = stateFromOptions(options, createState({}));
options.movement = movement;
var animation = createAttentionAnimation(options);
runningAnimations.push([element, animation]);
function tick(time) {
animation.tick(time);
animation.updateElement(element);
if(!animation.completed()) {
queueTick(tick);
} else {
if(options.callback) {
options.callback(element);
}
if(options.loop && options.loop > 1) {
options.loop--;
animation.restart();
queueTick(tick);
}
}
}
queueTick(tick);
};
var stopAnimation = function(element) {
for(var i= 0,len=runningAnimations.length;i<len;++i) {
var currentAnimation = runningAnimations[i];
var animatedElement = currentAnimation[0];
var animation = currentAnimation[1];
if(animatedElement === element) {
animation.stop();
}
}
};
var indexOfElement = function(arr, element) {
for(var i=0,len=arr.length;i<len;++i) {
if (arr[i][0]===element) {
return i;
}
}
return -1;
};
var detachChildren = function(element) {
var elements = [];
var animations = runningAnimations.concat(completedAnimations);
var el;
var i;
var len = animations.length;
for(i=0;i<len;++i) {
el = animations[i][0];
if (element.contains(el) || element === el) {
elements.push(el);
}
}
len=elements.length;
for(i=0;i<len;++i) {
detachElement(elements[i]);
}
};
var detachElement = function(element) {
// stop animations
stopAnimation(element);
// remove
var index = indexOfElement(runningAnimations, element);
if (index >= 0) {
runningAnimations.splice(index,1);
}
index = indexOfElement(completedAnimations, element);
if (index >= 0) {
completedAnimations.splice(index,1);
}
};
var findAnimationState = function(animationList, element) {
for(var i=0,len=animationList.length;i<len;++i) {
var currentAnimation = animationList[i];
var animatedElement = currentAnimation[0];
var animation = currentAnimation[1];
if(animatedElement === element) {
var state = animation.getCurrentState();
animation.stop();
return state;
}
}
};
var clearOphanedEndStates = function() {
completedAnimations = completedAnimations.filter(function(animation) {
return (findUltimateAncestor(animation[0]).body);
});
};
var findUltimateAncestor = function(node) {
var ancestor = node;
while(ancestor.parentNode) {
ancestor = ancestor.parentNode;
}
return ancestor;
};
/**
* Returns the current state of element if there is an ongoing or previously finished
* animation releated to it. Will also call stop on the animation.
* TODO: The stopping of the animation is better put somewhere else
*/
var currentAnimationState = function(element) {
// Check if a completed animation is stored for this element
var state = findAnimationState(runningAnimations, element);
if(state)
return state;
return findAnimationState(completedAnimations, element);
};
/**
* Parses an animation configuration object and returns a State instance
*/
var stateFromOptions = function(options, state, useFromPrefix) {
if (!state) {
state = createState({
position: [0, 0, 0],
rotation: [0, 0, 0],
rotationPost: [0, 0, 0],
scale: [1, 1],
skew: [0, 0]
});
}
var position = 'position';
var rotation = 'rotation';
var skew = 'skew';
var rotationPost = 'rotationPost';
var scale = 'scale';
var scalePost = 'scalePost';
var width = 'width';
var height = 'height';
var opacity = 'opacity';
if(useFromPrefix) {
position = 'fromPosition';
rotation = 'fromRotation';
skew = 'fromSkew';
rotationPost = 'fromRotationPost';
scale = 'fromScale';
scalePost = 'fromScalePost';
width = 'fromWidth';
height = 'fromHeight';
opacity = 'fromOpacity';
}
state.position = optionOrDefault(options[position], state.position);
state.rotation = optionOrDefault(options[rotation], state.rotation);
state.rotationPost = optionOrDefault(options[rotationPost], state.rotationPost);
state.skew = optionOrDefault(options[skew], state.skew);
state.scale = optionOrDefault(options[scale], state.scale);
state.scalePost = optionOrDefault(options[scalePost], state.scalePost);
state.opacity = options[opacity];
state.width = options[width];
state.height = options[height];
return state;
};
var setupAnimationOptions = function(start, end, options) {
options.startState = start;
options.endState = end;
return options;
};
var polyFillrAF = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { return setTimeout(callback, 1000 / 60); };
var queueTick = function(func) {
if(tickRequests.length === 0)
polyFillrAF(tickAnimations);
tickRequests.push(func);
};
var tickAnimations = function(time) {
var len = tickRequests.length;
for(var i=0;i<len;++i) {
tickRequests[i](time);
}
tickRequests.splice(0, len);
var finishedAnimations = runningAnimations.filter(function(animation) {
return animation[1].completed();
});
// See if there are any previously completed animations on the same element, if so, remove it before merging
completedAnimations = completedAnimations.filter(function(animation) {
for(var i=0,len=finishedAnimations.length;i<len;++i) {
if(animation[0] === finishedAnimations[i][0]) {
return false;
}
}
return true;
});
completedAnimations = completedAnimations.concat(finishedAnimations);
runningAnimations = runningAnimations.filter(function(animation) {
return !animation[1].completed();
});
if(tickRequests.length !== 0)
polyFillrAF(tickAnimations);
};
// Class for handling animation between two states
var createAnimation = function(options) {
var startState = options.startState;
var endState = options.endState;
var duration = optionOrDefault(options.duration, 500);
var delay = optionOrDefault(options.delay, 0);
var perspective = options.perspective;
var easing = createEaser(optionOrDefault(options.easing, 'linear'), options);
var currentState = duration === 0 ? endState.clone() : startState.clone();
var transformOrigin = options.transformOrigin;
currentState.transformOrigin = options.transformOrigin;
var startTime = 0;
var currentTime = 0;
var stopped = false;
var started = false;
// Manual related
var manual = options.manual;
var manualValue = 0;
var manualDelayFactor = delay / duration;
var manualCallback;
var tweener;
// Setup tweener
if(options.valueFeeder) {
tweener = createValueFeederTweener(options.valueFeeder,
startState,
endState,
currentState);
} else {
tweener = createStateTweener(startState, endState, currentState);
}
// Public api
return {
stop: function() {
stopped = true;
},
isStopped: function() {
return stopped;
},
finish: function(callback) {
manual = false;
var manualDuration = duration * manualValue;
startTime = currentTime - manualDuration;
manualCallback = callback;
easing.resetFrom = manualValue;
},
rollback: function(callback) {
manual = false;
tweener.setReverse();
var manualDuration = duration * (1 - manualValue);
startTime = currentTime - manualDuration;
manualCallback = callback;
easing.resetFrom = manualValue;
},
restart: function() {
// Restart timer
startTime = undefined;
easing.resetFrom(0);
},
tick: function(time) {
if(stopped)
return;
if(manual) {
currentTime = time;
this.updateCurrentTransform();
return;
}
// If first tick, set startTime
if(!startTime) {
startTime = time;
}
if(time - startTime > delay) {
started = true;
currentTime = time - delay;
var curr = Math.min(Math.max(0.0, currentTime - startTime), duration);
easing.tick(curr / duration);
this.updateCurrentTransform();
if(this.completed() && manualCallback) {
manualCallback();
}
}
},
getCurrentState: function() {
return currentState;
},
setValue: function(_manualValue) {
started = true;
manualValue = Math.min(Math.max(_manualValue, 0.0001), 1 + manualDelayFactor);
},
updateCurrentTransform: function() {
var tweenValue = easing.getValue();
if(manual) {
var val = Math.max(0.00001, manualValue - manualDelayFactor);
easing.tick(val);
tweenValue = easing.getValue();
}
tweener.tween(tweenValue);
},
completed: function() {
if(stopped)
return true;
if(startTime === 0) {
return false;
}
return easing.completed();
},
updateElement: function(element, forceUpdate) {
if(!started && !forceUpdate)
return;
var matrix = tweener.asMatrix();
var properties = tweener.getProperties();
updateElementTransform(element, matrix, perspective);
updateElementProperties(element, properties);
}
};
};
// ------------------------------
// End Time animation
// ------------------------------
// ------------------------
// -- AttentionAnimation --
// ------------------------
var createAttentionAnimation = function(options) {
var movement = options.movement;
options.initialVelocity = 0.1;
options.equilibriumPosition = 0;
var spring = createSpringEasing(options);
var stopped = false;
var tweenPosition = movement.position;
var tweenRotation = movement.rotation;
var tweenRotationPost = movement.rotationPost;
var tweenScale = movement.scale;
var tweenSkew = movement.skew;
var currentMovement = createState({
position: tweenPosition ? [0, 0, 0] : undefined,
rotation: tweenRotation ? [0, 0, 0] : undefined,
rotationPost: tweenRotationPost ? [0, 0, 0] : undefined,
scale: tweenScale ? [0, 0] : undefined,
skew: tweenSkew ? [0, 0] : undefined,
});
// Public API
return {
stop: function() {
stopped = true;
},
isStopped: function(time) {
return stopped;
},
tick: function(time) {
if(stopped)
return;
if(spring.equilibrium)
return;
spring.tick();
this.updateMovement();
},
updateMovement:function() {
var value = spring.getValue();
if(tweenPosition) {
currentMovement.position[0] = movement.position[0] * value;
currentMovement.position[1] = movement.position[1] * value;
currentMovement.position[2] = movement.position[2] * value;
}
if(tweenRotation) {
currentMovement.rotation[0] = movement.rotation[0] * value;
currentMovement.rotation[1] = movement.rotation[1] * value;
currentMovement.rotation[2] = movement.rotation[2] * value;
}
if(tweenRotationPost) {
currentMovement.rotationPost[0] = movement.rotationPost[0] * value;
currentMovement.rotationPost[1] = movement.rotationPost[1] * value;
currentMovement.rotationPost[2] = movement.rotationPost[2] * value;
}
if(tweenScale) {
currentMovement.scale[0] = 1 + movement.scale[0] * value;
currentMovement.scale[1] = 1 + movement.scale[1] * value;
}
if(tweenSkew) {
currentMovement.skew[0] = movement.skew[0] * value;
currentMovement.skew[1] = movement.skew[1] * value;
}
},
updateElement: function(element) {
updateElementTransform(element, currentMovement.asMatrix());
updateElementProperties(element, currentMovement.getProperties());
},
getCurrentState: function() {
return currentMovement;
},
completed: function() {
return spring.equilibrium || stopped;
},
restart: function() {
// Restart spring
spring = createSpringEasing(options);
}
};
};
/**********
* Easings *
***********/
var linearEasing = function(value) {
return value;
};
var ease = function(value) {
return (Math.cos(value*Math.PI + Math.PI) + 1)/2;
};
var easeIn = function(value) {
return value*value;
};
var easeOut = function(value) {
return -Math.pow(value - 1, 2) + 1;
};
var createSpringEasing = function(options) {
var position = optionOrDefault(options.startPosition, 0);
var equilibriumPosition = optionOrDefault(options.equilibriumPosition, 1);
var velocity = optionOrDefault(options.initialVelocity, 0);
var springConstant = optionOrDefault(options.springConstant, 0.8);
var deceleration = optionOrDefault(options.springDeceleration, 0.9);
var mass = optionOrDefault(options.springMass, 10);
var equilibrium = false;
// Public API
return {
tick: function(value) {
if(value === 0.0)
return;
if(equilibrium)
return;
var springForce = -(position - equilibriumPosition) * springConstant;
// f = m * a
// a = f / m
var a = springForce / mass;
// s = v * t
// t = 1 ( for now )
velocity += a;
position += velocity;
// Deceleration
velocity *= deceleration;
if(Math.abs(position - equilibriumPosition) < 0.001 && Math.abs(velocity) < 0.001) {
equilibrium = true;
}
},
resetFrom: function(value) {
position = value;
velocity = 0;
},
getValue: function() {
if(equilibrium)
return equilibriumPosition;
return position;
},
completed: function() {
return equilibrium;
}
};
};
var EASING_FUNCS = {
'linear': linearEasing,
'ease': ease,
'easeIn': easeIn,
'easeOut': easeOut,
};
var createEaser = function(easerName, options) {
if(easerName == 'spring') {
return createSpringEasing(options);
}
var easeFunction = easerName;
if(!isFunction(easerName)) {
easeFunction = EASING_FUNCS[easerName];
}
var easer = easeFunction;
var value = 0;
var lastValue;
// Public API
return {
tick: function(v) {
value = easer(v);
lastValue = v;
},
resetFrom: function(value) {
lastValue = 0;
},
getValue: function() {
return value;
},
completed: function() {
if(lastValue >= 1) {
return lastValue;
}
return false;
}
};
};
/***
* Matrix related
*/
var assignTranslate = function(matrix, x, y, z) {
matrix[0] = 1;
matrix[1] = 0;
matrix[2] = 0;
matrix[3] = 0;
matrix[4] = 0;
matrix[5] = 1;
matrix[6] = 0;
matrix[7] = 0;
matrix[8] = 0;
matrix[9] = 0;
matrix[10] = 1;
matrix[11] = 0;
matrix[12] = x;
matrix[13] = y;
matrix[14] = z;
matrix[15] = 1;
};
var assignRotateX = function(matrix, rad) {
matrix[0] = 1;
matrix[1] = 0;
matrix[2] = 0;
matrix[3] = 0;
matrix[4] = 0;
matrix[5] = Math.cos(rad);
matrix[6] = -Math.sin(rad);
matrix[7] = 0;
matrix[8] = 0;
matrix[9] = Math.sin(rad);
matrix[10] = Math.cos(rad);
matrix[11] = 0;
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 0;
matrix[15] = 1;
};
var assignRotateY = function(matrix, rad) {
matrix[0] = Math.cos(rad);
matrix[1] = 0;
matrix[2] = Math.sin(rad);
matrix[3] = 0;
matrix[4] = 0;
matrix[5] = 1;
matrix[6] = 0;
matrix[7] = 0;
matrix[8] = -Math.sin(rad);
matrix[9] = 0;
matrix[10] = Math.cos(rad);
matrix[11] = 0;
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 0;
matrix[15] = 1;
};
var assignRotateZ = function(matrix, rad) {
matrix[0] = Math.cos(rad);
matrix[1] = -Math.sin(rad);
matrix[2] = 0;
matrix[3] = 0;
matrix[4] = Math.sin(rad);
matrix[5] = Math.cos(rad);
matrix[6] = 0;
matrix[7] = 0;
matrix[8] = 0;
matrix[9] = 0;
matrix[10] = 1;
matrix[11] = 0;
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 0;
matrix[15] = 1;
};
var assignSkew = function(matrix, ax, ay) {
matrix[0] = 1;
matrix[1] = Math.tan(ax);
matrix[2] = 0;
matrix[3] = 0;
matrix[4] = Math.tan(ay);
matrix[5] = 1;
matrix[6] = 0;
matrix[7] = 0;
matrix[8] = 0;
matrix[9] = 0;
matrix[10] = 1;
matrix[11] = 0;
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 0;
matrix[15] = 1;
};
var assignScale = function(matrix, x, y) {
matrix[0] = x;
matrix[1] = 0;
matrix[2] = 0;
matrix[3] = 0;
matrix[4] = 0;
matrix[5] = y;
matrix[6] = 0;
matrix[7] = 0;
matrix[8] = 0;
matrix[9] = 0;
matrix[10] = 1;
matrix[11] = 0;
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 0;
matrix[15] = 1;
};
var assignIdentity = function(matrix) {
matrix[0] = 1;
matrix[1] = 0;
matrix[2] = 0;
matrix[3] = 0;
matrix[4] = 0;
matrix[5] = 1;
matrix[6] = 0;
matrix[7] = 0;
matrix[8] = 0;
matrix[9] = 0;
matrix[10] = 1;
matrix[11] = 0;
matrix[12] = 0;
matrix[13] = 0;
matrix[14] = 0;
matrix[15] = 1;
};
var copyArray = function(a, b) {
b[0] = a[0];
b[1] = a[1];
b[2] = a[2];
b[3] = a[3];
b[4] = a[4];
b[5] = a[5];
b[6] = a[6];
b[7] = a[7];
b[8] = a[8];
b[9] = a[9];
b[10] = a[10];
b[11] = a[11];
b[12] = a[12];
b[13] = a[13];
b[14] = a[14];
b[15] = a[15];
};
var createMatrix = function() {
var data = new Float32Array(16);
var a = new Float32Array(16);
var b = new Float32Array(16);
assignIdentity(data);
return {
data: data,
asCSS: function() {
var css = 'matrix3d(';
for(var i=0;i<15;++i) {
if(Math.abs(data[i]) < 0.0001)
css += '0,';
else
css += data[i].toFixed(10) + ',';
}
if(Math.abs(data[15]) < 0.0001)
css += '0)';
else
css += data[15].toFixed(10) + ')';
return css;
},
clear: function() {
assignIdentity(data);
},
translate: function(x, y, z) {
copyArray(data, a);
assignTranslate(b, x, y, z);
assignedMatrixMultiplication(a, b, data);
return this;
},
rotateX: function(radians) {
copyArray(data, a);
assignRotateX(b, radians);
assignedMatrixMultiplication(a, b, data);
return this;
},
rotateY: function(radians) {
copyArray(data, a);
assignRotateY(b, radians);
assignedMatrixMultiplication(a, b, data);
return this;
},
rotateZ: function(radians) {
copyArray(data, a);
assignRotateZ(b, radians);
assignedMatrixMultiplication(a, b, data);
return this;
},
scale: function(x, y) {
copyArray(data, a);
assignScale(b, x, y);
assignedMatrixMultiplication(a, b, data);
return this;
},
skew: function(ax, ay) {
copyArray(data, a);
assignSkew(b, ax, ay);
assignedMatrixMultiplication(a, b, data);
return this;
}
};
};
var assignedMatrixMultiplication = function(a, b, res) {
// Unrolled loop
res[0] = a[0] * b[0] + a[1] * b[4] + a[2] * b[8] + a[3] * b[12];
res[1] = a[0] * b[1] + a[1] * b[5] + a[2] * b[9] + a[3] * b[13];
res[2] = a[0] * b[2] + a[1] * b[6] + a[2] * b[10] + a[3] * b[14];
res[3] = a[0] * b[3] + a[1] * b[7] + a[2] * b[11] + a[3] * b[15];
res[4] = a[4] * b[0] + a[5] * b[4] + a[6] * b[8] + a[7] * b[12];
res[5] = a[4] * b[1] + a[5] * b[5] + a[6] * b[9] + a[7] * b[13];
res[6] = a[4] * b[2] + a[5] * b[6] + a[6] * b[10] + a[7] * b[14];
res[7] = a[4] * b[3] + a[5] * b[7] + a[6] * b[11] + a[7] * b[15];
res[8] = a[8] * b[0] + a[9] * b[4] + a[10] * b[8] + a[11] * b[12];
res[9] = a[8] * b[1] + a[9] * b[5] + a[10] * b[9] + a[11] * b[13];
res[10] = a[8] * b[2] + a[9] * b[6] + a[10] * b[10] + a[11] * b[14];
res[11] = a[8] * b[3] + a[9] * b[7] + a[10] * b[11] + a[11] * b[15];
res[12] = a[12] * b[0] + a[13] * b[4] + a[14] * b[8] + a[15] * b[12];
res[13] = a[12] * b[1] + a[13] * b[5] + a[14] * b[9] + a[15] * b[13];
res[14] = a[12] * b[2] + a[13] * b[6] + a[14] * b[10] + a[15] * b[14];
res[15] = a[12] * b[3] + a[13] * b[7] + a[14] * b[11] + a[15] * b[15];
return res;
};
var createState = function(config) {
// Caching of matrix and properties so we don't have to create new ones everytime they are needed
var matrix = createMatrix();
var properties = {
opacity: undefined,
width: undefined,
height: undefined
};
// Public API
return {
position: config.position,
rotation: config.rotation,
rotationPost: config.rotationPost,
skew: config.skew,
scale: config.scale,
scalePost: config.scalePost,
opacity: config.opacity,
width: config.width,
height: config.height,
clone: function() {
return createState({
position: this.position ? this.position.slice(0) : undefined,
rotation: this.rotation ? this.rotation.slice(0) : undefined,
rotationPost: this.rotationPost ? this.rotationPost.slice(0) : undefined,
skew: this.skew ? this.skew.slice(0) : undefined,
scale: this.scale ? this.scale.slice(0) : undefined,
scalePost: this.scalePost ? this.scalePost.slice(0) : undefined,
height: this.height,
width: this.width,
opacity: this.opacity
});
},
asMatrix: function() {
var m = matrix;
m.clear();
if(this.transformOrigin)
m.translate(-this.transformOrigin[0], -this.transformOrigin[1], -this.transformOrigin[2]);
if(this.scale) {
m.scale(this.scale[0], this.scale[1]);
}
if(this.skew) {
m.skew(this.skew[0], this.skew[1]);
}
if(this.rotation) {
m.rotateX(this.rotation[0]);
m.rotateY(this.rotation[1]);
m.rotateZ(this.rotation[2]);
}
if(this.position) {
m.translate(this.position[0], this.position[1], this.position[2]);
}
if(this.rotationPost) {
m.rotateX(this.rotationPost[0]);
m.rotateY(this.rotationPost[1]);
m.rotateZ(this.rotationPost[2]);
}
if(this.scalePost) {
m.scale(this.scalePost[0], this.scalePost[1]);
}
if(this.transformOrigin)
m.translate(this.transformOrigin[0], this.transformOrigin[1], this.transformOrigin[2]);
return m;
},
getProperties: function() {
properties.opacity = this.opacity;
properties.width = this.width + 'px';
properties.height = this.height + 'px';
return properties;
}
};
};
// ------------------
// -- StateTweener --
// -------------------
var createStateTweener = function(startState, endState, resultState) {
var start = startState;
var end = endState;
var result = resultState;
var tweenPosition = end.position !== undefined;
var tweenRotation = end.rotation !== undefined;
var tweenRotationPost = end.rotationPost !== undefined;
var tweenScale = end.scale !== undefined;
var tweenSkew = end.skew !== undefined;
var tweenWidth = end.width !== undefined;
var tweenHeight = end.height !== undefined;
var tweenOpacity = end.opacity !== undefined;
// Public API
return {
tween: function(tweenValue) {
if(tweenPosition) {
var dX = (end.position[0] - start.position[0]);
var dY = (end.position[1] - start.position[1]);
var dZ = (end.position[2] - start.position[2]);
result.position[0] = start.position[0] + tweenValue*dX;
result.position[1] = start.position[1] + tweenValue*dY;
result.position[2] = start.position[2] + tweenValue*dZ;
}
if(tweenRotation) {
var dAX = (end.rotation[0] - start.rotation[0]);
var dAY = (end.rotation[1] - start.rotation[1]);
var dAZ = (end.rotation[2] - start.rotation[2]);
result.rotation[0] = start.rotation[0] + tweenValue*dAX;
result.rotation[1] = start.rotation[1] + tweenValue*dAY;
result.rotation[2] = start.rotation[2] + tweenValue*dAZ;
}
if(tweenRotationPost) {
var dBX = (end.rotationPost[0] - start.rotationPost[0]);
var dBY = (end.rotationPost[1] - start.rotationPost[1]);
var dBZ = (end.rotationPost[2] - start.rotationPost[2]);
result.rotationPost[0] = start.rotationPost[0] + tweenValue*dBX;
result.rotationPost[1] = start.rotationPost[1] + tweenValue*dBY;
result.rotationPost[2] = start.rotationPost[2] + tweenValue*dBZ;
}
if(tweenSkew) {
var dSX = (end.scale[0] - start.scale[0]);
var dSY = (end.scale[1] - start.scale[1]);
result.scale[0] = start.scale[0] + tweenValue*dSX;
result.scale[1] = start.scale[1] + tweenValue*dSY;
}
if(tweenScale) {
var dSkewX = (end.skew[0] - start.skew[0]);
var dSkewY = (end.skew[1] - start.skew[1]);
result.skew[0] = start.skew[0] + tweenValue*dSkewX;
result.skew[1] = start.skew[1] + tweenValue*dSkewY;
}
if(tweenWidth) {
var dWidth = (end.width - start.width);
result.width = start.width + tweenValue*dWidth;
}
if(tweenHeight) {
var dHeight = (end.height - start.height);
result.height = start.height + tweenValue*dHeight;
}
if(tweenOpacity) {
var dOpacity = (end.opacity - start.opacity);
result.opacity = start.opacity + tweenValue*dOpacity;
}
},
asMatrix: function() {
return result.asMatrix();
},
getProperties: function() {
return result.getProperties();
},
setReverse: function() {
var oldStart = start;
start = end;
end = oldStart;
}
};
};
// ------------------------
// -- ValueFeederTweener --
// ------------------------
var createValueFeederTweener = function(valueFeeder, startState, endState, resultState) {
var currentMatrix = valueFeeder(0, createMatrix());
var start = startState;
var end = endState;
var result = resultState;
var reverse = false;
// Public API
return {
tween: function(tweenValue) {
if(reverse)
tweenValue = 1 - tweenValue;
currentMatrix.clear();
currentMatrix = valueFeeder(tweenValue, currentMatrix);
var dWidth = (end.width - start.width);
var dHeight = (end.height - start.height);
var dOpacity = (end.opacity - start.opacity);
if(end.width !== undefined)
result.width = start.width + tweenValue*dWidth;
if(end.height !== undefined)
result.height = start.height + tweenValue*dHeight;
if(end.opacity !== undefined)
result.opacity = start.opacity + tweenValue*dOpacity;
},
asMatrix: function() {
return currentMatrix;
},
getProperties: function() {
return result.getProperties();
},
setReverse: function() {
reverse = true;
}
};
};
var optionOrDefault = function(option, def) {
if(typeof option == 'undefined') {
return def;
}
return option;
};
var updateElementTransform = function(element, matrix, perspective) {
var cssPerspective = '';
if(perspective) {
cssPerspective = 'perspective(' + perspective + 'px) ';
}
var cssMatrix = matrix.asCSS();
element.style[transformProperty] = cssPerspective + cssMatrix;
};
var updateElementProperties = function(element, properties) {
for(var key in properties) {
element.style[key] = properties[key];
}
};
var isFunction = function(object) {
return (typeof object === "function");
};
var cloneObject = function(object) {
if(!object)
return object;
var clone = {};
for(var key in object) {
clone[key] = object[key];
}
return clone;
};
snabbt.createMatrix = createMatrix;
snabbt.setElementTransform = updateElementTransform;
return snabbt;
}());
var stackBlur = (function(){
var mul_table = [
512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,
454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,
482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,
437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,
497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,
320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,
446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,
329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,
505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,
399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,
324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,
268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,
451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,
385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,
332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,
289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];
var shg_table = [
9, 11, 12, 13, 13, 14, 14, 15, 15, 15, 15, 16, 16, 16, 16, 17,
17, 17, 17, 17, 17, 17, 18, 18, 18, 18, 18, 18, 18, 18, 18, 19,
19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 19, 20, 20, 20,
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 21,
21, 21, 21, 21, 21, 21, 21, 21, 21, 21, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22,
22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 22, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23, 23,
23, 23, 23, 23, 23, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24, 24 ];
function getImageDataFromCanvas(canvas, top_x, top_y, width, height)
{
if (typeof(canvas) == 'string')
canvas = document.getElementById(canvas);
else if (!canvas instanceof HTMLCanvasElement)
return;
var context = canvas.getContext('2d');
var imageData;
try {
try {
imageData = context.getImageData(top_x, top_y, width, height);
} catch(e) {
throw new Error("unable to access local image data: " + e);
return;
}
} catch(e) {
throw new Error("unable to access image data: " + e);
}
return imageData;
}
function processCanvasRGBA(canvas, top_x, top_y, width, height, radius)
{
if (isNaN(radius) || radius < 1) return;
radius |= 0;
var imageData = getImageDataFromCanvas(canvas, top_x, top_y, width, height);
imageData = processImageDataRGBA(imageData, top_x, top_y, width, height, radius);
canvas.getContext('2d').putImageData(imageData, top_x, top_y);
}
function processImageDataRGBA(imageData, top_x, top_y, width, height, radius)
{
var pixels = imageData.data;
var x, y, i, p, yp, yi, yw, r_sum, g_sum, b_sum, a_sum,
r_out_sum, g_out_sum, b_out_sum, a_out_sum,
r_in_sum, g_in_sum, b_in_sum, a_in_sum,
pr, pg, pb, pa, rbs;
var div = radius + radius + 1;
var w4 = width << 2;
var widthMinus1 = width - 1;
var heightMinus1 = height - 1;
var radiusPlus1 = radius + 1;
var sumFactor = radiusPlus1 * (radiusPlus1 + 1) / 2;
var stackStart = new BlurStack();
var stack = stackStart;
for (i = 1; i < div; i++)
{
stack = stack.next = new BlurStack();
if (i == radiusPlus1) var stackEnd = stack;
}
stack.next = stackStart;
var stackIn = null;
var stackOut = null;
yw = yi = 0;
var mul_sum = mul_table[radius];
var shg_sum = shg_table[radius];
for (y = 0; y < height; y++)
{
r_in_sum = g_in_sum = b_in_sum = a_in_sum = r_sum = g_sum = b_sum = a_sum = 0;
r_out_sum = radiusPlus1 * (pr = pixels[yi]);
g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
a_out_sum = radiusPlus1 * (pa = pixels[yi+3]);
r_sum += sumFactor * pr;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb;
a_sum += sumFactor * pa;
stack = stackStart;
for (i = 0; i < radiusPlus1; i++)
{
stack.r = pr;
stack.g = pg;
stack.b = pb;
stack.a = pa;
stack = stack.next;
}
for (i = 1; i < radiusPlus1; i++)
{
p = yi + ((widthMinus1 < i ? widthMinus1 : i) << 2);
r_sum += (stack.r = (pr = pixels[p])) * (rbs = radiusPlus1 - i);
g_sum += (stack.g = (pg = pixels[p+1])) * rbs;
b_sum += (stack.b = (pb = pixels[p+2])) * rbs;
a_sum += (stack.a = (pa = pixels[p+3])) * rbs;
r_in_sum += pr;
g_in_sum += pg;
b_in_sum += pb;
a_in_sum += pa;
stack = stack.next;
}
stackIn = stackStart;
stackOut = stackEnd;
for (x = 0; x < width; x++)
{
pixels[yi+3] = pa = (a_sum * mul_sum) >> shg_sum;
if (pa != 0)
{
pa = 255 / pa;
pixels[yi] = ((r_sum * mul_sum) >> shg_sum) * pa;
pixels[yi+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
pixels[yi+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
} else {
pixels[yi] = pixels[yi+1] = pixels[yi+2] = 0;
}
r_sum -= r_out_sum;
g_sum -= g_out_sum;
b_sum -= b_out_sum;
a_sum -= a_out_sum;
r_out_sum -= stackIn.r;
g_out_sum -= stackIn.g;
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a;
p = (yw + ((p = x + radius + 1) < widthMinus1 ? p : widthMinus1)) << 2;
r_in_sum += (stackIn.r = pixels[p]);
g_in_sum += (stackIn.g = pixels[p+1]);
b_in_sum += (stackIn.b = pixels[p+2]);
a_in_sum += (stackIn.a = pixels[p+3]);
r_sum += r_in_sum;
g_sum += g_in_sum;
b_sum += b_in_sum;
a_sum += a_in_sum;
stackIn = stackIn.next;
r_out_sum += (pr = stackOut.r);
g_out_sum += (pg = stackOut.g);
b_out_sum += (pb = stackOut.b);
a_out_sum += (pa = stackOut.a);
r_in_sum -= pr;
g_in_sum -= pg;
b_in_sum -= pb;
a_in_sum -= pa;
stackOut = stackOut.next;
yi += 4;
}
yw += width;
}
for (x = 0; x < width; x++)
{
g_in_sum = b_in_sum = a_in_sum = r_in_sum = g_sum = b_sum = a_sum = r_sum = 0;
yi = x << 2;
r_out_sum = radiusPlus1 * (pr = pixels[yi]);
g_out_sum = radiusPlus1 * (pg = pixels[yi+1]);
b_out_sum = radiusPlus1 * (pb = pixels[yi+2]);
a_out_sum = radiusPlus1 * (pa = pixels[yi+3]);
r_sum += sumFactor * pr;
g_sum += sumFactor * pg;
b_sum += sumFactor * pb;
a_sum += sumFactor * pa;
stack = stackStart;
for (i = 0; i < radiusPlus1; i++)
{
stack.r = pr;
stack.g = pg;
stack.b = pb;
stack.a = pa;
stack = stack.next;
}
yp = width;
for (i = 1; i <= radius; i++)
{
yi = (yp + x) << 2;
r_sum += (stack.r = (pr = pixels[yi])) * (rbs = radiusPlus1 - i);
g_sum += (stack.g = (pg = pixels[yi+1])) * rbs;
b_sum += (stack.b = (pb = pixels[yi+2])) * rbs;
a_sum += (stack.a = (pa = pixels[yi+3])) * rbs;
r_in_sum += pr;
g_in_sum += pg;
b_in_sum += pb;
a_in_sum += pa;
stack = stack.next;
if(i < heightMinus1)
{
yp += width;
}
}
yi = x;
stackIn = stackStart;
stackOut = stackEnd;
for (y = 0; y < height; y++)
{
p = yi << 2;
pixels[p+3] = pa = (a_sum * mul_sum) >> shg_sum;
if (pa > 0)
{
pa = 255 / pa;
pixels[p] = ((r_sum * mul_sum) >> shg_sum) * pa;
pixels[p+1] = ((g_sum * mul_sum) >> shg_sum) * pa;
pixels[p+2] = ((b_sum * mul_sum) >> shg_sum) * pa;
} else {
pixels[p] = pixels[p+1] = pixels[p+2] = 0;
}
r_sum -= r_out_sum;
g_sum -= g_out_sum;
b_sum -= b_out_sum;
a_sum -= a_out_sum;
r_out_sum -= stackIn.r;
g_out_sum -= stackIn.g;
b_out_sum -= stackIn.b;
a_out_sum -= stackIn.a;
p = (x + (((p = y + radiusPlus1) < heightMinus1 ? p : heightMinus1) * width)) << 2;
r_sum += (r_in_sum += (stackIn.r = pixels[p]));
g_sum += (g_in_sum += (stackIn.g = pixels[p+1]));
b_sum += (b_in_sum += (stackIn.b = pixels[p+2]));
a_sum += (a_in_sum += (stackIn.a = pixels[p+3]));
stackIn = stackIn.next;
r_out_sum += (pr = stackOut.r);
g_out_sum += (pg = stackOut.g);
b_out_sum += (pb = stackOut.b);
a_out_sum += (pa = stackOut.a);
r_in_sum -= pr;
g_in_sum -= pg;
b_in_sum -= pb;
a_in_sum -= pa;
stackOut = stackOut.next;
yi += width;
}
}
return imageData;
}
function BlurStack()
{
this.r = 0;
this.g = 0;
this.b = 0;
this.a = 0;
this.next = null;
}
return processCanvasRGBA;
}());
// canvas to blob polyfill
// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill
if (!HTMLCanvasElement.prototype.toBlob) {
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
value: function (callback, type, quality) {
var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
len = binStr.length,
arr = new Uint8Array(len);
for (var i=0; i<len; i++ ) {
arr[i] = binStr.charCodeAt(i);
}
callback( new Blob( [arr], {type: type || 'image/png'} ) );
}
});
}
'use strict';
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
var _arguments = arguments;
var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var copyString = function copyString(str) {
return str + '';
};
var getDataset = function getDataset(el) {
if (typeof el.dataset === 'undefined') {
var res = {};
var attr;
var attrName;
var attrs = el.attributes;
for (attr in attrs) {
if (attrs.hasOwnProperty(attr) && attrs[attr].name && /^data-[a-z_\-\d]*$/i.test(attrs[attr].name)) {
attrName = toCamelCase(attrs[attr].name.substr(5));
res[attrName] = attrs[attr].value;
}
}
return res;
}
return el.dataset;
};
var toCamelCase = function toCamelCase(str) {
return str.replace(/\-./g, function (substr) {
return substr.charAt(1).toUpperCase();
});
};
var getElementAttributes = function getElementAttributes(el) {
// is a for loop on purpose as this should still function when Slim not supported
var result = [];
var attributes = Array.prototype.slice.call(el.attributes);
var l = attributes.length;
for (var i = 0; i < l; i++) {
result.push({
name: attributes[i].name,
value: attributes[i].value
});
}
return result;
};
// helper method
var getOffsetByEvent = function getOffsetByEvent(e) {
return {
x: typeof e.offsetX === 'undefined' ? e.layerX : e.offsetX,
y: typeof e.offsetY === 'undefined' ? e.layerY : e.offsetY
};
};
// merge two objects together
var mergeOptions = function mergeOptions(base, additives) {
var key;
var options = {};
var optionsToMerge = additives || {};
for (key in base) {
if (!base.hasOwnProperty(key)) {
continue;
}
options[key] = typeof optionsToMerge[key] === 'undefined' ? base[key] : optionsToMerge[key];
}
return options;
};
// keys
var Key = {
ESC: 27,
RETURN: 13
};
// pointer events
var Events = {
DOWN: ['touchstart', 'pointerdown', 'mousedown'],
MOVE: ['touchmove', 'pointermove', 'mousemove'],
UP: ['touchend', 'touchcancel', 'pointerup', 'mouseup']
};
var MimeTypes = {
jpeg: 'image/jpeg',
jpg: 'image/jpeg',
jpe: 'image/jpeg',
png: 'image/png',
gif: 'image/gif',
bmp: 'image/bmp'
};
var ImageExtensionsRegex = /(\.png|\.bmp|\.gif|\.jpg|\.jpe|\.jpg|\.jpeg)$/;
var CanvasExportExtensions = /(jpe|jpg|jpeg|png)/;
// shortcuts
var create = function create(name, className) {
var node = document.createElement(name);
if (className) {
node.className = className;
}
return node;
};
// events
var addEvents = function addEvents(obj, events, scope) {
events.forEach(function (event) {
obj.addEventListener(event, scope, false);
});
};
var removeEvents = function removeEvents(obj, events, scope) {
events.forEach(function (event) {
obj.removeEventListener(event, scope, false);
});
};
var getEventOffset = function getEventOffset(e) {
var event = e.changedTouches ? e.changedTouches[0] : e;
// no event found, quit!
if (!event) {
return;
}
// get offset from events
return {
x: event.pageX,
y: event.pageY
};
};
var rotate = function rotate(rect, angle) {
var cx = 0.5;
var cy = 0.5;
var radians = Math.PI / 180 * angle;
var cos = Math.cos(radians);
var sin = Math.sin(radians);
var x1 = rect.x;
var y1 = rect.y;
var x2 = rect.x + rect.width;
var y2 = rect.y + rect.height;
var rx1 = cos * (x1 - cx) + sin * (y1 - cy) + cx;
var ry1 = cos * (y1 - cy) - sin * (x1 - cx) + cy;
var rx2 = cos * (x2 - cx) + sin * (y2 - cy) + cx;
var ry2 = cos * (y2 - cy) - sin * (x2 - cx) + cy;
if (rx1 <= rx2) {
rect.x = rx1;
rect.width = rx2 - rx1;
} else {
rect.x = rx2;
rect.width = rx1 - rx2;
}
if (ry1 <= ry2) {
rect.y = ry1;
rect.height = ry2 - ry1;
} else {
rect.y = ry2;
rect.height = ry1 - ry2;
}
};
var getEventOffsetScroll = function getEventOffsetScroll(e) {
var offset = getEventOffset(e);
offset.x -= window.pageXOffset || document.documentElement.scrollLeft;
offset.y -= window.pageYOffset || document.documentElement.scrollTop;
return offset;
};
var lowercaseFirstLetter = function lowercaseFirstLetter(string) {
return string.charAt(0).toLowerCase() + string.slice(1);
};
var capitalizeFirstLetter = function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
};
var last = function last(array) {
return array[array.length - 1];
};
var limit = function limit(value, min, max) {
return Math.max(min, Math.min(max, value));
};
var inArray = function inArray(needle, arr) {
// is for loop so we can use this method on older browsers to render fallback message
if (!arr) {
return false;
}
for (var i = 0; i < arr.length; i++) {
if (arr[i] === needle) {
return true;
}
}
return false;
};
var send = function send(url) {
var method = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'POST';
var data = arguments[2];
var requestDecorator = arguments[3];
var progress = arguments[4];
var success = arguments[5];
var err = arguments[6];
var xhr = new XMLHttpRequest();
// if progress callback defined handle progress events
if (progress) {
xhr.upload.addEventListener('progress', function (e) {
progress(e.loaded, e.total);
});
}
// open the request
xhr.open(method, url, true);
// if request decorator defined pass XMLHttpRequest instance to decorator
if (requestDecorator) {
requestDecorator(xhr, data);
}
// handle state changes
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status >= 200 && xhr.status < 300) {
var text = xhr.responseText;
// if no data returned from server assume success
if (!text.length) {
success();
return;
}
// catch possible PHP content length problem
if (text.indexOf('Content-Length') !== -1) {
err('file-too-big');
return;
}
// if data returned it should be in suggested JSON format
var obj = void 0;
try {
obj = JSON.parse(xhr.responseText);
} catch (e) {}
// if is failure response
if ((typeof obj === 'undefined' ? 'undefined' : _typeof(obj)) === 'object' && obj.status === 'failure') {
err(obj.message);
return;
}
success(obj || text);
} else if (xhr.readyState === 4) {
var _obj = void 0;
try {
_obj = JSON.parse(xhr.responseText);
} catch (e) {}
// if is clean failure response
if ((typeof _obj === 'undefined' ? 'undefined' : _typeof(_obj)) === 'object' && _obj.status === 'failure') {
err(_obj.message);
return;
}
err('fail');
}
};
// do request
xhr.send(data);
};
var resetTransforms = function resetTransforms(element) {
if (!element) {
return;
}
element.style.webkitTransform = '';
element.style.transform = '';
};
var bytesToMegaBytes = function bytesToMegaBytes(b) {
return b / 1000000;
};
var megaBytesToBytes = function megaBytesToBytes(mb) {
return mb * 1000000;
};
var getCommonMimeTypes = function getCommonMimeTypes() {
var types = [];
var type = void 0;
var mimeType = void 0;
for (type in MimeTypes) {
if (!MimeTypes.hasOwnProperty(type)) {
continue;
}
mimeType = MimeTypes[type];
if (types.indexOf(mimeType) == -1) {
types.push(mimeType);
}
}
return types;
};
var isJPEGMimeType = function isJPEGMimeType(type) {
return type === 'image/jpeg';
};
var getExtensionByMimeType = function getExtensionByMimeType(mimetype) {
var type = void 0;
for (type in MimeTypes) {
if (!MimeTypes.hasOwnProperty(type)) {
continue;
}
if (MimeTypes[type] === mimetype) {
return type;
}
}
return mimetype;
};
var getMimeTypeFromResponseType = function getMimeTypeFromResponseType(responseType) {
var type = void 0;
for (type in MimeTypes) {
if (!MimeTypes.hasOwnProperty(type)) {
continue;
}
if (responseType.indexOf(MimeTypes[type]) !== -1) {
return MimeTypes[type];
}
}
return null;
};
var getFileName = function getFileName(path) {
return path.split('/').pop().split('?').shift();
};
var leftPad = function leftPad(value) {
var padding = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '';
return (padding + value).slice(-padding.length);
};
var getDateString = function getDateString(date) {
return date.getFullYear() + '-' + leftPad(date.getMonth() + 1, '00') + '-' + leftPad(date.getDate(), '00') + '_' + leftPad(date.getHours(), '00') + '-' + leftPad(date.getMinutes(), '00') + '-' + leftPad(date.getSeconds(), '00');
};
var getFileNameByFile = function getFileNameByFile(file) {
if (typeof file.name === 'undefined') {
return getDateString(new Date()) + '.' + getExtensionByMimeType(getFileTypeByFile(file));
}
return file.name;
};
var getFileTypeByFile = function getFileTypeByFile(file) {
return file.type || 'image/jpeg';
};
var getFileNameWithoutExtension = function getFileNameWithoutExtension(path) {
if (typeof path !== 'string') {
return getDateString(new Date());
}
var name = getFileName(path);
return name.split('.').shift();
};
var blobToFile = function blobToFile(blob, name) {
try {
return new File([blob], name, { type: blob.type, lastModified: Date.now() });
} catch (e) {
blob.lastModified = new Date();
blob.name = name;
return blob;
}
};
var resourceIsFetchURL = function resourceIsFetchURL(resource) {
return (/^fetch\//.test(resource)
);
};
var resourceIsBase64Data = function resourceIsBase64Data(resource) {
return (/^data:image/.test(resource)
);
};
var loadRemoteURL = function loadRemoteURL(fetcher, fetchRequestDecorator, loadRequestDecorator, url, err, cb) {
fetcher = '' + fetcher + (fetcher.indexOf('?') !== -1 ? '&' : '?') + 'url=' + url;
var xhr = new XMLHttpRequest();
xhr.open('GET', fetcher, true);
fetchRequestDecorator(xhr);
xhr.responseType = 'json';
xhr.onload = function () {
if (this.response.status === 'failure') {
err(this.response.message);
return;
}
loadURL(this.response.body, loadRequestDecorator, cb);
};
xhr.send();
};
var loadURL = function loadURL(url, requestDecorator, cb, err) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
requestDecorator(xhr);
xhr.responseType = 'blob';
xhr.onload = function (e) {
if (xhr.status >= 200 && xhr.status < 300) {
var name = getFileName(url);
var type = getMimeTypeFromResponseType(this.response.type);
if (!ImageExtensionsRegex.test(name)) {
name += '.' + getExtensionByMimeType(type);
}
// get as file
var file = blobToFile(this.response, name);
// need to set correct type
cb(cloneFile(file, type));
} else {
err(xhr.status + ': ' + xhr.statusText);
}
};
xhr.onerror = function () {
err();
};
xhr.send();
};
var base64ToByteString = function base64ToByteString(dataURI) {
// get data part of string (remove data:image/jpeg...,)
var dataPart = dataURI.split(',')[1];
// remove any whitespace as that causes InvalidCharacterError in IE
var dataPartCleaned = dataPart.replace(/\s/g, '');
// to bytestring
return atob(dataPartCleaned);
};
var base64ToArrayBuffer = function base64ToArrayBuffer(dataURI) {
var byteString = base64ToByteString(dataURI);
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return ab;
};
var base64ToBlob = function base64ToBlob(dataURI, filename) {
var byteString = base64ToByteString(dataURI);
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var mimeType = getMimeTypeFromDataURI(dataURI);
if (typeof filename === 'undefined') {
filename = getDateString(new Date()) + '.' + getExtensionByMimeType(mimeType);
}
return blobToFile(createBlob(ab, mimeType), filename);
};
var createBlob = function createBlob(data, mimeType) {
var BB = window.BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || window.MozBlobBuilder || window.MSBlobBuilder;
if (BB) {
var bb = new BB();
bb.append(data);
return bb.getBlob(mimeType);
}
return new Blob([data], {
type: mimeType
});
};
var arrayBufferConcat = function arrayBufferConcat(buffers) {
var length = 0;
var buffer = null;
for (var i in buffers) {
if (!_arguments.hasOwnProperty(i)) {
continue;
}
buffer = buffers[i];
length += buffer.byteLength;
}
var joined = new Uint8Array(length);
var offset = 0;
for (var _i in buffers) {
if (!_arguments.hasOwnProperty(_i)) {
continue;
}
buffer = buffers[_i];
joined.set(new Uint8Array(buffer), offset);
offset += buffer.byteLength;
}
return joined.buffer;
};
var getImageAsCanvas = function getImageAsCanvas(src, size, callback) {
// only cross origin when it's not base64 data, to prevent errors in Safari
// http://stackoverflow.com/questions/31643096/why-does-safari-throw-cors-error-when-setting-base64-data-on-a-crossorigin-an
var crossOrigin = typeof src === 'string' ? src.indexOf('data:image') !== 0 : true;
loadImage.parseMetaData(src, function (meta) {
var options = {
canvas: true,
crossOrigin: crossOrigin
};
if (size) {
options.maxWidth = size.width;
options.maxHeight = size.height;
}
if (meta.exif) {
options.orientation = meta.exif.get('Orientation');
}
loadImage(src, function (res) {
if (res.type === 'error') {
callback();
return;
}
callback(res, meta);
}, options);
});
};
var getAutoCropRect = function getAutoCropRect(width, height, ratioOut) {
var x,
y,
w,
h,
ratioIn = height / width;
// if input is portrait and required is landscape
// width is portrait width, height is width times outputRatio
if (ratioIn < ratioOut) {
h = height;
w = h / ratioOut;
x = (width - w) * 0.5;
y = 0;
} else {
// if input is landscape and required is portrait
// height is landscape height, width is height divided by outputRatio
w = width;
h = w * ratioOut;
x = 0;
y = (height - h) * 0.5;
}
return {
x: x,
y: y,
height: h,
width: w
};
};
var transformCanvas = function transformCanvas(canvas) {
var transforms = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var cb = arguments[2];
var result = create('canvas');
var rotation = transforms.rotation,
crop = transforms.crop,
size = transforms.size,
filters = transforms.filters,
minSize = transforms.minSize;
// do crop transforms
if (crop) {
// do crop
var isTilted = rotation % 180 !== 0;
var space = {
width: isTilted ? canvas.height : canvas.width,
height: isTilted ? canvas.width : canvas.height
};
// limit crop to size of canvas else safari might return transparent image
if (crop.x < 0) {
crop.x = 0;
}
if (crop.y < 0) {
crop.y = 0;
}
if (crop.width > space.width) {
crop.width = space.width;
}
if (crop.height > space.height) {
crop.height = space.height;
}
if (crop.y + crop.height > space.height) {
crop.y = Math.max(0, space.height - crop.height);
}
if (crop.x + crop.width > space.width) {
crop.x = Math.max(0, space.width - crop.width);
}
// crop offsets in percentages
var px = crop.x / space.width;
var py = crop.y / space.height;
var pw = crop.width / space.width;
var ph = crop.height / space.height;
// resize canvas to the final crop result size
result.width = crop.width;
result.height = crop.height;
// draw the crop
var ctx = result.getContext('2d');
if (rotation === 90) {
ctx.translate(result.width * 0.5, result.height * 0.5);
ctx.rotate(-90 * Math.PI / 180);
ctx.drawImage(canvas,
// source rectangle (crop area)
(1 - py) * canvas.width - canvas.width * ph, crop.x, crop.height, crop.width,
// target area (cover)
-result.height * 0.5, -result.width * 0.5, result.height, result.width);
} else if (rotation === 180) {
ctx.translate(result.width * 0.5, result.height * 0.5);
ctx.rotate(-180 * Math.PI / 180);
ctx.drawImage(canvas,
// source rectangle (crop area)
(1 - (px + pw)) * space.width, (1 - (py + ph)) * space.height, pw * space.width, ph * space.height,
// target area (cover)
-result.width * 0.5, -result.height * 0.5, result.width, result.height);
} else if (rotation === 270) {
ctx.translate(result.width * 0.5, result.height * 0.5);
ctx.rotate(-270 * Math.PI / 180);
ctx.drawImage(canvas,
// source rectangle (crop area)
crop.y, (1 - px) * canvas.height - canvas.height * pw, crop.height, crop.width,
// target area (cover)
-result.height * 0.5, -result.width * 0.5, result.height, result.width);
} else {
ctx.drawImage(canvas,
// source rectangle (crop area)
crop.x, crop.y, crop.width, crop.height,
// target area (cover)
0, 0, result.width, result.height);
}
}
// do size transforms
if (size) {
var scalarX = size.width / result.width;
var scalarY = size.height / result.height;
var scalar = Math.min(scalarX, scalarY);
scaleCanvas(result, scalar, size, minSize);
// sharpen result
if (filters.sharpen > 0) {
filter(result, sharpen(filters.sharpen));
}
}
cb(result);
};
function scaleCanvas(canvas, scalar, bounds, min) {
// if not scaling down, bail out
if (scalar >= 1) {
return;
}
var w = canvas.width;
var h = canvas.height;
// calculate min target width and height
var targetWidth = Math.max(min.width, Math.min(bounds.width, Math.round(canvas.width * scalar)));
var targetHeight = Math.max(min.height, Math.min(bounds.height, Math.round(canvas.height * scalar)));
var tmp = cloneCanvas(canvas);
var c = void 0;
var ctx = void 0;
while (w > targetWidth && h > targetHeight) {
c = document.createElement('canvas');
w = Math.round(tmp.width * 0.5);
h = Math.round(tmp.height * 0.5);
if (w < targetWidth) {
w = targetWidth;
}
if (h < targetHeight) {
h = targetHeight;
}
c.width = w;
c.height = h;
ctx = c.getContext('2d');
ctx.drawImage(tmp, 0, 0, w, h);
tmp = cloneCanvas(c);
}
canvas.width = targetWidth;
canvas.height = targetHeight;
ctx = canvas.getContext('2d');
ctx.drawImage(tmp, 0, 0, targetWidth, targetHeight);
}
var getPixels = function getPixels(canvas) {
var ctx = canvas.getContext('2d');
return ctx.getImageData(0, 0, canvas.width, canvas.height);
};
var filter = function filter(canvas, _filter) {
var ctx = canvas.getContext('2d');
ctx.putImageData(_filter(getPixels(canvas), canvas.width, canvas.height), 0, 0);
};
var createImageData = function createImageData(w, h, pixels) {
var c = document.createElement('canvas');
c.width = w;
c.height = h;
var ctx = c.getContext('2d');
var data = ctx.createImageData(c.width, c.height);
if (pixels) {
data.set(pixels.data);
}
return data;
};
var sharpen = function sharpen(mix) {
return function (pixels, w, h) {
var weights = [0, -1, 0, -1, 5, -1, 0, -1, 0],
katet = Math.round(Math.sqrt(weights.length)),
half = katet * 0.5 | 0,
dstData = createImageData(w, h),
dstBuff = dstData.data,
srcBuff = pixels.data,
y = h,
x = void 0;
while (y--) {
x = w;
while (x--) {
var sy = y,
sx = x,
dstOff = (y * w + x) * 4,
r = 0,
g = 0,
b = 0,
a = 0;
for (var cy = 0; cy < katet; cy++) {
for (var cx = 0; cx < katet; cx++) {
var scy = sy + cy - half;
var scx = sx + cx - half;
if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
var srcOff = (scy * w + scx) * 4;
var wt = weights[cy * katet + cx];
r += srcBuff[srcOff] * wt;
g += srcBuff[srcOff + 1] * wt;
b += srcBuff[srcOff + 2] * wt;
a += srcBuff[srcOff + 3] * wt;
}
}
}
dstBuff[dstOff] = r * mix + srcBuff[dstOff] * (1 - mix);
dstBuff[dstOff + 1] = g * mix + srcBuff[dstOff + 1] * (1 - mix);
dstBuff[dstOff + 2] = b * mix + srcBuff[dstOff + 2] * (1 - mix);
dstBuff[dstOff + 3] = srcBuff[dstOff + 3];
}
}
return dstData;
};
};
var sizeDist = function sizeDist(rect, canvas) {
var dx = Math.abs(rect.width - canvas.width);
var dy = Math.abs(rect.height - canvas.height);
return Math.max(dx, dy);
};
var cloneCanvas = function cloneCanvas(original) {
return cloneCanvasScaled(original, 1);
};
var cloneCanvasScaled = function cloneCanvasScaled(original, scalar) {
if (!original) {
return null;
}
var duplicate = document.createElement('canvas');
var ctx = duplicate.getContext('2d');
duplicate.width = original.width;
duplicate.height = original.height;
ctx.drawImage(original, 0, 0);
if (scalar > 0 && scalar !== 1) {
scaleCanvas(duplicate, scalar, {
width: Math.round(original.width * scalar),
height: Math.round(original.height * scalar)
}, {
width: 0,
height: 0
});
}
return duplicate;
};
var canvasHasDimensions = function canvasHasDimensions(canvas) {
return canvas.width && canvas.height;
};
var copyCanvas = function copyCanvas(original, destination) {
var ctx = destination.getContext('2d');
if (canvasHasDimensions(destination)) {
ctx.drawImage(original, 0, 0, destination.width, destination.height);
} else {
destination.width = original.width;
destination.height = original.height;
ctx.drawImage(original, 0, 0);
}
};
var clearCanvas = function clearCanvas(canvas) {
var ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
};
var blurCanvas = function blurCanvas(canvas) {
stackBlur(canvas, 0, 0, canvas.width, canvas.height, 3);
};
var covers = function covers(image, rect) {
return parseInt(image.width, 10) >= rect.width && parseInt(image.height, 10) >= rect.height;
};
var scaleRect = function scaleRect(rect, w, h) {
return {
x: rect.x * w,
y: rect.y * h,
width: rect.width * w,
height: rect.height * h
};
};
var divideRect = function divideRect(rect, w, h) {
return {
x: rect.x / w,
y: rect.y / h,
width: rect.width / w,
height: rect.height / h
};
};
var resetFileInput = function resetFileInput(input) {
// no value, no need to reset
if (!input || input.value === '') {
return;
}
try {
// for modern browsers
input.value = '';
} catch (err) {}
// for IE10
if (input.value) {
// quickly append input to temp form and reset form
var form = document.createElement('form');
var parentNode = input.parentNode;
var ref = input.nextSibling;
form.appendChild(input);
form.reset();
// re-inject input where it originally was
if (ref) {
parentNode.insertBefore(input, ref);
} else {
parentNode.appendChild(input);
}
}
};
var clone = function clone(obj) {
if ((typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' && value !== null) {
return JSON.parse(JSON.stringify(obj));
}
return obj;
};
var cloneFile = function cloneFile(file) {
var type = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
if (!file) {
return null;
}
var dupe = file.slice(0, file.size, type || file.type);
dupe.name = file.name;
dupe.lastModified = new Date(file.lastModified);
return dupe;
};
var cloneData = function cloneData(obj) {
var dupe = clone(obj);
dupe.input.file = cloneFile(obj.input.file);
dupe.output.image = cloneCanvas(obj.output.image);
return dupe;
};
/**
* @param image
* @param type
* @param jpegCompression - value between 0 and 100 or undefined/null to use default compression
* @returns {*}
*/
var toDataURL = function toDataURL(image, type, jpegCompression) {
if (!image || !type) {
return null;
}
return image.toDataURL(type, isJPEGMimeType(type) && typeof jpegCompression === 'number' ? jpegCompression / 100 : undefined);
};
var getMimeTypeFromDataURI = function getMimeTypeFromDataURI(dataUri) {
if (!dataUri) {
return null;
}
var matches = dataUri.substr(0, 16).match(/^.+;/);
if (matches.length) {
return matches[0].substring(5, matches[0].length - 1);
}
return null;
};
var flattenData = function flattenData(obj) {
var props = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
var jpegCompression = arguments[2];
var forcedType = arguments[3];
var async = arguments[4];
var data = {
server: clone(obj.server),
meta: clone(obj.meta),
input: {
name: obj.input.name,
type: obj.input.type,
size: obj.input.size,
width: obj.input.width,
height: obj.input.height,
field: obj.input.field
}
};
if (inArray('input', props) && !async) {
data.input.image = toDataURL(obj.input.image, obj.input.type);
}
if (inArray('output', props)) {
data.output = {
name: forcedType ? getFileNameWithoutExtension(obj.input.name) + '.' + forcedType : obj.input.name,
type: MimeTypes[forcedType] || obj.input.type,
width: obj.output.width,
height: obj.output.height
};
data.output.image = toDataURL(obj.output.image, data.output.type, jpegCompression);
data.output.type = getMimeTypeFromDataURI(data.output.image);
// browser problem:
// if output is of type png and input was of type jpeg we need to fix extension of filename
// so instead of testing the above situation we just always fix extension when handling PNGs
if (data.output.type === 'image/png') {
data.output.name = getFileNameWithoutExtension(data.input.name) + '.png';
}
}
if (inArray('actions', props)) {
data.actions = clone(obj.actions);
}
return data;
};
var downloadCanvas = function downloadCanvas(data, jpegCompression, forcedType) {
var canvas = data.output.image;
var filename = forcedType ? getFileNameWithoutExtension(data.input.name) + '.' + forcedType : data.input.name;
var type = MimeTypes[forcedType] || data.input.type;
// browser problem:
// if output is of type png and input was of type jpeg we need to fix extension of filename
// so instead of testing the above situation we just always fix extension when handling PNGs
if (type === 'image/png') {
filename = getFileNameWithoutExtension(data.input.name) + '.png';
}
canvas.toBlob(function (blob) {
if ('msSaveBlob' in window.navigator) {
window.navigator.msSaveBlob(blob, filename);
return;
}
var url = (window.URL || window.webkitURL).createObjectURL(blob);
// setup hidden link
var link = create('a');
link.style.display = 'none';
link.download = filename;
link.href = url;
// attach to DOM otherwise this does not work in Firefox
document.body.appendChild(link);
// fire click
link.click();
// delay on remove otherwise does not work in Firefox
setTimeout(function () {
document.body.removeChild(link);
(window.URL || window.webkitURL).revokeObjectURL(url);
}, 0);
}, type, typeof jpegCompression === 'number' ? jpegCompression / 100 : undefined);
};
var toggleDisplayBySelector = function toggleDisplayBySelector(selector, enabled, root) {
var node = root.querySelector(selector);
if (!node) {
return;
}
node.style.display = enabled ? '' : 'none';
};
var nodeListToArray = function nodeListToArray(nl) {
return Array.prototype.slice.call(nl);
};
var removeElement = function removeElement(el) {
el.parentNode.removeChild(el);
};
var wrap = function wrap(element) {
var wrapper = create('div');
if (element.parentNode) {
if (element.nextSibling) {
element.parentNode.insertBefore(wrapper, element.nextSibling);
} else {
element.parentNode.appendChild(wrapper);
}
}
wrapper.appendChild(element);
return wrapper;
};
var polarToCartesian = function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
return {
x: centerX + radius * Math.cos(angleInRadians),
y: centerY + radius * Math.sin(angleInRadians)
};
};
var describeArc = function describeArc(x, y, radius, startAngle, endAngle) {
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var arcSweep = endAngle - startAngle <= 180 ? '0' : '1';
var d = ['M', start.x, start.y, 'A', radius, radius, 0, arcSweep, 0, end.x, end.y].join(' ');
return d;
};
var percentageArc = function percentageArc(x, y, radius, p) {
return describeArc(x, y, radius, 0, p * 360);
};
var CropArea = function () {
var resizers = {
n: function n(rect, offset, space, ratio) {
var t, r, b, l, w, h, p, d;
// bottom is fixed
b = rect.y + rect.height;
// intended top
t = limit(offset.y, 0, b);
// if is too small vertically
if (b - t < space.min.height) {
t = b - space.min.height;
}
// if should scale by ratio, pick width by ratio of new height
w = ratio ? (b - t) / ratio : rect.width;
// check if has fallen below min width or height
if (w < space.min.width) {
w = space.min.width;
t = b - w * ratio;
}
// add half to left and half to right edge
p = (w - rect.width) * 0.5;
l = rect.x - p;
r = rect.x + rect.width + p;
// check if any of the edges has moved out of the available space, if so,
// set max size of rectangle from original position
if (l < 0 || Math.round(r) > Math.round(space.width)) {
// smallest distance to edge of space
d = Math.min(rect.x, space.width - (rect.x + rect.width));
// new left and right offsets
l = rect.x - d;
r = rect.x + rect.width + d;
// resulting width
w = r - l;
// resulting height based on ratio
h = w * ratio;
// new top position
t = b - h;
}
return {
x: l,
y: t,
width: r - l,
height: b - t
};
},
s: function s(rect, offset, space, ratio) {
var t, r, b, l, w, h, p, d;
// top is fixed
t = rect.y;
// intended bottom
b = limit(offset.y, t, space.height);
// if is too small vertically
if (b - t < space.min.height) {
b = t + space.min.height;
}
// if should scale by ratio, pick width by ratio of new height
w = ratio ? (b - t) / ratio : rect.width;
// check if has fallen below min width or height
if (w < space.min.width) {
w = space.min.width;
b = t + w * ratio;
}
// add half to left and half to right edge
p = (w - rect.width) * 0.5;
l = rect.x - p;
r = rect.x + rect.width + p;
// check if any of the edges has moved out of the available space, if so,
// set max size of rectangle from original position
if (l < 0 || Math.round(r) > Math.round(space.width)) {
// smallest distance to edge of space
d = Math.min(rect.x, space.width - (rect.x + rect.width));
// new left and right offsets
l = rect.x - d;
r = rect.x + rect.width + d;
// resulting width
w = r - l;
// resulting height based on ratio
h = w * ratio;
// new bottom position
b = t + h;
}
return {
x: l,
y: t,
width: r - l,
height: b - t
};
},
e: function e(rect, offset, space, ratio) {
var t, r, b, l, w, h, p, d;
// left is fixed
l = rect.x;
// intended right edge
r = limit(offset.x, l, space.width);
// if is too small vertically
if (r - l < space.min.width) {
r = l + space.min.width;
}
// if should scale by ratio, pick height by ratio of new width
h = ratio ? (r - l) * ratio : rect.height;
// check if has fallen below min width or height
if (h < space.min.height) {
h = space.min.height;
r = l + h / ratio;
}
// add half to top and bottom
p = (h - rect.height) * 0.5;
t = rect.y - p;
b = rect.y + rect.height + p;
// check if any of the edges has moved out of the available space, if so,
// set max size of rectangle from original position
if (t < 0 || Math.round(b) > Math.round(space.height)) {
// smallest distance to edge of space
d = Math.min(rect.y, space.height - (rect.y + rect.height));
// new top and bottom offsets
t = rect.y - d;
b = rect.y + rect.height + d;
// resulting height
h = b - t;
// resulting width based on ratio
w = h / ratio;
// new right position
r = l + w;
}
return {
x: l,
y: t,
width: r - l,
height: b - t
};
},
w: function w(rect, offset, space, ratio) {
var t, r, b, l, w, h, p, d;
// right is fixed
r = rect.x + rect.width;
// intended left edge
l = limit(offset.x, 0, r);
// if is too small vertically
if (r - l < space.min.width) {
l = r - space.min.width;
}
// if should scale by ratio, pick height by ratio of new width
h = ratio ? (r - l) * ratio : rect.height;
// check if has fallen below min width or height
if (h < space.min.height) {
h = space.min.height;
l = r - h / ratio;
}
// add half to top and bottom
p = (h - rect.height) * 0.5;
t = rect.y - p;
b = rect.y + rect.height + p;
// check if any of the edges has moved out of the available space, if so,
// set max size of rectangle from original position
if (t < 0 || Math.round(b) > Math.round(space.height)) {
// smallest distance to edge of space
d = Math.min(rect.y, space.height - (rect.y + rect.height));
// new top and bottom offsets
t = rect.y - d;
b = rect.y + rect.height + d;
// resulting height
h = b - t;
// resulting width based on ratio
w = h / ratio;
// new right position
l = r - w;
}
return {
x: l,
y: t,
width: r - l,
height: b - t
};
},
ne: function ne(rect, offset, space, ratio) {
var t, r, b, l, w, h, d;
// left and bottom are fixed
l = rect.x;
b = rect.y + rect.height;
// intended right edge
r = limit(offset.x, l, space.width);
// if is too small vertically
if (r - l < space.min.width) {
r = l + space.min.width;
}
// if should scale by ratio, pick height by ratio of new width
h = ratio ? (r - l) * ratio : limit(b - offset.y, space.min.height, b);
// check if has fallen below min width or height
if (h < space.min.height) {
h = space.min.height;
r = l + h / ratio;
}
// add height difference with original height to top
t = rect.y - (h - rect.height);
// check if any of the edges has moved out of the available space, if so,
// set max size of rectangle from original position
if (t < 0 || Math.round(b) > Math.round(space.height)) {
// smallest distance to edge of space
d = Math.min(rect.y, space.height - (rect.y + rect.height));
// new top and bottom offsets
t = rect.y - d;
// resulting height
h = b - t;
// resulting width based on ratio
w = h / ratio;
// new right position
r = l + w;
}
return {
x: l,
y: t,
width: r - l,
height: b - t
};
},
se: function se(rect, offset, space, ratio) {
var t, r, b, l, w, h, d;
// left and top are fixed
l = rect.x;
t = rect.y;
// intended right edge
r = limit(offset.x, l, space.width);
// if is too small vertically
if (r - l < space.min.width) {
r = l + space.min.width;
}
// if should scale by ratio, pick height by ratio of new width
h = ratio ? (r - l) * ratio : limit(offset.y - rect.y, space.min.height, space.height - t);
// check if has fallen below min width or height
if (h < space.min.height) {
h = space.min.height;
r = l + h / ratio;
}
// add height difference with original height to bottom
b = rect.y + rect.height + (h - rect.height);
// check if any of the edges has moved out of the available space, if so,
// set max size of rectangle from original position
if (t < 0 || Math.round(b) > Math.round(space.height)) {
// smallest distance to edge of space
d = Math.min(rect.y, space.height - (rect.y + rect.height));
// new bottom offset
b = rect.y + rect.height + d;
// resulting height
h = b - t;
// resulting width based on ratio
w = h / ratio;
// new right position
r = l + w;
}
return {
x: l,
y: t,
width: r - l,
height: b - t
};
},
sw: function sw(rect, offset, space, ratio) {
var t, r, b, l, w, h, d;
// right and top are fixed
r = rect.x + rect.width;
t = rect.y;
// intended left edge
l = limit(offset.x, 0, r);
// if is too small vertically
if (r - l < space.min.width) {
l = r - space.min.width;
}
// if should scale by ratio, pick height by ratio of new width
h = ratio ? (r - l) * ratio : limit(offset.y - rect.y, space.min.height, space.height - t);
// check if has fallen below min width or height
if (h < space.min.height) {
h = space.min.height;
l = r - h / ratio;
}
// add height difference with original height to bottom
b = rect.y + rect.height + (h - rect.height);
// check if any of the edges has moved out of the available space, if so,
// set max size of rectangle from original position
if (t < 0 || Math.round(b) > Math.round(space.height)) {
// smallest distance to edge of space
d = Math.min(rect.y, space.height - (rect.y + rect.height));
// new bottom offset
b = rect.y + rect.height + d;
// resulting height
h = b - t;
// resulting width based on ratio
w = h / ratio;
// new left position
l = r - w;
}
return {
x: l,
y: t,
width: r - l,
height: b - t
};
},
nw: function nw(rect, offset, space, ratio) {
var t, r, b, l, w, h, d;
// right and bottom are fixed
r = rect.x + rect.width;
b = rect.y + rect.height;
// intended left edge
l = limit(offset.x, 0, r);
// if is too small vertically
if (r - l < space.min.width) {
l = r - space.min.width;
}
// if should scale by ratio, pick height by ratio of new width
h = ratio ? (r - l) * ratio : limit(b - offset.y, space.min.height, b);
// check if has fallen below min width or height
if (h < space.min.height) {
h = space.min.height;
l = r - h / ratio;
}
// add height difference with original height to bottom
t = rect.y - (h - rect.height);
// check if any of the edges has moved out of the available space, if so,
// set max size of rectangle from original position
if (t < 0 || Math.round(b) > Math.round(space.height)) {
// smallest distance to edge of space
d = Math.min(rect.y, space.height - (rect.y + rect.height));
// new bottom offset
t = rect.y - d;
// resulting height
h = b - t;
// resulting width based on ratio
w = h / ratio;
// new left position
l = r - w;
}
return {
x: l,
y: t,
width: r - l,
height: b - t
};
}
};
/**
* CropArea
*/
return function () {
function CropArea() {
var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document.createElement('div');
_classCallCheck(this, CropArea);
this._element = element;
this._interaction = null;
this._minWidth = 1;
this._minHeight = 1;
this._ratio = null;
this._rect = {
x: 0,
y: 0,
width: 0,
height: 0
};
this._space = {
width: 0,
height: 0
};
this._rectChanged = false;
this._init();
}
_createClass(CropArea, [{
key: '_init',
value: function _init() {
this._element.className = 'slim-crop-area';
// lines
var lines = create('div', 'grid');
this._element.appendChild(lines);
// corner & edge resize buttons
for (var handler in resizers) {
if (!resizers.hasOwnProperty(handler)) {
continue;
}
var _btn = create('button', handler);
this._element.appendChild(_btn);
}
var btn = create('button', 'c');
this._element.appendChild(btn);
addEvents(document, Events.DOWN, this);
}
}, {
key: 'reset',
value: function reset() {
this._interaction = null;
this._rect = {
x: 0,
y: 0,
width: 0,
height: 0
};
this._rectChanged = true;
this._redraw();
this._element.dispatchEvent(new CustomEvent('change'));
}
}, {
key: 'rescale',
value: function rescale(scale) {
// no rescale
if (scale === 1) {
return;
}
this._interaction = null;
this._rectChanged = true;
this._rect.x *= scale;
this._rect.y *= scale;
this._rect.width *= scale;
this._rect.height *= scale;
this._redraw();
this._element.dispatchEvent(new CustomEvent('change'));
}
}, {
key: 'limit',
value: function limit(width, height) {
this._space.width = width;
this._space.height = height;
}
}, {
key: 'offset',
value: function offset(x, y) {
this._space.x = x;
this._space.y = y;
}
}, {
key: 'resize',
value: function resize(x, y, width, height) {
this._interaction = null;
this._rect = {
x: limit(x, 0, this._space.width - this._minWidth),
y: limit(y, 0, this._space.height - this._minHeight),
width: limit(width, this._minWidth, this._space.width),
height: limit(height, this._minHeight, this._space.height)
};
this._rectChanged = true;
this._redraw();
this._element.dispatchEvent(new CustomEvent('change'));
}
}, {
key: 'handleEvent',
value: function handleEvent(e) {
switch (e.type) {
case 'touchstart':
case 'pointerdown':
case 'mousedown':
{
this._onStartDrag(e);
}
break;
case 'touchmove':
case 'pointermove':
case 'mousemove':
{
this._onDrag(e);
}
break;
case 'touchend':
case 'touchcancel':
case 'pointerup':
case 'mouseup':
{
this._onStopDrag(e);
}
}
}
}, {
key: '_onStartDrag',
value: function _onStartDrag(e) {
// is not my event?
if (!this._element.contains(e.target)) {
return;
}
e.preventDefault();
// listen to drag related events
addEvents(document, Events.MOVE, this);
addEvents(document, Events.UP, this);
this._interaction = {
type: e.target.className,
offset: getEventOffsetScroll(e)
};
this._interaction.offset.x -= this._rect.x;
this._interaction.offset.y -= this._rect.y;
// now dragging
this._element.setAttribute('data-dragging', 'true');
// start the redraw update loop
this._redraw();
}
}, {
key: '_onDrag',
value: function _onDrag(e) {
e.preventDefault();
// get local offset for this event
var offset = getEventOffsetScroll(e);
var type = this._interaction.type;
// drag
if (type === 'c') {
this._rect.x = limit(offset.x - this._interaction.offset.x, 0, this._space.width - this._rect.width);
this._rect.y = limit(offset.y - this._interaction.offset.y, 0, this._space.height - this._rect.height);
} else if (resizers[type]) {
// resize
this._rect = resizers[type](this._rect, {
x: offset.x - this._space.x,
y: offset.y - this._space.y
}, {
x: 0,
y: 0,
width: this._space.width,
height: this._space.height,
min: {
width: this._minWidth,
height: this._minHeight
}
}, this._ratio);
}
this._rectChanged = true;
// dispatch
this._element.dispatchEvent(new CustomEvent('input'));
}
}, {
key: '_onStopDrag',
value: function _onStopDrag(e) {
e.preventDefault();
// stop listening to drag related events
removeEvents(document, Events.MOVE, this);
removeEvents(document, Events.UP, this);
// no longer interacting, so no need to redraw
this._interaction = null;
// now dragging
this._element.setAttribute('data-dragging', 'false');
// fire change event
this._element.dispatchEvent(new CustomEvent('change'));
}
}, {
key: '_redraw',
value: function _redraw() {
var _this = this;
if (this._rectChanged) {
var transform = 'translate(' + this._rect.x + 'px,' + this._rect.y + 'px);';
this._element.style.cssText = '\n\t\t\t\t\t-webkit-transform: ' + transform + ';\n\t\t\t\t\ttransform: ' + transform + ';\n\t\t\t\t\twidth:' + this._rect.width + 'px;\n\t\t\t\t\theight:' + this._rect.height + 'px;\n\t\t\t\t';
this._rectChanged = false;
}
// if no longer interacting with crop area stop here
if (!this._interaction) {
return;
}
// redraw
requestAnimationFrame(function () {
return _this._redraw();
});
}
}, {
key: 'destroy',
value: function destroy() {
this._interaction = false;
this._rectChanged = false;
removeEvents(document, Events.DOWN, this);
removeEvents(document, Events.MOVE, this);
removeEvents(document, Events.UP, this);
removeElement(this._element);
}
}, {
key: 'element',
get: function get() {
return this._element;
}
}, {
key: 'space',
get: function get() {
return this._space;
}
}, {
key: 'area',
get: function get() {
var x = this._rect.x / this._space.width;
var y = this._rect.y / this._space.height;
var width = this._rect.width / this._space.width;
var height = this._rect.height / this._space.height;
return {
x: x,
y: y,
width: width,
height: height
};
}
}, {
key: 'dirty',
get: function get() {
return this._rect.x !== 0 || this._rect.y !== 0 || this._rect.width !== 0 || this._rect.height !== 0;
}
}, {
key: 'minWidth',
set: function set(value) {
this._minWidth = Math.max(value, 1);
}
}, {
key: 'minHeight',
set: function set(value) {
this._minHeight = Math.max(value, 1);
}
}, {
key: 'ratio',
set: function set(value) {
this._ratio = value;
}
}]);
return CropArea;
}();
}();
var ImageEditor = function () {
/**
* ImageEditor
* @param element
* @param options
* @constructor
*/
var CropAreaEvents = ['input', 'change'];
var ImageEditor = function () {
function ImageEditor() {
var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document.createElement('div');
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, ImageEditor);
this._element = element;
this._options = mergeOptions(ImageEditor.options(), options);
this._ratio = null;
this._output = null;
this._rotating = false;
this._input = null;
this._preview = null;
this._previewBlurred = null;
this._blurredPreview = false;
this._cropper = null;
this._straightCrop = null;
this._previewWrapper = null;
this._currentWindowSize = {};
this._btnGroup = null;
this._maskFrame = null;
this._dirty = false;
this._wrapperRotation = 0;
this._wrapperScale = 1.0;
this._init();
}
_createClass(ImageEditor, [{
key: '_init',
value: function _init() {
var _this2 = this;
this._element.className = 'slim-image-editor';
// container
this._container = create('div', 'slim-container');
// wrapper
this._wrapper = create('div', 'slim-wrapper');
// photo crop mark container
this._stage = create('div', 'slim-stage');
this._container.appendChild(this._stage);
// create crop marks
this._cropper = new CropArea();
CropAreaEvents.forEach(function (e) {
_this2._cropper.element.addEventListener(e, _this2);
});
this._stage.appendChild(this._cropper.element);
// canvas ghost
this._previewWrapper = create('div', 'slim-image-editor-preview slim-crop-preview');
this._previewBlurred = create('canvas', 'slim-crop-blur');
this._previewWrapper.appendChild(this._previewBlurred);
this._wrapper.appendChild(this._previewWrapper);
this._previewMask = create('div', 'slim-crop-mask');
this._preview = create('img');
this._previewMask.appendChild(this._preview);
this._cropper.element.appendChild(this._previewMask);
// buttons
this._btnGroup = create('div', 'slim-editor-btn-group');
ImageEditor.Buttons.forEach(function (c) {
var prop = capitalizeFirstLetter(c);
var label = _this2._options['button' + prop + 'Label'];
var title = _this2._options['button' + prop + 'Title'];
var className = _this2._options['button' + prop + 'ClassName'];
var btn = create('button', 'slim-editor-btn slim-btn-' + c + (className ? ' ' + className : ''));
btn.innerHTML = label;
btn.title = title || label;
btn.type = 'button';
btn.setAttribute('data-action', c);
btn.addEventListener('click', _this2);
_this2._btnGroup.appendChild(btn);
});
// utils
this._utilsGroup = create('div', 'slim-editor-utils-group');
// create rotation button
var btn = create('button', 'slim-editor-utils-btn slim-btn-rotate' + (this._options.buttonRotateClassName ? ' ' + this._options.buttonRotateClassName : ''));
btn.setAttribute('data-action', 'rotate');
btn.addEventListener('click', this);
btn.title = this._options.buttonRotateTitle;
this._utilsGroup.appendChild(btn);
this._container.appendChild(this._wrapper);
this._element.appendChild(this._container);
this._element.appendChild(this._utilsGroup);
this._element.appendChild(this._btnGroup);
}
}, {
key: 'dirty',
value: function dirty() {
this._dirty = true;
}
}, {
key: 'handleEvent',
value: function handleEvent(e) {
switch (e.type) {
case 'click':
this._onClick(e);
break;
case 'change':
this._onGridChange(e);
break;
case 'input':
this._onGridInput(e);
break;
case 'keydown':
this._onKeyDown(e);
break;
case 'resize':
this._onResize(e);
break;
}
}
}, {
key: '_onKeyDown',
value: function _onKeyDown(e) {
switch (e.keyCode) {
case Key.RETURN:
{
this._confirm();
break;
}
case Key.ESC:
{
this._cancel();
break;
}
}
}
}, {
key: '_onClick',
value: function _onClick(e) {
if (e.target.classList.contains('slim-btn-cancel')) {
this._cancel();
}
if (e.target.classList.contains('slim-btn-confirm')) {
this._confirm();
}
if (e.target.classList.contains('slim-btn-rotate')) {
this._rotate();
}
}
}, {
key: '_onResize',
value: function _onResize() {
// remember window size
this._currentWindowSize = {
width: window.innerWidth,
height: window.innerHeight
};
// redraw the image editor based on new dimensions
this._redraw();
this._redrawCropper(this._cropper.area);
this._updateWrapperScale();
// apply rotation and scale
this._redrawWrapper();
}
}, {
key: '_redrawWrapper',
value: function _redrawWrapper() {
var matrix = snabbt.createMatrix();
matrix.scale(this._wrapperScale, this._wrapperScale);
matrix.rotateZ(this._wrapperRotation * (Math.PI / 180));
snabbt.setElementTransform(this._previewWrapper, matrix);
}
}, {
key: '_onGridInput',
value: function _onGridInput() {
this._redrawCropMask();
}
}, {
key: '_onGridChange',
value: function _onGridChange() {
this._redrawCropMask();
}
}, {
key: '_updateWrapperRotation',
value: function _updateWrapperRotation() {
if (this._options.minSize.width > this._input.height || this._options.minSize.height > this._input.width) {
this._wrapperRotation += 180;
} else {
this._wrapperRotation += 90;
}
}
}, {
key: '_updateWrapperScale',
value: function _updateWrapperScale() {
// test if is tilted
var isTilted = this._wrapperRotation % 180 !== 0;
// if tilted determine correct scale factor
if (isTilted) {
// maximum size
var maxWidth = this._container.offsetWidth;
var maxHeight = this._container.offsetHeight;
// get wrapper size
var wrapperWidth = this._wrapper.offsetHeight;
var wrapperHeight = this._wrapper.offsetWidth;
var scalar = maxWidth / wrapperWidth;
if (scalar * wrapperHeight > maxHeight) {
scalar = maxHeight / wrapperHeight;
}
this._wrapperScale = scalar;
} else {
this._wrapperScale = 1.0;
}
}
/**
* Action handlers
*/
}, {
key: '_cancel',
value: function _cancel() {
if (this._rotating) {
return;
}
this._element.dispatchEvent(new CustomEvent('cancel'));
}
}, {
key: '_confirm',
value: function _confirm() {
if (this._rotating) {
return;
}
var isTilted = this._wrapperRotation % 180 !== 0;
var area = this._cropper.area;
var result = scaleRect(area, isTilted ? this._input.height : this._input.width, isTilted ? this._input.width : this._input.height);
this._element.dispatchEvent(new CustomEvent('confirm', {
detail: {
rotation: this._wrapperRotation % 360,
crop: result
}
}));
}
}, {
key: '_rotate',
value: function _rotate() {
var _this3 = this;
if (this._rotating) {
return;
}
this._rotating = true;
// rotate!
this._updateWrapperRotation();
// only pass current rect if is 1:1 or free
var rect = this.ratio === 1 || this._ratio === null ? this._cropper.area : null;
if (rect) {
rotate(rect, 90);
}
// wrapper might also need to be scaled
this._updateWrapperScale();
// hide the cropper
this._hideCropper();
// rotation effect
snabbt(this._previewWrapper, {
rotation: [0, 0, this._wrapperRotation * (Math.PI / 180)],
scale: [this._wrapperScale, this._wrapperScale],
easing: 'spring',
springConstant: 0.8,
springDeceleration: 0.65,
complete: function complete() {
// redraws auto cropper
_this3._redrawCropper(rect);
// shows the cropper
_this3._showCropper();
// done rotating
_this3._rotating = false;
}
});
}
}, {
key: '_showCropper',
value: function _showCropper() {
snabbt(this._stage, {
easing: 'ease',
duration: 250,
fromOpacity: 0,
opacity: 1.0
});
}
}, {
key: '_hideCropper',
value: function _hideCropper() {
snabbt(this._stage, {
duration: 0,
fromOpacity: 0,
opacity: 0
});
}
}, {
key: '_redrawCropMask',
value: function _redrawCropMask() {
var _this4 = this;
// get rotation
var rotation = this._wrapperRotation % 360;
// get scale
var scale = this._wrapperScale;
// get image size
var canvas = {
width: this._wrapper.offsetWidth,
height: this._wrapper.offsetHeight
};
// get default mask cropper area
var mask = this._cropper.area;
var preview = {
x: 0,
y: 0
};
if (rotation === 0) {
preview.x = -mask.x;
preview.y = -mask.y;
} else if (rotation === 90) {
preview.x = -(1 - mask.y);
preview.y = -mask.x;
} else if (rotation === 180) {
preview.x = -(1 - mask.x);
preview.y = -(1 - mask.y);
} else if (rotation === 270) {
preview.x = -mask.y;
preview.y = -(1 - mask.x);
}
// scale rect to fit canvas
preview.x *= canvas.width;
preview.y *= canvas.height;
// update on next frame (so it's in sync with crop area redraw)
cancelAnimationFrame(this._maskFrame);
this._maskFrame = requestAnimationFrame(function () {
var transform = 'scale(' + scale + ') rotate(' + -rotation + 'deg) translate(' + preview.x + 'px, ' + preview.y + 'px);';
_this4._preview.style.cssText = '\n\t\t\t\t\twidth: ' + _this4._previewSize.width + 'px;\n\t\t\t\t\theight: ' + _this4._previewSize.height + 'px;\n\t\t\t\t\t-webkit-transform: ' + transform + ';\n\t\t\t\t\ttransform: ' + transform + ';\n\t\t\t\t';
});
}
}, {
key: 'open',
value: function open(image, ratio, crop, rotation, complete) {
var _this5 = this;
// test if is same image
if (this._input && !this._dirty && this._ratio === ratio) {
complete();
return;
}
// remember current window size
this._currentWindowSize = {
width: window.innerWidth,
height: window.innerHeight
};
// reset dirty value
this._dirty = false;
// reset rotation
this._wrapperRotation = rotation || 0;
// we'll always have to blur the preview of a newly opened image
this._blurredPreview = false;
// set ratio
this._ratio = ratio;
// reset preview size
this._previewSize = null;
// hide editor
this._element.style.opacity = '0';
// set as new input image
this._input = image;
// calculate crop
var tilted = rotation % 180 !== 0;
var relativeCrop = divideRect(crop, tilted ? image.height : image.width, tilted ? image.width : image.height);
// preview has now loaded
this._preview.onload = function () {
// reset onload listener
_this5._preview.onload = null;
// setup cropper
_this5._cropper.ratio = _this5.ratio;
// redraw view (for first time)
_this5._redraw();
// redraw cropper
_this5._redrawCropper(relativeCrop);
// done
complete();
// fade in
_this5._element.style.opacity = '';
};
// load lower resolution preview image
this._preview.src = '';
this._preview.src = cloneCanvasScaled(this._input, Math.min(this._container.offsetWidth / this._input.width, this._container.offsetHeight / this._input.height) * this._options.devicePixelRatio).toDataURL();
}
}, {
key: '_redrawCropper',
value: function _redrawCropper(rect) {
var isTilted = this._wrapperRotation % 180 !== 0;
// image ratio
var imageRatio = isTilted ? this._input.height / this._input.width : this._input.width / this._input.height;
// get dimensions of image wrapper
var width = this._wrapper.offsetWidth;
var height = this._wrapper.offsetHeight;
// get space
var maxWidth = this._container.offsetWidth;
var maxHeight = this._container.offsetHeight;
// rescale wrapper
this._updateWrapperScale();
// position cropper container to fit wrapper
var sw = this._wrapperScale * (isTilted ? height : width);
var sh = this._wrapperScale * (isTilted ? width : height);
var sx = isTilted ? (maxWidth - sw) * 0.5 : this._wrapper.offsetLeft;
var sy = isTilted ? (maxHeight - sh) * 0.5 : this._wrapper.offsetTop;
this._stage.style.cssText = '\n\t\t\t\tleft:' + sx + 'px;\n\t\t\t\ttop:' + sy + 'px;\n\t\t\t\twidth:' + sw + 'px;\n\t\t\t\theight:' + sh + 'px;\n\t\t\t';
// set new size limit for crops
// use image ratio so we have exact amount of pixels
this._cropper.limit(sw, sw / imageRatio);
this._cropper.offset(sx + this._element.offsetLeft, sy + this._element.offsetTop);
// set min and max height of cropper
this._cropper.minWidth = this._wrapperScale * this._options.minSize.width * this.scalar;
this._cropper.minHeight = this._wrapperScale * this._options.minSize.height * this.scalar;
// set crop rect
var crop = null;
if (rect) {
crop = {
x: rect.x * sw,
y: rect.y * sh,
width: rect.width * sw,
height: rect.height * sh
};
} else {
crop = getAutoCropRect(sw, sh, this._ratio || sh / sw);
}
this._cropper.resize(crop.x, crop.y, crop.width, crop.height);
}
}, {
key: '_redraw',
value: function _redraw() {
// image ratio
var ratio = this._input.height / this._input.width;
// determine rounded size
var maxWidth = this._container.clientWidth;
var maxHeight = this._container.clientHeight;
var width = maxWidth;
var height = width * ratio;
if (height > maxHeight) {
height = maxHeight;
width = height / ratio;
}
width = Math.round(width);
height = Math.round(height);
// rescale and recenter wrapper
var x = (maxWidth - width) / 2;
var y = (maxHeight - height) / 2;
this._wrapper.style.cssText = '\n\t\t\t\tleft:' + x + 'px;\n\t\t\t\ttop:' + y + 'px;\n\t\t\t\twidth:' + width + 'px;\n\t\t\t\theight:' + height + 'px;\n\t\t\t';
// set image size based on container dimensions
this._previewBlurred.style.cssText = '\n\t\t\t\twidth:' + width + 'px;\n\t\t\t\theight:' + height + 'px;\n\t\t\t';
this._preview.style.cssText = '\n\t\t\t\twidth:' + width + 'px;\n\t\t\t\theight:' + height + 'px;\n\t\t\t';
this._previewSize = {
width: width,
height: height
};
// scale and blur the blurry preview
if (!this._blurredPreview) {
this._previewBlurred.width = 300;
this._previewBlurred.height = this._previewBlurred.width * ratio;
copyCanvas(this._input, this._previewBlurred);
blurCanvas(this._previewBlurred, 3);
this._blurredPreview = true;
}
}
}, {
key: 'show',
value: function show() {
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
// test if window size has changed since previous showing
if (this._currentWindowSize.width !== window.innerWidth || this._currentWindowSize.height !== window.innerHeight) {
// if so, reposition elements
this._redraw();
// redraw cropper position
this._redrawCropper(this._cropper.area);
}
// listen to keydown so we can close or confirm with RETURN / ESC
document.addEventListener('keydown', this);
// when window is scaled we want to resize the editor
window.addEventListener('resize', this);
// fade in preview
var rotation = this._wrapperRotation * (Math.PI / 180);
snabbt(this._previewWrapper, {
fromRotation: [0, 0, rotation],
rotation: [0, 0, rotation],
fromPosition: [0, 0, 0],
position: [0, 0, 0],
fromOpacity: 0,
opacity: 1,
fromScale: [this._wrapperScale - 0.02, this._wrapperScale - 0.02],
scale: [this._wrapperScale, this._wrapperScale],
easing: 'spring',
springConstant: 0.3,
springDeceleration: 0.85,
delay: 450,
complete: function complete() {
// don't reset transforms because we need rotation to stick
}
});
if (this._cropper.dirty) {
// now show cropper
snabbt(this._stage, {
fromPosition: [0, 0, 0],
position: [0, 0, 0],
fromOpacity: 0,
opacity: 1,
duration: 250,
delay: 850,
complete: function complete() {
resetTransforms(this);
callback();
}
});
} else {
// now show cropper
snabbt(this._stage, {
fromPosition: [0, 0, 0],
position: [0, 0, 0],
fromOpacity: 0,
opacity: 1,
duration: 250,
delay: 1000,
complete: function complete() {
resetTransforms(this);
}
});
}
// now show buttons
snabbt(this._btnGroup.childNodes, {
fromScale: [0.9, 0.9],
scale: [1, 1],
fromOpacity: 0,
opacity: 1,
delay: function delay(i) {
return 1000 + i * 100;
},
easing: 'spring',
springConstant: 0.3,
springDeceleration: 0.85,
complete: function complete() {
resetTransforms(this);
}
});
snabbt(this._utilsGroup.childNodes, {
fromScale: [0.9, 0.9],
scale: [1, 1],
fromOpacity: 0,
opacity: 1,
easing: 'spring',
springConstant: 0.3,
springDeceleration: 0.85,
delay: 1250,
complete: function complete() {
resetTransforms(this);
}
});
}
}, {
key: 'hide',
value: function hide() {
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
// no more need to listen to keydown
document.removeEventListener('keydown', this);
// no more need to resize editor when window is scaled
window.removeEventListener('resize', this);
// fade out buttons
snabbt(this._utilsGroup.childNodes, {
fromOpacity: 1,
opacity: 0,
duration: 250
});
snabbt(this._btnGroup.childNodes, {
fromOpacity: 1,
opacity: 0,
delay: 200,
duration: 350
});
snabbt([this._stage, this._previewWrapper], {
fromPosition: [0, 0, 0],
position: [0, -250, 0],
fromOpacity: 1,
opacity: 0,
easing: 'spring',
springConstant: 0.3,
springDeceleration: 0.75,
delay: 250,
allDone: function allDone() {
callback();
}
});
}
}, {
key: 'destroy',
value: function destroy() {
var _this6 = this;
nodeListToArray(this._btnGroup.children).forEach(function (btn) {
btn.removeEventListener('click', _this6);
});
CropAreaEvents.forEach(function (e) {
_this6._cropper.element.removeEventListener(e, _this6);
});
this._cropper.destroy();
// if still attached to DOM, detach
if (this._element.parentNode) {
removeElement(this._element);
}
}
}, {
key: 'showRotateButton',
set: function set(enabled) {
if (enabled) {
this._element.classList.remove('slim-rotation-disabled');
} else {
this._element.classList.add('slim-rotation-disabled');
}
}
}, {
key: 'element',
get: function get() {
return this._element;
}
}, {
key: 'ratio',
get: function get() {
if (this._ratio === 'input') {
return this._input.height / this._input.width;
}
return this._ratio;
}
}, {
key: 'offset',
get: function get() {
return this._element.getBoundingClientRect();
}
}, {
key: 'original',
get: function get() {
return this._input;
}
}, {
key: 'scalar',
get: function get() {
return this._previewSize.width / this._input.width;
}
}], [{
key: 'options',
value: function options() {
return {
buttonCancelClassName: null,
buttonConfirmClassName: null,
buttonCancelLabel: 'Cancel',
buttonConfirmLabel: 'Confirm',
buttonCancelTitle: null,
buttonConfirmTitle: null,
buttonRotateTitle: 'Rotate',
buttonRotateClassName: null,
devicePixelRatio: null,
minSize: {
width: 0,
height: 0
}
};
}
}]);
return ImageEditor;
}();
ImageEditor.Buttons = ['cancel', 'confirm'];
return ImageEditor;
}(CropArea);
var FileHopper = function () {
/**
* FileHopper
* @param element
* @param options
* @constructor
*/
var DragDropEvents = ['dragenter', 'dragover', 'dragleave', 'drop'];
return function () {
function FileHopper() {
var element = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : document.createElement('div');
_classCallCheck(this, FileHopper);
this._element = element;
this._accept = [];
this._allowURLs = false;
this._dragPath = null;
this._init();
}
// public properties
_createClass(FileHopper, [{
key: 'isValidDataTransfer',
value: function isValidDataTransfer(dataTransfer) {
if (dataTransfer.files && dataTransfer.files.length) {
return this.areValidDataTransferFiles(dataTransfer.files);
}
if (dataTransfer.items && dataTransfer.items.length) {
return this.areValidDataTransferItems(dataTransfer.items);
}
return null;
}
}, {
key: 'areValidDataTransferFiles',
value: function areValidDataTransferFiles(files) {
if (this._accept.length && files) {
return this._accept.indexOf(files[0].type) !== -1;
}
return true;
}
}, {
key: 'areValidDataTransferItems',
value: function areValidDataTransferItems(items) {
if (this._accept.length && items) {
// is possibly dropping url
if (this._allowURLs && items[0].kind === 'string') {
return null;
}
// is http website so firefox will not return file type
if (items[0].type && items[0].type.indexOf('application') === 0) {
return null;
}
return this._accept.indexOf(items[0].type) !== -1;
}
return true;
}
// public methods
}, {
key: 'reset',
value: function reset() {
this._element.files = null;
}
// private
}, {
key: '_init',
value: function _init() {
var _this7 = this;
this._element.className = 'slim-file-hopper';
DragDropEvents.forEach(function (e) {
_this7._element.addEventListener(e, _this7);
});
}
// the input has changed
}, {
key: 'handleEvent',
value: function handleEvent(e) {
switch (e.type) {
case 'dragenter':
case 'dragover':
this._onDragOver(e);
break;
case 'dragleave':
this._onDragLeave(e);
break;
case 'drop':
this._onDrop(e);
break;
}
}
}, {
key: '_onDrop',
value: function _onDrop(e) {
// prevents browser from opening image
e.preventDefault();
// test if a url was dropped
var remote = null;
if (this._allowURLs) {
var url = void 0;
var meta = void 0;
try {
url = e.dataTransfer.getData('url');
meta = e.dataTransfer.getData('text/html');
} catch (e) {
// nope nope nope (ie11 trouble)
}
if (meta && meta.length) {
var parsed = meta.match(/src\s*=\s*"(.+?)"/);
if (parsed) {
remote = parsed[1];
}
} else if (url && url.length) {
remote = url;
}
}
if (remote) {
this._element.files = [{ remote: remote }];
} else {
// nope, must have been a file
// test type in older browsers
var filesValidity = this.isValidDataTransfer(e.dataTransfer);
if (!filesValidity) {
// invalid files, stop here
this._element.dispatchEvent(new CustomEvent('file-invalid-drop'));
// no longer hovering
this._dragPath = null;
return;
}
// store dropped files on element files property, so can be accessed by same function as file input handler
this._element.files = e.dataTransfer.files;
}
// file has been dropped
this._element.dispatchEvent(new CustomEvent('file-drop', {
detail: getOffsetByEvent(e)
}));
// file list has changed, let's notify others
this._element.dispatchEvent(new CustomEvent('change'));
// no longer hovering
this._dragPath = null;
}
}, {
key: '_onDragOver',
value: function _onDragOver(e) {
// prevents browser from opening image
e.preventDefault();
// set drop effect
e.dataTransfer.dropEffect = 'copy';
// determin if is valid data
var dataValidity = this.isValidDataTransfer(e.dataTransfer);
// if validity is null is undetermined
if (dataValidity !== null && !dataValidity) {
// indicate drop mode to user
e.dataTransfer.dropEffect = 'none';
// invalid files, stop here
this._element.dispatchEvent(new CustomEvent('file-invalid'));
return;
}
// now hovering file over the area
if (!this._dragPath) {
this._dragPath = [];
}
// push location
this._dragPath.push(getOffsetByEvent(e));
// file is hovering over element
this._element.dispatchEvent(new CustomEvent('file-over', {
detail: {
x: last(this._dragPath).x,
y: last(this._dragPath).y
}
}));
}
}, {
key: '_onDragLeave',
value: function _onDragLeave(e) {
// user has dragged file out of element area
this._element.dispatchEvent(new CustomEvent('file-out', {
detail: getOffsetByEvent(e)
}));
// now longer hovering file
this._dragPath = null;
}
}, {
key: 'destroy',
value: function destroy() {
var _this8 = this;
DragDropEvents.forEach(function (e) {
_this8._element.removeEventListener(e, _this8);
});
removeElement(this._element);
this._element = null;
this._dragPath = null;
this._accept = null;
}
}, {
key: 'element',
get: function get() {
return this._element;
}
}, {
key: 'dragPath',
get: function get() {
return this._dragPath;
}
}, {
key: 'enabled',
get: function get() {
return this._element.style.display === '';
},
set: function set(value) {
this._element.style.display = value ? '' : 'none';
}
}, {
key: 'allowURLs',
set: function set(value) {
this._allowURLs = value;
}
}, {
key: 'accept',
set: function set(mimetypes) {
this._accept = mimetypes;
},
get: function get() {
return this._accept;
}
}]);
return FileHopper;
}();
}();
var Popover = function () {
/**
* Popover
*/
return function () {
function Popover() {
_classCallCheck(this, Popover);
this._element = null;
this._inner = null;
this._init();
}
_createClass(Popover, [{
key: '_init',
value: function _init() {
this._element = create('div', 'slim-popover');
this._element.setAttribute('data-state', 'off');
document.body.appendChild(this._element);
// prevent body scrolling on iOS 11.3
this._element.addEventListener('touchmove', function (e) {
e.preventDefault();
}, true);
}
}, {
key: 'show',
value: function show() {
var _this9 = this;
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
// turn on
this._element.setAttribute('data-state', 'on');
// show and animate in
snabbt(this._element, {
fromOpacity: 0,
opacity: 1,
duration: 350,
complete: function complete() {
// clean up transforms
resetTransforms(_this9._element);
// ready!
callback();
}
});
}
}, {
key: 'hide',
value: function hide() {
var _this10 = this;
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
// animate out and hide
snabbt(this._element, {
fromOpacity: 1,
opacity: 0,
duration: 500,
complete: function complete() {
// clean up transforms
resetTransforms(_this10._element);
// hide the editor
_this10._element.setAttribute('data-state', 'off');
// ready!
callback();
}
});
}
}, {
key: 'destroy',
value: function destroy() {
if (!this._element.parentNode) {
return;
}
this._element.parentNode.removeChild(this._element);
this._element = null;
this._inner = null;
}
}, {
key: 'inner',
set: function set(element) {
this._inner = element;
if (this._element.firstChild) {
this._element.removeChild(this._element.firstChild);
}
this._element.appendChild(this._inner);
}
}, {
key: 'className',
set: function set(value) {
this._element.className = 'slim-popover' + (value === null ? '' : ' ' + value);
}
}]);
return Popover;
}();
}();
var intSplit = function intSplit(v, c) {
return v.split(c).map(function (v) {
return parseInt(v, 10);
});
};
var isWrapper = function isWrapper(element) {
return element.nodeName === 'DIV' || element.nodeName === 'SPAN';
};
var CropType = {
AUTO: 'auto',
INITIAL: 'initial',
MANUAL: 'manual'
};
var Rect = ['x', 'y', 'width', 'height'];
var HopperEvents = ['file-invalid-drop', 'file-invalid', 'file-drop', 'file-over', 'file-out', 'click'];
var ImageEditorEvents = ['cancel', 'confirm'];
var SlimButtons = ['remove', 'edit', 'download', 'upload'];
var SlimPopover = null;
var SlimCount = 0;
var SlimLoaderHTML = '\n<div class="slim-loader">\n\t<svg>\n\t\t<path class="slim-loader-background" fill="none" stroke-width="3" />\n\t\t<path class="slim-loader-foreground" fill="none" stroke-width="3" />\n\t</svg>\n</div>\n';
var SlimUploadStatusHTML = '\n<div class="slim-upload-status"></div>\n';
var stringToSize = function stringToSize(str) {
var size = str.split(',');
return {
width: parseInt(size[0], 10),
height: parseInt(size[1], 10)
};
};
var Slim = function () {
function Slim(element) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
_classCallCheck(this, Slim);
// create popover element if not already created
if (!SlimPopover) {
SlimPopover = new Popover();
}
// we create a new counter, we use this counter to determine if we need to clean up the popover
this._uid = SlimCount++;
// the options to use
this._options = mergeOptions(Slim.options(), options);
// test options correctness
if (this._options.forceSize) {
if (typeof this._options.forceSize === 'string') {
this._options.forceSize = stringToSize(this._options.forceSize);
}
this._options.ratio = this._options.forceSize.width + ':' + this._options.forceSize.height;
this._options.size = clone(this._options.forceSize);
}
// test for size set as string
if (typeof this._options.size === 'string') {
this._options.size = stringToSize(this._options.size);
}
// test for size set as string
if (typeof this._options.minSize === 'string') {
this._options.minSize = stringToSize(this._options.minSize);
}
// fix post formatting if is supplied as string
if (typeof this._options.post === 'string') {
this._options.post = this._options.post.split(',').map(function (part) {
return part.trim();
});
}
// reference to original element so we can restore original situation
this._originalElement = element;
this._originalElementInner = element.innerHTML;
this._originalElementAttributes = getElementAttributes(element);
// should be file input, image or slim wrapper, if not wrapper, wrap
if (!isWrapper(element)) {
this._element = wrap(element);
this._element.className = element.className;
element.className = '';
// ratio is used for CSS so let's set default attribute
this._element.setAttribute('data-ratio', this._options.ratio);
} else {
this._element = element;
}
this._element.classList.add('slim');
this._element.setAttribute('data-state', 'init');
// editor state
this._state = [];
// internal timer array for async processes
this._timers = [];
// the source element (can either be an input or an img)
this._input = null;
// the source element unique name if is input type file
this._inputReference = null;
// the output element (hidden input that contains the resulting json object)
this._output = null;
// current image ratio
this._ratio = null;
// was required field
this._isRequired = false;
// components
this._imageHopper = null;
this._imageEditor = null;
// progress path
this._progressEnabled = true;
// resulting data
this._data = {};
this._resetData();
// the circle below the mouse hover point
this._drip = null;
// had initial image
this._hasInitialImage = false;
// initial crop
this._initialCrop = this._options.crop;
// initial rotation
this._initialRotation = this._options.rotation && this._options.rotation % 90 === 0 ? this._options.rotation : null;
// set to true when destroy method is called, used to halt any timeouts or async processes
this._isBeingDestroyed = false;
// stop here if not supported
if (!Slim.supported) {
this._fallback();
} else {
// let's go!
this._init();
}
}
_createClass(Slim, [{
key: 'setRotation',
value: function setRotation(rotation, callback) {
if (typeof rotation !== 'number' && rotation % 90 !== 0) {
return;
}
this._data.actions.rotation = rotation;
var isTilted = this._data.actions.rotation % 180 !== 0;
if (this._data.input.image) {
var w = isTilted ? this._data.input.image.height : this._data.input.image.width;
var h = isTilted ? this._data.input.image.width : this._data.input.image.height;
this._data.actions.crop = getAutoCropRect(w, h, this._ratio);
this._data.actions.crop.type = CropType.AUTO;
}
if (this._data.input.image && callback) {
this._manualTransform(callback);
}
}
}, {
key: 'setSize',
value: function setSize(dimensions, callback) {
if (typeof dimensions === 'string') {
dimensions = stringToSize(dimensions);
}
if (!dimensions || !dimensions.width || !dimensions.height) {
return;
}
this._options.size = clone(dimensions);
this._data.actions.size = clone(dimensions);
// if a crop area is set do re-crop to conform to size
if (this._data.input.image && callback) {
this._manualTransform(callback);
}
}
}, {
key: 'setForceSize',
value: function setForceSize(dimensions, callback) {
if (typeof dimensions === 'string') {
dimensions = stringToSize(dimensions);
}
if (!dimensions || !dimensions.width || !dimensions.height) {
return;
}
this._options.size = clone(dimensions);
this._options.forceSize = clone(dimensions);
this._data.actions.size = clone(dimensions);
this.setRatio(this._options.forceSize.width + ':' + this._options.forceSize.height, callback);
}
}, {
key: 'setRatio',
value: function setRatio(ratio, callback) {
var _this11 = this;
if (!ratio || typeof ratio !== 'string') {
return;
}
this._options.ratio = ratio;
if (this._isFixedRatio()) {
var parts = intSplit(this._options.ratio, ':');
this._ratio = parts[1] / parts[0];
if (this._data.input.image && callback) {
this._cropAuto(function (data) {
_this11._scaleDropArea(_this11._ratio);
if (callback) {
callback(data);
}
});
} else {
if (this._data.input.image) {
this._data.actions.crop = getAutoCropRect(this._data.input.image.width, this._data.input.image.height, this._ratio);
this._data.actions.crop.type = CropType.AUTO;
}
this._scaleDropArea(this._ratio);
if (callback) {
callback(null);
}
}
}
}
// methods
// Test if this Slim object has been bound to the given element
}, {
key: 'isAttachedTo',
value: function isAttachedTo(element) {
return this._element === element || this._originalElement === element;
}
}, {
key: 'isDetached',
value: function isDetached() {
return this._element.parentNode === null;
}
}, {
key: 'load',
value: function load(src) {
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
var callback = arguments[2];
if (typeof options === 'function') {
callback = options;
} else {
// store in options
this._options.crop = options.crop;
// set rotation
this._options.rotation = options.rotation;
// initial rotation
this._initialRotation = options.rotation && options.rotation % 90 === 0 ? options.rotation : null;
// set initial crop
this._initialCrop = this._options.crop;
}
this._load(src, callback, { blockPush: options.blockPush });
}
}, {
key: 'upload',
value: function upload(callback) {
this._doUpload(callback);
}
}, {
key: 'download',
value: function download() {
this._doDownload();
}
}, {
key: 'remove',
value: function remove() {
return this._doRemove();
}
}, {
key: 'destroy',
value: function destroy() {
this._doDestroy();
}
}, {
key: 'edit',
value: function edit() {
this._doEdit();
}
}, {
key: 'crop',
value: function crop(rect, callback) {
this._crop(rect.x, rect.y, rect.width, rect.height, callback);
}
}, {
key: 'containsImage',
value: function containsImage() {
return this._data.input.name !== null;
}
/**
* State Related
*/
}, {
key: '_canInstantEdit',
value: function _canInstantEdit() {
return this._options.instantEdit && !this._isInitialising;
}
}, {
key: '_getFileInput',
value: function _getFileInput() {
return this._element.querySelector('input[type=file]');
}
}, {
key: '_getInitialImage',
value: function _getInitialImage() {
return this._element.querySelector('img');
}
}, {
key: '_getInputElement',
value: function _getInputElement() {
return this._getFileInput() || this._getInitialImage();
}
}, {
key: '_getRatioSpacerElement',
value: function _getRatioSpacerElement() {
return this._element.children[0];
}
}, {
key: '_isImageOnly',
value: function _isImageOnly() {
return this._input.nodeName !== 'INPUT';
}
}, {
key: '_isFixedRatio',
value: function _isFixedRatio() {
return this._options.ratio.indexOf(':') !== -1;
}
}, {
key: '_isAutoCrop',
value: function _isAutoCrop() {
return this._data.actions.crop.type === CropType.AUTO;
}
}, {
key: '_toggleButton',
value: function _toggleButton(action, state) {
toggleDisplayBySelector('.slim-btn[data-action="' + action + '"]', state, this._element);
}
}, {
key: '_clearState',
value: function _clearState() {
this._state = [];
this._updateState();
}
}, {
key: '_removeState',
value: function _removeState(state) {
this._state = this._state.filter(function (item) {
return item !== state;
});
this._updateState();
}
}, {
key: '_addState',
value: function _addState(state) {
if (inArray(state, this._state)) {
return;
}
this._state.push(state);
this._updateState();
}
}, {
key: '_updateState',
value: function _updateState() {
if (!this._element) {
return;
}
this._element.setAttribute('data-state', this._state.join(','));
}
}, {
key: '_resetData',
value: function _resetData() {
// resets data object
this._data = {
server: null,
meta: clone(this._options.meta),
input: {
field: this._inputReference,
name: null,
type: null,
width: 0,
height: 0,
file: null
},
output: {
image: null,
width: 0,
height: 0
},
actions: {
rotation: null,
crop: null,
size: null
}
};
// resets hidden input clone (has not yet been created when reset call in constructor, hence the check)
if (this._output) {
this._output.value = '';
}
// should reset file input
resetFileInput(this._getFileInput());
}
/**
* Initialisation
*/
}, {
key: '_init',
value: function _init() {
var _this12 = this;
// busy initialising
this._isInitialising = true;
// cropper root is not file input
this._addState('empty');
// define input reference field name
if (inArray('input', this._options.post)) {
this._inputReference = 'slim_input_' + this._uid;
}
// get input element
this._input = this._getInputElement();
// if no input supplied we'll automatically create one
if (!this._input) {
this._input = create('input');
this._input.type = 'file';
this._element.appendChild(this._input);
}
// get required state of input element
this._isRequired = this._input.required === true;
// set or create output field
this._output = this._element.querySelector('input[type=hidden]');
// if no output element defined we'll create one automagically
if (!this._output) {
this._output = create('input');
this._output.type = 'hidden';
this._output.name = this._input.name || this._options.defaultInputName;
this._element.appendChild(this._output);
} else {
var initialData = null;
try {
initialData = JSON.parse(this._output.value);
} catch (e) {}
if (initialData) {
var img = new Image();
img.src = initialData.output.image;
img.setAttribute('data-filename', initialData.output.name);
this._element.insertBefore(img, this._element.firstChild);
}
}
// prevent the original file input field from posting (value will be duplicated to output field)
this._input.removeAttribute('name');
// create drop area
var area = create('div', 'slim-area');
// test if contains initial image
var initialImage = this._getInitialImage();
var initialImageSrc = (initialImage || {}).src;
var initialImageName = initialImage ? initialImage.getAttribute('data-filename') : null;
if (initialImageSrc) {
this._hasInitialImage = true;
} else {
this._initialCrop = null;
this._initialRotation = null;
}
var resultHTML = '\n\t\t<div class="slim-result">\n\t\t\t<img class="in" style="opacity:0" ' + (initialImageSrc ? 'src="' + initialImageSrc + '"' : '') + '><img><img style="opacity:0">\n\t\t</div>';
// add drop overlay
if (this._isImageOnly()) {
area.innerHTML = '\n\t\t\t\t' + SlimLoaderHTML + '\n\t\t\t\t' + SlimUploadStatusHTML + '\n\t\t\t\t' + resultHTML + '\n\t\t\t\t<div class="slim-status"><div class="slim-label-loading">' + (this._options.labelLoading || '') + '</div></div>\n\t\t\t';
} else {
// if should post input data
if (inArray('input', this._options.post)) {
this._data.input.field = this._inputReference;
// if is sync post
// and should post input data
if (!this._options.service) {
this._input.name = this._inputReference;
}
}
// set common image mime type to the accept attribute
var mimetypes = void 0;
if (!this._input.hasAttribute('accept') || this._input.getAttribute('accept') === 'image/*') {
mimetypes = getCommonMimeTypes();
this._input.setAttribute('accept', mimetypes.join(','));
} else {
mimetypes = this._input.accept.split(',').map(function (mimetype) {
return mimetype.trim();
}).filter(function (mimetype) {
return mimetype.length > 0;
});
}
// setup hopper
this._imageHopper = new FileHopper();
this._imageHopper.accept = mimetypes;
this._imageHopper.allowURLs = typeof this._options.fetcher === 'string';
this._element.appendChild(this._imageHopper.element);
HopperEvents.forEach(function (e) {
_this12._imageHopper.element.addEventListener(e, _this12);
});
// render area
area.innerHTML = '\n\t\t\t\t' + SlimLoaderHTML + '\n\t\t\t\t' + SlimUploadStatusHTML + '\n\t\t\t\t<div class="slim-drip"><span><span></span></span></div>\n\t\t\t\t<div class="slim-status"><div class="slim-label">' + (this._options.label || '') + '</div><div class="slim-label-loading">' + (this._options.labelLoading || '') + '</div></div>\n\t\t\t\t' + resultHTML + '\n\t\t\t';
// start listening for events so we can load image on file input change
this._input.addEventListener('change', this);
}
// add area node
this._element.appendChild(area);
// add button group
this._btnGroup = create('div', 'slim-btn-group');
this._btnGroup.style.display = 'none';
this._element.appendChild(this._btnGroup);
SlimButtons.filter(function (c) {
return _this12._isButtonAllowed(c);
}).forEach(function (c) {
var prop = capitalizeFirstLetter(c);
var label = _this12._options['button' + prop + 'Label'];
var title = _this12._options['button' + prop + 'Title'] || label;
var className = _this12._options['button' + prop + 'ClassName'];
var btn = create('button', 'slim-btn slim-btn-' + c + (className ? ' ' + className : ''));
btn.innerHTML = label;
btn.title = title;
btn.type = 'button';
btn.addEventListener('click', _this12);
btn.setAttribute('data-action', c);
_this12._btnGroup.appendChild(btn);
});
// add ratio padding
if (this._isFixedRatio()) {
var parts = intSplit(this._options.ratio, ':');
this._ratio = parts[1] / parts[0];
this._scaleDropArea(this._ratio);
}
// setup loader
this._updateProgress(0.5);
// preload source
if (initialImageSrc) {
this._load(initialImageSrc, function () {
_this12._onInit();
}, { name: initialImageName });
} else {
this._onInit();
}
}
}, {
key: '_onInit',
value: function _onInit() {
var _this13 = this;
// we're done initialising
this._isInitialising = false;
// done initialising now, else is only called after image load
var done = function done() {
// we call this async so the constructor of Slim has returned before the onInit is called, allowing clean immidiate destroy
var timer = setTimeout(function () {
_this13._options.didInit.apply(_this13, [_this13.data, _this13]);
}, 0);
_this13._timers.push(timer);
};
// save initial image
if (this._options.saveInitialImage && this.containsImage()) {
if (!this._options.service) {
this._save(function () {
done();
}, false);
}
} else {
// by default upload button is disabled for existing images
if (this._options.service && this.containsImage()) {
this._toggleButton('upload', false);
}
done();
}
}
}, {
key: '_updateProgress',
value: function _updateProgress(progress) {
progress = Math.min(0.99999, progress);
if (!this._element) {
return;
}
if (!this._progressEnabled) {
return;
}
var loader = this._element.querySelector('.slim-loader');
if (!loader) {
return;
}
var size = loader.offsetWidth;
var paths = loader.querySelectorAll('path');
var ringWidth = parseInt(paths[0].getAttribute('stroke-width'), 10);
paths[0].setAttribute('d', percentageArc(size * 0.5, size * 0.5, size * 0.5 - ringWidth, 0.9999));
paths[1].setAttribute('d', percentageArc(size * 0.5, size * 0.5, size * 0.5 - ringWidth, progress));
}
}, {
key: '_startProgress',
value: function _startProgress(cb) {
var _this14 = this;
if (!this._element) {
return;
}
this._progressEnabled = false;
var loader = this._element.querySelector('.slim-loader');
if (!loader) {
return;
}
var ring = loader.children[0];
this._stopProgressLoop(function () {
loader.removeAttribute('style');
ring.removeAttribute('style');
_this14._progressEnabled = true;
_this14._updateProgress(0);
_this14._progressEnabled = false;
snabbt(ring, {
fromOpacity: 0,
opacity: 1,
duration: 250,
complete: function complete() {
_this14._progressEnabled = true;
if (cb) {
cb();
}
}
});
});
}
}, {
key: '_stopProgress',
value: function _stopProgress() {
var _this15 = this;
if (!this._element) {
return;
}
var loader = this._element.querySelector('.slim-loader');
if (!loader) {
return;
}
var ring = loader.children[0];
this._updateProgress(1);
snabbt(ring, {
fromOpacity: 1,
opacity: 0,
duration: 250,
complete: function complete() {
loader.removeAttribute('style');
ring.removeAttribute('style');
_this15._updateProgress(0.5);
_this15._progressEnabled = false;
}
});
}
}, {
key: '_startProgressLoop',
value: function _startProgressLoop() {
if (!this._element) {
return;
}
// start loading animation
var loader = this._element.querySelector('.slim-loader');
if (!loader) {
return;
}
var ring = loader.children[0];
loader.removeAttribute('style');
ring.removeAttribute('style');
// set infinite load state
this._updateProgress(0.5);
// repeat!
var repeat = 1000;
snabbt(loader, 'stop');
snabbt(loader, {
rotation: [0, 0, -(Math.PI * 2) * repeat],
easing: 'linear',
duration: repeat * 1000
});
snabbt(ring, {
fromOpacity: 0,
opacity: 1,
duration: 250
});
}
}, {
key: '_stopProgressLoop',
value: function _stopProgressLoop(callback) {
if (!this._element) {
return;
}
var loader = this._element.querySelector('.slim-loader');
if (!loader) {
return;
}
var ring = loader.children[0];
snabbt(ring, {
fromOpacity: parseFloat(ring.style.opacity),
opacity: 0,
duration: 250,
complete: function complete() {
snabbt(loader, 'stop');
loader.removeAttribute('style');
ring.removeAttribute('style');
if (callback) {
callback();
}
}
});
}
}, {
key: '_isButtonAllowed',
value: function _isButtonAllowed(button) {
if (button === 'edit') {
return this._options.edit;
}
if (button === 'download') {
return this._options.download;
}
if (button === 'upload') {
// if no service defined upload button makes no sense
if (!this._options.service) {
return false;
}
// if push mode is set, no need for upload button
if (this._options.push) {
return false;
}
// set upload button
return true;
}
if (button === 'remove') {
return !this._isImageOnly();
}
return true;
}
}, {
key: '_fallback',
value: function _fallback() {
// create status area
var area = create('div', 'slim-area');
area.innerHTML = '\n\t\t\t<div class="slim-status"><div class="slim-label">' + (this._options.label || '') + '</div></div>\n\t\t';
this._element.appendChild(area);
this._throwError(this._options.statusNoSupport);
}
/**
* Event routing
*/
}, {
key: 'handleEvent',
value: function handleEvent(e) {
switch (e.type) {
case 'click':
this._onClick(e);
break;
case 'change':
this._onChange(e);
break;
case 'cancel':
this._onCancel(e);
break;
case 'confirm':
this._onConfirm(e);
break;
case 'file-over':
this._onFileOver(e);
break;
case 'file-out':
this._onFileOut(e);
break;
case 'file-drop':
this._onDropFile(e);
break;
case 'file-invalid':
this._onInvalidFile(e);
break;
case 'file-invalid-drop':
this._onInvalidFileDrop(e);
break;
}
}
}, {
key: '_getIntro',
value: function _getIntro() {
return this._element.querySelector('.slim-result .in');
}
}, {
key: '_getOutro',
value: function _getOutro() {
return this._element.querySelector('.slim-result .out');
}
}, {
key: '_getInOut',
value: function _getInOut() {
return this._element.querySelectorAll('.slim-result img');
}
}, {
key: '_getDrip',
value: function _getDrip() {
if (!this._drip) {
this._drip = this._element.querySelector('.slim-drip > span');
}
return this._drip;
}
// errors
}, {
key: '_throwError',
value: function _throwError(error) {
this._addState('error');
this._element.querySelector('.slim-label').style.display = 'none';
var node = this._element.querySelector('.slim-error');
if (!node) {
node = create('div', 'slim-error');
this._element.querySelector('.slim-status').appendChild(node);
}
node.innerHTML = error;
this._options.didThrowError.apply(this, [error]);
}
}, {
key: '_removeError',
value: function _removeError() {
this._removeState('error');
this._element.querySelector('.slim-label').style.display = '';
var error = this._element.querySelector('.slim-error');
if (error) {
error.parentNode.removeChild(error);
}
}
}, {
key: '_openFileDialog',
value: function _openFileDialog() {
this._removeError();
this._input.click();
}
// drop area clicked
}, {
key: '_onClick',
value: function _onClick(e) {
var _this16 = this;
var list = e.target.classList;
var target = e.target;
// no preview, so possible to drop file
if (list.contains('slim-file-hopper')) {
e.preventDefault();
this._openFileDialog();
return;
}
// decide what button was clicked
switch (target.getAttribute('data-action')) {
case 'remove':
this._options.willRemove.apply(this, [this.data, function () {
_this16._doRemove();
}]);
break;
case 'edit':
this._doEdit();
break;
case 'download':
this._doDownload();
break;
case 'upload':
this._doUpload();
break;
}
}
}, {
key: '_onInvalidFileDrop',
value: function _onInvalidFileDrop() {
this._onInvalidFile();
this._removeState('file-over');
// animate out drip
var drip = this._getDrip();
snabbt(drip.firstChild, {
fromScale: [0.5, 0.5],
scale: [0, 0],
fromOpacity: 0.5,
opacity: 0,
duration: 150,
complete: function complete() {
// clean up transforms
resetTransforms(drip.firstChild);
}
});
}
}, {
key: '_onInvalidFile',
value: function _onInvalidFile() {
var types = this._imageHopper.accept.map(getExtensionByMimeType);
var error = this._options.statusFileType.replace('$0', types.join(', '));
this._throwError(error);
}
}, {
key: '_onImageTooSmall',
value: function _onImageTooSmall() {
var error = this._options.statusImageTooSmall.replace('$0', this._options.minSize.width + ' \xD7 ' + this._options.minSize.height);
this._throwError(error);
}
}, {
key: '_onOverWeightFile',
value: function _onOverWeightFile() {
var error = this._options.statusFileSize.replace('$0', this._options.maxFileSize);
this._throwError(error);
}
}, {
key: '_onLocalURLProblem',
value: function _onLocalURLProblem(error) {
this._throwError(this._options.statusLocalUrlProblem || error);
}
}, {
key: '_onRemoteURLProblem',
value: function _onRemoteURLProblem(error) {
this._throwError(error);
}
}, {
key: '_onFileOver',
value: function _onFileOver(e) {
this._addState('file-over');
this._removeError();
// animations
var drip = this._getDrip();
// move around drip
var matrix = snabbt.createMatrix();
matrix.translate(e.detail.x, e.detail.y, 0);
snabbt.setElementTransform(drip, matrix);
// on first entry fade in blob
if (this._imageHopper.dragPath.length == 1) {
// show
drip.style.opacity = 1;
// animate in
snabbt(drip.firstChild, {
fromOpacity: 0,
opacity: 0.5,
fromScale: [0, 0],
scale: [0.5, 0.5],
duration: 150
});
}
}
}, {
key: '_onFileOut',
value: function _onFileOut(e) {
this._removeState('file-over');
this._removeState('file-invalid');
this._removeError();
// move to last position
var drip = this._getDrip();
var matrix = snabbt.createMatrix();
matrix.translate(e.detail.x, e.detail.y, 0);
snabbt.setElementTransform(drip, matrix);
// animate out
snabbt(drip.firstChild, {
fromScale: [0.5, 0.5],
scale: [0, 0],
fromOpacity: 0.5,
opacity: 0,
duration: 150,
complete: function complete() {
// clean up transforms
resetTransforms(drip.firstChild);
}
});
}
/**
* When a file was literally dropped on the drop area
* @param e
* @private
*/
}, {
key: '_onDropFile',
value: function _onDropFile(e) {
var _this17 = this;
this._removeState('file-over');
// get drip node reference and set to last position
var drip = this._getDrip();
var matrix = snabbt.createMatrix();
matrix.translate(e.detail.x, e.detail.y, 0);
snabbt.setElementTransform(drip, matrix);
// get element minimum 10 entries distant from the last entry so we can calculate velocity of movement
var l = this._imageHopper.dragPath.length;
var jump = this._imageHopper.dragPath[l - Math.min(10, l)];
// velocity
var dx = e.detail.x - jump.x;
var dy = e.detail.y - jump.y;
snabbt(drip, {
fromPosition: [e.detail.x, e.detail.y, 0],
position: [e.detail.x + dx, e.detail.y + dy, 0],
duration: 200
});
// animate out drip
snabbt(drip.firstChild, {
fromScale: [0.5, 0.5],
scale: [2, 2],
fromOpacity: 1,
opacity: 0,
duration: 200,
complete: function complete() {
// clean up transforms
resetTransforms(drip.firstChild);
// load dropped resource
_this17._load(e.target.files[0]);
}
});
}
/**
* When a file has been selected after a click on the drop area
* @param e
* @private
*/
}, {
key: '_onChange',
value: function _onChange(e) {
if (e.target.files.length) {
this._load(e.target.files[0]);
}
}
/**
* Loads a resource (blocking operation)
* @param resource
* @param callback(err)
* @param options
* @private
*/
}, {
key: '_load',
value: function _load(resource, callback) {
var _this18 = this;
var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
// stop here
if (this._isBeingDestroyed) {
return;
}
// if currently contains image, remove it
if (this.containsImage()) {
clearTimeout(this._replaceTimeout);
this._doRemove(function () {
_this18._replaceTimeout = setTimeout(function () {
_this18._load(resource, callback, options);
}, 100);
});
return;
}
// no longer empty
this._removeState('empty');
this._addState('busy');
// start loading indicator
this._startProgressLoop();
// can't drop any other image while loading
if (this._imageHopper) {
this._imageHopper.enabled = false;
}
clearTimeout(this._loadTimeout);
var load = function load() {
clearTimeout(_this18._loadTimeout);
_this18._loadTimeout = setTimeout(function () {
if (_this18._isBeingDestroyed) {
return;
}
_this18._addState('loading');
snabbt(_this18._element.querySelector('.slim-label-loading'), {
fromOpacity: 0,
opacity: 1,
duration: 250
});
}, 500);
};
// early exit
var exit = function exit() {
if (_this18._imageHopper) {
_this18._imageHopper.enabled = true;
}
_this18._removeState('loading');
_this18._removeState('busy');
_this18._addState('empty');
_this18._stopProgressLoop();
};
// turn string based resources (url / base64) into file objects
if (typeof resource === 'string') {
if (resourceIsBase64Data(resource)) {
// resource is base64 data, turn into file
this._load(base64ToBlob(resource), callback, options);
} else {
// will take a while, show loading indicator
load();
// resource is url, load with XHR
loadURL(resource, this._options.willLoad, function (file) {
// continue with file object
_this18._load(file, callback, options);
}, function (error) {
setTimeout(function () {
exit();
_this18._onLocalURLProblem('<p>' + error + '</p>');
if (callback) {
callback.apply(_this18, ['local-url-problem']);
}
}, 500);
});
}
// don't continue, wait for load
return;
} else if (typeof resource.remote !== 'undefined') {
// is dropped link
// test if happens to be base64 data
if (resourceIsBase64Data(resource.remote)) {
this._load(base64ToBlob(resource.remote), callback, options);
return;
}
if (this._options.fetcher) {
loadRemoteURL(this._options.fetcher, this._options.willFetch, this._options.willLoad, resource.remote, function (error) {
exit();
_this18._onRemoteURLProblem('<p>' + error + '</p>');
if (callback) {
callback.apply(_this18, ['remote-url-problem']);
}
}, function (file) {
// continue with file object
_this18._load(file, callback, options);
});
}
// don't continue wait for server fetch
return;
}
// let's continue with file resource
var file = resource;
// re-test if is valid file type
// in case of loading base64 data or url
if (this._imageHopper && this._imageHopper.accept.indexOf(file.type) === -1) {
exit();
this._onInvalidFile();
if (callback) {
callback.apply(this, ['file-invalid']);
}
return;
}
// test if too big
if (file.size && this._options.maxFileSize && bytesToMegaBytes(file.size) > this._options.maxFileSize) {
exit();
this._onOverWeightFile();
if (callback) {
callback.apply(this, ['file-too-big']);
}
return;
}
// if has loaded image editor set to dirty
if (this._imageEditor) {
this._imageEditor.dirty();
}
// continue
this._data.input.name = options && options.name ? options.name : getFileNameByFile(file);
this._data.input.type = getFileTypeByFile(file);
this._data.input.size = file.size;
this._data.input.file = file;
// fetch resource
getImageAsCanvas(file, this._options.internalCanvasSize, function (image, meta) {
var rewind = function rewind() {
// rewind state
if (_this18._imageHopper) {
_this18._imageHopper.enabled = true;
}
_this18._removeState('loading');
_this18._removeState('busy');
_this18._addState('empty');
_this18._stopProgressLoop();
_this18._resetData();
};
// if no image, something went wrong
if (!image) {
rewind();
if (callback) {
callback.apply(_this18, ['file-not-found']);
}
return;
}
// test if image is too small
if (!covers(image, _this18._options.minSize)) {
rewind();
_this18._onImageTooSmall();
if (callback) {
callback.apply(_this18, ['image-too-small']);
}
return;
}
var status = _this18._options.didLoad.apply(_this18, [file, image, meta, _this18]);
if (status !== true) {
rewind();
if (status !== false) {
_this18._throwError(status);
}
if (callback) {
callback.apply(_this18, [status]);
}
return;
}
// done loading file
_this18._removeState('loading');
var revealCanvas = function revealCanvas(done) {
// done, enable hopper
if (_this18._imageHopper && _this18._options.dropReplace) {
_this18._imageHopper.enabled = true;
}
// do intro stuff
var intro = _this18._getIntro();
// setup base animation
var animation = {
fromScale: [1.25, 1.25],
scale: [1, 1],
fromOpacity: 0,
opacity: 1,
complete: function complete() {
resetTransforms(intro);
intro.style.opacity = 1;
done();
}
};
// if not attached to DOM, don't animate
if (_this18.isDetached()) {
animation.duration = 1;
} else {
animation.easing = 'spring';
animation.springConstant = 0.3;
animation.springDeceleration = 0.7;
}
// if is instant edit mode don't zoom out but zoom in
if (_this18._canInstantEdit()) {
animation.delay = 500;
animation.duration = 1;
// instant edit mode just fire up the editor immidiately
_this18._doEdit();
}
// reveal loaded image
snabbt(intro, animation);
};
// load the image
_this18._loadCanvas(image,
// done loading the canvas
function (isUploading) {
_this18._addState('preview');
revealCanvas(function () {
// don't show buttons when instant editing
// the buttons will be triggered by the closing of the popup
if (!_this18._canInstantEdit() && !isUploading) {
_this18._showButtons();
}
if (!isUploading) {
_this18._stopProgressLoop();
_this18._removeState('busy');
}
if (callback) {
callback.apply(_this18, [null, _this18.data]);
}
});
},
// done uploading
function () {
// don't show buttons when instant editing
if (!_this18._canInstantEdit()) {
_this18._showButtons();
}
_this18._removeState('busy');
},
// options for canvas load
{
blockPush: options.blockPush
});
});
}
}, {
key: '_loadCanvas',
value: function _loadCanvas(image, ready, complete, options) {
var _this19 = this;
// set default options object if not supplied
if (!options) {
options = {};
}
// halt here if cropper is currently being destroyed
if (this._isBeingDestroyed) {
return;
}
// store raw data
this._data.input.image = image;
this._data.input.width = image.width;
this._data.input.height = image.height;
if (this._initialRotation) {
this._data.actions.rotation = this._initialRotation;
this._initialRotation = null;
}
var isTilted = this._data.actions.rotation % 180 !== 0;
// scales the drop area
// if is 'input' or 'free' parameter
if (!this._isFixedRatio()) {
if (this._initialCrop) {
this._ratio = this._initialCrop.height / this._initialCrop.width;
} else {
this._ratio = isTilted ? image.width / image.height : image.height / image.width;
}
this._scaleDropArea(this._ratio);
}
var applyTransforms = function applyTransforms() {
// if max size set
if (_this19._options.size) {
_this19._data.actions.size = {
width: _this19._options.size.width,
height: _this19._options.size.height
};
}
// do initial auto transform
_this19._applyTransforms(image, function (transformedImage) {
var intro = _this19._getIntro();
var scalar = intro.offsetWidth / transformedImage.width;
// store data, if has preview image this prevents initial load from pushing
var willUpload = false;
// can only do auto upload when service is defined and push is enabled...
if (_this19._options.service && _this19._options.push && !options.blockPush) {
// ...and is not transformation of initial image
// + is not instant edit mode
if (!_this19._hasInitialImage && !_this19._canInstantEdit()) {
willUpload = true;
_this19._stopProgressLoop(function () {
_this19._startProgress(function () {
_this19._updateProgress(0.1);
});
});
}
}
// no service set, and instant edit
if (!_this19._canInstantEdit()) {
// store data (possibly)
_this19._save(function () {
if (_this19._isBeingDestroyed) {
return;
}
if (willUpload) {
_this19._stopProgress();
complete();
}
}, willUpload);
}
// scale ratio
var ratio = _this19._options.devicePixelRatio === 'auto' ? window.devicePixelRatio : _this19._options.devicePixelRatio;
// show intro animation
intro.src = '';
intro.src = cloneCanvasScaled(transformedImage, scalar * ratio).toDataURL();
intro.onload = function () {
intro.onload = null;
// bail out if we've been cleaned up
if (_this19._isBeingDestroyed) {
return;
}
if (ready) {
ready(willUpload);
}
};
});
};
if (this._initialCrop) {
// use initial supplied crop rectangle
this._data.actions.crop = clone(this._initialCrop);
this._data.actions.crop.type = CropType.INITIAL;
// clear initial crop, it's no longer useful
this._initialCrop = null;
// go!
applyTransforms();
} else {
this._options.willCropInitial.apply(this, [this.data, function (rect) {
if (rect) {
// apply custom crop rectangle
_this19._data.actions.crop = rect;
_this19._data.actions.crop.type = CropType.INITIAL;
} else {
// get automagical crop rectangle
_this19._data.actions.crop = getAutoCropRect(isTilted ? image.height : image.width, isTilted ? image.width : image.height, _this19._ratio);
_this19._data.actions.crop.type = CropType.AUTO;
}
applyTransforms();
}, this]);
}
}
}, {
key: '_applyTransforms',
value: function _applyTransforms(image, ready) {
var _this20 = this;
var actions = clone(this._data.actions);
actions.filters = {
sharpen: this._options.filterSharpen / 100
};
// if should force minimum size on output image
if (this._options.forceMinSize) {
actions.minSize = this._options.minSize;
} else {
actions.minSize = {
width: 0,
height: 0
};
}
transformCanvas(image, actions, function (transformedImage) {
var outputImage = transformedImage;
// if should force/correct output size?
// - is forced size set?
// - is a discrepancy found between requested output size and transformed size
if (_this20._options.forceSize || _this20._options.size && sizeDist(_this20._options.size, transformedImage) == 1) {
outputImage = create('canvas');
outputImage.width = _this20._options.size.width;
outputImage.height = _this20._options.size.height;
var ctx = outputImage.getContext('2d');
ctx.drawImage(transformedImage, 0, 0, _this20._options.size.width, _this20._options.size.height);
}
// make sure min size is respected when size is equal to min size
if (_this20._options.forceMinSize && _this20._options.size && _this20._options.minSize.width === _this20._options.size.width && _this20._options.minSize.height === _this20._options.size.height && (outputImage.width < _this20._options.minSize.width || outputImage.height < _this20._options.minSize.height)) {
var w = Math.max(outputImage.width, _this20._options.minSize.width);
var h = Math.max(outputImage.height, _this20._options.minSize.height);
outputImage = create('canvas');
outputImage.width = w;
outputImage.height = h;
var _ctx = outputImage.getContext('2d');
_ctx.drawImage(transformedImage, 0, 0, w, h);
}
// prevent smaller square output image, this is a quick fix for square images, should be done a lot neater
if (_this20._options.forceMinSize && _this20._ratio === 1 && (outputImage.width < _this20._options.minSize.width || outputImage.height < _this20._options.minSize.height)) {
outputImage = create('canvas');
outputImage.width = _this20._options.minSize.width;
outputImage.height = _this20._options.minSize.height;
var _ctx2 = outputImage.getContext('2d');
_ctx2.drawImage(transformedImage, 0, 0, outputImage.width, outputImage.height);
}
// store output
_this20._data.output.width = outputImage.width;
_this20._data.output.height = outputImage.height;
_this20._data.output.image = outputImage;
_this20._onTransformCanvas(function (transformedData) {
_this20._data = transformedData;
_this20._options.didTransform.apply(_this20, [_this20.data, _this20]);
ready(_this20._data.output.image);
});
});
}
}, {
key: '_onTransformCanvas',
value: function _onTransformCanvas(ready) {
this._options.willTransform.apply(this, [this.data, ready, this]);
}
/**
* Creates the editor nodes
* @private
*/
}, {
key: '_appendEditor',
value: function _appendEditor() {
var _this21 = this;
// we already have an editor
if (this._imageEditor) {
return;
}
// add editor
this._imageEditor = new ImageEditor(create('div'), {
minSize: this._options.minSize,
devicePixelRatio: this._options.devicePixelRatio,
buttonConfirmClassName: this._options.buttonConfirmClassName,
buttonCancelClassName: this._options.buttonCancelClassName,
buttonRotateClassName: this._options.buttonRotateClassName,
buttonConfirmLabel: this._options.buttonConfirmLabel,
buttonCancelLabel: this._options.buttonCancelLabel,
buttonRotateLabel: this._options.buttonRotateLabel,
buttonConfirmTitle: this._options.buttonConfirmTitle,
buttonCancelTitle: this._options.buttonCancelTitle,
buttonRotateTitle: this._options.buttonRotateTitle
});
// listen to events
ImageEditorEvents.forEach(function (e) {
_this21._imageEditor.element.addEventListener(e, _this21);
});
}
}, {
key: '_scaleDropArea',
value: function _scaleDropArea(ratio) {
var node = this._getRatioSpacerElement();
if (!node || !this._element) {
return;
}
node.style.marginBottom = ratio * 100 + '%';
this._element.setAttribute('data-ratio', '1:' + ratio);
}
/**
* Data Layer
* @private
*/
// image editor closed
}, {
key: '_onCancel',
value: function _onCancel(e) {
this._removeState('editor');
this._options.didCancel.apply(this, [this]);
this._showButtons();
this._hideEditor();
if (this._options.instantEdit && !this._hasInitialImage && this._isAutoCrop()) {
this._doRemove();
}
}
// user confirmed changes
}, {
key: '_onConfirm',
value: function _onConfirm(e) {
var _this22 = this;
// if
// - service set
// - and we are pushing
// - and we don't instant edit
// we will upload
var willUpload = this._options.service && this._options.push;
if (willUpload) {
this._startProgress(function () {
_this22._updateProgress(0.1);
});
} else {
this._startProgressLoop();
}
this._removeState('editor');
this._addState('busy');
// clear data
this._output.value = '';
// apply new action object to this._data
this._data.actions.rotation = e.detail.rotation;
this._data.actions.crop = e.detail.crop;
this._data.actions.crop.type = CropType.MANUAL;
// do transforms
this._applyTransforms(this._data.input.image, function (transformedImage) {
// user confirmed the crop (and changes have been applied to data)
_this22._options.didConfirm.apply(_this22, [_this22.data, _this22]);
// set new image result
var images = _this22._getInOut();
var intro = images[0].className === 'out' ? images[0] : images[1];
var outro = intro === images[0] ? images[1] : images[0];
intro.className = 'in';
intro.style.opacity = '0';
intro.style.zIndex = '2';
outro.className = 'out';
outro.style.zIndex = '1';
// scale ratio
var ratio = _this22._options.devicePixelRatio === 'auto' ? window.devicePixelRatio : _this22._options.devicePixelRatio;
// new image get's
intro.src = '';
intro.src = cloneCanvasScaled(transformedImage, intro.offsetWidth / transformedImage.width * ratio).toDataURL();
intro.onload = function () {
intro.onload = null;
// scale the dropzone
if (_this22._options.ratio === 'free') {
_this22._ratio = intro.naturalHeight / intro.naturalWidth;
_this22._scaleDropArea(_this22._ratio);
}
// close the editor
_this22._hideEditor();
// wait a tiny bit so animations sync up nicely
var timer = setTimeout(function () {
// show the preview
_this22._showPreview(intro, function () {
// save the data
_this22._save(function (err, data, res) {
// done!
_this22._toggleButton('upload', true);
if (willUpload) {
_this22._stopProgress();
} else {
_this22._stopProgressLoop();
}
_this22._removeState('busy');
_this22._showButtons();
}, willUpload);
});
}, 250);
_this22._timers.push(timer);
};
});
}
}, {
key: '_cropAuto',
value: function _cropAuto() {
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function (data) {};
var isTilted = this._data.actions.rotation % 180 !== 0;
var rect = getAutoCropRect(isTilted ? this._data.input.image.height : this._data.input.image.width, isTilted ? this._data.input.image.width : this._data.input.image.height, this._ratio);
this._crop(rect.x, rect.y, rect.width, rect.height, callback, CropType.AUTO);
}
}, {
key: '_crop',
value: function _crop(x, y, width, height) {
var callback = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : function (data) {};
var cropType = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : CropType.MANUAL;
// clear data
this._output.value = '';
// apply new action object to this._data
this._data.actions.crop = {
x: x,
y: y,
width: width,
height: height
};
this._data.actions.crop.type = cropType;
this._manualTransform(callback);
}
}, {
key: '_manualTransform',
value: function _manualTransform(callback) {
var _this23 = this;
this._startProgressLoop();
this._addState('busy');
// do transforms
this._applyTransforms(this._data.input.image, function (transformedImage) {
// set new image result
var images = _this23._getInOut();
var intro = images[0].className === 'out' ? images[0] : images[1];
var outro = intro === images[0] ? images[1] : images[0];
intro.className = 'in';
intro.style.opacity = '1';
intro.style.zIndex = '2';
outro.className = 'out';
outro.style.zIndex = '0';
// scale ratio
var pixelRatio = _this23._options.devicePixelRatio === 'auto' ? window.devicePixelRatio : _this23._options.devicePixelRatio;
// new image
intro.src = '';
intro.src = cloneCanvasScaled(transformedImage, intro.offsetWidth / transformedImage.width * pixelRatio).toDataURL();
intro.onload = function () {
intro.onload = null;
// scale the dropzone
if (_this23._options.ratio === 'free') {
_this23._ratio = intro.naturalHeight / intro.naturalWidth;
_this23._scaleDropArea(_this23._ratio);
}
// determine if will also upload
var willUpload = _this23._options.service && _this23._options.push;
var save = function save() {
// save the data
_this23._save(function (err, data, res) {
// stop loader
if (!willUpload) {
_this23._stopProgressLoop();
}
_this23._removeState('busy');
callback.apply(_this23, [_this23.data]);
}, willUpload);
};
if (willUpload) {
_this23._startProgress(save);
} else {
save();
}
};
});
}
}, {
key: '_save',
value: function _save() {
var _this24 = this;
var callback = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : function () {};
var allowUpload = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
if (this._isBeingDestroyed) {
return;
}
// flatten data also turns output canvas into data uri
// removes input file object and image object
var data = this.dataBase64;
// decide if we need to
// - A. Store the data in an output field
// - B. Upload the data and store the response in output field
// - we are not doing async uploading (in which case output is used for response)
// - we are not initialising a replaceable image
if (!this._options.service && !(this._isInitialising && !this._isImageOnly())) {
this._options.willSave.apply(this, [data, function (data) {
_this24._store(data);
_this24._options.didSave.apply(_this24, [data, _this24]);
}, this]);
}
if (this._isBeingDestroyed) {
return;
}
// is remote service defined upload async
if (this._options.service && allowUpload) {
// allow user to modify the data
this._options.willSave.apply(this, [data, function (data) {
_this24._addState('upload');
if (_this24._imageHopper && _this24._options.dropReplace) {
_this24._imageHopper.enabled = false;
}
// do the actual uploading
_this24._upload(data, function (err, res) {
if (_this24._imageHopper && _this24._options.dropReplace) {
_this24._imageHopper.enabled = true;
}
// store response
if (!err) {
_this24._storeServerResponse(res);
}
// we did upload data
_this24._options.didUpload.apply(_this24, [err, data, res, _this24]);
_this24._removeState('upload');
// done!
callback(err, data, res);
});
}, this]);
}
// if no service, we're done here
if (!this._options.service || !allowUpload) {
callback();
}
}
// stores active file information in hidden output field
}, {
key: '_storeServerResponse',
value: function _storeServerResponse(data) {
// remove required flag
if (this._isRequired) {
this._input.required = false;
}
// store data returned from server
this._data.server = data;
// sync with output value
this._output.value = (typeof data === 'undefined' ? 'undefined' : _typeof(data)) === 'object' ? JSON.stringify(this._data.server) : data;
}
// stores data in output field
}, {
key: '_store',
value: function _store(data) {
if (this._isRequired) {
this._input.required = false;
}
this._output.value = JSON.stringify(data);
}
// uploads given data to server
}, {
key: '_upload',
value: function _upload(data, callback) {
var // function(error) {} // error message (string),
_this25 = this;
this.requestOutput(function (fileData, formData) {
var statusNode = _this25._element.querySelector('.slim-upload-status');
var requestDecorator = _this25._options.willRequest;
// callback methods
var onProgress = function onProgress(loaded, total) {
_this25._updateProgress(Math.max(0.1, loaded / total));
};
var onSuccess = function onSuccess(obj) {
var timer = setTimeout(function () {
// it's possible that Slim has been destroyed in the mean time.
if (_this25._isBeingDestroyed) {
return;
}
statusNode.innerHTML = _this25._options.statusUploadSuccess;
statusNode.setAttribute('data-state', 'success');
statusNode.style.opacity = 1;
// hide status update after 2 seconds
var timer = setTimeout(function () {
statusNode.style.opacity = 0;
}, 2000);
_this25._timers.push(timer);
}, 250);
_this25._timers.push(timer);
callback(null, obj);
};
var onError = function onError(status) {
var html = '';
if (status === 'file-too-big') {
html = _this25._options.statusContentLength;
} else {
html = _this25._options.didReceiveServerError.apply(_this25, [status, _this25._options.statusUnknownResponse, _this25]);
}
// when an error occurs the status update is not automatically hidden
var timer = setTimeout(function () {
statusNode.innerHTML = html;
statusNode.setAttribute('data-state', 'error');
statusNode.style.opacity = 1;
}, 250);
_this25._timers.push(timer);
callback(status);
};
// use default send method or custom implementation
if (typeof _this25._options.service === 'string') {
send(_this25._options.service, _this25._options.uploadMethod, formData, requestDecorator, onProgress, onSuccess, onError);
} else if (typeof _this25._options.service === 'function') {
_this25._options.service.apply(_this25, [_this25._options.serviceFormat === 'file' ? fileData : formData, onProgress, // function(loaded, total) {} // loaded bytes (number), total bytes (number)
onSuccess, // function(response) {} // response (object or string)
onError, _this25]);
}
}, data);
}
}, {
key: 'requestOutput',
value: function requestOutput(cb, data) {
var _this26 = this;
if (!this._data.input.file) {
cb(null, null);
return;
}
if (!data) {
data = this.dataBase64;
}
// copy the meta data of the original file to the output
loadImage.parseMetaData(this._data.input.file,
// receives image data from input file
function (imageData) {
var fileData = [];
var formData = new FormData();
// if input should be posted along, append data
// to FormData object as file
if (inArray('input', _this26._options.post)) {
// add to data array
fileData.push(_this26._data.input.file);
// add to formdata
formData.append(_this26._inputReference, _this26._data.input.file, _this26._data.input.file.name);
}
// if image data is defined, turn it into a file object (we can send files if we're uploading)
if (inArray('output', _this26._options.post) && _this26._data.output.image !== null && _this26._options.uploadBase64 === false) {
var output = base64ToBlob(data.output.image, data.output.name);
// if image head available, inject in output
if (imageData.imageHead && _this26._options.copyImageHead) {
try {
output = new Blob([imageData.imageHead, loadImage.blobSlice.call(output, 20)], {
type: getMimeTypeFromDataURI(data.output.image)
});
output = blobToFile(output, data.output.name);
} catch (e) {}
}
// add to data array
fileData.push(output);
// add to formdata
var field = 'slim_output_' + _this26._uid;
data.output.image = null; // clear base64 data
data.output.field = field;
formData.append(field, output, data.output.name);
}
// output dataset
formData.append(_this26._output.name, JSON.stringify(data));
// done
cb(fileData, formData);
}, {
maxMetaDataSize: 262144,
disableImageHead: false
});
}
}, {
key: '_showEditor',
value: function _showEditor() {
SlimPopover.className = this._options.popoverClassName;
SlimPopover.show();
this._imageEditor.show();
}
}, {
key: '_hideEditor',
value: function _hideEditor() {
this._imageEditor.hide();
var timer = setTimeout(function () {
SlimPopover.hide();
}, 250);
this._timers.push(timer);
}
/**
* Animations
*/
}, {
key: '_showPreview',
value: function _showPreview(intro, callback) {
snabbt(intro, {
fromPosition: [0, 50, 0],
position: [0, 0, 0],
fromScale: [1.5, 1.5],
scale: [1, 1],
fromOpacity: 0,
opacity: 1,
easing: 'spring',
springConstant: 0.3,
springDeceleration: 0.7,
complete: function complete() {
resetTransforms(intro);
if (callback) {
callback();
}
}
});
}
}, {
key: '_hideResult',
value: function _hideResult(callback) {
var intro = this._getIntro();
if (!intro) {
return;
}
snabbt(intro, {
fromScale: [1, 1],
scale: [0.5, 0.5],
fromOpacity: 1,
opacity: 0,
easing: 'spring',
springConstant: 0.3,
springDeceleration: 0.75,
complete: function complete() {
resetTransforms(intro);
if (callback) {
callback();
}
}
});
}
}, {
key: '_showButtons',
value: function _showButtons(callback) {
if (!this._btnGroup) {
return;
}
this._btnGroup.style.display = '';
// setup animation
var animation = {
fromScale: [0.5, 0.5],
scale: [1, 1],
fromPosition: [0, 10, 0],
position: [0, 0, 0],
fromOpacity: 0,
opacity: 1,
complete: function complete() {
resetTransforms(this);
},
allDone: function allDone() {
if (callback) {
callback();
}
}
};
// don't animate when detached
if (this.isDetached()) {
animation.duration = 1;
} else {
animation.delay = function (i) {
return 250 + i * 50;
};
animation.easing = 'spring';
animation.springConstant = 0.3;
animation.springDeceleration = 0.85;
}
snabbt(this._btnGroup.childNodes, animation);
}
}, {
key: '_hideButtons',
value: function _hideButtons(callback) {
var _this27 = this;
if (!this._btnGroup) {
return;
}
var animation = {
fromScale: [1, 1],
scale: [0.85, 0.85],
fromOpacity: 1,
opacity: 0,
allDone: function allDone() {
_this27._btnGroup.style.display = 'none';
if (callback) {
callback();
}
}
};
// don't animate when detached
if (this.isDetached()) {
animation.duration = 1;
} else {
animation.easing = 'spring';
animation.springConstant = 0.3;
animation.springDeceleration = 0.75;
}
// go hide the buttons
snabbt(this._btnGroup.childNodes, animation);
}
}, {
key: '_hideStatus',
value: function _hideStatus() {
var statusNode = this._element.querySelector('.slim-upload-status');
statusNode.style.opacity = 0;
}
}, {
key: '_doEdit',
value: function _doEdit() {
var _this28 = this;
// if no input data available, can't edit anything
if (!this._data.input.image) {
return;
}
// now in editor mode
this._addState('editor');
// create editor (if not already created)
if (!this._imageEditor) {
this._appendEditor();
}
// hide or show rotate button
this._imageEditor.showRotateButton = this._options.rotateButton;
// append to popover
SlimPopover.inner = this._imageEditor.element;
// read the data
this._imageEditor.open(
// send copy of canvas to the editor
cloneCanvas(this._data.input.image),
// determine ratio
this._options.ratio === 'free' ? null : this._ratio,
// the initial crop to show
this._data.actions.crop,
// the initial rotation of the image
this._data.actions.rotation,
// handle editor load
function () {
_this28._showEditor();
_this28._hideButtons();
_this28._hideStatus();
});
}
}, {
key: '_doRemove',
value: function _doRemove(done) {
var _this29 = this;
// cannot remove when is only one image
if (this._isImageOnly()) {
return;
}
this._clearState();
this._addState('empty');
this._hasInitialImage = false;
if (this._imageHopper) {
this._imageHopper.enabled = true;
}
if (this._isRequired) {
this._input.required = true;
}
var out = this._getOutro();
if (out) {
out.style.opacity = '0';
}
// get public available clone of data
var data = this.data;
// now reset all data
this._resetData();
var timer = setTimeout(function () {
if (_this29._isBeingDestroyed) {
return;
}
_this29._hideButtons(function () {
_this29._toggleButton('upload', true);
});
_this29._hideStatus();
_this29._hideResult();
_this29._options.didRemove.apply(_this29, [data, _this29]);
if (done) {
done();
}
}, this.isDetached() ? 0 : 250);
this._timers.push(timer);
return data;
}
}, {
key: '_doUpload',
value: function _doUpload(callback) {
var _this30 = this;
// if no input data available, can't upload anything
if (!this._data.input.image) {
return;
}
this._addState('upload');
this._startProgress();
this._hideButtons(function () {
// block upload button
_this30._toggleButton('upload', false);
_this30._save(function (err, data, res) {
_this30._removeState('upload');
_this30._stopProgress();
if (callback) {
callback.apply(_this30, [err, data, res]);
}
if (err) {
_this30._toggleButton('upload', true);
}
_this30._showButtons();
});
});
}
}, {
key: '_doDownload',
value: function _doDownload() {
var image = this._data.output.image;
if (!image) {
return;
}
downloadCanvas(this._data, this._options.jpegCompression, this._options.forceType);
}
}, {
key: '_doDestroy',
value: function _doDestroy() {
var _this31 = this;
// set destroy flag to halt any running functionality
this._isBeingDestroyed = true;
// clear timers
this._timers.forEach(function (timer) {
clearTimeout(timer);
});
this._timers = [];
// clean up snabbt animations
snabbt(this._element, 'detach');
// this removes the image hopper if it's attached
if (this._imageHopper) {
HopperEvents.forEach(function (e) {
_this31._imageHopper.element.removeEventListener(e, _this31);
});
this._imageHopper.destroy();
this._imageHopper = null;
}
// this block removes the image editor
if (this._imageEditor) {
ImageEditorEvents.forEach(function (e) {
_this31._imageEditor.element.removeEventListener(e, _this31);
});
this._imageEditor.destroy();
this._imageEditor = null;
}
// remove button event listeners
nodeListToArray(this._btnGroup.children).forEach(function (btn) {
btn.removeEventListener('click', _this31);
});
// stop listening to input
this._input.removeEventListener('change', this);
// detect if was wrapped, if so, remove wrapping (needs to have parent node)
if (this._element !== this._originalElement && this._element.parentNode) {
this._element.parentNode.replaceChild(this._originalElement, this._element);
}
// restore HTML of original element
this._originalElement.innerHTML = this._originalElementInner;
// get current attributes and remove all, then add original attributes
function matchesAttributeInList(a, attributes) {
return attributes.filter(function (attr) {
return a.name === attr.name && a.value === attr.value;
}).length !== 0;
}
var attributes = getElementAttributes(this._originalElement);
attributes.forEach(function (attribute) {
// if attribute is contained in original element attribute list and is the same, don't remove
if (matchesAttributeInList(attribute, _this31._originalElementAttributes)) {
return;
}
// else remove
_this31._originalElement.removeAttribute(attribute.name);
});
this._originalElementAttributes.forEach(function (attribute) {
// attribute was never removed
if (matchesAttributeInList(attribute, attributes)) {
return;
}
// add attribute
_this31._originalElement.setAttribute(attribute.name, attribute.value);
});
// now destroyed this counter so the total Slim count can be lowered
SlimCount = Math.max(0, SlimCount - 1);
// if slim count has reached 0 it's time to clean up the popover
if (SlimPopover && SlimCount === 0) {
SlimPopover.destroy();
SlimPopover = null;
}
this._originalElement = null;
this._element = null;
this._input = null;
this._output = null;
this._btnGroup = null;
this._options = null;
}
}, {
key: 'dataBase64',
/**
* Public API
*/
// properties
get: function get() {
return flattenData(this._data, this._options.post, this._options.jpegCompression, this._options.forceType, this._options.service !== null);
}
}, {
key: 'data',
get: function get() {
return cloneData(this._data);
}
}, {
key: 'element',
get: function get() {
return this._element;
}
}, {
key: 'service',
set: function set(service) {
this._options.service = service;
}
}, {
key: 'size',
set: function set(dimensions) {
this.setSize(dimensions, null);
}
}, {
key: 'rotation',
set: function set(rotation) {
this.setRotation(rotation, null);
}
}, {
key: 'forceSize',
set: function set(dimensions) {
this.setForceSize(dimensions, null);
}
}, {
key: 'ratio',
set: function set(ratio) {
this.setRatio(ratio, null);
}
}], [{
key: 'options',
value: function options() {
var defaults = {
// edit button is enabled by default
edit: true,
// immidiately summons editor on load
instantEdit: false,
// set to true to upload data as base64 string
uploadBase64: false,
// metadata values
meta: {},
// ratio of crop by default is the same as input image ratio
ratio: 'free',
// use fix value for device pixel ratio
devicePixelRatio: 1, // set to 'auto' to use device ratio
// dimensions to resize the resulting image to
size: null,
// set initial rotation
rotation: null,
// initial crop settings for example: {x:0, y:0, width:100, height:100}
crop: null,
// post these values
post: ['output', 'actions'],
// call this service to submit cropped data
service: null,
serviceFormat: null,
// sharpen filter value, really low values might improve image output
filterSharpen: 0,
// when service is set, and this is set to true, Soon will auto upload all crops (also auto crops)
push: false,
// default fallback name for field
defaultInputName: 'slim[]',
// minimum size of cropped area object with width and height property
minSize: {
width: 0,
height: 0
},
// maximum file size in MB to upload
maxFileSize: null,
// compression of JPEG (between 0 and 100)
jpegCompression: null,
// upload method of request
uploadMethod: 'POST',
// render download link
download: false,
// save initially loaded image
saveInitialImage: false,
// the type to force (jpe|jpg|jpeg or png)
forceType: false,
// the forced output size of the image
forceSize: null,
forceMinSize: true,
// disable drop to replace
dropReplace: true,
// remote URL service
fetcher: null,
// set the internal canvas size
internalCanvasSize: {
width: 4096,
height: 4096
},
// copies the input image meta data to the output image
copyImageHead: false,
// enable or disable rotation
rotateButton: true,
// popover classname
popoverClassName: null,
// label HTML to show inside drop area
label: '<p>Drop your image here</p>',
labelLoading: '<p>Loading image...</p>',
// error messages
statusFileType: '<p>Invalid file type, expects: $0.</p>',
statusFileSize: '<p>File is too big, maximum file size: $0 MB.</p>',
statusNoSupport: '<p>Your browser does not support image cropping.</p>',
statusImageTooSmall: '<p>Image is too small, minimum size is: $0 pixels.</p>',
statusContentLength: '<span class="slim-upload-status-icon"></span> The file is probably too big',
statusUnknownResponse: '<span class="slim-upload-status-icon"></span> An unknown error occurred',
statusUploadSuccess: '<span class="slim-upload-status-icon"></span> Saved',
statusLocalUrlProblem: null,
// callback methods
didInit: function didInit(data) {},
didLoad: function didLoad(file, image, meta) {
return true;
},
didSave: function didSave(data) {},
didUpload: function didUpload(err, data, res) {},
didReceiveServerError: function didReceiveServerError(err, defaultError) {
return defaultError;
},
didRemove: function didRemove(data) {},
didTransform: function didTransform(data) {},
didConfirm: function didConfirm(data) {},
didCancel: function didCancel() {},
didThrowError: function didThrowError() {},
willCropInitial: function willCropInitial(file, cb) {
cb(null);
},
willTransform: function willTransform(data, cb) {
cb(data);
},
willSave: function willSave(data, cb) {
cb(data);
},
willRemove: function willRemove(data, cb) {
cb();
},
willRequest: function willRequest(xhr, data) {},
willFetch: function willFetch(xhr) {},
willLoad: function willLoad(xhr) {}
};
// add default button labels
SlimButtons.concat(ImageEditor.Buttons).concat('rotate').forEach(function (btn) {
var capitalized = capitalizeFirstLetter(btn);
defaults['button' + capitalized + 'ClassName'] = null;
defaults['button' + capitalized + 'Label'] = capitalized;
defaults['button' + capitalized + 'Title'] = capitalized;
});
return defaults;
}
}]);
return Slim;
}();
/**
* Slim Static Methods
*/
(function () {
var instances = [];
var indexOfElement = function indexOfElement(element) {
var i = 0;
var l = instances.length;
for (; i < l; i++) {
if (instances[i].isAttachedTo(element)) {
return i;
}
}
return -1;
};
function toLabel(v) {
// if value set, use as label
if (v) {
return '<p>' + v + '</p>';
}
// else use default text
return null;
}
function toFunctionReference(name) {
var ref = window;
var levels = name.split('.');
levels.forEach(function (level, index) {
if (!ref[levels[index]]) {
return;
}
ref = ref[levels[index]];
});
return ref !== window ? ref : null;
}
var passThrough = function passThrough(v) {
return v;
};
var defaultFalse = function defaultFalse(v) {
return v === 'true';
};
var defaultTrue = function defaultTrue(v) {
return v ? v === 'true' : true;
};
var defaultLabel = function defaultLabel(v) {
return toLabel(v);
};
var defaultFunction = function defaultFunction(v) {
return v ? toFunctionReference(v) : null;
};
var defaultSize = function defaultSize(v) {
if (!v) {
return null;
}
var parts = intSplit(v, ',');
return {
width: parts[0],
height: parts[1]
};
};
var toFloat = function toFloat(v) {
if (!v) {
return null;
}
return parseFloat(v);
};
var toInt = function toInt(v) {
if (!v) {
return null;
}
return parseInt(v, 10);
};
var toRect = function toRect(v) {
if (!v) {
return null;
}
var obj = {};
v.split(',').map(function (p) {
return parseInt(p, 10);
}).forEach(function (v, i) {
obj[Rect[i]] = v;
});
return obj;
};
var defaults = {
// is user allowed to download the cropped image?
download: defaultFalse,
// is user allowed to edit the cropped image?
edit: defaultTrue,
// open editor immidiately on file drop
instantEdit: defaultFalse,
// minimum crop size in pixels of original image
minSize: defaultSize,
// the final bounding box of the output image
size: defaultSize,
// the forced output size of the image
forceSize: defaultSize,
forceMinSize: defaultTrue,
// the internal data canvas size
internalCanvasSize: defaultSize,
// url to post to
service: function service(v) {
if (typeof v === 'undefined') {
return null;
}
var fn = toFunctionReference(v);
if (fn) {
return fn;
}
return v;
},
// format of service data
serviceFormat: function serviceFormat(v) {
return typeof v === 'undefined' ? null : v;
},
// url to fetch service
fetcher: function fetcher(v) {
return typeof v === 'undefined' ? null : v;
},
// set auto push mode
push: defaultFalse,
// initial rotation
rotation: function rotation(v) {
return typeof v === 'undefined' ? null : parseInt(v, 10);
},
// set crop rect
crop: toRect,
// what to post
post: function post(v) {
if (!v) {
return null;
}
return v.split(',').map(function (item) {
return item.trim();
});
},
// default input name
defaultInputName: passThrough,
// the ratio of the crop
ratio: function ratio(v) {
if (!v) {
return null;
}
return v;
},
// maximum file size
maxFileSize: toFloat,
// sharpen filter
filterSharpen: toInt,
// jpeg compression
jpegCompression: toInt,
// base64 data uploading
uploadBase64: defaultFalse,
// sets file type to force output to
forceType: passThrough,
// drop to replace
dropReplace: defaultTrue,
// bool determining if initial image should be saved
saveInitialImage: defaultFalse,
// copies input image head to output image
copyImageHead: defaultFalse,
// rotate button
rotateButton: defaultTrue,
// default labels
label: defaultLabel,
labelLoading: defaultLabel,
// class name to put on popover element
popoverClassName: passThrough,
// the ratio for pixels
devicePixelRatio: passThrough,
// upload request method
uploadMethod: passThrough
};
// labels
['FileSize', 'FileType', 'NoSupport', 'ImageTooSmall'].forEach(function (status) {
defaults['status' + status] = defaultLabel;
});
// status
['ContentLength', 'UnknownResponse', 'UploadSuccess', 'localUrlProblem'].forEach(function (status) {
defaults['status' + status] = passThrough;
});
// the did callbacks
['Init', 'Load', 'Save', 'Upload', 'Remove', 'Transform', 'ReceiveServerError', 'Confirm', 'Cancel', 'ThrowError'].forEach(function (cb) {
defaults['did' + cb] = defaultFunction;
});
// the will callbacks
['CropInitial', 'Transform', 'Save', 'Remove', 'Request', 'Load', 'Fetch'].forEach(function (cb) {
defaults['will' + cb] = defaultFunction;
});
// button defaults
var buttonOptions = ['ClassName', 'Label', 'Title'];
SlimButtons.concat(ImageEditor.Buttons).concat('rotate').forEach(function (btn) {
var capitalized = capitalizeFirstLetter(btn);
buttonOptions.forEach(function (opt) {
defaults['button' + capitalized + opt] = passThrough;
});
});
Slim.supported = function () {
return !( // is opera mini
Object.prototype.toString.call(window.operamini) === '[object OperaMini]' ||
// no event listener support
typeof window.addEventListener === 'undefined' ||
// no file reader support
typeof window.FileReader === 'undefined' ||
// no blob slicing (can't dupe files)
!('slice' in Blob.prototype) ||
// no .createObjectURL support, used by download method but also convenient to exclude Android 4.3 and lower
// Android 4.3 and lower don't support XHR2 responseType blob
typeof window.URL === 'undefined' || typeof window.URL.createObjectURL === 'undefined');
}();
Slim.parse = function (context) {
var elements;
var element;
var i;
var croppers = [];
// find all crop elements and bind Crop behavior
elements = context.querySelectorAll('.slim:not([data-state])');
i = elements.length;
while (i--) {
element = elements[i];
croppers.push(Slim.create(element, Slim.getOptionsFromAttributes(element)));
}
return croppers;
};
Slim.getOptionsFromAttributes = function (element) {
var dataset = getDataset(element);
var options = {
meta: {}
};
for (var prop in dataset) {
var valueTransformer = defaults[prop];
var _value = dataset[prop];
if (valueTransformer) {
_value = valueTransformer(_value);
_value = _value === null ? clone(Slim.options()[prop]) : _value;
options[prop] = _value;
} else if (prop.indexOf('meta') === 0) {
options['meta'][lowercaseFirstLetter(prop.substr(4))] = _value;
}
}
return options;
};
Slim.find = function (element) {
var result = instances.filter(function (instance) {
return instance.isAttachedTo(element);
});
return result ? result[0] : null;
};
Slim.create = function (element, options) {
// if already in array, can't create another slim
if (Slim.find(element)) {
return;
}
// if no options supplied, try to get the options from the element
if (!options) {
options = Slim.getOptionsFromAttributes(element);
}
// instance
var slim = new Slim(element, options);
// add new slim
instances.push(slim);
// return the slim instance
return slim;
};
Slim.destroy = function (element) {
var index = indexOfElement(element);
if (index < 0) {
return false;
}
instances[index].destroy();
instances.splice(index, 1);
return true;
};
})();
return Slim;
});