Browse Source

tick beta version v2.1.0-beta.1

Try npm release

Update deps:

+ jQuery Bridget v1.1.0
+ docReady v1.0.4
+ EventEmitter v4.2.9
+ getSize v1.2.0
+ matchesSelector v1.0.2
+ Outlayer v1.3.0
+ Masonry v3.2.1
pull/829/head v2.1.0-beta.1
David DeSandro 10 years ago
parent
commit
b2c4df979c
  1. 8
      README.mdown
  2. 2
      bower.json
  3. 248
      dist/isotope.pkgd.js
  4. 6
      dist/isotope.pkgd.min.js
  5. 2
      js/isotope.js
  6. 3
      package.json
  7. 2
      sandbox/browserify/main.js

8
README.mdown

@ -11,13 +11,11 @@ A packaged source file includes everything you need to use Isotope.
+ [isotope.pkgd.js](http://isotope.metafizzy.co/isotope.pkgd.js) + [isotope.pkgd.js](http://isotope.metafizzy.co/isotope.pkgd.js)
+ [isotope.pkgd.min.js](http://isotope.metafizzy.co/isotope.pkgd.min.js) + [isotope.pkgd.min.js](http://isotope.metafizzy.co/isotope.pkgd.min.js)
### Bower If you are cool with the command line...
If you are cool with the command line, install Isotope as a [Bower](http://bower.io) package: Install with [Bower](http://bower.io): `bower install isotope`
``` bash [Install with npm](https://www.npmjs.org/package/isotope-layout): `npm install isotope-layout`
bower install isotope
```
## License ## License

2
bower.json

@ -1,6 +1,6 @@
{ {
"name": "isotope", "name": "isotope",
"version": "2.0.1", "version": "2.1.0-beta.1",
"description": "Filter and sort magical layouts", "description": "Filter and sort magical layouts",
"main": [ "main": [
"js/item.js", "js/item.js",

248
dist/isotope.pkgd.js vendored

@ -1,12 +1,13 @@
/*! /*!
* Isotope PACKAGED v2.0.1 * Isotope PACKAGED v2.1.0-beta.1
* Filter & sort magical layouts * Filter & sort magical layouts
* http://isotope.metafizzy.co * http://isotope.metafizzy.co
*/ */
/** /**
* Bridget makes jQuery widgets * Bridget makes jQuery widgets
* v1.0.1 * v1.1.0
* MIT license
*/ */
( function( window ) { ( function( window ) {
@ -50,7 +51,6 @@ function addOptionMethod( PluginClass ) {
}; };
} }
// -------------------------- plugin bridge -------------------------- // // -------------------------- plugin bridge -------------------------- //
// helper function for logging errors // helper function for logging errors
@ -135,6 +135,8 @@ return $.bridget;
if ( typeof define === 'function' && define.amd ) { if ( typeof define === 'function' && define.amd ) {
// AMD // AMD
define( 'jquery-bridget/jquery.bridget',[ 'jquery' ], defineBridget ); define( 'jquery-bridget/jquery.bridget',[ 'jquery' ], defineBridget );
} else if ( typeof exports === 'object' ) {
defineBridget( require('jquery') );
} else { } else {
// get jquery from browser global // get jquery from browser global
defineBridget( window.jQuery ); defineBridget( window.jQuery );
@ -226,12 +228,13 @@ if ( typeof define === 'function' && define.amd ) {
})( this ); })( this );
/*! /*!
* docReady * docReady v1.0.4
* Cross browser DOMContentLoaded event emitter * Cross browser DOMContentLoaded event emitter
* MIT license
*/ */
/*jshint browser: true, strict: true, undef: true, unused: true*/ /*jshint browser: true, strict: true, undef: true, unused: true*/
/*global define: false */ /*global define: false, require: false, module: false */
( function( window ) { ( function( window ) {
@ -259,14 +262,18 @@ function docReady( fn ) {
docReady.isReady = false; docReady.isReady = false;
// triggered on various doc ready events // triggered on various doc ready events
function init( event ) { function onReady( event ) {
// bail if IE8 document is not ready just yet // bail if already triggered or IE8 document is not ready just yet
var isIE8NotReady = event.type === 'readystatechange' && document.readyState !== 'complete'; var isIE8NotReady = event.type === 'readystatechange' && document.readyState !== 'complete';
if ( docReady.isReady || isIE8NotReady ) { if ( docReady.isReady || isIE8NotReady ) {
return; return;
} }
docReady.isReady = true;
trigger();
}
function trigger() {
docReady.isReady = true;
// process queue // process queue
for ( var i=0, len = queue.length; i < len; i++ ) { for ( var i=0, len = queue.length; i < len; i++ ) {
var fn = queue[i]; var fn = queue[i];
@ -275,9 +282,15 @@ function init( event ) {
} }
function defineDocReady( eventie ) { function defineDocReady( eventie ) {
eventie.bind( document, 'DOMContentLoaded', init ); // trigger ready if page is ready
eventie.bind( document, 'readystatechange', init ); if ( document.readyState === 'complete' ) {
eventie.bind( window, 'load', init ); trigger();
} else {
// listen for events
eventie.bind( document, 'DOMContentLoaded', onReady );
eventie.bind( document, 'readystatechange', onReady );
eventie.bind( window, 'load', onReady );
}
return docReady; return docReady;
} }
@ -285,18 +298,18 @@ function defineDocReady( eventie ) {
// transport // transport
if ( typeof define === 'function' && define.amd ) { if ( typeof define === 'function' && define.amd ) {
// AMD // AMD
// if RequireJS, then doc is already ready
docReady.isReady = typeof requirejs === 'function';
define( 'doc-ready/doc-ready',[ 'eventie/eventie' ], defineDocReady ); define( 'doc-ready/doc-ready',[ 'eventie/eventie' ], defineDocReady );
} else if ( typeof exports === 'object' ) {
module.exports = defineDocReady( require('eventie') );
} else { } else {
// browser global // browser global
window.docReady = defineDocReady( window.eventie ); window.docReady = defineDocReady( window.eventie );
} }
})( this ); })( window );
/*! /*!
* EventEmitter v4.2.7 - git.io/ee * EventEmitter v4.2.9 - git.io/ee
* Oliver Caldwell * Oliver Caldwell
* MIT license * MIT license
* @preserve * @preserve
@ -319,7 +332,7 @@ if ( typeof define === 'function' && define.amd ) {
var originalGlobalValue = exports.EventEmitter; var originalGlobalValue = exports.EventEmitter;
/** /**
* Finds the index of the listener for the event in it's storage array. * Finds the index of the listener for the event in its storage array.
* *
* @param {Function[]} listeners Array of listeners to search through. * @param {Function[]} listeners Array of listeners to search through.
* @param {Function} listener Method to look for. * @param {Function} listener Method to look for.
@ -450,7 +463,7 @@ if ( typeof define === 'function' && define.amd ) {
/** /**
* Semi-alias of addListener. It will add a listener that will be * Semi-alias of addListener. It will add a listener that will be
* automatically removed after it's first execution. * automatically removed after its first execution.
* *
* @param {String|RegExp} evt Name of the event to attach the listener to. * @param {String|RegExp} evt Name of the event to attach the listener to.
* @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling. * @param {Function} listener Method to be called when the event is emitted. If the function returns true then it will be removed after calling.
@ -572,7 +585,7 @@ if ( typeof define === 'function' && define.amd ) {
var single = remove ? this.removeListener : this.addListener; var single = remove ? this.removeListener : this.addListener;
var multiple = remove ? this.removeListeners : this.addListeners; var multiple = remove ? this.removeListeners : this.addListeners;
// If evt is an object then pass each of it's properties to this method // If evt is an object then pass each of its properties to this method
if (typeof evt === 'object' && !(evt instanceof RegExp)) { if (typeof evt === 'object' && !(evt instanceof RegExp)) {
for (i in evt) { for (i in evt) {
if (evt.hasOwnProperty(i) && (value = evt[i])) { if (evt.hasOwnProperty(i) && (value = evt[i])) {
@ -764,14 +777,15 @@ if ( typeof define === 'function' && define.amd ) {
module.exports = EventEmitter; module.exports = EventEmitter;
} }
else { else {
this.EventEmitter = EventEmitter; exports.EventEmitter = EventEmitter;
} }
}.call(this)); }.call(this));
/*! /*!
* getStyleProperty v1.0.3 * getStyleProperty v1.0.4
* original by kangax * original by kangax
* http://perfectionkills.com/feature-testing-css-properties/ * http://perfectionkills.com/feature-testing-css-properties/
* MIT license
*/ */
/*jshint browser: true, strict: true, undef: true */ /*jshint browser: true, strict: true, undef: true */
@ -823,9 +837,10 @@ if ( typeof define === 'function' && define.amd ) {
})( window ); })( window );
/** /*!
* getSize v1.1.7 * getSize v1.2.0
* measure size of elements * measure size of elements
* MIT license
*/ */
/*jshint browser: true, strict: true, undef: true, unused: true */ /*jshint browser: true, strict: true, undef: true, unused: true */
@ -837,15 +852,6 @@ if ( typeof define === 'function' && define.amd ) {
// -------------------------- helpers -------------------------- // // -------------------------- helpers -------------------------- //
var getComputedStyle = window.getComputedStyle;
var getStyle = getComputedStyle ?
function( elem ) {
return getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
// get a number from a string, not a percentage // get a number from a string, not a percentage
function getStyleSize( value ) { function getStyleSize( value ) {
var num = parseFloat( value ); var num = parseFloat( value );
@ -854,6 +860,11 @@ function getStyleSize( value ) {
return isValid && num; return isValid && num;
} }
var logError = typeof console === 'undefined' ? noop :
function( message ) {
console.error( message );
};
// -------------------------- measurements -------------------------- // // -------------------------- measurements -------------------------- //
var measurements = [ var measurements = [
@ -891,20 +902,54 @@ function getZeroSize() {
function defineGetSize( getStyleProperty ) { function defineGetSize( getStyleProperty ) {
// -------------------------- box sizing -------------------------- // // -------------------------- setup -------------------------- //
var boxSizingProp = getStyleProperty('boxSizing'); var isSetup = false;
var isBoxSizeOuter;
var getStyle, boxSizingProp, isBoxSizeOuter;
/** /**
* WebKit measures the outer-width on style.width on border-box elems * setup vars and functions
* IE & Firefox measures the inner-width * do it on initial getSize(), rather than on script load
* For Firefox bug https://bugzilla.mozilla.org/show_bug.cgi?id=548397
*/ */
( function() { function setup() {
if ( !boxSizingProp ) { // setup once
if ( isSetup ) {
return; return;
} }
isSetup = true;
var getComputedStyle = window.getComputedStyle;
getStyle = ( function() {
var getStyleFn = getComputedStyle ?
function( elem ) {
return getComputedStyle( elem, null );
} :
function( elem ) {
return elem.currentStyle;
};
return function getStyle( elem ) {
var style = getStyleFn( elem );
if ( !style ) {
logError( 'Style returned ' + style +
'. Are you running this code in a hidden iframe on Firefox? ' +
'See http://bit.ly/getsizeiframe' );
}
return style;
}
})();
// -------------------------- box sizing -------------------------- //
boxSizingProp = getStyleProperty('boxSizing');
/**
* WebKit measures the outer-width on style.width on border-box elems
* IE & Firefox measures the inner-width
*/
if ( boxSizingProp ) {
var div = document.createElement('div'); var div = document.createElement('div');
div.style.width = '200px'; div.style.width = '200px';
div.style.padding = '1px 2px 3px 4px'; div.style.padding = '1px 2px 3px 4px';
@ -918,12 +963,15 @@ var isBoxSizeOuter;
isBoxSizeOuter = getStyleSize( style.width ) === 200; isBoxSizeOuter = getStyleSize( style.width ) === 200;
body.removeChild( div ); body.removeChild( div );
})(); }
}
// -------------------------- getSize -------------------------- // // -------------------------- getSize -------------------------- //
function getSize( elem ) { function getSize( elem ) {
setup();
// use querySeletor if elem is string // use querySeletor if elem is string
if ( typeof elem === 'string' ) { if ( typeof elem === 'string' ) {
elem = document.querySelector( elem ); elem = document.querySelector( elem );
@ -1030,7 +1078,7 @@ if ( typeof define === 'function' && define.amd ) {
define( 'get-size/get-size',[ 'get-style-property/get-style-property' ], defineGetSize ); define( 'get-size/get-size',[ 'get-style-property/get-style-property' ], defineGetSize );
} else if ( typeof exports === 'object' ) { } else if ( typeof exports === 'object' ) {
// CommonJS for Component // CommonJS for Component
module.exports = defineGetSize( require('get-style-property') ); module.exports = defineGetSize( require('desandro-get-style-property') );
} else { } else {
// browser global // browser global
window.getSize = defineGetSize( window.getStyleProperty ); window.getSize = defineGetSize( window.getStyleProperty );
@ -1039,17 +1087,15 @@ if ( typeof define === 'function' && define.amd ) {
})( window ); })( window );
/** /**
* matchesSelector helper v1.0.1 * matchesSelector v1.0.2
* * matchesSelector( element, '.selector' )
* @name matchesSelector * MIT license
* @param {Element} elem
* @param {String} selector
*/ */
/*jshint browser: true, strict: true, undef: true, unused: true */ /*jshint browser: true, strict: true, undef: true, unused: true */
/*global define: false */ /*global define: false, module: false */
( function( global, ElemProto ) { ( function( ElemProto ) {
@ -1134,12 +1180,15 @@ if ( typeof define === 'function' && define.amd ) {
define( 'matches-selector/matches-selector',[],function() { define( 'matches-selector/matches-selector',[],function() {
return matchesSelector; return matchesSelector;
}); });
} else { } else if ( typeof exports === 'object' ) {
module.exports = matchesSelector;
}
else {
// browser global // browser global
window.matchesSelector = matchesSelector; window.matchesSelector = matchesSelector;
} }
})( this, Element.prototype ); })( Element.prototype );
/** /**
* Outlayer Item * Outlayer Item
@ -1653,6 +1702,13 @@ if ( typeof define === 'function' && define.amd ) {
'get-style-property/get-style-property' 'get-style-property/get-style-property'
], ],
outlayerItemDefinition ); outlayerItemDefinition );
} else if (typeof exports === 'object') {
// CommonJS
module.exports = outlayerItemDefinition(
require('wolfy87-eventemitter'),
require('get-size'),
require('desandro-get-style-property')
);
} else { } else {
// browser global // browser global
window.Outlayer = {}; window.Outlayer = {};
@ -1666,7 +1722,7 @@ if ( typeof define === 'function' && define.amd ) {
})( window ); })( window );
/*! /*!
* Outlayer v1.2.0 * Outlayer v1.3.0
* the brains and guts of a layout library * the brains and guts of a layout library
* MIT license * MIT license
*/ */
@ -1680,7 +1736,6 @@ if ( typeof define === 'function' && define.amd ) {
var document = window.document; var document = window.document;
var console = window.console; var console = window.console;
var jQuery = window.jQuery; var jQuery = window.jQuery;
var noop = function() {}; var noop = function() {};
// -------------------------- helpers -------------------------- // // -------------------------- helpers -------------------------- //
@ -1718,7 +1773,7 @@ function makeArray( obj ) {
} }
// http://stackoverflow.com/a/384380/182183 // http://stackoverflow.com/a/384380/182183
var isElement = ( typeof HTMLElement === 'object' ) ? var isElement = ( typeof HTMLElement === 'function' || typeof HTMLElement === 'object' ) ?
function isElementDOM2( obj ) { function isElementDOM2( obj ) {
return obj instanceof HTMLElement; return obj instanceof HTMLElement;
} : } :
@ -2536,6 +2591,8 @@ Outlayer.prototype.destroy = function() {
this.unbindResize(); this.unbindResize();
var id = this.element.outlayerGUID;
delete instances[ id ]; // remove reference to instance by id
delete this.element.outlayerGUID; delete this.element.outlayerGUID;
// remove data for jQuery // remove data for jQuery
if ( jQuery ) { if ( jQuery ) {
@ -2661,6 +2718,16 @@ if ( typeof define === 'function' && define.amd ) {
'./item' './item'
], ],
outlayerDefinition ); outlayerDefinition );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = outlayerDefinition(
require('eventie'),
require('doc-ready'),
require('wolfy87-eventemitter'),
require('get-size'),
require('desandro-matches-selector'),
require('./item')
);
} else { } else {
// browser global // browser global
window.Outlayer = outlayerDefinition( window.Outlayer = outlayerDefinition(
@ -2741,6 +2808,11 @@ if ( typeof define === 'function' && define.amd ) {
'outlayer/outlayer' 'outlayer/outlayer'
], ],
itemDefinition ); itemDefinition );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = itemDefinition(
require('outlayer')
);
} else { } else {
// browser global // browser global
window.Isotope = window.Isotope || {}; window.Isotope = window.Isotope || {};
@ -2896,6 +2968,12 @@ if ( typeof define === 'function' && define.amd ) {
'outlayer/outlayer' 'outlayer/outlayer'
], ],
layoutModeDefinition ); layoutModeDefinition );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = layoutModeDefinition(
require('get-size'),
require('outlayer')
);
} else { } else {
// browser global // browser global
window.Isotope = window.Isotope || {}; window.Isotope = window.Isotope || {};
@ -2909,7 +2987,7 @@ if ( typeof define === 'function' && define.amd ) {
})( window ); })( window );
/*! /*!
* Masonry v3.1.5 * Masonry v3.2.1
* Cascading grid layout library * Cascading grid layout library
* http://masonry.desandro.com * http://masonry.desandro.com
* MIT License * MIT License
@ -3104,6 +3182,11 @@ if ( typeof define === 'function' && define.amd ) {
'get-size/get-size' 'get-size/get-size'
], ],
masonryDefinition ); masonryDefinition );
} else if (typeof exports === 'object') {
module.exports = masonryDefinition(
require('outlayer'),
require('get-size')
);
} else { } else {
// browser global // browser global
window.Masonry = masonryDefinition( window.Masonry = masonryDefinition(
@ -3181,6 +3264,12 @@ if ( typeof define === 'function' && define.amd ) {
'masonry/masonry' 'masonry/masonry'
], ],
masonryDefinition ); masonryDefinition );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = masonryDefinition(
require('../layout-mode'),
require('masonry-layout')
);
} else { } else {
// browser global // browser global
masonryDefinition( masonryDefinition(
@ -3203,13 +3292,16 @@ FitRows.prototype._resetLayout = function() {
this.x = 0; this.x = 0;
this.y = 0; this.y = 0;
this.maxY = 0; this.maxY = 0;
this._getMeasurement( 'gutter', 'outerWidth' );
}; };
FitRows.prototype._getItemLayoutPosition = function( item ) { FitRows.prototype._getItemLayoutPosition = function( item ) {
item.getSize(); item.getSize();
var itemWidth = item.size.outerWidth + this.gutter;
// if this element cannot fit in the current row // if this element cannot fit in the current row
if ( this.x !== 0 && item.size.outerWidth + this.x > this.isotope.size.innerWidth ) { var containerWidth = this.isotope.size.innerWidth + this.gutter;
if ( this.x !== 0 && itemWidth + this.x > containerWidth ) {
this.x = 0; this.x = 0;
this.y = this.maxY; this.y = this.maxY;
} }
@ -3220,7 +3312,7 @@ FitRows.prototype._getItemLayoutPosition = function( item ) {
}; };
this.maxY = Math.max( this.maxY, this.y + item.size.outerHeight ); this.maxY = Math.max( this.maxY, this.y + item.size.outerHeight );
this.x += item.size.outerWidth; this.x += itemWidth;
return position; return position;
}; };
@ -3239,6 +3331,11 @@ if ( typeof define === 'function' && define.amd ) {
'../layout-mode' '../layout-mode'
], ],
fitRowsDefinition ); fitRowsDefinition );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = fitRowsDefinition(
require('../layout-mode')
);
} else { } else {
// browser global // browser global
fitRowsDefinition( fitRowsDefinition(
@ -3285,6 +3382,11 @@ if ( typeof define === 'function' && define.amd ) {
'../layout-mode' '../layout-mode'
], ],
verticalDefinition ); verticalDefinition );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = verticalDefinition(
require('../layout-mode')
);
} else { } else {
// browser global // browser global
verticalDefinition( verticalDefinition(
@ -3295,7 +3397,7 @@ if ( typeof define === 'function' && define.amd ) {
})( window ); })( window );
/*! /*!
* Isotope v2.0.1 * Isotope v2.1.0-beta.1
* Filter & sort magical layouts * Filter & sort magical layouts
* http://isotope.metafizzy.co * http://isotope.metafizzy.co
*/ */
@ -3564,13 +3666,17 @@ function isotopeDefinition( Outlayer, getSize, matchesSelector, Item, LayoutMode
* @public * @public
*/ */
Isotope.prototype.updateSortData = function( elems ) { Isotope.prototype.updateSortData = function( elems ) {
this._getSorters(); // get items
// update item sort data var items;
// default to all items if none are passed in if ( elems ) {
elems = makeArray( elems ); elems = makeArray( elems );
var items = this.getItems( elems ); items = this.getItems( elems );
// if no items found, update all items } else {
items = items.length ? items : this.items; // update all items if no elems provided
items = this.items;
}
this._getSorters();
this._updateItemsSortData( items ); this._updateItemsSortData( items );
}; };
@ -3587,7 +3693,10 @@ function isotopeDefinition( Outlayer, getSize, matchesSelector, Item, LayoutMode
* @private * @private
*/ */
Isotope.prototype._updateItemsSortData = function( items ) { Isotope.prototype._updateItemsSortData = function( items ) {
for ( var i=0, len = items.length; i < len; i++ ) { // do not update if no items
var len = items && items.length;
for ( var i=0; len && i < len; i++ ) {
var item = items[i]; var item = items[i];
item.updateSortData(); item.updateSortData();
} }
@ -3910,6 +4019,19 @@ if ( typeof define === 'function' && define.amd ) {
'isotope/js/layout-modes/vertical' 'isotope/js/layout-modes/vertical'
], ],
isotopeDefinition ); isotopeDefinition );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = isotopeDefinition(
require('outlayer'),
require('get-size'),
require('desandro-matches-selector'),
require('./item'),
require('./layout-mode'),
// include default layout modes
require('./layout-modes/masonry'),
require('./layout-modes/fit-rows'),
require('./layout-modes/vertical')
);
} else { } else {
// browser global // browser global
window.Isotope = isotopeDefinition( window.Isotope = isotopeDefinition(

6
dist/isotope.pkgd.min.js vendored

File diff suppressed because one or more lines are too long

2
js/isotope.js

@ -1,5 +1,5 @@
/*! /*!
* Isotope v2.0.1 * Isotope v2.1.0-beta.1
* Filter & sort magical layouts * Filter & sort magical layouts
* http://isotope.metafizzy.co * http://isotope.metafizzy.co
*/ */

3
package.json

@ -1,6 +1,6 @@
{ {
"name": "isotope-layout", "name": "isotope-layout",
"version": "2.0.1", "version": "2.1.0-beta.1",
"description": "Filter and sort magical layouts", "description": "Filter and sort magical layouts",
"dependencies": { "dependencies": {
"get-size": ">=1.1.8 <1.3", "get-size": ">=1.1.8 <1.3",
@ -12,6 +12,7 @@
"desandro-matches-selector": "^1.0.2", "desandro-matches-selector": "^1.0.2",
"doc-ready": "1.x", "doc-ready": "1.x",
"eventie": "^1.0.5", "eventie": "^1.0.5",
"isotope-cells-by-column": "*",
"jquery": ">=1.4.3 <2", "jquery": ">=1.4.3 <2",
"jquery-bridget": "1.1.x", "jquery-bridget": "1.1.x",
"qunitjs": "^1.15", "qunitjs": "^1.15",

2
sandbox/browserify/main.js

@ -2,6 +2,8 @@ var Isotope = window.Isotope = require('../../js/isotope');
var eventie = require('eventie'); var eventie = require('eventie');
var matchesSelector = require('desandro-matches-selector'); var matchesSelector = require('desandro-matches-selector');
// require('isotope-cells-by-row');
function getText( elem ) { function getText( elem ) {
return elem.textContent || elem.innerText; return elem.textContent || elem.innerText;
} }

Loading…
Cancel
Save