mirror of
https://github.com/nunocoracao/blowfish.git
synced 2025-02-02 19:42:34 -06:00
668 lines
18 KiB
JavaScript
668 lines
18 KiB
JavaScript
/*!
|
|
* Packery v2.1.2
|
|
* Gapless, draggable grid layouts
|
|
*
|
|
* Licensed GPLv3 for open source use
|
|
* or Packery Commercial License for commercial use
|
|
*
|
|
* http://packery.metafizzy.co
|
|
* Copyright 2013-2018 Metafizzy
|
|
*/
|
|
|
|
( function( window, factory ) {
|
|
// universal module definition
|
|
/* jshint strict: false */ /* globals define, module, require */
|
|
if ( typeof define == 'function' && define.amd ) {
|
|
// AMD
|
|
define( [
|
|
'get-size/get-size',
|
|
'outlayer/outlayer',
|
|
'./rect',
|
|
'./packer',
|
|
'./item'
|
|
],
|
|
factory );
|
|
} else if ( typeof module == 'object' && module.exports ) {
|
|
// CommonJS
|
|
module.exports = factory(
|
|
require('get-size'),
|
|
require('outlayer'),
|
|
require('./rect'),
|
|
require('./packer'),
|
|
require('./item')
|
|
);
|
|
} else {
|
|
// browser global
|
|
window.Packery = factory(
|
|
window.getSize,
|
|
window.Outlayer,
|
|
window.Packery.Rect,
|
|
window.Packery.Packer,
|
|
window.Packery.Item
|
|
);
|
|
}
|
|
|
|
}( window, function factory( getSize, Outlayer, Rect, Packer, Item ) {
|
|
'use strict';
|
|
|
|
// ----- Rect ----- //
|
|
|
|
// allow for pixel rounding errors IE8-IE11 & Firefox; #227
|
|
Rect.prototype.canFit = function( rect ) {
|
|
return this.width >= rect.width - 1 && this.height >= rect.height - 1;
|
|
};
|
|
|
|
// -------------------------- Packery -------------------------- //
|
|
|
|
// create an Outlayer layout class
|
|
var Packery = Outlayer.create('packery');
|
|
Packery.Item = Item;
|
|
|
|
var proto = Packery.prototype;
|
|
|
|
proto._create = function() {
|
|
// call super
|
|
Outlayer.prototype._create.call( this );
|
|
|
|
// initial properties
|
|
this.packer = new Packer();
|
|
// packer for drop targets
|
|
this.shiftPacker = new Packer();
|
|
this.isEnabled = true;
|
|
|
|
this.dragItemCount = 0;
|
|
|
|
// create drag handlers
|
|
var _this = this;
|
|
this.handleDraggabilly = {
|
|
dragStart: function() {
|
|
_this.itemDragStart( this.element );
|
|
},
|
|
dragMove: function() {
|
|
_this.itemDragMove( this.element, this.position.x, this.position.y );
|
|
},
|
|
dragEnd: function() {
|
|
_this.itemDragEnd( this.element );
|
|
}
|
|
};
|
|
|
|
this.handleUIDraggable = {
|
|
start: function handleUIDraggableStart( event, ui ) {
|
|
// HTML5 may trigger dragstart, dismiss HTML5 dragging
|
|
if ( !ui ) {
|
|
return;
|
|
}
|
|
_this.itemDragStart( event.currentTarget );
|
|
},
|
|
drag: function handleUIDraggableDrag( event, ui ) {
|
|
if ( !ui ) {
|
|
return;
|
|
}
|
|
_this.itemDragMove( event.currentTarget, ui.position.left, ui.position.top );
|
|
},
|
|
stop: function handleUIDraggableStop( event, ui ) {
|
|
if ( !ui ) {
|
|
return;
|
|
}
|
|
_this.itemDragEnd( event.currentTarget );
|
|
}
|
|
};
|
|
|
|
};
|
|
|
|
|
|
// ----- init & layout ----- //
|
|
|
|
/**
|
|
* logic before any new layout
|
|
*/
|
|
proto._resetLayout = function() {
|
|
this.getSize();
|
|
|
|
this._getMeasurements();
|
|
|
|
// reset packer
|
|
var width, height, sortDirection;
|
|
// packer settings, if horizontal or vertical
|
|
if ( this._getOption('horizontal') ) {
|
|
width = Infinity;
|
|
height = this.size.innerHeight + this.gutter;
|
|
sortDirection = 'rightwardTopToBottom';
|
|
} else {
|
|
width = this.size.innerWidth + this.gutter;
|
|
height = Infinity;
|
|
sortDirection = 'downwardLeftToRight';
|
|
}
|
|
|
|
this.packer.width = this.shiftPacker.width = width;
|
|
this.packer.height = this.shiftPacker.height = height;
|
|
this.packer.sortDirection = this.shiftPacker.sortDirection = sortDirection;
|
|
|
|
this.packer.reset();
|
|
|
|
// layout
|
|
this.maxY = 0;
|
|
this.maxX = 0;
|
|
};
|
|
|
|
/**
|
|
* update columnWidth, rowHeight, & gutter
|
|
* @private
|
|
*/
|
|
proto._getMeasurements = function() {
|
|
this._getMeasurement( 'columnWidth', 'width' );
|
|
this._getMeasurement( 'rowHeight', 'height' );
|
|
this._getMeasurement( 'gutter', 'width' );
|
|
};
|
|
|
|
proto._getItemLayoutPosition = function( item ) {
|
|
this._setRectSize( item.element, item.rect );
|
|
if ( this.isShifting || this.dragItemCount > 0 ) {
|
|
var packMethod = this._getPackMethod();
|
|
this.packer[ packMethod ]( item.rect );
|
|
} else {
|
|
this.packer.pack( item.rect );
|
|
}
|
|
|
|
this._setMaxXY( item.rect );
|
|
return item.rect;
|
|
};
|
|
|
|
proto.shiftLayout = function() {
|
|
this.isShifting = true;
|
|
this.layout();
|
|
delete this.isShifting;
|
|
};
|
|
|
|
proto._getPackMethod = function() {
|
|
return this._getOption('horizontal') ? 'rowPack' : 'columnPack';
|
|
};
|
|
|
|
|
|
/**
|
|
* set max X and Y value, for size of container
|
|
* @param {Packery.Rect} rect
|
|
* @private
|
|
*/
|
|
proto._setMaxXY = function( rect ) {
|
|
this.maxX = Math.max( rect.x + rect.width, this.maxX );
|
|
this.maxY = Math.max( rect.y + rect.height, this.maxY );
|
|
};
|
|
|
|
/**
|
|
* set the width and height of a rect, applying columnWidth and rowHeight
|
|
* @param {Element} elem
|
|
* @param {Packery.Rect} rect
|
|
*/
|
|
proto._setRectSize = function( elem, rect ) {
|
|
var size = getSize( elem );
|
|
var w = size.outerWidth;
|
|
var h = size.outerHeight;
|
|
// size for columnWidth and rowHeight, if available
|
|
// only check if size is non-zero, #177
|
|
if ( w || h ) {
|
|
w = this._applyGridGutter( w, this.columnWidth );
|
|
h = this._applyGridGutter( h, this.rowHeight );
|
|
}
|
|
// rect must fit in packer
|
|
rect.width = Math.min( w, this.packer.width );
|
|
rect.height = Math.min( h, this.packer.height );
|
|
};
|
|
|
|
/**
|
|
* fits item to columnWidth/rowHeight and adds gutter
|
|
* @param {Number} measurement - item width or height
|
|
* @param {Number} gridSize - columnWidth or rowHeight
|
|
* @returns measurement
|
|
*/
|
|
proto._applyGridGutter = function( measurement, gridSize ) {
|
|
// just add gutter if no gridSize
|
|
if ( !gridSize ) {
|
|
return measurement + this.gutter;
|
|
}
|
|
gridSize += this.gutter;
|
|
// fit item to columnWidth/rowHeight
|
|
var remainder = measurement % gridSize;
|
|
var mathMethod = remainder && remainder < 1 ? 'round' : 'ceil';
|
|
measurement = Math[ mathMethod ]( measurement / gridSize ) * gridSize;
|
|
return measurement;
|
|
};
|
|
|
|
proto._getContainerSize = function() {
|
|
if ( this._getOption('horizontal') ) {
|
|
return {
|
|
width: this.maxX - this.gutter
|
|
};
|
|
} else {
|
|
return {
|
|
height: this.maxY - this.gutter
|
|
};
|
|
}
|
|
};
|
|
|
|
|
|
// -------------------------- stamp -------------------------- //
|
|
|
|
/**
|
|
* makes space for element
|
|
* @param {Element} elem
|
|
*/
|
|
proto._manageStamp = function( elem ) {
|
|
|
|
var item = this.getItem( elem );
|
|
var rect;
|
|
if ( item && item.isPlacing ) {
|
|
rect = item.rect;
|
|
} else {
|
|
var offset = this._getElementOffset( elem );
|
|
rect = new Rect({
|
|
x: this._getOption('originLeft') ? offset.left : offset.right,
|
|
y: this._getOption('originTop') ? offset.top : offset.bottom
|
|
});
|
|
}
|
|
|
|
this._setRectSize( elem, rect );
|
|
// save its space in the packer
|
|
this.packer.placed( rect );
|
|
this._setMaxXY( rect );
|
|
};
|
|
|
|
// -------------------------- methods -------------------------- //
|
|
|
|
function verticalSorter( a, b ) {
|
|
return a.position.y - b.position.y || a.position.x - b.position.x;
|
|
}
|
|
|
|
function horizontalSorter( a, b ) {
|
|
return a.position.x - b.position.x || a.position.y - b.position.y;
|
|
}
|
|
|
|
proto.sortItemsByPosition = function() {
|
|
var sorter = this._getOption('horizontal') ? horizontalSorter : verticalSorter;
|
|
this.items.sort( sorter );
|
|
};
|
|
|
|
/**
|
|
* Fit item element in its current position
|
|
* Packery will position elements around it
|
|
* useful for expanding elements
|
|
*
|
|
* @param {Element} elem
|
|
* @param {Number} x - horizontal destination position, optional
|
|
* @param {Number} y - vertical destination position, optional
|
|
*/
|
|
proto.fit = function( elem, x, y ) {
|
|
var item = this.getItem( elem );
|
|
if ( !item ) {
|
|
return;
|
|
}
|
|
|
|
// stamp item to get it out of layout
|
|
this.stamp( item.element );
|
|
// set placing flag
|
|
item.enablePlacing();
|
|
this.updateShiftTargets( item );
|
|
// fall back to current position for fitting
|
|
x = x === undefined ? item.rect.x: x;
|
|
y = y === undefined ? item.rect.y: y;
|
|
// position it best at its destination
|
|
this.shift( item, x, y );
|
|
this._bindFitEvents( item );
|
|
item.moveTo( item.rect.x, item.rect.y );
|
|
// layout everything else
|
|
this.shiftLayout();
|
|
// return back to regularly scheduled programming
|
|
this.unstamp( item.element );
|
|
this.sortItemsByPosition();
|
|
item.disablePlacing();
|
|
};
|
|
|
|
/**
|
|
* emit event when item is fit and other items are laid out
|
|
* @param {Packery.Item} item
|
|
* @private
|
|
*/
|
|
proto._bindFitEvents = function( item ) {
|
|
var _this = this;
|
|
var ticks = 0;
|
|
function onLayout() {
|
|
ticks++;
|
|
if ( ticks != 2 ) {
|
|
return;
|
|
}
|
|
_this.dispatchEvent( 'fitComplete', null, [ item ] );
|
|
}
|
|
// when item is laid out
|
|
item.once( 'layout', onLayout );
|
|
// when all items are laid out
|
|
this.once( 'layoutComplete', onLayout );
|
|
};
|
|
|
|
// -------------------------- resize -------------------------- //
|
|
|
|
// debounced, layout on resize
|
|
proto.resize = function() {
|
|
// don't trigger if size did not change
|
|
// or if resize was unbound. See #285, outlayer#9
|
|
if ( !this.isResizeBound || !this.needsResizeLayout() ) {
|
|
return;
|
|
}
|
|
|
|
if ( this.options.shiftPercentResize ) {
|
|
this.resizeShiftPercentLayout();
|
|
} else {
|
|
this.layout();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* check if layout is needed post layout
|
|
* @returns Boolean
|
|
*/
|
|
proto.needsResizeLayout = function() {
|
|
var size = getSize( this.element );
|
|
var innerSize = this._getOption('horizontal') ? 'innerHeight' : 'innerWidth';
|
|
return size[ innerSize ] != this.size[ innerSize ];
|
|
};
|
|
|
|
proto.resizeShiftPercentLayout = function() {
|
|
var items = this._getItemsForLayout( this.items );
|
|
|
|
var isHorizontal = this._getOption('horizontal');
|
|
var coord = isHorizontal ? 'y' : 'x';
|
|
var measure = isHorizontal ? 'height' : 'width';
|
|
var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth';
|
|
var innerSize = isHorizontal ? 'innerHeight' : 'innerWidth';
|
|
|
|
// proportional re-align items
|
|
var previousSegment = this[ segmentName ];
|
|
previousSegment = previousSegment && previousSegment + this.gutter;
|
|
|
|
if ( previousSegment ) {
|
|
this._getMeasurements();
|
|
var currentSegment = this[ segmentName ] + this.gutter;
|
|
items.forEach( function( item ) {
|
|
var seg = Math.round( item.rect[ coord ] / previousSegment );
|
|
item.rect[ coord ] = seg * currentSegment;
|
|
});
|
|
} else {
|
|
var currentSize = getSize( this.element )[ innerSize ] + this.gutter;
|
|
var previousSize = this.packer[ measure ];
|
|
items.forEach( function( item ) {
|
|
item.rect[ coord ] = ( item.rect[ coord ] / previousSize ) * currentSize;
|
|
});
|
|
}
|
|
|
|
this.shiftLayout();
|
|
};
|
|
|
|
// -------------------------- drag -------------------------- //
|
|
|
|
/**
|
|
* handle an item drag start event
|
|
* @param {Element} elem
|
|
*/
|
|
proto.itemDragStart = function( elem ) {
|
|
if ( !this.isEnabled ) {
|
|
return;
|
|
}
|
|
this.stamp( elem );
|
|
// this.ignore( elem );
|
|
var item = this.getItem( elem );
|
|
if ( !item ) {
|
|
return;
|
|
}
|
|
|
|
item.enablePlacing();
|
|
item.showDropPlaceholder();
|
|
this.dragItemCount++;
|
|
this.updateShiftTargets( item );
|
|
};
|
|
|
|
proto.updateShiftTargets = function( dropItem ) {
|
|
this.shiftPacker.reset();
|
|
|
|
// pack stamps
|
|
this._getBoundingRect();
|
|
var isOriginLeft = this._getOption('originLeft');
|
|
var isOriginTop = this._getOption('originTop');
|
|
this.stamps.forEach( function( stamp ) {
|
|
// ignore dragged item
|
|
var item = this.getItem( stamp );
|
|
if ( item && item.isPlacing ) {
|
|
return;
|
|
}
|
|
var offset = this._getElementOffset( stamp );
|
|
var rect = new Rect({
|
|
x: isOriginLeft ? offset.left : offset.right,
|
|
y: isOriginTop ? offset.top : offset.bottom
|
|
});
|
|
this._setRectSize( stamp, rect );
|
|
// save its space in the packer
|
|
this.shiftPacker.placed( rect );
|
|
}, this );
|
|
|
|
// reset shiftTargets
|
|
var isHorizontal = this._getOption('horizontal');
|
|
var segmentName = isHorizontal ? 'rowHeight' : 'columnWidth';
|
|
var measure = isHorizontal ? 'height' : 'width';
|
|
|
|
this.shiftTargetKeys = [];
|
|
this.shiftTargets = [];
|
|
var boundsSize;
|
|
var segment = this[ segmentName ];
|
|
segment = segment && segment + this.gutter;
|
|
|
|
if ( segment ) {
|
|
var segmentSpan = Math.ceil( dropItem.rect[ measure ] / segment );
|
|
var segs = Math.floor( ( this.shiftPacker[ measure ] + this.gutter ) / segment );
|
|
boundsSize = ( segs - segmentSpan ) * segment;
|
|
// add targets on top
|
|
for ( var i=0; i < segs; i++ ) {
|
|
var initialX = isHorizontal ? 0 : i * segment;
|
|
var initialY = isHorizontal ? i * segment : 0;
|
|
this._addShiftTarget( initialX, initialY, boundsSize );
|
|
}
|
|
} else {
|
|
boundsSize = ( this.shiftPacker[ measure ] + this.gutter ) - dropItem.rect[ measure ];
|
|
this._addShiftTarget( 0, 0, boundsSize );
|
|
}
|
|
|
|
// pack each item to measure where shiftTargets are
|
|
var items = this._getItemsForLayout( this.items );
|
|
var packMethod = this._getPackMethod();
|
|
items.forEach( function( item ) {
|
|
var rect = item.rect;
|
|
this._setRectSize( item.element, rect );
|
|
this.shiftPacker[ packMethod ]( rect );
|
|
|
|
// add top left corner
|
|
this._addShiftTarget( rect.x, rect.y, boundsSize );
|
|
// add bottom left / top right corner
|
|
var cornerX = isHorizontal ? rect.x + rect.width : rect.x;
|
|
var cornerY = isHorizontal ? rect.y : rect.y + rect.height;
|
|
this._addShiftTarget( cornerX, cornerY, boundsSize );
|
|
|
|
if ( segment ) {
|
|
// add targets for each column on bottom / row on right
|
|
var segSpan = Math.round( rect[ measure ] / segment );
|
|
for ( var i=1; i < segSpan; i++ ) {
|
|
var segX = isHorizontal ? cornerX : rect.x + segment * i;
|
|
var segY = isHorizontal ? rect.y + segment * i : cornerY;
|
|
this._addShiftTarget( segX, segY, boundsSize );
|
|
}
|
|
}
|
|
}, this );
|
|
|
|
};
|
|
|
|
proto._addShiftTarget = function( x, y, boundsSize ) {
|
|
var checkCoord = this._getOption('horizontal') ? y : x;
|
|
if ( checkCoord !== 0 && checkCoord > boundsSize ) {
|
|
return;
|
|
}
|
|
// create string for a key, easier to keep track of what targets
|
|
var key = x + ',' + y;
|
|
var hasKey = this.shiftTargetKeys.indexOf( key ) != -1;
|
|
if ( hasKey ) {
|
|
return;
|
|
}
|
|
this.shiftTargetKeys.push( key );
|
|
this.shiftTargets.push({ x: x, y: y });
|
|
};
|
|
|
|
// -------------------------- drop -------------------------- //
|
|
|
|
proto.shift = function( item, x, y ) {
|
|
var shiftPosition;
|
|
var minDistance = Infinity;
|
|
var position = { x: x, y: y };
|
|
this.shiftTargets.forEach( function( target ) {
|
|
var distance = getDistance( target, position );
|
|
if ( distance < minDistance ) {
|
|
shiftPosition = target;
|
|
minDistance = distance;
|
|
}
|
|
});
|
|
item.rect.x = shiftPosition.x;
|
|
item.rect.y = shiftPosition.y;
|
|
};
|
|
|
|
function getDistance( a, b ) {
|
|
var dx = b.x - a.x;
|
|
var dy = b.y - a.y;
|
|
return Math.sqrt( dx * dx + dy * dy );
|
|
}
|
|
|
|
// -------------------------- drag move -------------------------- //
|
|
|
|
var DRAG_THROTTLE_TIME = 120;
|
|
|
|
/**
|
|
* handle an item drag move event
|
|
* @param {Element} elem
|
|
* @param {Number} x - horizontal change in position
|
|
* @param {Number} y - vertical change in position
|
|
*/
|
|
proto.itemDragMove = function( elem, x, y ) {
|
|
var item = this.isEnabled && this.getItem( elem );
|
|
if ( !item ) {
|
|
return;
|
|
}
|
|
|
|
x -= this.size.paddingLeft;
|
|
y -= this.size.paddingTop;
|
|
|
|
var _this = this;
|
|
function onDrag() {
|
|
_this.shift( item, x, y );
|
|
item.positionDropPlaceholder();
|
|
_this.layout();
|
|
}
|
|
|
|
// throttle
|
|
var now = new Date();
|
|
var isThrottled = this._itemDragTime && now - this._itemDragTime < DRAG_THROTTLE_TIME;
|
|
if ( isThrottled ) {
|
|
clearTimeout( this.dragTimeout );
|
|
this.dragTimeout = setTimeout( onDrag, DRAG_THROTTLE_TIME );
|
|
} else {
|
|
onDrag();
|
|
this._itemDragTime = now;
|
|
}
|
|
};
|
|
|
|
// -------------------------- drag end -------------------------- //
|
|
|
|
/**
|
|
* handle an item drag end event
|
|
* @param {Element} elem
|
|
*/
|
|
proto.itemDragEnd = function( elem ) {
|
|
var item = this.isEnabled && this.getItem( elem );
|
|
if ( !item ) {
|
|
return;
|
|
}
|
|
|
|
clearTimeout( this.dragTimeout );
|
|
item.element.classList.add('is-positioning-post-drag');
|
|
|
|
var completeCount = 0;
|
|
var _this = this;
|
|
function onDragEndLayoutComplete() {
|
|
completeCount++;
|
|
if ( completeCount != 2 ) {
|
|
return;
|
|
}
|
|
// reset drag item
|
|
item.element.classList.remove('is-positioning-post-drag');
|
|
item.hideDropPlaceholder();
|
|
_this.dispatchEvent( 'dragItemPositioned', null, [ item ] );
|
|
}
|
|
|
|
item.once( 'layout', onDragEndLayoutComplete );
|
|
this.once( 'layoutComplete', onDragEndLayoutComplete );
|
|
item.moveTo( item.rect.x, item.rect.y );
|
|
this.layout();
|
|
this.dragItemCount = Math.max( 0, this.dragItemCount - 1 );
|
|
this.sortItemsByPosition();
|
|
item.disablePlacing();
|
|
this.unstamp( item.element );
|
|
};
|
|
|
|
/**
|
|
* binds Draggabilly events
|
|
* @param {Draggabilly} draggie
|
|
*/
|
|
proto.bindDraggabillyEvents = function( draggie ) {
|
|
this._bindDraggabillyEvents( draggie, 'on' );
|
|
};
|
|
|
|
proto.unbindDraggabillyEvents = function( draggie ) {
|
|
this._bindDraggabillyEvents( draggie, 'off' );
|
|
};
|
|
|
|
proto._bindDraggabillyEvents = function( draggie, method ) {
|
|
var handlers = this.handleDraggabilly;
|
|
draggie[ method ]( 'dragStart', handlers.dragStart );
|
|
draggie[ method ]( 'dragMove', handlers.dragMove );
|
|
draggie[ method ]( 'dragEnd', handlers.dragEnd );
|
|
};
|
|
|
|
/**
|
|
* binds jQuery UI Draggable events
|
|
* @param {jQuery} $elems
|
|
*/
|
|
proto.bindUIDraggableEvents = function( $elems ) {
|
|
this._bindUIDraggableEvents( $elems, 'on' );
|
|
};
|
|
|
|
proto.unbindUIDraggableEvents = function( $elems ) {
|
|
this._bindUIDraggableEvents( $elems, 'off' );
|
|
};
|
|
|
|
proto._bindUIDraggableEvents = function( $elems, method ) {
|
|
var handlers = this.handleUIDraggable;
|
|
$elems
|
|
[ method ]( 'dragstart', handlers.start )
|
|
[ method ]( 'drag', handlers.drag )
|
|
[ method ]( 'dragstop', handlers.stop );
|
|
};
|
|
|
|
// ----- destroy ----- //
|
|
|
|
var _destroy = proto.destroy;
|
|
proto.destroy = function() {
|
|
_destroy.apply( this, arguments );
|
|
// disable flag; prevent drag events from triggering. #72
|
|
this.isEnabled = false;
|
|
};
|
|
|
|
// ----- ----- //
|
|
|
|
Packery.Rect = Rect;
|
|
Packery.Packer = Packer;
|
|
|
|
return Packery;
|
|
|
|
}));
|