Merge remote-tracking branch 'mike/master' into dev

This commit is contained in:
Mario Vavti 2018-01-24 11:17:25 +01:00
commit 503b2225f0
164 changed files with 34835 additions and 9761 deletions

View File

@ -1,4 +1,4 @@
Copyright (c) 2010-2017 the Hubzilla Community
Copyright (c) 2010-2018 the Hubzilla Community
All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy

View File

@ -64,12 +64,12 @@ class Cover_photo extends \Zotlabs\Web\Controller {
$image_id = substr($image_id,0,-2);
}
$srcX = $_POST['xstart'];
$srcY = $_POST['ystart'];
$srcW = $_POST['xfinal'] - $srcX;
$srcH = $_POST['yfinal'] - $srcY;
$srcX = intval($_POST['xstart']);
$srcY = intval($_POST['ystart']);
$srcW = intval($_POST['xfinal']) - $srcX;
$srcH = intval($_POST['yfinal']) - $srcY;
$r = q("select gender from profile where uid = %d and is_default = 1 limit 1",
intval(local_channel())

View File

@ -9,9 +9,41 @@ require_once('include/items.php');
class Like extends \Zotlabs\Web\Controller {
function get() {
private function reaction_to_activity($reaction) {
$acts = [
'like' => ACTIVITY_LIKE ,
'dislike' => ACTIVITY_DISLIKE ,
'agree' => ACTIVITY_AGREE ,
'disagree' => ACTIVITY_DISAGREE ,
'abstain' => ACTIVITY_ABSTAIN ,
'attendyes' => ACTIVITY_ATTEND ,
'attendno' => ACTIVITY_ATTENDNO ,
'attendmaybe' => ACTIVITY_ATTENDMAYBE
];
// unlike (etc.) reactions are an undo of positive reactions, rather than a negative action.
// The activity is the same in undo actions and will have the same activity mapping
if(substr($reaction,0,2) === 'un') {
$reaction = substr($reaction,2);
}
if(array_key_exists($reaction,$acts)) {
return $acts[$reaction];
}
return EMPTY_STR;
}
public function get() {
$o = '';
$o = EMPTY_STR;
$sys_channel = get_sys_channel();
$sys_channel_id = (($sys_channel) ? $sys_channel['channel_id'] : 0);
@ -35,48 +67,17 @@ class Like extends \Zotlabs\Web\Controller {
if(! $verb)
$verb = 'like';
switch($verb) {
case 'like':
case 'unlike':
$activity = ACTIVITY_LIKE;
break;
case 'dislike':
case 'undislike':
$activity = ACTIVITY_DISLIKE;
break;
case 'agree':
case 'unagree':
$activity = ACTIVITY_AGREE;
break;
case 'disagree':
case 'undisagree':
$activity = ACTIVITY_DISAGREE;
break;
case 'abstain':
case 'unabstain':
$activity = ACTIVITY_ABSTAIN;
break;
case 'attendyes':
case 'unattendyes':
$activity = ACTIVITY_ATTEND;
break;
case 'attendno':
case 'unattendno':
$activity = ACTIVITY_ATTENDNO;
break;
case 'attendmaybe':
case 'unattendmaybe':
$activity = ACTIVITY_ATTENDMAYBE;
break;
default:
return;
break;
$activity = $this->reaction_to_activity($verb);
if(! $activity) {
return EMPTY_STR;
}
$extended_like = false;
$object = $target = null;
$post_type = '';
$objtype = '';
$post_type = EMPTY_STR;
$objtype = EMPTY_STR;
if(argc() == 3) {

View File

@ -1,10 +1,11 @@
<?php
namespace Zotlabs\Module;
/* @file profile_photo.php
@brief Module-file with functions for handling of profile-photos
*/
/*
* @file Profile_photo.php
* @brief Module-file with functions for handling of profile-photos
*
*/
require_once('include/photo/photo_driver.php');
@ -55,6 +56,10 @@ class Profile_photo extends \Zotlabs\Web\Controller {
if((array_key_exists('cropfinal',$_POST)) && (intval($_POST['cropfinal']) == 1)) {
// logger('crop: ' . print_r($_POST,true));
// phase 2 - we have finished cropping
if(argc() != 2) {
@ -86,10 +91,10 @@ class Profile_photo extends \Zotlabs\Web\Controller {
}
$srcX = $_POST['xstart'];
$srcY = $_POST['ystart'];
$srcW = $_POST['xfinal'] - $srcX;
$srcH = $_POST['yfinal'] - $srcY;
$srcX = intval($_POST['xstart']);
$srcY = intval($_POST['ystart']);
$srcW = intval($_POST['xfinal']) - $srcX;
$srcH = intval($_POST['yfinal']) - $srcY;
$r = q("SELECT * FROM photo WHERE resource_id = '%s' AND uid = %d AND imgscale = %d LIMIT 1",
dbesc($image_id),

View File

@ -49,7 +49,7 @@ class Permcats {
if(argc() > 2)
$name = argv(2);
$name = hex2bin(argv(2));
if(argc() > 3 && argv(3) === 'drop') {
\Zotlabs\Lib\Permcat::delete(local_channel(),$name);
@ -70,7 +70,7 @@ class Permcats {
if(($pc['name']) && ($name) && ($pc['name'] == $name))
$existing = $pc['perms'];
if(! $pc['system'])
$permcats[$pc['name']] = $pc['localname'];
$permcats[bin2hex($pc['name'])] = $pc['localname'];
}
}

View File

@ -5,7 +5,6 @@ namespace Zotlabs\Module;
class Siteinfo extends \Zotlabs\Web\Controller {
function init() {
logger(print_r($_REQUEST,true));
if (argv(1) === 'json' || $_REQUEST['module_format'] === 'json') {
$data = get_site_info();
json_return_and_die($data);

View File

@ -66,6 +66,7 @@ define ( 'PROJECT_BASE', __DIR__ );
* This can be used in HTML and JavaScript where needed a line break.
*/
define ( 'EOL', '<br>' . "\r\n" );
define ( 'EMPTY_STR', '' );
define ( 'ATOM_TIME', 'Y-m-d\\TH:i:s\\Z' ); // aka ISO 8601 "Zulu"
define ( 'TEMPLATE_BUILD_PATH', 'store/[data]/smarty3' );

View File

@ -1579,6 +1579,7 @@ function get_site_info() {
$channels_active_halfyear_stat = intval(get_config('system','channels_active_halfyear_stat'));
$channels_active_monthly_stat = intval(get_config('system','channels_active_monthly_stat'));
$local_posts_stat = intval(get_config('system','local_posts_stat'));
$local_comments_stat = intval(get_config('system','local_comments_stat'));
$hide_in_statistics = intval(get_config('system','hide_in_statistics'));
$site_expire = intval(get_config('system', 'default_expire_days'));
@ -1612,13 +1613,14 @@ function get_site_info() {
'default_service_restrictions' => $service_class,
'locked_features' => $locked_features,
'admin' => $admin,
'dbdriver' => DBA::$dba->getdriver(),
'dbdriver' => DBA::$dba->getdriver() . ' ' . ((ACTIVE_DBTYPE == DBTYPE_POSTGRES) ? 'postgres' : 'mysql'),
'lastpoll' => get_config('system','lastpoll'),
'info' => (($site_info) ? $site_info : ''),
'channels_total' => $channels_total_stat,
'channels_active_halfyear' => $channels_active_halfyear_stat,
'channels_active_monthly' => $channels_active_monthly_stat,
'local_posts' => $local_posts_stat,
'local_comments' => $local_comments_stat,
'hide_in_statistics' => $hide_in_statistics
];

View File

@ -1,182 +0,0 @@
.imgCrop_wrap {
/* width: 500px; @done_in_js */
/* height: 375px; @done_in_js */
position: relative;
cursor: crosshair;
}
/* an extra classname is applied for Opera < 9.0 to fix it's lack of opacity support */
.imgCrop_wrap.opera8 .imgCrop_overlay,
.imgCrop_wrap.opera8 .imgCrop_clickArea {
background-color: transparent;
}
/* fix for IE displaying all boxes at line-height by default, although they are still 1 pixel high until we combine them with the pointless span */
.imgCrop_wrap,
.imgCrop_wrap * {
font-size: 0;
}
.imgCrop_overlay {
background-color: #000;
opacity: 0.5;
filter:alpha(opacity=50);
position: absolute;
width: 100%;
height: 100%;
}
.imgCrop_selArea {
position: absolute;
/* @done_in_js
top: 20px;
left: 20px;
width: 200px;
height: 200px;
background: transparent url(castle.jpg) no-repeat -210px -110px;
*/
cursor: move;
z-index: 2;
}
/* clickArea is all a fix for IE 5.5 & 6 to allow the user to click on the given area */
.imgCrop_clickArea {
width: 100%;
height: 100%;
background-color: #FFF;
opacity: 0.01;
filter:alpha(opacity=01);
}
.imgCrop_marqueeHoriz {
position: absolute;
width: 100%;
height: 1px;
background: transparent url(marqueeHoriz.gif) repeat-x 0 0;
z-index: 3;
}
.imgCrop_marqueeVert {
position: absolute;
height: 100%;
width: 1px;
background: transparent url(marqueeVert.gif) repeat-y 0 0;
z-index: 3;
}
/*
* FIX MARCHING ANTS IN IE
* As IE <6 tries to load background images we can uncomment the follwoing hack
* to remove that issue, not as pretty - but is anything in IE?
* And yes I do know that 'filter' is evil, but it will make it look semi decent in IE
*
* html .imgCrop_marqueeHoriz,
* html .imgCrop_marqueeVert {
background: transparent;
filter: Invert;
}
* html .imgCrop_marqueeNorth { border-top: 1px dashed #000; }
* html .imgCrop_marqueeEast { border-right: 1px dashed #000; }
* html .imgCrop_marqueeSouth { border-bottom: 1px dashed #000; }
* html .imgCrop_marqueeWest { border-left: 1px dashed #000; }
*/
.imgCrop_marqueeNorth { top: 0; left: 0; }
.imgCrop_marqueeEast { top: 0; right: 0; }
.imgCrop_marqueeSouth { bottom: 0px; left: 0; }
.imgCrop_marqueeWest { top: 0; left: 0; }
.imgCrop_handle {
position: absolute;
border: 1px solid #333;
width: 6px;
height: 6px;
background: #FFF;
opacity: 0.5;
filter:alpha(opacity=50);
z-index: 4;
}
/* fix IE 5 box model */
* html .imgCrop_handle {
width: 8px;
height: 8px;
wid\th: 6px;
hei\ght: 6px;
}
.imgCrop_handleN {
top: -3px;
left: 0;
/* margin-left: 49%; @done_in_js */
cursor: n-resize;
}
.imgCrop_handleNE {
top: -3px;
right: -3px;
cursor: ne-resize;
}
.imgCrop_handleE {
top: 0;
right: -3px;
/* margin-top: 49%; @done_in_js */
cursor: e-resize;
}
.imgCrop_handleSE {
right: -3px;
bottom: -3px;
cursor: se-resize;
}
.imgCrop_handleS {
right: 0;
bottom: -3px;
/* margin-right: 49%; @done_in_js */
cursor: s-resize;
}
.imgCrop_handleSW {
left: -3px;
bottom: -3px;
cursor: sw-resize;
}
.imgCrop_handleW {
top: 0;
left: -3px;
/* margin-top: 49%; @done_in_js */
cursor: w-resize;
}
.imgCrop_handleNW {
top: -3px;
left: -3px;
cursor: nw-resize;
}
/**
* Create an area to click & drag around on as the default browser behaviour is to let you drag the image
*/
.imgCrop_dragArea {
width: 100%;
height: 100%;
z-index: 200;
position: absolute;
top: 0;
left: 0;
}
.imgCrop_previewWrap {
/* width: 200px; @done_in_js */
/* height: 200px; @done_in_js */
overflow: hidden;
position: relative;
}
.imgCrop_previewWrap img {
position: absolute;
}

View File

@ -1,228 +0,0 @@
1.
<script type="text/javascript" src="scripts/cropper/lib/prototype.js" language="javascript"></script>
2.
<script type="text/javascript" src="scripts/cropper/lib/scriptaculous.js?load=builder,dragdrop" language="javascript"></script>
3.
<script type="text/javascript" src="scripts/cropper/cropper.js" language="javascript"></script>
Options
ratioDim obj
The pixel dimensions to apply as a restrictive ratio, with properties x & y.
minWidth int
The minimum width for the select area in pixels.
minHeight int
The mimimum height for the select area in pixels.
maxWidth int
The maximum width for the select areas in pixels (if both minWidth & maxWidth set to same the width of the cropper will be fixed)
maxHeight int
The maximum height for the select areas in pixels (if both minHeight & maxHeight set to same the height of the cropper will be fixed)
displayOnInit int
Whether to display the select area on initialisation, only used when providing minimum width & height or ratio.
onEndCrop func
The callback function to provide the crop details to on end of a crop.
captureKeys boolean
Whether to capture the keys for moving the select area, as these can cause some problems at the moment.
onloadCoords obj
A coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area to display onload
The callback function
The callback function is a function that allows you to capture the crop co-ordinates when the user finished a crop movement, it is passed two arguments:
* coords, obj, coordinates object with properties x1, y1, x2 & y2; for the coordinates of the select area.
* dimensions, obj, dimensions object with properities width & height; for the dimensions of the select area.
An example function which outputs the crop values to form fields:
Display code as plain text
JavaScript:
1.
function onEndCrop( coords, dimensions ) {
2.
$( 'x1' ).value = coords.x1;
3.
$( 'y1' ).value = coords.y1;
4.
$( 'x2' ).value = coords.x2;
5.
$( 'y2' ).value = coords.y2;
6.
$( 'width' ).value = dimensions.width;
7.
$( 'height' ).value = dimensions.height;
8.
}
Basic interface
This basic example will attach the cropper UI to the test image and return crop results to the provided callback function.
Display code as plain text
HTML:
1.
<img src="test.jpg" alt="Test image" id="testImage" width="500" height="333" />
2.
3.
<script type="text/javascript" language="javascript">
4.
Event.observe( window, 'load', function() {
5.
new Cropper.Img(
6.
'testImage',
7.
{ onEndCrop: onEndCrop }
8.
);
9.
} );
10.
</script>
Minimum dimensions
You can apply minimum dimensions to a single axis or both, this example applies minimum dimensions to both axis.
Display code as plain text
HTML:
1.
<img src="test.jpg" alt="Test image" id="testImage" width="500" height="333" />
2.
3.
<script type="text/javascript" language="javascript">
4.
Event.observe( window, 'load', function() {
5.
new Cropper.Img(
6.
'testImage',
7.
{
8.
minWidth: 220,
9.
minHeight: 120,
10.
onEndCrop: onEndCrop
11.
}
12.
);
13.
} );
14.
</script>
Select area ratio
You can apply a ratio to the selection area, this example applies a 4:3 ratio to the select area.
Display code as plain text
HTML:
1.
<img src="test.jpg" alt="Test image" id="testImage" width="500" height="333" />
2.
3.
<script type="text/javascript" language="javascript">
4.
Event.observe( window, 'load', function() {
5.
new Cropper.Img(
6.
'testImage',
7.
{
8.
ratioDim: {
9.
x: 220,
10.
y: 165
11.
},
12.
displayOnInit: true,
13.
onEndCrop: onEndCrop
14.
}
15.
);
16.
} );
17.
</script>
With crop preview
You can display a dynamically prouced preview of the resulting crop by using the ImgWithPreview subclass, a preview can only be displayed when we have a fixed size (set via minWidth & minHeight options). Note that the displayOnInit option is not required as this is the default behaviour when displaying a crop preview.
Display code as plain text
HTML:
1.
<img src="test.jpg" alt="Test image" id="testImage" width="500" height="333" />
2.
<div id="previewWrap"></div>
3.
4.
<script type="text/javascript" language="javascript">
5.
Event.observe( window, 'load', function() {
6.
new Cropper.ImgWithPreview(
7.
'testImage',
8.
{
9.
previewWrap: 'previewWrap',
10.
minWidth: 120,
11.
minHeight: 120,
12.
ratioDim: { x: 200, y: 120 },
13.
onEndCrop: onEndCrop
14.
}
15.
);
16.
} );
17.
</script>
Known Issues
* Safari animated gifs, only one of each will animate, this seems to be a known Safari issue.
* After drawing an area and then clicking to start a new drag in IE 5.5 the rendered height appears as the last height until the user drags, this appears to be the related to another IE error (which has been fixed) where IE does not always redraw the select area properly.
* Lack of CSS opacity support in Opera before version 9 mean we disable those style rules, if Opera 8 support is important you & you want the overlay to work then you can use the Opera rules in the CSS to apply a black PNG with 50% alpha transparency to replicate the effect.
* Styling & borders on image, any CSS styling applied directly to the image itself (floats, borders, padding, margin, etc.) will cause problems with the cropper. The use of a wrapper element to apply these styles to is recommended.
* overflow: auto or overflow: scroll on parent will cause cropper to burst out of parent in IE and Opera when applied (maybe Mac browsers too) I'm not sure why yet.
If you use CakePHP you will notice that including this in your script will break the CSS layout. This is due to the CSS rule
form div{
vertical-align: text-top;
margin-left: 1em;
margin-bottom:2em;
overflow: auto;
}
A simple workaround is to add another rule directly after this like so:
form div.no_cake, form div.no_cake div {
margin:0;
overflow:hidden;
}
and then in your code surround the img tag with a div with the class name of no_cake.
Cheers

View File

@ -1,566 +0,0 @@
/**
* Copyright (c) 2006, David Spurr (http://www.defusion.org.uk/)
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* * Neither the name of the David Spurr nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* http://www.opensource.org/licenses/bsd-license.php
*
* See scriptaculous.js for full scriptaculous licence
*/
var CropDraggable=Class.create();
Object.extend(Object.extend(CropDraggable.prototype,Draggable.prototype),{initialize:function(_1){
this.options=Object.extend({drawMethod:function(){
}},arguments[1]||{});
this.element=$(_1);
this.handle=this.element;
this.delta=this.currentDelta();
this.dragging=false;
this.eventMouseDown=this.initDrag.bindAsEventListener(this);
Event.observe(this.handle,"mousedown",this.eventMouseDown);
Draggables.register(this);
},draw:function(_2){
var _3=Position.cumulativeOffset(this.element);
var d=this.currentDelta();
_3[0]-=d[0];
_3[1]-=d[1];
var p=[0,1].map(function(i){
return (_2[i]-_3[i]-this.offset[i]);
}.bind(this));
this.options.drawMethod(p);
}});
var Cropper={};
Cropper.Img=Class.create();
Cropper.Img.prototype={initialize:function(_7,_8){
this.options=Object.extend({ratioDim:{x:0,y:0},minWidth:0,minHeight:0,displayOnInit:false,onEndCrop:Prototype.emptyFunction,captureKeys:true,onloadCoords:null,maxWidth:0,maxHeight:0},_8||{});
this.img=$(_7);
this.clickCoords={x:0,y:0};
this.dragging=false;
this.resizing=false;
this.isWebKit=/Konqueror|Safari|KHTML/.test(navigator.userAgent);
this.isIE=/MSIE/.test(navigator.userAgent);
this.isOpera8=/Opera\s[1-8]/.test(navigator.userAgent);
this.ratioX=0;
this.ratioY=0;
this.attached=false;
this.fixedWidth=(this.options.maxWidth>0&&(this.options.minWidth>=this.options.maxWidth));
this.fixedHeight=(this.options.maxHeight>0&&(this.options.minHeight>=this.options.maxHeight));
if(typeof this.img=="undefined"){
return;
}
$A(document.getElementsByTagName("script")).each(function(s){
if(s.src.match(/cropper\.js/)){
var _a=s.src.replace(/cropper\.js(.*)?/,"");
var _b=document.createElement("link");
_b.rel="stylesheet";
_b.type="text/css";
_b.href=_a+"cropper.css";
_b.media="screen";
document.getElementsByTagName("head")[0].appendChild(_b);
}
});
if(this.options.ratioDim.x>0&&this.options.ratioDim.y>0){
var _c=this.getGCD(this.options.ratioDim.x,this.options.ratioDim.y);
this.ratioX=this.options.ratioDim.x/_c;
this.ratioY=this.options.ratioDim.y/_c;
}
this.subInitialize();
if(this.img.complete||this.isWebKit){
this.onLoad();
}else{
Event.observe(this.img,"load",this.onLoad.bindAsEventListener(this));
}
},getGCD:function(a,b){
if(b==0){
return a;
}
return this.getGCD(b,a%b);
},onLoad:function(){
var _f="imgCrop_";
var _10=this.img.parentNode;
var _11="";
if(this.isOpera8){
_11=" opera8";
}
this.imgWrap=Builder.node("div",{"class":_f+"wrap"+_11});
this.north=Builder.node("div",{"class":_f+"overlay "+_f+"north"},[Builder.node("span")]);
this.east=Builder.node("div",{"class":_f+"overlay "+_f+"east"},[Builder.node("span")]);
this.south=Builder.node("div",{"class":_f+"overlay "+_f+"south"},[Builder.node("span")]);
this.west=Builder.node("div",{"class":_f+"overlay "+_f+"west"},[Builder.node("span")]);
var _12=[this.north,this.east,this.south,this.west];
this.dragArea=Builder.node("div",{"class":_f+"dragArea"},_12);
this.handleN=Builder.node("div",{"class":_f+"handle "+_f+"handleN"});
this.handleNE=Builder.node("div",{"class":_f+"handle "+_f+"handleNE"});
this.handleE=Builder.node("div",{"class":_f+"handle "+_f+"handleE"});
this.handleSE=Builder.node("div",{"class":_f+"handle "+_f+"handleSE"});
this.handleS=Builder.node("div",{"class":_f+"handle "+_f+"handleS"});
this.handleSW=Builder.node("div",{"class":_f+"handle "+_f+"handleSW"});
this.handleW=Builder.node("div",{"class":_f+"handle "+_f+"handleW"});
this.handleNW=Builder.node("div",{"class":_f+"handle "+_f+"handleNW"});
this.selArea=Builder.node("div",{"class":_f+"selArea"},[Builder.node("div",{"class":_f+"marqueeHoriz "+_f+"marqueeNorth"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeVert "+_f+"marqueeEast"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeHoriz "+_f+"marqueeSouth"},[Builder.node("span")]),Builder.node("div",{"class":_f+"marqueeVert "+_f+"marqueeWest"},[Builder.node("span")]),this.handleN,this.handleNE,this.handleE,this.handleSE,this.handleS,this.handleSW,this.handleW,this.handleNW,Builder.node("div",{"class":_f+"clickArea"})]);
this.imgWrap.appendChild(this.img);
this.imgWrap.appendChild(this.dragArea);
this.dragArea.appendChild(this.selArea);
this.dragArea.appendChild(Builder.node("div",{"class":_f+"clickArea"}));
_10.appendChild(this.imgWrap);
this.startDragBind=this.startDrag.bindAsEventListener(this);
Event.observe(this.dragArea,"mousedown",this.startDragBind);
this.onDragBind=this.onDrag.bindAsEventListener(this);
Event.observe(document,"mousemove",this.onDragBind);
this.endCropBind=this.endCrop.bindAsEventListener(this);
Event.observe(document,"mouseup",this.endCropBind);
this.resizeBind=this.startResize.bindAsEventListener(this);
this.handles=[this.handleN,this.handleNE,this.handleE,this.handleSE,this.handleS,this.handleSW,this.handleW,this.handleNW];
this.registerHandles(true);
if(this.options.captureKeys){
this.keysBind=this.handleKeys.bindAsEventListener(this);
Event.observe(document,"keypress",this.keysBind);
}
new CropDraggable(this.selArea,{drawMethod:this.moveArea.bindAsEventListener(this)});
this.setParams();
},registerHandles:function(_13){
for(var i=0;i<this.handles.length;i++){
var _15=$(this.handles[i]);
if(_13){
var _16=false;
if(this.fixedWidth&&this.fixedHeight){
_16=true;
}else{
if(this.fixedWidth||this.fixedHeight){
var _17=_15.className.match(/([S|N][E|W])$/);
var _18=_15.className.match(/(E|W)$/);
var _19=_15.className.match(/(N|S)$/);
if(_17){
_16=true;
}else{
if(this.fixedWidth&&_18){
_16=true;
}else{
if(this.fixedHeight&&_19){
_16=true;
}
}
}
}
}
if(_16){
_15.hide();
}else{
Event.observe(_15,"mousedown",this.resizeBind);
}
}else{
_15.show();
Event.stopObserving(_15,"mousedown",this.resizeBind);
}
}
},setParams:function(){
this.imgW=this.img.width;
this.imgH=this.img.height;
$(this.north).setStyle({height:0});
$(this.east).setStyle({width:0,height:0});
$(this.south).setStyle({height:0});
$(this.west).setStyle({width:0,height:0});
$(this.imgWrap).setStyle({"width":this.imgW+"px","height":this.imgH+"px"});
$(this.selArea).hide();
var _1a={x1:0,y1:0,x2:0,y2:0};
var _1b=false;
if(this.options.onloadCoords!=null){
_1a=this.cloneCoords(this.options.onloadCoords);
_1b=true;
}else{
if(this.options.ratioDim.x>0&&this.options.ratioDim.y>0){
_1a.x1=Math.ceil((this.imgW-this.options.ratioDim.x)/2);
_1a.y1=Math.ceil((this.imgH-this.options.ratioDim.y)/2);
_1a.x2=_1a.x1+this.options.ratioDim.x;
_1a.y2=_1a.y1+this.options.ratioDim.y;
_1b=true;
}
}
this.setAreaCoords(_1a,false,false,1);
if(this.options.displayOnInit&&_1b){
this.selArea.show();
this.drawArea();
this.endCrop();
}
this.attached=true;
},remove:function(){
if(this.attached){
this.attached=false;
this.imgWrap.parentNode.insertBefore(this.img,this.imgWrap);
this.imgWrap.parentNode.removeChild(this.imgWrap);
Event.stopObserving(this.dragArea,"mousedown",this.startDragBind);
Event.stopObserving(document,"mousemove",this.onDragBind);
Event.stopObserving(document,"mouseup",this.endCropBind);
this.registerHandles(false);
if(this.options.captureKeys){
Event.stopObserving(document,"keypress",this.keysBind);
}
}
},reset:function(){
if(!this.attached){
this.onLoad();
}else{
this.setParams();
}
this.endCrop();
},handleKeys:function(e){
var dir={x:0,y:0};
if(!this.dragging){
switch(e.keyCode){
case (37):
dir.x=-1;
break;
case (38):
dir.y=-1;
break;
case (39):
dir.x=1;
break;
case (40):
dir.y=1;
break;
}
if(dir.x!=0||dir.y!=0){
if(e.shiftKey){
dir.x*=10;
dir.y*=10;
}
this.moveArea([this.areaCoords.x1+dir.x,this.areaCoords.y1+dir.y]);
Event.stop(e);
}
}
},calcW:function(){
return (this.areaCoords.x2-this.areaCoords.x1);
},calcH:function(){
return (this.areaCoords.y2-this.areaCoords.y1);
},moveArea:function(_1e){
this.setAreaCoords({x1:_1e[0],y1:_1e[1],x2:_1e[0]+this.calcW(),y2:_1e[1]+this.calcH()},true,false);
this.drawArea();
},cloneCoords:function(_1f){
return {x1:_1f.x1,y1:_1f.y1,x2:_1f.x2,y2:_1f.y2};
},setAreaCoords:function(_20,_21,_22,_23,_24){
if(_21){
var _25=_20.x2-_20.x1;
var _26=_20.y2-_20.y1;
if(_20.x1<0){
_20.x1=0;
_20.x2=_25;
}
if(_20.y1<0){
_20.y1=0;
_20.y2=_26;
}
if(_20.x2>this.imgW){
_20.x2=this.imgW;
_20.x1=this.imgW-_25;
}
if(_20.y2>this.imgH){
_20.y2=this.imgH;
_20.y1=this.imgH-_26;
}
}else{
if(_20.x1<0){
_20.x1=0;
}
if(_20.y1<0){
_20.y1=0;
}
if(_20.x2>this.imgW){
_20.x2=this.imgW;
}
if(_20.y2>this.imgH){
_20.y2=this.imgH;
}
if(_23!=null){
if(this.ratioX>0){
this.applyRatio(_20,{x:this.ratioX,y:this.ratioY},_23,_24);
}else{
if(_22){
this.applyRatio(_20,{x:1,y:1},_23,_24);
}
}
var _27=[this.options.minWidth,this.options.minHeight];
var _28=[this.options.maxWidth,this.options.maxHeight];
if(_27[0]>0||_27[1]>0||_28[0]>0||_28[1]>0){
var _29={a1:_20.x1,a2:_20.x2};
var _2a={a1:_20.y1,a2:_20.y2};
var _2b={min:0,max:this.imgW};
var _2c={min:0,max:this.imgH};
if((_27[0]!=0||_27[1]!=0)&&_22){
if(_27[0]>0){
_27[1]=_27[0];
}else{
if(_27[1]>0){
_27[0]=_27[1];
}
}
}
if((_28[0]!=0||_28[0]!=0)&&_22){
if(_28[0]>0&&_28[0]<=_28[1]){
_28[1]=_28[0];
}else{
if(_28[1]>0&&_28[1]<=_28[0]){
_28[0]=_28[1];
}
}
}
if(_27[0]>0){
this.applyDimRestriction(_29,_27[0],_23.x,_2b,"min");
}
if(_27[1]>1){
this.applyDimRestriction(_2a,_27[1],_23.y,_2c,"min");
}
if(_28[0]>0){
this.applyDimRestriction(_29,_28[0],_23.x,_2b,"max");
}
if(_28[1]>1){
this.applyDimRestriction(_2a,_28[1],_23.y,_2c,"max");
}
_20={x1:_29.a1,y1:_2a.a1,x2:_29.a2,y2:_2a.a2};
}
}
}
this.areaCoords=_20;
},applyDimRestriction:function(_2d,val,_2f,_30,_31){
var _32;
if(_31=="min"){
_32=((_2d.a2-_2d.a1)<val);
}else{
_32=((_2d.a2-_2d.a1)>val);
}
if(_32){
if(_2f==1){
_2d.a2=_2d.a1+val;
}else{
_2d.a1=_2d.a2-val;
}
if(_2d.a1<_30.min){
_2d.a1=_30.min;
_2d.a2=val;
}else{
if(_2d.a2>_30.max){
_2d.a1=_30.max-val;
_2d.a2=_30.max;
}
}
}
},applyRatio:function(_33,_34,_35,_36){
var _37;
if(_36=="N"||_36=="S"){
_37=this.applyRatioToAxis({a1:_33.y1,b1:_33.x1,a2:_33.y2,b2:_33.x2},{a:_34.y,b:_34.x},{a:_35.y,b:_35.x},{min:0,max:this.imgW});
_33.x1=_37.b1;
_33.y1=_37.a1;
_33.x2=_37.b2;
_33.y2=_37.a2;
}else{
_37=this.applyRatioToAxis({a1:_33.x1,b1:_33.y1,a2:_33.x2,b2:_33.y2},{a:_34.x,b:_34.y},{a:_35.x,b:_35.y},{min:0,max:this.imgH});
_33.x1=_37.a1;
_33.y1=_37.b1;
_33.x2=_37.a2;
_33.y2=_37.b2;
}
},applyRatioToAxis:function(_38,_39,_3a,_3b){
var _3c=Object.extend(_38,{});
var _3d=_3c.a2-_3c.a1;
var _3e=Math.floor(_3d*_39.b/_39.a);
var _3f;
var _40;
var _41=null;
if(_3a.b==1){
_3f=_3c.b1+_3e;
if(_3f>_3b.max){
_3f=_3b.max;
_41=_3f-_3c.b1;
}
_3c.b2=_3f;
}else{
_3f=_3c.b2-_3e;
if(_3f<_3b.min){
_3f=_3b.min;
_41=_3f+_3c.b2;
}
_3c.b1=_3f;
}
if(_41!=null){
_40=Math.floor(_41*_39.a/_39.b);
if(_3a.a==1){
_3c.a2=_3c.a1+_40;
}else{
_3c.a1=_3c.a1=_3c.a2-_40;
}
}
return _3c;
},drawArea:function(){
var _42=this.calcW();
var _43=this.calcH();
var px="px";
var _45=[this.areaCoords.x1+px,this.areaCoords.y1+px,_42+px,_43+px,this.areaCoords.x2+px,this.areaCoords.y2+px,(this.img.width-this.areaCoords.x2)+px,(this.img.height-this.areaCoords.y2)+px];
var _46=this.selArea.style;
_46.left=_45[0];
_46.top=_45[1];
_46.width=_45[2];
_46.height=_45[3];
var _47=Math.ceil((_42-6)/2)+px;
var _48=Math.ceil((_43-6)/2)+px;
this.handleN.style.left=_47;
this.handleE.style.top=_48;
this.handleS.style.left=_47;
this.handleW.style.top=_48;
this.north.style.height=_45[1];
var _49=this.east.style;
_49.top=_45[1];
_49.height=_45[3];
_49.left=_45[4];
_49.width=_45[6];
var _4a=this.south.style;
_4a.top=_45[5];
_4a.height=_45[7];
var _4b=this.west.style;
_4b.top=_45[1];
_4b.height=_45[3];
_4b.width=_45[0];
this.subDrawArea();
this.forceReRender();
},forceReRender:function(){
if(this.isIE||this.isWebKit){
var n=document.createTextNode(" ");
var d,el,fixEL,i;
if(this.isIE){
fixEl=this.selArea;
}else{
if(this.isWebKit){
fixEl=document.getElementsByClassName("imgCrop_marqueeSouth",this.imgWrap)[0];
d=Builder.node("div","");
d.style.visibility="hidden";
var _4e=["SE","S","SW"];
for(i=0;i<_4e.length;i++){
el=document.getElementsByClassName("imgCrop_handle"+_4e[i],this.selArea)[0];
if(el.childNodes.length){
el.removeChild(el.childNodes[0]);
}
el.appendChild(d);
}
}
}
fixEl.appendChild(n);
fixEl.removeChild(n);
}
},startResize:function(e){
this.startCoords=this.cloneCoords(this.areaCoords);
this.resizing=true;
this.resizeHandle=Event.element(e).classNames().toString().replace(/([^N|NE|E|SE|S|SW|W|NW])+/,"");
Event.stop(e);
},startDrag:function(e){
this.selArea.show();
this.clickCoords=this.getCurPos(e);
this.setAreaCoords({x1:this.clickCoords.x,y1:this.clickCoords.y,x2:this.clickCoords.x,y2:this.clickCoords.y},false,false,null);
this.dragging=true;
this.onDrag(e);
Event.stop(e);
},getCurPos:function(e){
var el=this.imgWrap,wrapOffsets=Position.cumulativeOffset(el);
while(el.nodeName!="BODY"){
wrapOffsets[1]-=el.scrollTop||0;
wrapOffsets[0]-=el.scrollLeft||0;
el=el.parentNode;
}
return curPos={x:Event.pointerX(e)-wrapOffsets[0],y:Event.pointerY(e)-wrapOffsets[1]};
},onDrag:function(e){
if(this.dragging||this.resizing){
var _54=null;
var _55=this.getCurPos(e);
var _56=this.cloneCoords(this.areaCoords);
var _57={x:1,y:1};
if(this.dragging){
if(_55.x<this.clickCoords.x){
_57.x=-1;
}
if(_55.y<this.clickCoords.y){
_57.y=-1;
}
this.transformCoords(_55.x,this.clickCoords.x,_56,"x");
this.transformCoords(_55.y,this.clickCoords.y,_56,"y");
}else{
if(this.resizing){
_54=this.resizeHandle;
if(_54.match(/E/)){
this.transformCoords(_55.x,this.startCoords.x1,_56,"x");
if(_55.x<this.startCoords.x1){
_57.x=-1;
}
}else{
if(_54.match(/W/)){
this.transformCoords(_55.x,this.startCoords.x2,_56,"x");
if(_55.x<this.startCoords.x2){
_57.x=-1;
}
}
}
if(_54.match(/N/)){
this.transformCoords(_55.y,this.startCoords.y2,_56,"y");
if(_55.y<this.startCoords.y2){
_57.y=-1;
}
}else{
if(_54.match(/S/)){
this.transformCoords(_55.y,this.startCoords.y1,_56,"y");
if(_55.y<this.startCoords.y1){
_57.y=-1;
}
}
}
}
}
this.setAreaCoords(_56,false,e.shiftKey,_57,_54);
this.drawArea();
Event.stop(e);
}
},transformCoords:function(_58,_59,_5a,_5b){
var _5c=[_58,_59];
if(_58>_59){
_5c.reverse();
}
_5a[_5b+"1"]=_5c[0];
_5a[_5b+"2"]=_5c[1];
},endCrop:function(){
this.dragging=false;
this.resizing=false;
this.options.onEndCrop(this.areaCoords,{width:this.calcW(),height:this.calcH()});
},subInitialize:function(){
},subDrawArea:function(){
}};
Cropper.ImgWithPreview=Class.create();
Object.extend(Object.extend(Cropper.ImgWithPreview.prototype,Cropper.Img.prototype),{subInitialize:function(){
this.hasPreviewImg=false;
if(typeof (this.options.previewWrap)!="undefined"&&this.options.minWidth>0&&this.options.minHeight>0){
this.previewWrap=$(this.options.previewWrap);
this.previewImg=this.img.cloneNode(false);
this.previewImg.id="imgCrop_"+this.previewImg.id;
this.options.displayOnInit=true;
this.hasPreviewImg=true;
this.previewWrap.addClassName("imgCrop_previewWrap");
this.previewWrap.setStyle({width:this.options.minWidth+"px",height:this.options.minHeight+"px"});
this.previewWrap.appendChild(this.previewImg);
}
},subDrawArea:function(){
if(this.hasPreviewImg){
var _5d=this.calcW();
var _5e=this.calcH();
var _5f={x:this.imgW/_5d,y:this.imgH/_5e};
var _60={x:_5d/this.options.minWidth,y:_5e/this.options.minHeight};
var _61={w:Math.ceil(this.options.minWidth*_5f.x)+"px",h:Math.ceil(this.options.minHeight*_5f.y)+"px",x:"-"+Math.ceil(this.areaCoords.x1/_60.x)+"px",y:"-"+Math.ceil(this.areaCoords.y1/_60.y)+"px"};
var _62=this.previewImg.style;
_62.width=_61.w;
_62.height=_61.h;
_62.left=_61.x;
_62.top=_61.y;
}
}});

File diff suppressed because it is too large Load Diff

View File

@ -1,101 +0,0 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// See scriptaculous.js for full license.
var Builder = {
NODEMAP: {
AREA: 'map',
CAPTION: 'table',
COL: 'table',
COLGROUP: 'table',
LEGEND: 'fieldset',
OPTGROUP: 'select',
OPTION: 'select',
PARAM: 'object',
TBODY: 'table',
TD: 'table',
TFOOT: 'table',
TH: 'table',
THEAD: 'table',
TR: 'table'
},
// note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
// due to a Firefox bug
node: function(elementName) {
elementName = elementName.toUpperCase();
// try innerHTML approach
var parentTag = this.NODEMAP[elementName] || 'div';
var parentElement = document.createElement(parentTag);
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
} catch(e) {}
var element = parentElement.firstChild || null;
// see if browser added wrapping tags
if(element && (element.tagName != elementName))
element = element.getElementsByTagName(elementName)[0];
// fallback to createElement approach
if(!element) element = document.createElement(elementName);
// abort if nothing could be created
if(!element) return;
// attributes (or text)
if(arguments[1])
if(this._isStringOrNumber(arguments[1]) ||
(arguments[1] instanceof Array)) {
this._children(element, arguments[1]);
} else {
var attrs = this._attributes(arguments[1]);
if(attrs.length) {
try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
parentElement.innerHTML = "<" +elementName + " " +
attrs + "></" + elementName + ">";
} catch(e) {}
element = parentElement.firstChild || null;
// workaround firefox 1.0.X bug
if(!element) {
element = document.createElement(elementName);
for(attr in arguments[1])
element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
}
if(element.tagName != elementName)
element = parentElement.getElementsByTagName(elementName)[0];
}
}
// text, or array of children
if(arguments[2])
this._children(element, arguments[2]);
return element;
},
_text: function(text) {
return document.createTextNode(text);
},
_attributes: function(attributes) {
var attrs = [];
for(attribute in attributes)
attrs.push((attribute=='className' ? 'class' : attribute) +
'="' + attributes[attribute].toString().escapeHTML() + '"');
return attrs.join(" ");
},
_children: function(element, children) {
if(typeof children=='object') { // array can hold nodes and text
children.flatten().each( function(e) {
if(typeof e=='object')
element.appendChild(e)
else
if(Builder._isStringOrNumber(e))
element.appendChild(Builder._text(e));
});
} else
if(Builder._isStringOrNumber(children))
element.appendChild(Builder._text(children));
},
_isStringOrNumber: function(param) {
return(typeof param=='string' || typeof param=='number');
}
}

View File

@ -1,815 +0,0 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
// Contributors:
// Richard Livsey
// Rahul Bhargava
// Rob Wills
//
// See scriptaculous.js for full license.
// Autocompleter.Base handles all the autocompletion functionality
// that's independent of the data source for autocompletion. This
// includes drawing the autocompletion menu, observing keyboard
// and mouse events, and similar.
//
// Specific autocompleters need to provide, at the very least,
// a getUpdatedChoices function that will be invoked every time
// the text inside the monitored textbox changes. This method
// should get the text for which to provide autocompletion by
// invoking this.getToken(), NOT by directly accessing
// this.element.value. This is to allow incremental tokenized
// autocompletion. Specific auto-completion logic (AJAX, etc)
// belongs in getUpdatedChoices.
//
// Tokenized incremental autocompletion is enabled automatically
// when an autocompleter is instantiated with the 'tokens' option
// in the options parameter, e.g.:
// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
// will incrementally autocomplete with a comma as the token.
// Additionally, ',' in the above example can be replaced with
// a token array, e.g. { tokens: [',', '\n'] } which
// enables autocompletion on multiple tokens. This is most
// useful when one of the tokens is \n (a newline), as it
// allows smart autocompletion after linebreaks.
var Autocompleter = {}
Autocompleter.Base = function() {};
Autocompleter.Base.prototype = {
baseInitialize: function(element, update, options) {
this.element = $(element);
this.update = $(update);
this.hasFocus = false;
this.changed = false;
this.active = false;
this.index = 0;
this.entryCount = 0;
if (this.setOptions)
this.setOptions(options);
else
this.options = options || {};
this.options.paramName = this.options.paramName || this.element.name;
this.options.tokens = this.options.tokens || [];
this.options.frequency = this.options.frequency || 0.4;
this.options.minChars = this.options.minChars || 1;
this.options.onShow = this.options.onShow ||
function(element, update){
if(!update.style.position || update.style.position=='absolute') {
update.style.position = 'absolute';
Position.clone(element, update, {setHeight: false, offsetTop: element.offsetHeight});
}
Effect.Appear(update,{duration:0.15});
};
this.options.onHide = this.options.onHide ||
function(element, update){ new Effect.Fade(update,{duration:0.15}) };
if (typeof(this.options.tokens) == 'string')
this.options.tokens = new Array(this.options.tokens);
this.observer = null;
this.element.setAttribute('autocomplete','off');
Element.hide(this.update);
Event.observe(this.element, "blur", this.onBlur.bindAsEventListener(this));
Event.observe(this.element, "keypress", this.onKeyPress.bindAsEventListener(this));
},
show: function() {
if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
if(!this.iefix &&
(navigator.appVersion.indexOf('MSIE')>0) &&
(navigator.userAgent.indexOf('Opera')<0) &&
(Element.getStyle(this.update, 'position')=='absolute')) {
new Insertion.After(this.update,
'<iframe id="' + this.update.id + '_iefix" '+
'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
this.iefix = $(this.update.id+'_iefix');
}
if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
},
fixIEOverlapping: function() {
Position.clone(this.update, this.iefix);
this.iefix.style.zIndex = 1;
this.update.style.zIndex = 2;
Element.show(this.iefix);
},
hide: function() {
this.stopIndicator();
if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
if(this.iefix) Element.hide(this.iefix);
},
startIndicator: function() {
if(this.options.indicator) Element.show(this.options.indicator);
},
stopIndicator: function() {
if(this.options.indicator) Element.hide(this.options.indicator);
},
onKeyPress: function(event) {
if(this.active)
switch(event.keyCode) {
case Event.KEY_TAB:
case Event.KEY_RETURN:
this.selectEntry();
Event.stop(event);
case Event.KEY_ESC:
this.hide();
this.active = false;
Event.stop(event);
return;
case Event.KEY_LEFT:
case Event.KEY_RIGHT:
return;
case Event.KEY_UP:
this.markPrevious();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
case Event.KEY_DOWN:
this.markNext();
this.render();
if(navigator.appVersion.indexOf('AppleWebKit')>0) Event.stop(event);
return;
}
else
if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
(navigator.appVersion.indexOf('AppleWebKit') > 0 && event.keyCode == 0)) return;
this.changed = true;
this.hasFocus = true;
if(this.observer) clearTimeout(this.observer);
this.observer =
setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
},
activate: function() {
this.changed = false;
this.hasFocus = true;
this.getUpdatedChoices();
},
onHover: function(event) {
var element = Event.findElement(event, 'LI');
if(this.index != element.autocompleteIndex)
{
this.index = element.autocompleteIndex;
this.render();
}
Event.stop(event);
},
onClick: function(event) {
var element = Event.findElement(event, 'LI');
this.index = element.autocompleteIndex;
this.selectEntry();
this.hide();
},
onBlur: function(event) {
// needed to make click events working
setTimeout(this.hide.bind(this), 250);
this.hasFocus = false;
this.active = false;
},
render: function() {
if(this.entryCount > 0) {
for (var i = 0; i < this.entryCount; i++)
this.index==i ?
Element.addClassName(this.getEntry(i),"selected") :
Element.removeClassName(this.getEntry(i),"selected");
if(this.hasFocus) {
this.show();
this.active = true;
}
} else {
this.active = false;
this.hide();
}
},
markPrevious: function() {
if(this.index > 0) this.index--
else this.index = this.entryCount-1;
},
markNext: function() {
if(this.index < this.entryCount-1) this.index++
else this.index = 0;
},
getEntry: function(index) {
return this.update.firstChild.childNodes[index];
},
getCurrentEntry: function() {
return this.getEntry(this.index);
},
selectEntry: function() {
this.active = false;
this.updateElement(this.getCurrentEntry());
},
updateElement: function(selectedElement) {
if (this.options.updateElement) {
this.options.updateElement(selectedElement);
return;
}
var value = '';
if (this.options.select) {
var nodes = document.getElementsByClassName(this.options.select, selectedElement) || [];
if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
} else
value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
var lastTokenPos = this.findLastToken();
if (lastTokenPos != -1) {
var newValue = this.element.value.substr(0, lastTokenPos + 1);
var whitespace = this.element.value.substr(lastTokenPos + 1).match(/^\s+/);
if (whitespace)
newValue += whitespace[0];
this.element.value = newValue + value;
} else {
this.element.value = value;
}
this.element.focus();
if (this.options.afterUpdateElement)
this.options.afterUpdateElement(this.element, selectedElement);
},
updateChoices: function(choices) {
if(!this.changed && this.hasFocus) {
this.update.innerHTML = choices;
Element.cleanWhitespace(this.update);
Element.cleanWhitespace(this.update.firstChild);
if(this.update.firstChild && this.update.firstChild.childNodes) {
this.entryCount =
this.update.firstChild.childNodes.length;
for (var i = 0; i < this.entryCount; i++) {
var entry = this.getEntry(i);
entry.autocompleteIndex = i;
this.addObservers(entry);
}
} else {
this.entryCount = 0;
}
this.stopIndicator();
this.index = 0;
this.render();
}
},
addObservers: function(element) {
Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
Event.observe(element, "click", this.onClick.bindAsEventListener(this));
},
onObserverEvent: function() {
this.changed = false;
if(this.getToken().length>=this.options.minChars) {
this.startIndicator();
this.getUpdatedChoices();
} else {
this.active = false;
this.hide();
}
},
getToken: function() {
var tokenPos = this.findLastToken();
if (tokenPos != -1)
var ret = this.element.value.substr(tokenPos + 1).replace(/^\s+/,'').replace(/\s+$/,'');
else
var ret = this.element.value;
return /\n/.test(ret) ? '' : ret;
},
findLastToken: function() {
var lastTokenPos = -1;
for (var i=0; i<this.options.tokens.length; i++) {
var thisTokenPos = this.element.value.lastIndexOf(this.options.tokens[i]);
if (thisTokenPos > lastTokenPos)
lastTokenPos = thisTokenPos;
}
return lastTokenPos;
}
}
Ajax.Autocompleter = Class.create();
Object.extend(Object.extend(Ajax.Autocompleter.prototype, Autocompleter.Base.prototype), {
initialize: function(element, update, url, options) {
this.baseInitialize(element, update, options);
this.options.asynchronous = true;
this.options.onComplete = this.onComplete.bind(this);
this.options.defaultParams = this.options.parameters || null;
this.url = url;
},
getUpdatedChoices: function() {
entry = encodeURIComponent(this.options.paramName) + '=' +
encodeURIComponent(this.getToken());
this.options.parameters = this.options.callback ?
this.options.callback(this.element, entry) : entry;
if(this.options.defaultParams)
this.options.parameters += '&' + this.options.defaultParams;
new Ajax.Request(this.url, this.options);
},
onComplete: function(request) {
this.updateChoices(request.responseText);
}
});
// The local array autocompleter. Used when you'd prefer to
// inject an array of autocompletion options into the page, rather
// than sending out Ajax queries, which can be quite slow sometimes.
//
// The constructor takes four parameters. The first two are, as usual,
// the id of the monitored textbox, and id of the autocompletion menu.
// The third is the array you want to autocomplete from, and the fourth
// is the options block.
//
// Extra local autocompletion options:
// - choices - How many autocompletion choices to offer
//
// - partialSearch - If false, the autocompleter will match entered
// text only at the beginning of strings in the
// autocomplete array. Defaults to true, which will
// match text at the beginning of any *word* in the
// strings in the autocomplete array. If you want to
// search anywhere in the string, additionally set
// the option fullSearch to true (default: off).
//
// - fullSsearch - Search anywhere in autocomplete array strings.
//
// - partialChars - How many characters to enter before triggering
// a partial match (unlike minChars, which defines
// how many characters are required to do any match
// at all). Defaults to 2.
//
// - ignoreCase - Whether to ignore case when autocompleting.
// Defaults to true.
//
// It's possible to pass in a custom function as the 'selector'
// option, if you prefer to write your own autocompletion logic.
// In that case, the other options above will not apply unless
// you support them.
Autocompleter.Local = Class.create();
Autocompleter.Local.prototype = Object.extend(new Autocompleter.Base(), {
initialize: function(element, update, array, options) {
this.baseInitialize(element, update, options);
this.options.array = array;
},
getUpdatedChoices: function() {
this.updateChoices(this.options.selector(this));
},
setOptions: function(options) {
this.options = Object.extend({
choices: 10,
partialSearch: true,
partialChars: 2,
ignoreCase: true,
fullSearch: false,
selector: function(instance) {
var ret = []; // Beginning matches
var partial = []; // Inside matches
var entry = instance.getToken();
var count = 0;
for (var i = 0; i < instance.options.array.length &&
ret.length < instance.options.choices ; i++) {
var elem = instance.options.array[i];
var foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase()) :
elem.indexOf(entry);
while (foundPos != -1) {
if (foundPos == 0 && elem.length != entry.length) {
ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
elem.substr(entry.length) + "</li>");
break;
} else if (entry.length >= instance.options.partialChars &&
instance.options.partialSearch && foundPos != -1) {
if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
foundPos + entry.length) + "</li>");
break;
}
}
foundPos = instance.options.ignoreCase ?
elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
elem.indexOf(entry, foundPos + 1);
}
}
if (partial.length)
ret = ret.concat(partial.slice(0, instance.options.choices - ret.length))
return "<ul>" + ret.join('') + "</ul>";
}
}, options || {});
}
});
// AJAX in-place editor
//
// see documentation on http://wiki.script.aculo.us/scriptaculous/show/Ajax.InPlaceEditor
// Use this if you notice weird scrolling problems on some browsers,
// the DOM might be a bit confused when this gets called so do this
// waits 1 ms (with setTimeout) until it does the activation
Field.scrollFreeActivate = function(field) {
setTimeout(function() {
Field.activate(field);
}, 1);
}
Ajax.InPlaceEditor = Class.create();
Ajax.InPlaceEditor.defaultHighlightColor = "#FFFF99";
Ajax.InPlaceEditor.prototype = {
initialize: function(element, url, options) {
this.url = url;
this.element = $(element);
this.options = Object.extend({
okButton: true,
okText: "ok",
cancelLink: true,
cancelText: "cancel",
savingText: "Saving...",
clickToEditText: "Click to edit",
okText: "ok",
rows: 1,
onComplete: function(transport, element) {
new Effect.Highlight(element, {startcolor: this.options.highlightcolor});
},
onFailure: function(transport) {
alert("Error communicating with the server: " + transport.responseText.stripTags());
},
callback: function(form) {
return Form.serialize(form);
},
handleLineBreaks: true,
loadingText: 'Loading...',
savingClassName: 'inplaceeditor-saving',
loadingClassName: 'inplaceeditor-loading',
formClassName: 'inplaceeditor-form',
highlightcolor: Ajax.InPlaceEditor.defaultHighlightColor,
highlightendcolor: "#FFFFFF",
externalControl: null,
submitOnBlur: false,
ajaxOptions: {},
evalScripts: false
}, options || {});
if(!this.options.formId && this.element.id) {
this.options.formId = this.element.id + "-inplaceeditor";
if ($(this.options.formId)) {
// there's already a form with that name, don't specify an id
this.options.formId = null;
}
}
if (this.options.externalControl) {
this.options.externalControl = $(this.options.externalControl);
}
this.originalBackground = Element.getStyle(this.element, 'background-color');
if (!this.originalBackground) {
this.originalBackground = "transparent";
}
this.element.title = this.options.clickToEditText;
this.onclickListener = this.enterEditMode.bindAsEventListener(this);
this.mouseoverListener = this.enterHover.bindAsEventListener(this);
this.mouseoutListener = this.leaveHover.bindAsEventListener(this);
Event.observe(this.element, 'click', this.onclickListener);
Event.observe(this.element, 'mouseover', this.mouseoverListener);
Event.observe(this.element, 'mouseout', this.mouseoutListener);
if (this.options.externalControl) {
Event.observe(this.options.externalControl, 'click', this.onclickListener);
Event.observe(this.options.externalControl, 'mouseover', this.mouseoverListener);
Event.observe(this.options.externalControl, 'mouseout', this.mouseoutListener);
}
},
enterEditMode: function(evt) {
if (this.saving) return;
if (this.editing) return;
this.editing = true;
this.onEnterEditMode();
if (this.options.externalControl) {
Element.hide(this.options.externalControl);
}
Element.hide(this.element);
this.createForm();
this.element.parentNode.insertBefore(this.form, this.element);
Field.scrollFreeActivate(this.editField);
// stop the event to avoid a page refresh in Safari
if (evt) {
Event.stop(evt);
}
return false;
},
createForm: function() {
this.form = document.createElement("form");
this.form.id = this.options.formId;
Element.addClassName(this.form, this.options.formClassName)
this.form.onsubmit = this.onSubmit.bind(this);
this.createEditField();
if (this.options.textarea) {
var br = document.createElement("br");
this.form.appendChild(br);
}
if (this.options.okButton) {
okButton = document.createElement("input");
okButton.type = "submit";
okButton.value = this.options.okText;
okButton.className = 'editor_ok_button';
this.form.appendChild(okButton);
}
if (this.options.cancelLink) {
cancelLink = document.createElement("a");
cancelLink.href = "#";
cancelLink.appendChild(document.createTextNode(this.options.cancelText));
cancelLink.onclick = this.onclickCancel.bind(this);
cancelLink.className = 'editor_cancel';
this.form.appendChild(cancelLink);
}
},
hasHTMLLineBreaks: function(string) {
if (!this.options.handleLineBreaks) return false;
return string.match(/<br/i) || string.match(/<p>/i);
},
convertHTMLLineBreaks: function(string) {
return string.replace(/<br>/gi, "\n").replace(/<br\/>/gi, "\n").replace(/<\/p>/gi, "\n").replace(/<p>/gi, "");
},
createEditField: function() {
var text;
if(this.options.loadTextURL) {
text = this.options.loadingText;
} else {
text = this.getText();
}
var obj = this;
if (this.options.rows == 1 && !this.hasHTMLLineBreaks(text)) {
this.options.textarea = false;
var textField = document.createElement("input");
textField.obj = this;
textField.type = "text";
textField.name = "value";
textField.value = text;
textField.style.backgroundColor = this.options.highlightcolor;
textField.className = 'editor_field';
var size = this.options.size || this.options.cols || 0;
if (size != 0) textField.size = size;
if (this.options.submitOnBlur)
textField.onblur = this.onSubmit.bind(this);
this.editField = textField;
} else {
this.options.textarea = true;
var textArea = document.createElement("textarea");
textArea.obj = this;
textArea.name = "value";
textArea.value = this.convertHTMLLineBreaks(text);
textArea.rows = this.options.rows;
textArea.cols = this.options.cols || 40;
textArea.className = 'editor_field';
if (this.options.submitOnBlur)
textArea.onblur = this.onSubmit.bind(this);
this.editField = textArea;
}
if(this.options.loadTextURL) {
this.loadExternalText();
}
this.form.appendChild(this.editField);
},
getText: function() {
return this.element.innerHTML;
},
loadExternalText: function() {
Element.addClassName(this.form, this.options.loadingClassName);
this.editField.disabled = true;
new Ajax.Request(
this.options.loadTextURL,
Object.extend({
asynchronous: true,
onComplete: this.onLoadedExternalText.bind(this)
}, this.options.ajaxOptions)
);
},
onLoadedExternalText: function(transport) {
Element.removeClassName(this.form, this.options.loadingClassName);
this.editField.disabled = false;
this.editField.value = transport.responseText.stripTags();
},
onclickCancel: function() {
this.onComplete();
this.leaveEditMode();
return false;
},
onFailure: function(transport) {
this.options.onFailure(transport);
if (this.oldInnerHTML) {
this.element.innerHTML = this.oldInnerHTML;
this.oldInnerHTML = null;
}
return false;
},
onSubmit: function() {
// onLoading resets these so we need to save them away for the Ajax call
var form = this.form;
var value = this.editField.value;
// do this first, sometimes the ajax call returns before we get a chance to switch on Saving...
// which means this will actually switch on Saving... *after* we've left edit mode causing Saving...
// to be displayed indefinitely
this.onLoading();
if (this.options.evalScripts) {
new Ajax.Request(
this.url, Object.extend({
parameters: this.options.callback(form, value),
onComplete: this.onComplete.bind(this),
onFailure: this.onFailure.bind(this),
asynchronous:true,
evalScripts:true
}, this.options.ajaxOptions));
} else {
new Ajax.Updater(
{ success: this.element,
// don't update on failure (this could be an option)
failure: null },
this.url, Object.extend({
parameters: this.options.callback(form, value),
onComplete: this.onComplete.bind(this),
onFailure: this.onFailure.bind(this)
}, this.options.ajaxOptions));
}
// stop the event to avoid a page refresh in Safari
if (arguments.length > 1) {
Event.stop(arguments[0]);
}
return false;
},
onLoading: function() {
this.saving = true;
this.removeForm();
this.leaveHover();
this.showSaving();
},
showSaving: function() {
this.oldInnerHTML = this.element.innerHTML;
this.element.innerHTML = this.options.savingText;
Element.addClassName(this.element, this.options.savingClassName);
this.element.style.backgroundColor = this.originalBackground;
Element.show(this.element);
},
removeForm: function() {
if(this.form) {
if (this.form.parentNode) Element.remove(this.form);
this.form = null;
}
},
enterHover: function() {
if (this.saving) return;
this.element.style.backgroundColor = this.options.highlightcolor;
if (this.effect) {
this.effect.cancel();
}
Element.addClassName(this.element, this.options.hoverClassName)
},
leaveHover: function() {
if (this.options.backgroundColor) {
this.element.style.backgroundColor = this.oldBackground;
}
Element.removeClassName(this.element, this.options.hoverClassName)
if (this.saving) return;
this.effect = new Effect.Highlight(this.element, {
startcolor: this.options.highlightcolor,
endcolor: this.options.highlightendcolor,
restorecolor: this.originalBackground
});
},
leaveEditMode: function() {
Element.removeClassName(this.element, this.options.savingClassName);
this.removeForm();
this.leaveHover();
this.element.style.backgroundColor = this.originalBackground;
Element.show(this.element);
if (this.options.externalControl) {
Element.show(this.options.externalControl);
}
this.editing = false;
this.saving = false;
this.oldInnerHTML = null;
this.onLeaveEditMode();
},
onComplete: function(transport) {
this.leaveEditMode();
this.options.onComplete.bind(this)(transport, this.element);
},
onEnterEditMode: function() {},
onLeaveEditMode: function() {},
dispose: function() {
if (this.oldInnerHTML) {
this.element.innerHTML = this.oldInnerHTML;
}
this.leaveEditMode();
Event.stopObserving(this.element, 'click', this.onclickListener);
Event.stopObserving(this.element, 'mouseover', this.mouseoverListener);
Event.stopObserving(this.element, 'mouseout', this.mouseoutListener);
if (this.options.externalControl) {
Event.stopObserving(this.options.externalControl, 'click', this.onclickListener);
Event.stopObserving(this.options.externalControl, 'mouseover', this.mouseoverListener);
Event.stopObserving(this.options.externalControl, 'mouseout', this.mouseoutListener);
}
}
};
Ajax.InPlaceCollectionEditor = Class.create();
Object.extend(Ajax.InPlaceCollectionEditor.prototype, Ajax.InPlaceEditor.prototype);
Object.extend(Ajax.InPlaceCollectionEditor.prototype, {
createEditField: function() {
if (!this.cached_selectTag) {
var selectTag = document.createElement("select");
var collection = this.options.collection || [];
var optionTag;
collection.each(function(e,i) {
optionTag = document.createElement("option");
optionTag.value = (e instanceof Array) ? e[0] : e;
if(this.options.value==optionTag.value) optionTag.selected = true;
optionTag.appendChild(document.createTextNode((e instanceof Array) ? e[1] : e));
selectTag.appendChild(optionTag);
}.bind(this));
this.cached_selectTag = selectTag;
}
this.editField = this.cached_selectTag;
if(this.options.loadTextURL) this.loadExternalText();
this.form.appendChild(this.editField);
this.options.callback = function(form, value) {
return "value=" + encodeURIComponent(value);
}
}
});
// Delayed observer, like Form.Element.Observer,
// but waits for delay after last key input
// Ideal for live-search fields
Form.Element.DelayedObserver = Class.create();
Form.Element.DelayedObserver.prototype = {
initialize: function(element, delay, callback) {
this.delay = delay || 0.5;
this.element = $(element);
this.callback = callback;
this.timer = null;
this.lastValue = $F(this.element);
Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
},
delayedListener: function(event) {
if(this.lastValue == $F(this.element)) return;
if(this.timer) clearTimeout(this.timer);
this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
this.lastValue = $F(this.element);
},
onTimerEvent: function() {
this.timer = null;
this.callback(this.element, $F(this.element));
}
};

View File

@ -1,915 +0,0 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz)
//
// See scriptaculous.js for full license.
/*--------------------------------------------------------------------------*/
var Droppables = {
drops: [],
remove: function(element) {
this.drops = this.drops.reject(function(d) { return d.element==$(element) });
},
add: function(element) {
element = $(element);
var options = Object.extend({
greedy: true,
hoverclass: null,
tree: false
}, arguments[1] || {});
// cache containers
if(options.containment) {
options._containers = [];
var containment = options.containment;
if((typeof containment == 'object') &&
(containment.constructor == Array)) {
containment.each( function(c) { options._containers.push($(c)) });
} else {
options._containers.push($(containment));
}
}
if(options.accept) options.accept = [options.accept].flatten();
Element.makePositioned(element); // fix IE
options.element = element;
this.drops.push(options);
},
findDeepestChild: function(drops) {
deepest = drops[0];
for (i = 1; i < drops.length; ++i)
if (Element.isParent(drops[i].element, deepest.element))
deepest = drops[i];
return deepest;
},
isContained: function(element, drop) {
var containmentNode;
if(drop.tree) {
containmentNode = element.treeNode;
} else {
containmentNode = element.parentNode;
}
return drop._containers.detect(function(c) { return containmentNode == c });
},
isAffected: function(point, element, drop) {
return (
(drop.element!=element) &&
((!drop._containers) ||
this.isContained(element, drop)) &&
((!drop.accept) ||
(Element.classNames(element).detect(
function(v) { return drop.accept.include(v) } ) )) &&
Position.within(drop.element, point[0], point[1]) );
},
deactivate: function(drop) {
if(drop.hoverclass)
Element.removeClassName(drop.element, drop.hoverclass);
this.last_active = null;
},
activate: function(drop) {
if(drop.hoverclass)
Element.addClassName(drop.element, drop.hoverclass);
this.last_active = drop;
},
show: function(point, element) {
if(!this.drops.length) return;
var affected = [];
if(this.last_active) this.deactivate(this.last_active);
this.drops.each( function(drop) {
if(Droppables.isAffected(point, element, drop))
affected.push(drop);
});
if(affected.length>0) {
drop = Droppables.findDeepestChild(affected);
Position.within(drop.element, point[0], point[1]);
if(drop.onHover)
drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
Droppables.activate(drop);
}
},
fire: function(event, element) {
if(!this.last_active) return;
Position.prepare();
if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
if (this.last_active.onDrop)
this.last_active.onDrop(element, this.last_active.element, event);
},
reset: function() {
if(this.last_active)
this.deactivate(this.last_active);
}
}
var Draggables = {
drags: [],
observers: [],
register: function(draggable) {
if(this.drags.length == 0) {
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
this.eventKeypress = this.keyPress.bindAsEventListener(this);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
Event.observe(document, "keypress", this.eventKeypress);
}
this.drags.push(draggable);
},
unregister: function(draggable) {
this.drags = this.drags.reject(function(d) { return d==draggable });
if(this.drags.length == 0) {
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
Event.stopObserving(document, "keypress", this.eventKeypress);
}
},
activate: function(draggable) {
window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
this.activeDraggable = draggable;
},
deactivate: function() {
this.activeDraggable = null;
},
updateDrag: function(event) {
if(!this.activeDraggable) return;
var pointer = [Event.pointerX(event), Event.pointerY(event)];
// Mozilla-based browsers fire successive mousemove events with
// the same coordinates, prevent needless redrawing (moz bug?)
if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
this._lastPointer = pointer;
this.activeDraggable.updateDrag(event, pointer);
},
endDrag: function(event) {
if(!this.activeDraggable) return;
this._lastPointer = null;
this.activeDraggable.endDrag(event);
this.activeDraggable = null;
},
keyPress: function(event) {
if(this.activeDraggable)
this.activeDraggable.keyPress(event);
},
addObserver: function(observer) {
this.observers.push(observer);
this._cacheObserverCallbacks();
},
removeObserver: function(element) { // element instead of observer fixes mem leaks
this.observers = this.observers.reject( function(o) { return o.element==element });
this._cacheObserverCallbacks();
},
notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag'
if(this[eventName+'Count'] > 0)
this.observers.each( function(o) {
if(o[eventName]) o[eventName](eventName, draggable, event);
});
},
_cacheObserverCallbacks: function() {
['onStart','onEnd','onDrag'].each( function(eventName) {
Draggables[eventName+'Count'] = Draggables.observers.select(
function(o) { return o[eventName]; }
).length;
});
}
}
/*--------------------------------------------------------------------------*/
var Draggable = Class.create();
Draggable.prototype = {
initialize: function(element) {
var options = Object.extend({
handle: false,
starteffect: function(element) {
element._opacity = Element.getOpacity(element);
new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
},
reverteffect: function(element, top_offset, left_offset) {
var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
element._revert = new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur});
},
endeffect: function(element) {
var toOpacity = typeof element._opacity == 'number' ? element._opacity : 1.0
new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity});
},
zindex: 1000,
revert: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
snap: false // false, or xy or [x,y] or function(x,y){ return [x,y] }
}, arguments[1] || {});
this.element = $(element);
if(options.handle && (typeof options.handle == 'string')) {
var h = Element.childrenWithClassName(this.element, options.handle, true);
if(h.length>0) this.handle = h[0];
}
if(!this.handle) this.handle = $(options.handle);
if(!this.handle) this.handle = this.element;
if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML)
options.scroll = $(options.scroll);
Element.makePositioned(this.element); // fix IE
this.delta = this.currentDelta();
this.options = options;
this.dragging = false;
this.eventMouseDown = this.initDrag.bindAsEventListener(this);
Event.observe(this.handle, "mousedown", this.eventMouseDown);
Draggables.register(this);
},
destroy: function() {
Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
Draggables.unregister(this);
},
currentDelta: function() {
return([
parseInt(Element.getStyle(this.element,'left') || '0'),
parseInt(Element.getStyle(this.element,'top') || '0')]);
},
initDrag: function(event) {
if(Event.isLeftClick(event)) {
// abort on form elements, fixes a Firefox issue
var src = Event.element(event);
if(src.tagName && (
src.tagName=='INPUT' ||
src.tagName=='SELECT' ||
src.tagName=='OPTION' ||
src.tagName=='BUTTON' ||
src.tagName=='TEXTAREA')) return;
if(this.element._revert) {
this.element._revert.cancel();
this.element._revert = null;
}
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var pos = Position.cumulativeOffset(this.element);
this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
Draggables.activate(this);
Event.stop(event);
}
},
startDrag: function(event) {
this.dragging = true;
if(this.options.zindex) {
this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
this.element.style.zIndex = this.options.zindex;
}
if(this.options.ghosting) {
this._clone = this.element.cloneNode(true);
Position.absolutize(this.element);
this.element.parentNode.insertBefore(this._clone, this.element);
}
if(this.options.scroll) {
if (this.options.scroll == window) {
var where = this._getWindowScroll(this.options.scroll);
this.originalScrollLeft = where.left;
this.originalScrollTop = where.top;
} else {
this.originalScrollLeft = this.options.scroll.scrollLeft;
this.originalScrollTop = this.options.scroll.scrollTop;
}
}
Draggables.notify('onStart', this, event);
if(this.options.starteffect) this.options.starteffect(this.element);
},
updateDrag: function(event, pointer) {
if(!this.dragging) this.startDrag(event);
Position.prepare();
Droppables.show(pointer, this.element);
Draggables.notify('onDrag', this, event);
this.draw(pointer);
if(this.options.change) this.options.change(this);
if(this.options.scroll) {
this.stopScrolling();
var p;
if (this.options.scroll == window) {
with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
} else {
p = Position.page(this.options.scroll);
p[0] += this.options.scroll.scrollLeft;
p[1] += this.options.scroll.scrollTop;
p.push(p[0]+this.options.scroll.offsetWidth);
p.push(p[1]+this.options.scroll.offsetHeight);
}
var speed = [0,0];
if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
this.startScrolling(speed);
}
// fix AppleWebKit rendering
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
Event.stop(event);
},
finishDrag: function(event, success) {
this.dragging = false;
if(this.options.ghosting) {
Position.relativize(this.element);
Element.remove(this._clone);
this._clone = null;
}
if(success) Droppables.fire(event, this.element);
Draggables.notify('onEnd', this, event);
var revert = this.options.revert;
if(revert && typeof revert == 'function') revert = revert(this.element);
var d = this.currentDelta();
if(revert && this.options.reverteffect) {
this.options.reverteffect(this.element,
d[1]-this.delta[1], d[0]-this.delta[0]);
} else {
this.delta = d;
}
if(this.options.zindex)
this.element.style.zIndex = this.originalZ;
if(this.options.endeffect)
this.options.endeffect(this.element);
Draggables.deactivate(this);
Droppables.reset();
},
keyPress: function(event) {
if(event.keyCode!=Event.KEY_ESC) return;
this.finishDrag(event, false);
Event.stop(event);
},
endDrag: function(event) {
if(!this.dragging) return;
this.stopScrolling();
this.finishDrag(event, true);
Event.stop(event);
},
draw: function(point) {
var pos = Position.cumulativeOffset(this.element);
var d = this.currentDelta();
pos[0] -= d[0]; pos[1] -= d[1];
if(this.options.scroll && (this.options.scroll != window)) {
pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
}
var p = [0,1].map(function(i){
return (point[i]-pos[i]-this.offset[i])
}.bind(this));
if(this.options.snap) {
if(typeof this.options.snap == 'function') {
p = this.options.snap(p[0],p[1],this);
} else {
if(this.options.snap instanceof Array) {
p = p.map( function(v, i) {
return Math.round(v/this.options.snap[i])*this.options.snap[i] }.bind(this))
} else {
p = p.map( function(v) {
return Math.round(v/this.options.snap)*this.options.snap }.bind(this))
}
}}
var style = this.element.style;
if((!this.options.constraint) || (this.options.constraint=='horizontal'))
style.left = p[0] + "px";
if((!this.options.constraint) || (this.options.constraint=='vertical'))
style.top = p[1] + "px";
if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
},
stopScrolling: function() {
if(this.scrollInterval) {
clearInterval(this.scrollInterval);
this.scrollInterval = null;
Draggables._lastScrollPointer = null;
}
},
startScrolling: function(speed) {
this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
this.lastScrolled = new Date();
this.scrollInterval = setInterval(this.scroll.bind(this), 10);
},
scroll: function() {
var current = new Date();
var delta = current - this.lastScrolled;
this.lastScrolled = current;
if(this.options.scroll == window) {
with (this._getWindowScroll(this.options.scroll)) {
if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
var d = delta / 1000;
this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
}
}
} else {
this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000;
}
Position.prepare();
Droppables.show(Draggables._lastPointer, this.element);
Draggables.notify('onDrag', this);
Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
if (Draggables._lastScrollPointer[0] < 0)
Draggables._lastScrollPointer[0] = 0;
if (Draggables._lastScrollPointer[1] < 0)
Draggables._lastScrollPointer[1] = 0;
this.draw(Draggables._lastScrollPointer);
if(this.options.change) this.options.change(this);
},
_getWindowScroll: function(w) {
var T, L, W, H;
with (w.document) {
if (w.document.documentElement && documentElement.scrollTop) {
T = documentElement.scrollTop;
L = documentElement.scrollLeft;
} else if (w.document.body) {
T = body.scrollTop;
L = body.scrollLeft;
}
if (w.innerWidth) {
W = w.innerWidth;
H = w.innerHeight;
} else if (w.document.documentElement && documentElement.clientWidth) {
W = documentElement.clientWidth;
H = documentElement.clientHeight;
} else {
W = body.offsetWidth;
H = body.offsetHeight
}
}
return { top: T, left: L, width: W, height: H };
}
}
/*--------------------------------------------------------------------------*/
var SortableObserver = Class.create();
SortableObserver.prototype = {
initialize: function(element, observer) {
this.element = $(element);
this.observer = observer;
this.lastValue = Sortable.serialize(this.element);
},
onStart: function() {
this.lastValue = Sortable.serialize(this.element);
},
onEnd: function() {
Sortable.unmark();
if(this.lastValue != Sortable.serialize(this.element))
this.observer(this.element)
}
}
var Sortable = {
sortables: {},
_findRootElement: function(element) {
while (element.tagName != "BODY") {
if(element.id && Sortable.sortables[element.id]) return element;
element = element.parentNode;
}
},
options: function(element) {
element = Sortable._findRootElement($(element));
if(!element) return;
return Sortable.sortables[element.id];
},
destroy: function(element){
var s = Sortable.options(element);
if(s) {
Draggables.removeObserver(s.element);
s.droppables.each(function(d){ Droppables.remove(d) });
s.draggables.invoke('destroy');
delete Sortable.sortables[s.element.id];
}
},
create: function(element) {
element = $(element);
var options = Object.extend({
element: element,
tag: 'li', // assumes li children, override with tag: 'tagname'
dropOnEmpty: false,
tree: false,
treeTag: 'ul',
overlap: 'vertical', // one of 'vertical', 'horizontal'
constraint: 'vertical', // one of 'vertical', 'horizontal', false
containment: element, // also takes array of elements (or id's); or false
handle: false, // or a CSS class
only: false,
hoverclass: null,
ghosting: false,
scroll: false,
scrollSensitivity: 20,
scrollSpeed: 15,
format: /^[^_]*_(.*)$/,
onChange: Prototype.emptyFunction,
onUpdate: Prototype.emptyFunction
}, arguments[1] || {});
// clear any old sortable with same element
this.destroy(element);
// build options for the draggables
var options_for_draggable = {
revert: true,
scroll: options.scroll,
scrollSpeed: options.scrollSpeed,
scrollSensitivity: options.scrollSensitivity,
ghosting: options.ghosting,
constraint: options.constraint,
handle: options.handle };
if(options.starteffect)
options_for_draggable.starteffect = options.starteffect;
if(options.reverteffect)
options_for_draggable.reverteffect = options.reverteffect;
else
if(options.ghosting) options_for_draggable.reverteffect = function(element) {
element.style.top = 0;
element.style.left = 0;
};
if(options.endeffect)
options_for_draggable.endeffect = options.endeffect;
if(options.zindex)
options_for_draggable.zindex = options.zindex;
// build options for the droppables
var options_for_droppable = {
overlap: options.overlap,
containment: options.containment,
tree: options.tree,
hoverclass: options.hoverclass,
onHover: Sortable.onHover
//greedy: !options.dropOnEmpty
}
var options_for_tree = {
onHover: Sortable.onEmptyHover,
overlap: options.overlap,
containment: options.containment,
hoverclass: options.hoverclass
}
// fix for gecko engine
Element.cleanWhitespace(element);
options.draggables = [];
options.droppables = [];
// drop on empty handling
if(options.dropOnEmpty || options.tree) {
Droppables.add(element, options_for_tree);
options.droppables.push(element);
}
(this.findElements(element, options) || []).each( function(e) {
// handles are per-draggable
var handle = options.handle ?
Element.childrenWithClassName(e, options.handle)[0] : e;
options.draggables.push(
new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
Droppables.add(e, options_for_droppable);
if(options.tree) e.treeNode = element;
options.droppables.push(e);
});
if(options.tree) {
(Sortable.findTreeElements(element, options) || []).each( function(e) {
Droppables.add(e, options_for_tree);
e.treeNode = element;
options.droppables.push(e);
});
}
// keep reference
this.sortables[element.id] = options;
// for onupdate
Draggables.addObserver(new SortableObserver(element, options.onUpdate));
},
// return all suitable-for-sortable elements in a guaranteed order
findElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.tag);
},
findTreeElements: function(element, options) {
return Element.findChildren(
element, options.only, options.tree ? true : false, options.treeTag);
},
onHover: function(element, dropon, overlap) {
if(Element.isParent(dropon, element)) return;
if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
return;
} else if(overlap>0.5) {
Sortable.mark(dropon, 'before');
if(dropon.previousSibling != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, dropon);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
} else {
Sortable.mark(dropon, 'after');
var nextElement = dropon.nextSibling || null;
if(nextElement != element) {
var oldParentNode = element.parentNode;
element.style.visibility = "hidden"; // fix gecko rendering
dropon.parentNode.insertBefore(element, nextElement);
if(dropon.parentNode!=oldParentNode)
Sortable.options(oldParentNode).onChange(element);
Sortable.options(dropon.parentNode).onChange(element);
}
}
},
onEmptyHover: function(element, dropon, overlap) {
var oldParentNode = element.parentNode;
var droponOptions = Sortable.options(dropon);
if(!Element.isParent(dropon, element)) {
var index;
var children = Sortable.findElements(dropon, {tag: droponOptions.tag});
var child = null;
if(children) {
var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
for (index = 0; index < children.length; index += 1) {
if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
offset -= Element.offsetSize (children[index], droponOptions.overlap);
} else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
child = index + 1 < children.length ? children[index + 1] : null;
break;
} else {
child = children[index];
break;
}
}
}
dropon.insertBefore(element, child);
Sortable.options(oldParentNode).onChange(element);
droponOptions.onChange(element);
}
},
unmark: function() {
if(Sortable._marker) Element.hide(Sortable._marker);
},
mark: function(dropon, position) {
// mark on ghosting only
var sortable = Sortable.options(dropon.parentNode);
if(sortable && !sortable.ghosting) return;
if(!Sortable._marker) {
Sortable._marker = $('dropmarker') || document.createElement('DIV');
Element.hide(Sortable._marker);
Element.addClassName(Sortable._marker, 'dropmarker');
Sortable._marker.style.position = 'absolute';
document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
}
var offsets = Position.cumulativeOffset(dropon);
Sortable._marker.style.left = offsets[0] + 'px';
Sortable._marker.style.top = offsets[1] + 'px';
if(position=='after')
if(sortable.overlap == 'horizontal')
Sortable._marker.style.left = (offsets[0]+dropon.clientWidth) + 'px';
else
Sortable._marker.style.top = (offsets[1]+dropon.clientHeight) + 'px';
Element.show(Sortable._marker);
},
_tree: function(element, options, parent) {
var children = Sortable.findElements(element, options) || [];
for (var i = 0; i < children.length; ++i) {
var match = children[i].id.match(options.format);
if (!match) continue;
var child = {
id: encodeURIComponent(match ? match[1] : null),
element: element,
parent: parent,
children: new Array,
position: parent.children.length,
container: Sortable._findChildrenElement(children[i], options.treeTag.toUpperCase())
}
/* Get the element containing the children and recurse over it */
if (child.container)
this._tree(child.container, options, child)
parent.children.push (child);
}
return parent;
},
/* Finds the first element of the given tag type within a parent element.
Used for finding the first LI[ST] within a L[IST]I[TEM].*/
_findChildrenElement: function (element, containerTag) {
if (element && element.hasChildNodes)
for (var i = 0; i < element.childNodes.length; ++i)
if (element.childNodes[i].tagName == containerTag)
return element.childNodes[i];
return null;
},
tree: function(element) {
element = $(element);
var sortableOptions = this.options(element);
var options = Object.extend({
tag: sortableOptions.tag,
treeTag: sortableOptions.treeTag,
only: sortableOptions.only,
name: element.id,
format: sortableOptions.format
}, arguments[1] || {});
var root = {
id: null,
parent: null,
children: new Array,
container: element,
position: 0
}
return Sortable._tree (element, options, root);
},
/* Construct a [i] index for a particular node */
_constructIndex: function(node) {
var index = '';
do {
if (node.id) index = '[' + node.position + ']' + index;
} while ((node = node.parent) != null);
return index;
},
sequence: function(element) {
element = $(element);
var options = Object.extend(this.options(element), arguments[1] || {});
return $(this.findElements(element, options) || []).map( function(item) {
return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
});
},
setSequence: function(element, new_sequence) {
element = $(element);
var options = Object.extend(this.options(element), arguments[2] || {});
var nodeMap = {};
this.findElements(element, options).each( function(n) {
if (n.id.match(options.format))
nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
n.parentNode.removeChild(n);
});
new_sequence.each(function(ident) {
var n = nodeMap[ident];
if (n) {
n[1].appendChild(n[0]);
delete nodeMap[ident];
}
});
},
serialize: function(element) {
element = $(element);
var options = Object.extend(Sortable.options(element), arguments[1] || {});
var name = encodeURIComponent(
(arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
if (options.tree) {
return Sortable.tree(element, arguments[1]).children.map( function (item) {
return [name + Sortable._constructIndex(item) + "=" +
encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
}).flatten().join('&');
} else {
return Sortable.sequence(element, arguments[1]).map( function(item) {
return name + "[]=" + encodeURIComponent(item);
}).join('&');
}
}
}
/* Returns true if child is contained within element */
Element.isParent = function(child, element) {
if (!child.parentNode || child == element) return false;
if (child.parentNode == element) return true;
return Element.isParent(child.parentNode, element);
}
Element.findChildren = function(element, only, recursive, tagName) {
if(!element.hasChildNodes()) return null;
tagName = tagName.toUpperCase();
if(only) only = [only].flatten();
var elements = [];
$A(element.childNodes).each( function(e) {
if(e.tagName && e.tagName.toUpperCase()==tagName &&
(!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
elements.push(e);
if(recursive) {
var grandchildren = Element.findChildren(e, only, recursive, tagName);
if(grandchildren) elements.push(grandchildren);
}
});
return (elements.length>0 ? elements.flatten() : []);
}
Element.offsetSize = function (element, type) {
if (type == 'vertical' || type == 'height')
return element.offsetHeight;
else
return element.offsetWidth;
}

View File

@ -1,958 +0,0 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// Contributors:
// Justin Palmer (http://encytemedia.com/)
// Mark Pilgrim (http://diveintomark.org/)
// Martin Bialasinki
//
// See scriptaculous.js for full license.
// converts rgb() and #xxx to #xxxxxx format,
// returns self (or first argument) if not convertable
String.prototype.parseColor = function() {
var color = '#';
if(this.slice(0,4) == 'rgb(') {
var cols = this.slice(4,this.length-1).split(',');
var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
} else {
if(this.slice(0,1) == '#') {
if(this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
if(this.length==7) color = this.toLowerCase();
}
}
return(color.length==7 ? color : (arguments[0] || this));
}
/*--------------------------------------------------------------------------*/
Element.collectTextNodes = function(element) {
return $A($(element).childNodes).collect( function(node) {
return (node.nodeType==3 ? node.nodeValue :
(node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
}).flatten().join('');
}
Element.collectTextNodesIgnoreClass = function(element, className) {
return $A($(element).childNodes).collect( function(node) {
return (node.nodeType==3 ? node.nodeValue :
((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
Element.collectTextNodesIgnoreClass(node, className) : ''));
}).flatten().join('');
}
Element.setContentZoom = function(element, percent) {
element = $(element);
Element.setStyle(element, {fontSize: (percent/100) + 'em'});
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
}
Element.getOpacity = function(element){
var opacity;
if (opacity = Element.getStyle(element, 'opacity'))
return parseFloat(opacity);
if (opacity = (Element.getStyle(element, 'filter') || '').match(/alpha\(opacity=(.*)\)/))
if(opacity[1]) return parseFloat(opacity[1]) / 100;
return 1.0;
}
Element.setOpacity = function(element, value){
element= $(element);
if (value == 1){
Element.setStyle(element, { opacity:
(/Gecko/.test(navigator.userAgent) && !/Konqueror|Safari|KHTML/.test(navigator.userAgent)) ?
0.999999 : null });
if(/MSIE/.test(navigator.userAgent))
Element.setStyle(element, {filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'')});
} else {
if(value < 0.00001) value = 0;
Element.setStyle(element, {opacity: value});
if(/MSIE/.test(navigator.userAgent))
Element.setStyle(element,
{ filter: Element.getStyle(element,'filter').replace(/alpha\([^\)]*\)/gi,'') +
'alpha(opacity='+value*100+')' });
}
}
Element.getInlineOpacity = function(element){
return $(element).style.opacity || '';
}
Element.childrenWithClassName = function(element, className, findFirst) {
var classNameRegExp = new RegExp("(^|\\s)" + className + "(\\s|$)");
var results = $A($(element).getElementsByTagName('*'))[findFirst ? 'detect' : 'select']( function(c) {
return (c.className && c.className.match(classNameRegExp));
});
if(!results) results = [];
return results;
}
Element.forceRerendering = function(element) {
try {
element = $(element);
var n = document.createTextNode(' ');
element.appendChild(n);
element.removeChild(n);
} catch(e) { }
};
/*--------------------------------------------------------------------------*/
Array.prototype.call = function() {
var args = arguments;
this.each(function(f){ f.apply(this, args) });
}
/*--------------------------------------------------------------------------*/
var Effect = {
tagifyText: function(element) {
var tagifyStyle = 'position:relative';
if(/MSIE/.test(navigator.userAgent)) tagifyStyle += ';zoom:1';
element = $(element);
$A(element.childNodes).each( function(child) {
if(child.nodeType==3) {
child.nodeValue.toArray().each( function(character) {
element.insertBefore(
Builder.node('span',{style: tagifyStyle},
character == ' ' ? String.fromCharCode(160) : character),
child);
});
Element.remove(child);
}
});
},
multiple: function(element, effect) {
var elements;
if(((typeof element == 'object') ||
(typeof element == 'function')) &&
(element.length))
elements = element;
else
elements = $(element).childNodes;
var options = Object.extend({
speed: 0.1,
delay: 0.0
}, arguments[2] || {});
var masterDelay = options.delay;
$A(elements).each( function(element, index) {
new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
});
},
PAIRS: {
'slide': ['SlideDown','SlideUp'],
'blind': ['BlindDown','BlindUp'],
'appear': ['Appear','Fade']
},
toggle: function(element, effect) {
element = $(element);
effect = (effect || 'appear').toLowerCase();
var options = Object.extend({
queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
}, arguments[2] || {});
Effect[element.visible() ?
Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options);
}
};
var Effect2 = Effect; // deprecated
/* ------------- transitions ------------- */
Effect.Transitions = {}
Effect.Transitions.linear = function(pos) {
return pos;
}
Effect.Transitions.sinoidal = function(pos) {
return (-Math.cos(pos*Math.PI)/2) + 0.5;
}
Effect.Transitions.reverse = function(pos) {
return 1-pos;
}
Effect.Transitions.flicker = function(pos) {
return ((-Math.cos(pos*Math.PI)/4) + 0.75) + Math.random()/4;
}
Effect.Transitions.wobble = function(pos) {
return (-Math.cos(pos*Math.PI*(9*pos))/2) + 0.5;
}
Effect.Transitions.pulse = function(pos) {
return (Math.floor(pos*10) % 2 == 0 ?
(pos*10-Math.floor(pos*10)) : 1-(pos*10-Math.floor(pos*10)));
}
Effect.Transitions.none = function(pos) {
return 0;
}
Effect.Transitions.full = function(pos) {
return 1;
}
/* ------------- core effects ------------- */
Effect.ScopedQueue = Class.create();
Object.extend(Object.extend(Effect.ScopedQueue.prototype, Enumerable), {
initialize: function() {
this.effects = [];
this.interval = null;
},
_each: function(iterator) {
this.effects._each(iterator);
},
add: function(effect) {
var timestamp = new Date().getTime();
var position = (typeof effect.options.queue == 'string') ?
effect.options.queue : effect.options.queue.position;
switch(position) {
case 'front':
// move unstarted effects after this effect
this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
e.startOn += effect.finishOn;
e.finishOn += effect.finishOn;
});
break;
case 'end':
// start effect after last queued effect has finished
timestamp = this.effects.pluck('finishOn').max() || timestamp;
break;
}
effect.startOn += timestamp;
effect.finishOn += timestamp;
if(!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
this.effects.push(effect);
if(!this.interval)
this.interval = setInterval(this.loop.bind(this), 40);
},
remove: function(effect) {
this.effects = this.effects.reject(function(e) { return e==effect });
if(this.effects.length == 0) {
clearInterval(this.interval);
this.interval = null;
}
},
loop: function() {
var timePos = new Date().getTime();
this.effects.invoke('loop', timePos);
}
});
Effect.Queues = {
instances: $H(),
get: function(queueName) {
if(typeof queueName != 'string') return queueName;
if(!this.instances[queueName])
this.instances[queueName] = new Effect.ScopedQueue();
return this.instances[queueName];
}
}
Effect.Queue = Effect.Queues.get('global');
Effect.DefaultOptions = {
transition: Effect.Transitions.sinoidal,
duration: 1.0, // seconds
fps: 25.0, // max. 25fps due to Effect.Queue implementation
sync: false, // true for combining
from: 0.0,
to: 1.0,
delay: 0.0,
queue: 'parallel'
}
Effect.Base = function() {};
Effect.Base.prototype = {
position: null,
start: function(options) {
this.options = Object.extend(Object.extend({},Effect.DefaultOptions), options || {});
this.currentFrame = 0;
this.state = 'idle';
this.startOn = this.options.delay*1000;
this.finishOn = this.startOn + (this.options.duration*1000);
this.event('beforeStart');
if(!this.options.sync)
Effect.Queues.get(typeof this.options.queue == 'string' ?
'global' : this.options.queue.scope).add(this);
},
loop: function(timePos) {
if(timePos >= this.startOn) {
if(timePos >= this.finishOn) {
this.render(1.0);
this.cancel();
this.event('beforeFinish');
if(this.finish) this.finish();
this.event('afterFinish');
return;
}
var pos = (timePos - this.startOn) / (this.finishOn - this.startOn);
var frame = Math.round(pos * this.options.fps * this.options.duration);
if(frame > this.currentFrame) {
this.render(pos);
this.currentFrame = frame;
}
}
},
render: function(pos) {
if(this.state == 'idle') {
this.state = 'running';
this.event('beforeSetup');
if(this.setup) this.setup();
this.event('afterSetup');
}
if(this.state == 'running') {
if(this.options.transition) pos = this.options.transition(pos);
pos *= (this.options.to-this.options.from);
pos += this.options.from;
this.position = pos;
this.event('beforeUpdate');
if(this.update) this.update(pos);
this.event('afterUpdate');
}
},
cancel: function() {
if(!this.options.sync)
Effect.Queues.get(typeof this.options.queue == 'string' ?
'global' : this.options.queue.scope).remove(this);
this.state = 'finished';
},
event: function(eventName) {
if(this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
if(this.options[eventName]) this.options[eventName](this);
},
inspect: function() {
return '#<Effect:' + $H(this).inspect() + ',options:' + $H(this.options).inspect() + '>';
}
}
Effect.Parallel = Class.create();
Object.extend(Object.extend(Effect.Parallel.prototype, Effect.Base.prototype), {
initialize: function(effects) {
this.effects = effects || [];
this.start(arguments[1]);
},
update: function(position) {
this.effects.invoke('render', position);
},
finish: function(position) {
this.effects.each( function(effect) {
effect.render(1.0);
effect.cancel();
effect.event('beforeFinish');
if(effect.finish) effect.finish(position);
effect.event('afterFinish');
});
}
});
Effect.Opacity = Class.create();
Object.extend(Object.extend(Effect.Opacity.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
// make this work on IE on elements without 'layout'
if(/MSIE/.test(navigator.userAgent) && (!this.element.hasLayout))
this.element.setStyle({zoom: 1});
var options = Object.extend({
from: this.element.getOpacity() || 0.0,
to: 1.0
}, arguments[1] || {});
this.start(options);
},
update: function(position) {
this.element.setOpacity(position);
}
});
Effect.Move = Class.create();
Object.extend(Object.extend(Effect.Move.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
var options = Object.extend({
x: 0,
y: 0,
mode: 'relative'
}, arguments[1] || {});
this.start(options);
},
setup: function() {
// Bug in Opera: Opera returns the "real" position of a static element or
// relative element that does not have top/left explicitly set.
// ==> Always set top and left for position relative elements in your stylesheets
// (to 0 if you do not need them)
this.element.makePositioned();
this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
this.originalTop = parseFloat(this.element.getStyle('top') || '0');
if(this.options.mode == 'absolute') {
// absolute movement, so we need to calc deltaX and deltaY
this.options.x = this.options.x - this.originalLeft;
this.options.y = this.options.y - this.originalTop;
}
},
update: function(position) {
this.element.setStyle({
left: this.options.x * position + this.originalLeft + 'px',
top: this.options.y * position + this.originalTop + 'px'
});
}
});
// for backwards compatibility
Effect.MoveBy = function(element, toTop, toLeft) {
return new Effect.Move(element,
Object.extend({ x: toLeft, y: toTop }, arguments[3] || {}));
};
Effect.Scale = Class.create();
Object.extend(Object.extend(Effect.Scale.prototype, Effect.Base.prototype), {
initialize: function(element, percent) {
this.element = $(element)
var options = Object.extend({
scaleX: true,
scaleY: true,
scaleContent: true,
scaleFromCenter: false,
scaleMode: 'box', // 'box' or 'contents' or {} with provided values
scaleFrom: 100.0,
scaleTo: percent
}, arguments[2] || {});
this.start(options);
},
setup: function() {
this.restoreAfterFinish = this.options.restoreAfterFinish || false;
this.elementPositioning = this.element.getStyle('position');
this.originalStyle = {};
['top','left','width','height','fontSize'].each( function(k) {
this.originalStyle[k] = this.element.style[k];
}.bind(this));
this.originalTop = this.element.offsetTop;
this.originalLeft = this.element.offsetLeft;
var fontSize = this.element.getStyle('font-size') || '100%';
['em','px','%'].each( function(fontSizeType) {
if(fontSize.indexOf(fontSizeType)>0) {
this.fontSize = parseFloat(fontSize);
this.fontSizeType = fontSizeType;
}
}.bind(this));
this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
this.dims = null;
if(this.options.scaleMode=='box')
this.dims = [this.element.offsetHeight, this.element.offsetWidth];
if(/^content/.test(this.options.scaleMode))
this.dims = [this.element.scrollHeight, this.element.scrollWidth];
if(!this.dims)
this.dims = [this.options.scaleMode.originalHeight,
this.options.scaleMode.originalWidth];
},
update: function(position) {
var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
if(this.options.scaleContent && this.fontSize)
this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
},
finish: function(position) {
if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
},
setDimensions: function(height, width) {
var d = {};
if(this.options.scaleX) d.width = width + 'px';
if(this.options.scaleY) d.height = height + 'px';
if(this.options.scaleFromCenter) {
var topd = (height - this.dims[0])/2;
var leftd = (width - this.dims[1])/2;
if(this.elementPositioning == 'absolute') {
if(this.options.scaleY) d.top = this.originalTop-topd + 'px';
if(this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
} else {
if(this.options.scaleY) d.top = -topd + 'px';
if(this.options.scaleX) d.left = -leftd + 'px';
}
}
this.element.setStyle(d);
}
});
Effect.Highlight = Class.create();
Object.extend(Object.extend(Effect.Highlight.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || {});
this.start(options);
},
setup: function() {
// Prevent executing on elements not in the layout flow
if(this.element.getStyle('display')=='none') { this.cancel(); return; }
// Disable background image during the effect
this.oldStyle = {
backgroundImage: this.element.getStyle('background-image') };
this.element.setStyle({backgroundImage: 'none'});
if(!this.options.endcolor)
this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
if(!this.options.restorecolor)
this.options.restorecolor = this.element.getStyle('background-color');
// init color calculations
this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
},
update: function(position) {
this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
return m+(Math.round(this._base[i]+(this._delta[i]*position)).toColorPart()); }.bind(this)) });
},
finish: function() {
this.element.setStyle(Object.extend(this.oldStyle, {
backgroundColor: this.options.restorecolor
}));
}
});
Effect.ScrollTo = Class.create();
Object.extend(Object.extend(Effect.ScrollTo.prototype, Effect.Base.prototype), {
initialize: function(element) {
this.element = $(element);
this.start(arguments[1] || {});
},
setup: function() {
Position.prepare();
var offsets = Position.cumulativeOffset(this.element);
if(this.options.offset) offsets[1] += this.options.offset;
var max = window.innerHeight ?
window.height - window.innerHeight :
document.body.scrollHeight -
(document.documentElement.clientHeight ?
document.documentElement.clientHeight : document.body.clientHeight);
this.scrollStart = Position.deltaY;
this.delta = (offsets[1] > max ? max : offsets[1]) - this.scrollStart;
},
update: function(position) {
Position.prepare();
window.scrollTo(Position.deltaX,
this.scrollStart + (position*this.delta));
}
});
/* ------------- combination effects ------------- */
Effect.Fade = function(element) {
element = $(element);
var oldOpacity = element.getInlineOpacity();
var options = Object.extend({
from: element.getOpacity() || 1.0,
to: 0.0,
afterFinishInternal: function(effect) {
if(effect.options.to!=0) return;
effect.element.hide();
effect.element.setStyle({opacity: oldOpacity});
}}, arguments[1] || {});
return new Effect.Opacity(element,options);
}
Effect.Appear = function(element) {
element = $(element);
var options = Object.extend({
from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
to: 1.0,
// force Safari to render floated elements properly
afterFinishInternal: function(effect) {
effect.element.forceRerendering();
},
beforeSetup: function(effect) {
effect.element.setOpacity(effect.options.from);
effect.element.show();
}}, arguments[1] || {});
return new Effect.Opacity(element,options);
}
Effect.Puff = function(element) {
element = $(element);
var oldStyle = { opacity: element.getInlineOpacity(), position: element.getStyle('position') };
return new Effect.Parallel(
[ new Effect.Scale(element, 200,
{ sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
Object.extend({ duration: 1.0,
beforeSetupInternal: function(effect) {
effect.effects[0].element.setStyle({position: 'absolute'}); },
afterFinishInternal: function(effect) {
effect.effects[0].element.hide();
effect.effects[0].element.setStyle(oldStyle); }
}, arguments[1] || {})
);
}
Effect.BlindUp = function(element) {
element = $(element);
element.makeClipping();
return new Effect.Scale(element, 0,
Object.extend({ scaleContent: false,
scaleX: false,
restoreAfterFinish: true,
afterFinishInternal: function(effect) {
effect.element.hide();
effect.element.undoClipping();
}
}, arguments[1] || {})
);
}
Effect.BlindDown = function(element) {
element = $(element);
var elementDimensions = element.getDimensions();
return new Effect.Scale(element, 100,
Object.extend({ scaleContent: false,
scaleX: false,
scaleFrom: 0,
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
restoreAfterFinish: true,
afterSetup: function(effect) {
effect.element.makeClipping();
effect.element.setStyle({height: '0px'});
effect.element.show();
},
afterFinishInternal: function(effect) {
effect.element.undoClipping();
}
}, arguments[1] || {})
);
}
Effect.SwitchOff = function(element) {
element = $(element);
var oldOpacity = element.getInlineOpacity();
return new Effect.Appear(element, {
duration: 0.4,
from: 0,
transition: Effect.Transitions.flicker,
afterFinishInternal: function(effect) {
new Effect.Scale(effect.element, 1, {
duration: 0.3, scaleFromCenter: true,
scaleX: false, scaleContent: false, restoreAfterFinish: true,
beforeSetup: function(effect) {
effect.element.makePositioned();
effect.element.makeClipping();
},
afterFinishInternal: function(effect) {
effect.element.hide();
effect.element.undoClipping();
effect.element.undoPositioned();
effect.element.setStyle({opacity: oldOpacity});
}
})
}
});
}
Effect.DropOut = function(element) {
element = $(element);
var oldStyle = {
top: element.getStyle('top'),
left: element.getStyle('left'),
opacity: element.getInlineOpacity() };
return new Effect.Parallel(
[ new Effect.Move(element, {x: 0, y: 100, sync: true }),
new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
Object.extend(
{ duration: 0.5,
beforeSetup: function(effect) {
effect.effects[0].element.makePositioned();
},
afterFinishInternal: function(effect) {
effect.effects[0].element.hide();
effect.effects[0].element.undoPositioned();
effect.effects[0].element.setStyle(oldStyle);
}
}, arguments[1] || {}));
}
Effect.Shake = function(element) {
element = $(element);
var oldStyle = {
top: element.getStyle('top'),
left: element.getStyle('left') };
return new Effect.Move(element,
{ x: 20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x: -40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x: 40, y: 0, duration: 0.1, afterFinishInternal: function(effect) {
new Effect.Move(effect.element,
{ x: -20, y: 0, duration: 0.05, afterFinishInternal: function(effect) {
effect.element.undoPositioned();
effect.element.setStyle(oldStyle);
}}) }}) }}) }}) }}) }});
}
Effect.SlideDown = function(element) {
element = $(element);
element.cleanWhitespace();
// SlideDown need to have the content of the element wrapped in a container element with fixed height!
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
var elementDimensions = element.getDimensions();
return new Effect.Scale(element, 100, Object.extend({
scaleContent: false,
scaleX: false,
scaleFrom: window.opera ? 0 : 1,
scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
restoreAfterFinish: true,
afterSetup: function(effect) {
effect.element.makePositioned();
effect.element.firstChild.makePositioned();
if(window.opera) effect.element.setStyle({top: ''});
effect.element.makeClipping();
effect.element.setStyle({height: '0px'});
effect.element.show(); },
afterUpdateInternal: function(effect) {
effect.element.firstChild.setStyle({bottom:
(effect.dims[0] - effect.element.clientHeight) + 'px' });
},
afterFinishInternal: function(effect) {
effect.element.undoClipping();
// IE will crash if child is undoPositioned first
if(/MSIE/.test(navigator.userAgent)){
effect.element.undoPositioned();
effect.element.firstChild.undoPositioned();
}else{
effect.element.firstChild.undoPositioned();
effect.element.undoPositioned();
}
effect.element.firstChild.setStyle({bottom: oldInnerBottom}); }
}, arguments[1] || {})
);
}
Effect.SlideUp = function(element) {
element = $(element);
element.cleanWhitespace();
var oldInnerBottom = $(element.firstChild).getStyle('bottom');
return new Effect.Scale(element, window.opera ? 0 : 1,
Object.extend({ scaleContent: false,
scaleX: false,
scaleMode: 'box',
scaleFrom: 100,
restoreAfterFinish: true,
beforeStartInternal: function(effect) {
effect.element.makePositioned();
effect.element.firstChild.makePositioned();
if(window.opera) effect.element.setStyle({top: ''});
effect.element.makeClipping();
effect.element.show(); },
afterUpdateInternal: function(effect) {
effect.element.firstChild.setStyle({bottom:
(effect.dims[0] - effect.element.clientHeight) + 'px' }); },
afterFinishInternal: function(effect) {
effect.element.hide();
effect.element.undoClipping();
effect.element.firstChild.undoPositioned();
effect.element.undoPositioned();
effect.element.setStyle({bottom: oldInnerBottom}); }
}, arguments[1] || {})
);
}
// Bug in opera makes the TD containing this element expand for a instance after finish
Effect.Squish = function(element) {
return new Effect.Scale(element, window.opera ? 1 : 0,
{ restoreAfterFinish: true,
beforeSetup: function(effect) {
effect.element.makeClipping(effect.element); },
afterFinishInternal: function(effect) {
effect.element.hide(effect.element);
effect.element.undoClipping(effect.element); }
});
}
Effect.Grow = function(element) {
element = $(element);
var options = Object.extend({
direction: 'center',
moveTransition: Effect.Transitions.sinoidal,
scaleTransition: Effect.Transitions.sinoidal,
opacityTransition: Effect.Transitions.full
}, arguments[1] || {});
var oldStyle = {
top: element.style.top,
left: element.style.left,
height: element.style.height,
width: element.style.width,
opacity: element.getInlineOpacity() };
var dims = element.getDimensions();
var initialMoveX, initialMoveY;
var moveX, moveY;
switch (options.direction) {
case 'top-left':
initialMoveX = initialMoveY = moveX = moveY = 0;
break;
case 'top-right':
initialMoveX = dims.width;
initialMoveY = moveY = 0;
moveX = -dims.width;
break;
case 'bottom-left':
initialMoveX = moveX = 0;
initialMoveY = dims.height;
moveY = -dims.height;
break;
case 'bottom-right':
initialMoveX = dims.width;
initialMoveY = dims.height;
moveX = -dims.width;
moveY = -dims.height;
break;
case 'center':
initialMoveX = dims.width / 2;
initialMoveY = dims.height / 2;
moveX = -dims.width / 2;
moveY = -dims.height / 2;
break;
}
return new Effect.Move(element, {
x: initialMoveX,
y: initialMoveY,
duration: 0.01,
beforeSetup: function(effect) {
effect.element.hide();
effect.element.makeClipping();
effect.element.makePositioned();
},
afterFinishInternal: function(effect) {
new Effect.Parallel(
[ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
new Effect.Scale(effect.element, 100, {
scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
], Object.extend({
beforeSetup: function(effect) {
effect.effects[0].element.setStyle({height: '0px'});
effect.effects[0].element.show();
},
afterFinishInternal: function(effect) {
effect.effects[0].element.undoClipping();
effect.effects[0].element.undoPositioned();
effect.effects[0].element.setStyle(oldStyle);
}
}, options)
)
}
});
}
Effect.Shrink = function(element) {
element = $(element);
var options = Object.extend({
direction: 'center',
moveTransition: Effect.Transitions.sinoidal,
scaleTransition: Effect.Transitions.sinoidal,
opacityTransition: Effect.Transitions.none
}, arguments[1] || {});
var oldStyle = {
top: element.style.top,
left: element.style.left,
height: element.style.height,
width: element.style.width,
opacity: element.getInlineOpacity() };
var dims = element.getDimensions();
var moveX, moveY;
switch (options.direction) {
case 'top-left':
moveX = moveY = 0;
break;
case 'top-right':
moveX = dims.width;
moveY = 0;
break;
case 'bottom-left':
moveX = 0;
moveY = dims.height;
break;
case 'bottom-right':
moveX = dims.width;
moveY = dims.height;
break;
case 'center':
moveX = dims.width / 2;
moveY = dims.height / 2;
break;
}
return new Effect.Parallel(
[ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
], Object.extend({
beforeStartInternal: function(effect) {
effect.effects[0].element.makePositioned();
effect.effects[0].element.makeClipping(); },
afterFinishInternal: function(effect) {
effect.effects[0].element.hide();
effect.effects[0].element.undoClipping();
effect.effects[0].element.undoPositioned();
effect.effects[0].element.setStyle(oldStyle); }
}, options)
);
}
Effect.Pulsate = function(element) {
element = $(element);
var options = arguments[1] || {};
var oldOpacity = element.getInlineOpacity();
var transition = options.transition || Effect.Transitions.sinoidal;
var reverser = function(pos){ return transition(1-Effect.Transitions.pulse(pos)) };
reverser.bind(transition);
return new Effect.Opacity(element,
Object.extend(Object.extend({ duration: 3.0, from: 0,
afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
}, options), {transition: reverser}));
}
Effect.Fold = function(element) {
element = $(element);
var oldStyle = {
top: element.style.top,
left: element.style.left,
width: element.style.width,
height: element.style.height };
Element.makeClipping(element);
return new Effect.Scale(element, 5, Object.extend({
scaleContent: false,
scaleX: false,
afterFinishInternal: function(effect) {
new Effect.Scale(element, 1, {
scaleContent: false,
scaleY: false,
afterFinishInternal: function(effect) {
effect.element.hide();
effect.element.undoClipping();
effect.element.setStyle(oldStyle);
} });
}}, arguments[1] || {}));
};
['setOpacity','getOpacity','getInlineOpacity','forceRerendering','setContentZoom',
'collectTextNodes','collectTextNodesIgnoreClass','childrenWithClassName'].each(
function(f) { Element.Methods[f] = Element[f]; }
);
Element.Methods.visualEffect = function(element, effect, options) {
s = effect.gsub(/_/, '-').camelize();
effect_class = s.charAt(0).toUpperCase() + s.substring(1);
new Effect[effect_class](element, options);
return $(element);
};
Element.addMethods();

File diff suppressed because it is too large Load Diff

View File

@ -1,47 +0,0 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
var Scriptaculous = {
Version: '1.6.1',
require: function(libraryName) {
// inserting via DOM fails in Safari 2.0, so brute force approach
document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
},
load: function() {
if((typeof Prototype=='undefined') ||
(typeof Element == 'undefined') ||
(typeof Element.Methods=='undefined') ||
parseFloat(Prototype.Version.split(".")[0] + "." +
Prototype.Version.split(".")[1]) < 1.5)
throw("script.aculo.us requires the Prototype JavaScript framework >= 1.5.0");
$A(document.getElementsByTagName("script")).findAll( function(s) {
return (s.src && s.src.match(/scriptaculous\.js(\?.*)?$/))
}).each( function(s) {
var path = s.src.replace(/scriptaculous\.js(\?.*)?$/,'');
var includes = s.src.match(/\?.*load=([a-z,]*)/);
(includes ? includes[1] : 'builder,effects,dragdrop,controls,slider').split(',').each(
function(include) { Scriptaculous.require(path+include+'.js') });
});
}
}
Scriptaculous.load();

View File

@ -1,283 +0,0 @@
// Copyright (c) 2005 Marty Haught, Thomas Fuchs
//
// See http://script.aculo.us for more info
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
if(!Control) var Control = {};
Control.Slider = Class.create();
// options:
// axis: 'vertical', or 'horizontal' (default)
//
// callbacks:
// onChange(value)
// onSlide(value)
Control.Slider.prototype = {
initialize: function(handle, track, options) {
var slider = this;
if(handle instanceof Array) {
this.handles = handle.collect( function(e) { return $(e) });
} else {
this.handles = [$(handle)];
}
this.track = $(track);
this.options = options || {};
this.axis = this.options.axis || 'horizontal';
this.increment = this.options.increment || 1;
this.step = parseInt(this.options.step || '1');
this.range = this.options.range || $R(0,1);
this.value = 0; // assure backwards compat
this.values = this.handles.map( function() { return 0 });
this.spans = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
this.options.startSpan = $(this.options.startSpan || null);
this.options.endSpan = $(this.options.endSpan || null);
this.restricted = this.options.restricted || false;
this.maximum = this.options.maximum || this.range.end;
this.minimum = this.options.minimum || this.range.start;
// Will be used to align the handle onto the track, if necessary
this.alignX = parseInt(this.options.alignX || '0');
this.alignY = parseInt(this.options.alignY || '0');
this.trackLength = this.maximumOffset() - this.minimumOffset();
this.handleLength = this.isVertical() ? this.handles[0].offsetHeight : this.handles[0].offsetWidth;
this.active = false;
this.dragging = false;
this.disabled = false;
if(this.options.disabled) this.setDisabled();
// Allowed values array
this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
if(this.allowedValues) {
this.minimum = this.allowedValues.min();
this.maximum = this.allowedValues.max();
}
this.eventMouseDown = this.startDrag.bindAsEventListener(this);
this.eventMouseUp = this.endDrag.bindAsEventListener(this);
this.eventMouseMove = this.update.bindAsEventListener(this);
// Initialize handles in reverse (make sure first handle is active)
this.handles.each( function(h,i) {
i = slider.handles.length-1-i;
slider.setValue(parseFloat(
(slider.options.sliderValue instanceof Array ?
slider.options.sliderValue[i] : slider.options.sliderValue) ||
slider.range.start), i);
Element.makePositioned(h); // fix IE
Event.observe(h, "mousedown", slider.eventMouseDown);
});
Event.observe(this.track, "mousedown", this.eventMouseDown);
Event.observe(document, "mouseup", this.eventMouseUp);
Event.observe(document, "mousemove", this.eventMouseMove);
this.initialized = true;
},
dispose: function() {
var slider = this;
Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
Event.stopObserving(document, "mouseup", this.eventMouseUp);
Event.stopObserving(document, "mousemove", this.eventMouseMove);
this.handles.each( function(h) {
Event.stopObserving(h, "mousedown", slider.eventMouseDown);
});
},
setDisabled: function(){
this.disabled = true;
},
setEnabled: function(){
this.disabled = false;
},
getNearestValue: function(value){
if(this.allowedValues){
if(value >= this.allowedValues.max()) return(this.allowedValues.max());
if(value <= this.allowedValues.min()) return(this.allowedValues.min());
var offset = Math.abs(this.allowedValues[0] - value);
var newValue = this.allowedValues[0];
this.allowedValues.each( function(v) {
var currentOffset = Math.abs(v - value);
if(currentOffset <= offset){
newValue = v;
offset = currentOffset;
}
});
return newValue;
}
if(value > this.range.end) return this.range.end;
if(value < this.range.start) return this.range.start;
return value;
},
setValue: function(sliderValue, handleIdx){
if(!this.active) {
this.activeHandle = this.handles[handleIdx];
this.activeHandleIdx = handleIdx;
this.updateStyles();
}
handleIdx = handleIdx || this.activeHandleIdx || 0;
if(this.initialized && this.restricted) {
if((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
sliderValue = this.values[handleIdx-1];
if((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
sliderValue = this.values[handleIdx+1];
}
sliderValue = this.getNearestValue(sliderValue);
this.values[handleIdx] = sliderValue;
this.value = this.values[0]; // assure backwards compat
this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
this.translateToPx(sliderValue);
this.drawSpans();
if(!this.dragging || !this.event) this.updateFinished();
},
setValueBy: function(delta, handleIdx) {
this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
handleIdx || this.activeHandleIdx || 0);
},
translateToPx: function(value) {
return Math.round(
((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
(value - this.range.start)) + "px";
},
translateToValue: function(offset) {
return ((offset/(this.trackLength-this.handleLength) *
(this.range.end-this.range.start)) + this.range.start);
},
getRange: function(range) {
var v = this.values.sortBy(Prototype.K);
range = range || 0;
return $R(v[range],v[range+1]);
},
minimumOffset: function(){
return(this.isVertical() ? this.alignY : this.alignX);
},
maximumOffset: function(){
return(this.isVertical() ?
this.track.offsetHeight - this.alignY : this.track.offsetWidth - this.alignX);
},
isVertical: function(){
return (this.axis == 'vertical');
},
drawSpans: function() {
var slider = this;
if(this.spans)
$R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
if(this.options.startSpan)
this.setSpan(this.options.startSpan,
$R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
if(this.options.endSpan)
this.setSpan(this.options.endSpan,
$R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
},
setSpan: function(span, range) {
if(this.isVertical()) {
span.style.top = this.translateToPx(range.start);
span.style.height = this.translateToPx(range.end - range.start + this.range.start);
} else {
span.style.left = this.translateToPx(range.start);
span.style.width = this.translateToPx(range.end - range.start + this.range.start);
}
},
updateStyles: function() {
this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
Element.addClassName(this.activeHandle, 'selected');
},
startDrag: function(event) {
if(Event.isLeftClick(event)) {
if(!this.disabled){
this.active = true;
var handle = Event.element(event);
var pointer = [Event.pointerX(event), Event.pointerY(event)];
if(handle==this.track) {
var offsets = Position.cumulativeOffset(this.track);
this.event = event;
this.setValue(this.translateToValue(
(this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
));
var offsets = Position.cumulativeOffset(this.activeHandle);
this.offsetX = (pointer[0] - offsets[0]);
this.offsetY = (pointer[1] - offsets[1]);
} else {
// find the handle (prevents issues with Safari)
while((this.handles.indexOf(handle) == -1) && handle.parentNode)
handle = handle.parentNode;
this.activeHandle = handle;
this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
this.updateStyles();
var offsets = Position.cumulativeOffset(this.activeHandle);
this.offsetX = (pointer[0] - offsets[0]);
this.offsetY = (pointer[1] - offsets[1]);
}
}
Event.stop(event);
}
},
update: function(event) {
if(this.active) {
if(!this.dragging) this.dragging = true;
this.draw(event);
// fix AppleWebKit rendering
if(navigator.appVersion.indexOf('AppleWebKit')>0) window.scrollBy(0,0);
Event.stop(event);
}
},
draw: function(event) {
var pointer = [Event.pointerX(event), Event.pointerY(event)];
var offsets = Position.cumulativeOffset(this.track);
pointer[0] -= this.offsetX + offsets[0];
pointer[1] -= this.offsetY + offsets[1];
this.event = event;
this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
if(this.initialized && this.options.onSlide)
this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
},
endDrag: function(event) {
if(this.active && this.dragging) {
this.finishDrag(event, true);
Event.stop(event);
}
this.active = false;
this.dragging = false;
},
finishDrag: function(event, success) {
this.active = false;
this.dragging = false;
this.updateFinished();
},
updateFinished: function() {
if(this.initialized && this.options.onChange)
this.options.onChange(this.values.length>1 ? this.values : this.value, this);
this.event = null;
}
}

View File

@ -1,383 +0,0 @@
// Copyright (c) 2005 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
// (c) 2005 Jon Tirsen (http://www.tirsen.com)
// (c) 2005 Michael Schuerig (http://www.schuerig.de/michael/)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// experimental, Firefox-only
Event.simulateMouse = function(element, eventName) {
var options = Object.extend({
pointerX: 0,
pointerY: 0,
buttons: 0
}, arguments[2] || {});
var oEvent = document.createEvent("MouseEvents");
oEvent.initMouseEvent(eventName, true, true, document.defaultView,
options.buttons, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
false, false, false, false, 0, $(element));
if(this.mark) Element.remove(this.mark);
this.mark = document.createElement('div');
this.mark.appendChild(document.createTextNode(" "));
document.body.appendChild(this.mark);
this.mark.style.position = 'absolute';
this.mark.style.top = options.pointerY + "px";
this.mark.style.left = options.pointerX + "px";
this.mark.style.width = "5px";
this.mark.style.height = "5px;";
this.mark.style.borderTop = "1px solid red;"
this.mark.style.borderLeft = "1px solid red;"
if(this.step)
alert('['+new Date().getTime().toString()+'] '+eventName+'/'+Test.Unit.inspect(options));
$(element).dispatchEvent(oEvent);
};
// Note: Due to a fix in Firefox 1.0.5/6 that probably fixed "too much", this doesn't work in 1.0.6 or DP2.
// You need to downgrade to 1.0.4 for now to get this working
// See https://bugzilla.mozilla.org/show_bug.cgi?id=289940 for the fix that fixed too much
Event.simulateKey = function(element, eventName) {
var options = Object.extend({
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
keyCode: 0,
charCode: 0
}, arguments[2] || {});
var oEvent = document.createEvent("KeyEvents");
oEvent.initKeyEvent(eventName, true, true, window,
options.ctrlKey, options.altKey, options.shiftKey, options.metaKey,
options.keyCode, options.charCode );
$(element).dispatchEvent(oEvent);
};
Event.simulateKeys = function(element, command) {
for(var i=0; i<command.length; i++) {
Event.simulateKey(element,'keypress',{charCode:command.charCodeAt(i)});
}
};
var Test = {}
Test.Unit = {};
// security exception workaround
Test.Unit.inspect = Object.inspect;
Test.Unit.Logger = Class.create();
Test.Unit.Logger.prototype = {
initialize: function(log) {
this.log = $(log);
if (this.log) {
this._createLogTable();
}
},
start: function(testName) {
if (!this.log) return;
this.testName = testName;
this.lastLogLine = document.createElement('tr');
this.statusCell = document.createElement('td');
this.nameCell = document.createElement('td');
this.nameCell.appendChild(document.createTextNode(testName));
this.messageCell = document.createElement('td');
this.lastLogLine.appendChild(this.statusCell);
this.lastLogLine.appendChild(this.nameCell);
this.lastLogLine.appendChild(this.messageCell);
this.loglines.appendChild(this.lastLogLine);
},
finish: function(status, summary) {
if (!this.log) return;
this.lastLogLine.className = status;
this.statusCell.innerHTML = status;
this.messageCell.innerHTML = this._toHTML(summary);
},
message: function(message) {
if (!this.log) return;
this.messageCell.innerHTML = this._toHTML(message);
},
summary: function(summary) {
if (!this.log) return;
this.logsummary.innerHTML = this._toHTML(summary);
},
_createLogTable: function() {
this.log.innerHTML =
'<div id="logsummary"></div>' +
'<table id="logtable">' +
'<thead><tr><th>Status</th><th>Test</th><th>Message</th></tr></thead>' +
'<tbody id="loglines"></tbody>' +
'</table>';
this.logsummary = $('logsummary')
this.loglines = $('loglines');
},
_toHTML: function(txt) {
return txt.escapeHTML().replace(/\n/g,"<br/>");
}
}
Test.Unit.Runner = Class.create();
Test.Unit.Runner.prototype = {
initialize: function(testcases) {
this.options = Object.extend({
testLog: 'testlog'
}, arguments[1] || {});
this.options.resultsURL = this.parseResultsURLQueryParameter();
if (this.options.testLog) {
this.options.testLog = $(this.options.testLog) || null;
}
if(this.options.tests) {
this.tests = [];
for(var i = 0; i < this.options.tests.length; i++) {
if(/^test/.test(this.options.tests[i])) {
this.tests.push(new Test.Unit.Testcase(this.options.tests[i], testcases[this.options.tests[i]], testcases["setup"], testcases["teardown"]));
}
}
} else {
if (this.options.test) {
this.tests = [new Test.Unit.Testcase(this.options.test, testcases[this.options.test], testcases["setup"], testcases["teardown"])];
} else {
this.tests = [];
for(var testcase in testcases) {
if(/^test/.test(testcase)) {
this.tests.push(new Test.Unit.Testcase(testcase, testcases[testcase], testcases["setup"], testcases["teardown"]));
}
}
}
}
this.currentTest = 0;
this.logger = new Test.Unit.Logger(this.options.testLog);
setTimeout(this.runTests.bind(this), 1000);
},
parseResultsURLQueryParameter: function() {
return window.location.search.parseQuery()["resultsURL"];
},
// Returns:
// "ERROR" if there was an error,
// "FAILURE" if there was a failure, or
// "SUCCESS" if there was neither
getResult: function() {
var hasFailure = false;
for(var i=0;i<this.tests.length;i++) {
if (this.tests[i].errors > 0) {
return "ERROR";
}
if (this.tests[i].failures > 0) {
hasFailure = true;
}
}
if (hasFailure) {
return "FAILURE";
} else {
return "SUCCESS";
}
},
postResults: function() {
if (this.options.resultsURL) {
new Ajax.Request(this.options.resultsURL,
{ method: 'get', parameters: 'result=' + this.getResult(), asynchronous: false });
}
},
runTests: function() {
var test = this.tests[this.currentTest];
if (!test) {
// finished!
this.postResults();
this.logger.summary(this.summary());
return;
}
if(!test.isWaiting) {
this.logger.start(test.name);
}
test.run();
if(test.isWaiting) {
this.logger.message("Waiting for " + test.timeToWait + "ms");
setTimeout(this.runTests.bind(this), test.timeToWait || 1000);
} else {
this.logger.finish(test.status(), test.summary());
this.currentTest++;
// tail recursive, hopefully the browser will skip the stackframe
this.runTests();
}
},
summary: function() {
var assertions = 0;
var failures = 0;
var errors = 0;
var messages = [];
for(var i=0;i<this.tests.length;i++) {
assertions += this.tests[i].assertions;
failures += this.tests[i].failures;
errors += this.tests[i].errors;
}
return (
this.tests.length + " tests, " +
assertions + " assertions, " +
failures + " failures, " +
errors + " errors");
}
}
Test.Unit.Assertions = Class.create();
Test.Unit.Assertions.prototype = {
initialize: function() {
this.assertions = 0;
this.failures = 0;
this.errors = 0;
this.messages = [];
},
summary: function() {
return (
this.assertions + " assertions, " +
this.failures + " failures, " +
this.errors + " errors" + "\n" +
this.messages.join("\n"));
},
pass: function() {
this.assertions++;
},
fail: function(message) {
this.failures++;
this.messages.push("Failure: " + message);
},
info: function(message) {
this.messages.push("Info: " + message);
},
error: function(error) {
this.errors++;
this.messages.push(error.name + ": "+ error.message + "(" + Test.Unit.inspect(error) +")");
},
status: function() {
if (this.failures > 0) return 'failed';
if (this.errors > 0) return 'error';
return 'passed';
},
assert: function(expression) {
var message = arguments[1] || 'assert: got "' + Test.Unit.inspect(expression) + '"';
try { expression ? this.pass() :
this.fail(message); }
catch(e) { this.error(e); }
},
assertEqual: function(expected, actual) {
var message = arguments[2] || "assertEqual";
try { (expected == actual) ? this.pass() :
this.fail(message + ': expected "' + Test.Unit.inspect(expected) +
'", actual "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertEnumEqual: function(expected, actual) {
var message = arguments[2] || "assertEnumEqual";
try { $A(expected).length == $A(actual).length &&
expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ?
this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) +
', actual ' + Test.Unit.inspect(actual)); }
catch(e) { this.error(e); }
},
assertNotEqual: function(expected, actual) {
var message = arguments[2] || "assertNotEqual";
try { (expected != actual) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(actual) + '"'); }
catch(e) { this.error(e); }
},
assertNull: function(obj) {
var message = arguments[1] || 'assertNull'
try { (obj==null) ? this.pass() :
this.fail(message + ': got "' + Test.Unit.inspect(obj) + '"'); }
catch(e) { this.error(e); }
},
assertHidden: function(element) {
var message = arguments[1] || 'assertHidden';
this.assertEqual("none", element.style.display, message);
},
assertNotNull: function(object) {
var message = arguments[1] || 'assertNotNull';
this.assert(object != null, message);
},
assertInstanceOf: function(expected, actual) {
var message = arguments[2] || 'assertInstanceOf';
try {
(actual instanceof expected) ? this.pass() :
this.fail(message + ": object was not an instance of the expected type"); }
catch(e) { this.error(e); }
},
assertNotInstanceOf: function(expected, actual) {
var message = arguments[2] || 'assertNotInstanceOf';
try {
!(actual instanceof expected) ? this.pass() :
this.fail(message + ": object was an instance of the not expected type"); }
catch(e) { this.error(e); }
},
_isVisible: function(element) {
element = $(element);
if(!element.parentNode) return true;
this.assertNotNull(element);
if(element.style && Element.getStyle(element, 'display') == 'none')
return false;
return this._isVisible(element.parentNode);
},
assertNotVisible: function(element) {
this.assert(!this._isVisible(element), Test.Unit.inspect(element) + " was not hidden and didn't have a hidden parent either. " + ("" || arguments[1]));
},
assertVisible: function(element) {
this.assert(this._isVisible(element), Test.Unit.inspect(element) + " was not visible. " + ("" || arguments[1]));
},
benchmark: function(operation, iterations) {
var startAt = new Date();
(iterations || 1).times(operation);
var timeTaken = ((new Date())-startAt);
this.info((arguments[2] || 'Operation') + ' finished ' +
iterations + ' iterations in ' + (timeTaken/1000)+'s' );
return timeTaken;
}
}
Test.Unit.Testcase = Class.create();
Object.extend(Object.extend(Test.Unit.Testcase.prototype, Test.Unit.Assertions.prototype), {
initialize: function(name, test, setup, teardown) {
Test.Unit.Assertions.prototype.initialize.bind(this)();
this.name = name;
this.test = test || function() {};
this.setup = setup || function() {};
this.teardown = teardown || function() {};
this.isWaiting = false;
this.timeToWait = 1000;
},
wait: function(time, nextPart) {
this.isWaiting = true;
this.test = nextPart;
this.timeToWait = time;
},
run: function() {
try {
try {
if (!this.isWaiting) this.setup.bind(this)();
this.isWaiting = false;
this.test.bind(this)();
} finally {
if(!this.isWaiting) {
this.teardown.bind(this)();
}
}
}
catch(e) { this.error(e); }
}
});

View File

@ -1,12 +0,0 @@
Copyright (c) 2006, David Spurr (www.defusion.org.uk)
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of the David Spurr nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
http://www.opensource.org/licenses/bsd-license.php

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@ -1,106 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Basic cropper test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
// setup the callback function
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// basic example
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testImage',
{
onEndCrop: onEndCrop
}
)
}
);
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
html, body {
margin: 0;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
</style>
</head>
<body>
<h2>Basic cropper test</h2>
<p>
Some test content before the image
</p>
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</body>
</html>

View File

@ -1,162 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>CSS - Absolute positioned (and draggable) test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop,effects" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
// setup the callback function
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// Absolute positioned example
Event.observe(
window,
'load',
function() {
new Cropper.Img( 'testAbsImage', { onEndCrop: onEndCrop } );
new Draggable( 'test-abs' );
}
);
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
html, body {
margin: 0;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
#test-abs {
width: 510px;
position: absolute;
top: 50px;
left: 25%;
background-color: #dee;
border: 3px solid #ccc;
z-index: 10;
}
</style>
</head>
<body>
<h2>CSS - Absolute positioned (and draggable) test</h2>
<p>
Some test content before the image
</p>
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Pellentesque consequat risus cursus ipsum. Etiam libero. Integer vel mauris. Donec vulputate. In ut augue vitae nibh lobortis tempor. Aliquam hendrerit quam. Phasellus sed orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut sed urna. Donec nunc urna, porttitor a, feugiat pellentesque, varius id, justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla facilisi. Sed sollicitudin. Integer enim. Aenean sollicitudin.
</p>
<p>
Integer lorem turpis, dapibus sed, vulputate nec, volutpat a, sem. Sed malesuada laoreet lorem. Duis mauris ipsum, fringilla nec, tristique vel, imperdiet vel, neque. Nulla vel purus. Fusce non lectus. Mauris pulvinar. Curabitur eget eros. Nunc ultrices, risus vitae adipiscing scelerisque, quam mi auctor lacus, non pellentesque augue sapien a magna. Etiam rutrum posuere tortor. Mauris rhoncus sagittis dolor. Donec sed quam. Vivamus vel diam id massa adipiscing bibendum. Suspendisse potenti. Integer arcu est, adipiscing sit amet, convallis eu, sollicitudin tincidunt, quam.
</p>
<p>
Etiam ligula lorem, imperdiet ac, luctus eget, ultrices at, odio. Vivamus malesuada, justo eu adipiscing semper, nisi dui tempus magna, quis ultrices nunc tellus id massa. Nullam lobortis auctor sapien. Quisque non nulla. Donec lobortis pellentesque nisl. Sed lacus sapien, viverra vitae, blandit ut, fermentum quis, leo. Morbi augue turpis, hendrerit non, feugiat vel, laoreet sed, est. Nunc velit. Praesent lobortis. Integer enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Curabitur faucibus lacus ac ante. Donec odio odio, tincidunt a, egestas nec, scelerisque nec, dui. Cras sollicitudin. Donec lacus enim, mollis sit amet, interdum quis, euismod et, nulla. Nunc sit amet dui eu magna dapibus mollis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla facilisi.
</p>
<p>
In hac habitasse platea dictumst. Nunc neque urna, dapibus ut, tristique ut, bibendum ac, felis. Donec dictum est ut dolor. Etiam accumsan, velit sit amet blandit vestibulum, turpis quam hendrerit risus, vel interdum eros orci in nunc. Curabitur tellus sapien, rutrum ac, euismod ac, malesuada nec, pede. Proin sit amet ipsum. Praesent quam nisl, adipiscing nec, tristique eget, fermentum sed, est. Praesent ac est sit amet orci facilisis placerat. Sed consequat, est sit amet consectetuer viverra, risus urna porttitor tellus, ut convallis nibh libero in lectus. Pellentesque molestie, erat non vehicula pretium, turpis nisi eleifend eros, sed scelerisque tortor odio non tellus. Nunc leo tellus, faucibus vitae, placerat a, accumsan vel, arcu. In et orci. Ut tristique euismod nibh. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Sed nulla nunc, placerat vitae, pellentesque non, interdum non, sapien. Quisque faucibus, eros sed venenatis sagittis, leo risus rhoncus risus, in pretium sem purus a lacus. Aliquam aliquam leo et diam.
</p>
<p>
Nulla sagittis diam. Phasellus vitae enim tristique libero molestie tristique. Nam mauris sem, elementum nec, cursus in, fringilla ac, neque. Nunc metus nisi, dictum vel, vulputate quis, porttitor bibendum, tortor. Vestibulum vehicula. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla ac magna sed purus ultricies euismod. Aliquam dictum. Sed mauris. Suspendisse justo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi purus lorem, auctor non, porta ac, vehicula vel, orci. Morbi pharetra massa nec leo. Maecenas et mauris. Aliquam porttitor tincidunt nulla. Vestibulum pede.
</p>
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Pellentesque consequat risus cursus ipsum. Etiam libero. Integer vel mauris. Donec vulputate. In ut augue vitae nibh lobortis tempor. Aliquam hendrerit quam. Phasellus sed orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut sed urna. Donec nunc urna, porttitor a, feugiat pellentesque, varius id, justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla facilisi. Sed sollicitudin. Integer enim. Aenean sollicitudin.
</p>
<p>
Integer lorem turpis, dapibus sed, vulputate nec, volutpat a, sem. Sed malesuada laoreet lorem. Duis mauris ipsum, fringilla nec, tristique vel, imperdiet vel, neque. Nulla vel purus. Fusce non lectus. Mauris pulvinar. Curabitur eget eros. Nunc ultrices, risus vitae adipiscing scelerisque, quam mi auctor lacus, non pellentesque augue sapien a magna. Etiam rutrum posuere tortor. Mauris rhoncus sagittis dolor. Donec sed quam. Vivamus vel diam id massa adipiscing bibendum. Suspendisse potenti. Integer arcu est, adipiscing sit amet, convallis eu, sollicitudin tincidunt, quam.
</p>
<p>
Etiam ligula lorem, imperdiet ac, luctus eget, ultrices at, odio. Vivamus malesuada, justo eu adipiscing semper, nisi dui tempus magna, quis ultrices nunc tellus id massa. Nullam lobortis auctor sapien. Quisque non nulla. Donec lobortis pellentesque nisl. Sed lacus sapien, viverra vitae, blandit ut, fermentum quis, leo. Morbi augue turpis, hendrerit non, feugiat vel, laoreet sed, est. Nunc velit. Praesent lobortis. Integer enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Curabitur faucibus lacus ac ante. Donec odio odio, tincidunt a, egestas nec, scelerisque nec, dui. Cras sollicitudin. Donec lacus enim, mollis sit amet, interdum quis, euismod et, nulla. Nunc sit amet dui eu magna dapibus mollis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla facilisi.
</p>
<p>
In hac habitasse platea dictumst. Nunc neque urna, dapibus ut, tristique ut, bibendum ac, felis. Donec dictum est ut dolor. Etiam accumsan, velit sit amet blandit vestibulum, turpis quam hendrerit risus, vel interdum eros orci in nunc. Curabitur tellus sapien, rutrum ac, euismod ac, malesuada nec, pede. Proin sit amet ipsum. Praesent quam nisl, adipiscing nec, tristique eget, fermentum sed, est. Praesent ac est sit amet orci facilisis placerat. Sed consequat, est sit amet consectetuer viverra, risus urna porttitor tellus, ut convallis nibh libero in lectus. Pellentesque molestie, erat non vehicula pretium, turpis nisi eleifend eros, sed scelerisque tortor odio non tellus. Nunc leo tellus, faucibus vitae, placerat a, accumsan vel, arcu. In et orci. Ut tristique euismod nibh. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Sed nulla nunc, placerat vitae, pellentesque non, interdum non, sapien. Quisque faucibus, eros sed venenatis sagittis, leo risus rhoncus risus, in pretium sem purus a lacus. Aliquam aliquam leo et diam.
</p>
<p>
Nulla sagittis diam. Phasellus vitae enim tristique libero molestie tristique. Nam mauris sem, elementum nec, cursus in, fringilla ac, neque. Nunc metus nisi, dictum vel, vulputate quis, porttitor bibendum, tortor. Vestibulum vehicula. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla ac magna sed purus ultricies euismod. Aliquam dictum. Sed mauris. Suspendisse justo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi purus lorem, auctor non, porta ac, vehicula vel, orci. Morbi pharetra massa nec leo. Maecenas et mauris. Aliquam porttitor tincidunt nulla. Vestibulum pede.
</p>
<p>
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Pellentesque consequat risus cursus ipsum. Etiam libero. Integer vel mauris. Donec vulputate. In ut augue vitae nibh lobortis tempor. Aliquam hendrerit quam. Phasellus sed orci. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Ut sed urna. Donec nunc urna, porttitor a, feugiat pellentesque, varius id, justo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Nulla facilisi. Sed sollicitudin. Integer enim. Aenean sollicitudin.
</p>
<p>
Integer lorem turpis, dapibus sed, vulputate nec, volutpat a, sem. Sed malesuada laoreet lorem. Duis mauris ipsum, fringilla nec, tristique vel, imperdiet vel, neque. Nulla vel purus. Fusce non lectus. Mauris pulvinar. Curabitur eget eros. Nunc ultrices, risus vitae adipiscing scelerisque, quam mi auctor lacus, non pellentesque augue sapien a magna. Etiam rutrum posuere tortor. Mauris rhoncus sagittis dolor. Donec sed quam. Vivamus vel diam id massa adipiscing bibendum. Suspendisse potenti. Integer arcu est, adipiscing sit amet, convallis eu, sollicitudin tincidunt, quam.
</p>
<p>
Etiam ligula lorem, imperdiet ac, luctus eget, ultrices at, odio. Vivamus malesuada, justo eu adipiscing semper, nisi dui tempus magna, quis ultrices nunc tellus id massa. Nullam lobortis auctor sapien. Quisque non nulla. Donec lobortis pellentesque nisl. Sed lacus sapien, viverra vitae, blandit ut, fermentum quis, leo. Morbi augue turpis, hendrerit non, feugiat vel, laoreet sed, est. Nunc velit. Praesent lobortis. Integer enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Curabitur faucibus lacus ac ante. Donec odio odio, tincidunt a, egestas nec, scelerisque nec, dui. Cras sollicitudin. Donec lacus enim, mollis sit amet, interdum quis, euismod et, nulla. Nunc sit amet dui eu magna dapibus mollis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nulla facilisi.
</p>
<p>
In hac habitasse platea dictumst. Nunc neque urna, dapibus ut, tristique ut, bibendum ac, felis. Donec dictum est ut dolor. Etiam accumsan, velit sit amet blandit vestibulum, turpis quam hendrerit risus, vel interdum eros orci in nunc. Curabitur tellus sapien, rutrum ac, euismod ac, malesuada nec, pede. Proin sit amet ipsum. Praesent quam nisl, adipiscing nec, tristique eget, fermentum sed, est. Praesent ac est sit amet orci facilisis placerat. Sed consequat, est sit amet consectetuer viverra, risus urna porttitor tellus, ut convallis nibh libero in lectus. Pellentesque molestie, erat non vehicula pretium, turpis nisi eleifend eros, sed scelerisque tortor odio non tellus. Nunc leo tellus, faucibus vitae, placerat a, accumsan vel, arcu. In et orci. Ut tristique euismod nibh. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Sed nulla nunc, placerat vitae, pellentesque non, interdum non, sapien. Quisque faucibus, eros sed venenatis sagittis, leo risus rhoncus risus, in pretium sem purus a lacus. Aliquam aliquam leo et diam.
</p>
<p>
Nulla sagittis diam. Phasellus vitae enim tristique libero molestie tristique. Nam mauris sem, elementum nec, cursus in, fringilla ac, neque. Nunc metus nisi, dictum vel, vulputate quis, porttitor bibendum, tortor. Vestibulum vehicula. Nulla facilisi. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla ac magna sed purus ultricies euismod. Aliquam dictum. Sed mauris. Suspendisse justo. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi purus lorem, auctor non, porta ac, vehicula vel, orci. Morbi pharetra massa nec leo. Maecenas et mauris. Aliquam porttitor tincidunt nulla. Vestibulum pede.
</p>
<div id="test-abs">
<h2>Absolute test</h2>
<div id="testAbsWrap">
<img src="castle.jpg" alt="test image" id="testAbsImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</div>
</body>
</html>

View File

@ -1,124 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>CSS - Float test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
// setup the callback function
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// float example
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testFloatImage',
{
onEndCrop: function( coords, dimensions ) {
$( 'floatX1' ).value = coords.x1;
$( 'floatY1' ).value = coords.y1;
$( 'floatX2' ).value = coords.x2;
$( 'floatY2' ).value = coords.y2;
$( 'floatWidth' ).value = dimensions.width;
$( 'floatHeight' ).value = dimensions.height;
}
}
);
}
);
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
html, body {
margin: 0;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
#test-float {
width: 600px;
float: right;
background-color: #eee;
border: 3px solid #000;
margin: 10px;
padding: 5px;
}
</style>
</head>
<body>
<h2>Test page with floating wrapper</h2>
<p>
Some test content before the image
</p>
<div id="test-float">
<h2>Float test</h2>
<div id="testFloatWrap">
<img src="castle.jpg" alt="test image" id="testFloatImage" width="500" height="333" />
</div>
<p>
<label for="floatX1">x1:</label>
<input type="text" name="floatX1" id="floatX1" />
</p>
<p>
<label for="floatY1">y1:</label>
<input type="text" name="floatY1" id="floatY1" />
</p>
<p>
<label for="floatX2">x2:</label>
<input type="text" name="floatX2" id="floatX2" />
</p>
<p>
<label for="floatY2">y2:</label>
<input type="text" name="floatY2" id="floatY2" />
</p>
<p>
<label for="floatWidth">width:</label>
<input type="text" name="floatWidth" id="floatWidth" />
</p>
<p>
<label for="floatHeight">height</label>
<input type="text" name="floatHeight" id="floatHeight" />
</p>
</div>
</body>
</html>

View File

@ -1,116 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>CSS - Relative test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
// setup the callback function
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// relative example
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testImage',
{
onEndCrop: onEndCrop
}
)
}
);
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
html, body {
margin: 0;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
#test-relative {
background-color: #ccc;
border: 3px solid #ddd;
position: relative;
top: 25px;
left: 25px;
}
</style>
</head>
<body>
<h2>Test page with relatively positioned wrapper</h2>
<p>
Some test content before the image
</p>
<div id="test-relative">
<h2>Relative test</h2>
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</div>
</body>
</html>

View File

@ -1,108 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Loading &amp; displaying co-ordinates of crop area on attachment test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
// setup the callback function
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// basic example
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testImage',
{
onEndCrop: onEndCrop,
displayOnInit: true,
onloadCoords: { x1: 10, y1: 10, x2: 250, y2: 100 }
}
)
}
);
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
html, body {
margin: 0;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
</style>
</head>
<body>
<h2>Loading &amp; displaying co-ordinates of crop area on attachment test</h2>
<p>
Some test content before the image
</p>
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</body>
</html>

View File

@ -1,109 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Loading &amp; displaying co-ordinates (with ratio) of crop area on attachment test<</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
// setup the callback function
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// basic example
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testImage',
{
onEndCrop: onEndCrop,
displayOnInit: true,
onloadCoords: { x1: 10, y1: 10, x2: 210, y2: 110 },
ratioDim: { x: 200, y: 100 }
}
)
}
);
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
html, body {
margin: 0;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
</style>
</head>
<body>
<h2>Loading &amp; displaying co-ordinates (with ratio) of crop area on attachment test</h2>
<p>
Some test content before the image
</p>
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</body>
</html>

View File

@ -1,225 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Different dimensions test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
/*
// example with minimum dimensions
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testImage',
{
minWidth: 200,
minHeight: 120,
maxWidth: 200,
//maxHeight: 120,
displayOnInit: true,
onEndCrop: onEndCrop
}
)
}
);
*/
Event.observe( window, 'load',
function() {
Event.observe( 'dimensionsForm', 'submit', CropManager.attachCropper.bindAsEventListener( CropManager ) );
CropManager.attachCropper();
}
);
/**
* A little manager that allows us to reset the options dynamically
*/
var CropManager = {
/**
* Holds the current Cropper.Img object
* @var obj
*/
curCrop: null,
/**
* Gets a min/max parameter from the form
*
* @access private
* @param string Form element ID
* @return int
*/
getParam: function( name ) {
var val = $F( name );
console.log( name + ' :: ' + val );
return parseInt( val );
},
/**
* Attaches/resets the image cropper
*
* @access private
* @param obj Event object
* @return void
*/
attachCropper: function( e ) {
if( this.curCrop == null ) {
this.curCrop = new Cropper.Img(
'testImage',
{
minWidth: this.getParam( 'minWidth' ),
minHeight: this.getParam( 'minHeight' ),
maxWidth: this.getParam( 'maxWidth' ),
maxHeight: this.getParam( 'maxHeight' ),
onEndCrop: onEndCrop
}
);
} else {
this.removeCropper();
this.curCrop.initialize(
'testImage',
{
minWidth: this.getParam( 'minWidth' ),
minHeight: this.getParam( 'minHeight' ),
maxWidth: this.getParam( 'maxWidth' ),
maxHeight: this.getParam( 'maxHeight' ),
onEndCrop: onEndCrop
}
);
}
if( e != null ) Event.stop( e );
},
/**
* Removes the cropper
*
* @access public
* @return void
*/
removeCropper: function() {
if( this.curCrop != null ) {
this.curCrop.remove();
}
},
/**
* Resets the cropper, either re-setting or re-applying
*
* @access public
* @return void
*/
resetCropper: function() {
this.attachCropper();
}
};
/*
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
// Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
*/
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
#dimensionsForm {
float: right;
width: 350px;
}
</style>
</head>
<body>
<h2>Multiple dimensions tests</h2>
<p>
Test of applying different dimension restrictions to the cropper
</p>
<form action="#" id="dimensionsForm">
<fieldset>
Set the cropper with the following dimension restrictions:
<p>
<label for="minWidth">Min Width</label>
<input type="text" size="10" maxlength="3" value="200" id="minWidth" name="minWidth" />
</p>
<p>
<label for="maxWidth">Max Width</label>
<input type="text" size="10" maxlength="3" value="200" id="maxWidth" name="maxWidth" />
</p>
<p>
<label for="minHeight">Min Height</label>
<input type="text" size="10" maxlength="3" value="120" id="minHeight" name="minHeight" />
</p>
<p>
<label for="maxHeight">Max Height</label>
<input type="text" size="10" maxlength="3" value="120" id="maxHeight" name="maxHeight" />
</p>
<input type="submit" value="Set Cropper" />
</fieldset>
</form>
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</body>
</html>

View File

@ -1,203 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Dynamic image test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
/**
* A little manager that allows us to swap the image dynamically
*
*/
var CropImageManager = {
/**
* Holds the current Cropper.Img object
* @var obj
*/
curCrop: null,
/**
* Initialises the cropImageManager
*
* @access public
* @return void
*/
init: function() {
this.attachCropper();
},
/**
* Handles the changing of the select to change the image, the option value
* is a pipe seperated list of imgSrc|width|height
*
* @access public
* @param obj event
* @return void
*/
onChange: function( e ) {
var vals = $F( Event.element( e ) ).split('|');
this.setImage( vals[0], vals[1], vals[2] );
},
/**
* Sets the image within the element & attaches/resets the image cropper
*
* @access private
* @param string Source path of new image
* @param int Width of new image in pixels
* @param int Height of new image in pixels
* @return void
*/
setImage: function( imgSrc, w, h ) {
$( 'testImage' ).src = imgSrc;
$( 'testImage' ).width = w;
$( 'testImage' ).height = h;
this.attachCropper();
},
/**
* Attaches/resets the image cropper
*
* @access private
* @return void
*/
attachCropper: function() {
if( this.curCrop == null ) this.curCrop = new Cropper.Img( 'testImage', { onEndCrop: onEndCrop } );
else this.curCrop.reset();
},
/**
* Removes the cropper
*
* @access public
* @return void
*/
removeCropper: function() {
if( this.curCrop != null ) {
this.curCrop.remove();
}
},
/**
* Resets the cropper, either re-setting or re-applying
*
* @access public
* @return void
*/
resetCropper: function() {
this.attachCropper();
}
};
// setup the callback function
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// basic example
Event.observe(
window,
'load',
function() {
CropImageManager.init();
Event.observe( $('removeCropper'), 'click', CropImageManager.removeCropper.bindAsEventListener( CropImageManager ), false );
Event.observe( $('resetCropper'), 'click', CropImageManager.resetCropper.bindAsEventListener( CropImageManager ), false );
Event.observe( $('imageChoice'), 'change', CropImageManager.onChange.bindAsEventListener( CropImageManager ), false );
}
);
/*
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
*/
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
html, body {
margin: 0;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
</style>
</head>
<body>
<h2>Dynamic image test</h2>
<p>
Test of dynamically changing images or removing & re-applying the cropper
</p>
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="imageChoice">image:</label>
<select name="imageChoice" id="imageChoice">
<option value="castle.jpg|500|333">Castle</option>
<option value="poppy.jpg|311|466">Flower</option>
</select>
</p>
<p>
<input type="button" id="removeCropper" value="Remove Cropper" />
<input type="button" id="resetCropper" value="Reset Cropper" />
</p>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</body>
</html>

View File

@ -1,104 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Fixed ratio test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// with a supplied ratio
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testImage',
{
ratioDim: { x: 220, y: 124 },
displayOnInit: true,
onEndCrop: onEndCrop
}
)
}
);
/*
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
// Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
*/
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
</style>
</head>
<body>
<h2>Fixed ratio test</h2>
<p>
Test of applying a fixed ratio to the cropper
</p>
<br />
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</body>
</html>

View File

@ -1,105 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Min dimensions test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// example with minimum dimensions
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testImage',
{
minWidth: 200,
minHeight: 120,
displayOnInit: true,
onEndCrop: onEndCrop
}
)
}
);
/*
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
// Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
*/
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
</style>
</head>
<body>
<h2>Minimum (both axes ) dimension test</h2>
<p>
Test of applying a minimum dimension to both axes to the cropper
</p>
<br />
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</body>
</html>

View File

@ -1,105 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title>Min (single axis) dimensions test</title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// example with minimum dimensions
Event.observe(
window,
'load',
function() {
new Cropper.Img(
'testImage',
{
minWidth: 200,
displayOnInit: true,
onEndCrop: onEndCrop
}
)
}
);
/*
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
// Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
*/
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
#testWrap {
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
</style>
</head>
<body>
<h2>Minimum (single axis) dimension test</h2>
<p>
Test of applying a minimum dimension to only one axis (width in this case) to the cropper
</p>
<br />
<br /><br />
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</body>
</html>

View File

@ -1,117 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title></title>
<script src="../lib/prototype.js" type="text/javascript"></script>
<script src="../lib/scriptaculous.js?load=builder,dragdrop" type="text/javascript"></script>
<script src="../cropper.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
function onEndCrop( coords, dimensions ) {
$( 'x1' ).value = coords.x1;
$( 'y1' ).value = coords.y1;
$( 'x2' ).value = coords.x2;
$( 'y2' ).value = coords.y2;
$( 'width' ).value = dimensions.width;
$( 'height' ).value = dimensions.height;
}
// example with a preview of crop results, must have minimumm dimensions
Event.observe(
window,
'load',
function() {
new Cropper.ImgWithPreview(
'testImage',
{
minWidth: 200,
minHeight: 120,
ratioDim: { x: 200, y: 120 },
displayOnInit: true,
onEndCrop: onEndCrop,
previewWrap: 'previewArea'
}
)
}
);
/*
if( typeof(dump) != 'function' ) {
Debug.init(true, '/');
function dump( msg ) {
// Debug.raise( msg );
};
} else dump( '---------------------------------------\n' );
*/
</script>
<link rel="stylesheet" type="text/css" href="debug.css" media="all" />
<style type="text/css">
label {
clear: left;
margin-left: 50px;
float: left;
width: 5em;
}
#testWrap {
width: 500px;
float: left;
margin: 20px 0 0 50px; /* Just while testing, to make sure we return the correct positions for the image & not the window */
}
#previewArea {
margin: 20px; 0 0 20px;
float: left;
}
#results {
clear: both;
}
</style>
</head>
<body>
<br /><br />
<div id="testWrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
</div>
<div id="previewArea"></div>
<div id="results">
<p>
<label for="x1">x1:</label>
<input type="text" name="x1" id="x1" />
</p>
<p>
<label for="y1">y1:</label>
<input type="text" name="y1" id="y1" />
</p>
<p>
<label for="x2">x2:</label>
<input type="text" name="x2" id="x2" />
</p>
<p>
<label for="y2">y2:</label>
<input type="text" name="y2" id="y2" />
</p>
<p>
<label for="width">width:</label>
<input type="text" name="width" id="width" />
</p>
<p>
<label for="height">height</label>
<input type="text" name="height" id="height" />
</p>
</div>
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,236 +0,0 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
<meta http-equiv="Content-Language" content="en-us" />
<title></title>
</head>
<body>
<!--
This is a static test file for the HTML & CSS structure employed, tested in
the following browsers:
PC:
IE 6: working
IE 5.5: working
IE 5.0: opacity issues
FF 1.5: working
Opera 9: working
MAC:
Camino 1.0: working
FF 1.5: working
Safari 2.0: working
-->
<style type="text/css">
.imgCrop_wrap {
width: 500px; /* @TODO IN JS */
height: 333px; /* @TODO IN JS */
position: relative;
cursor: crosshair;
}
/* fix for IE displaying all boxes at line-height by default */
.imgCrop_wrap,
.imgCrop_wrap * {
font-size: 0;
}
.imgCrop_overlay {
background-color: #000;
opacity: 0.5;
filter:alpha(opacity=50);
position: absolute;
width: 100%;
height: 100%;
}
.imgCrop_selArea {
position: absolute;
cursor: move;
/* @TODO: rest to be done via JS when selecting areas */
top: 110px;
left: 210px;
width: 200px;
height: 200px;
z-index: 2;
background: transparent url(castle.jpg) no-repeat -210px -110px;
}
/* imgCrop_clickArea is all a fix for IE 5.5 & 6 to allow the user to click on the given area */
.imgCrop_clickArea {
width: 100%;
height: 100%;
background-color: #FFF;
opacity: 0.01;
filter:alpha(opacity=01);
}
.imgCrop_marqueeHoriz {
position: absolute;
width: 100%;
height: 1px;
background: transparent url(marqueeHoriz.gif) repeat-x 0 0;
}
.imgCrop_marqueeVert {
position: absolute;
height: 100%;
width: 1px;
background: transparent url(marqueeVert.gif) repeat-y 0 0;
}
.imgCrop_marqueeNorth { top: 0; left: 0; }
.imgCrop_marqueeEast { top: 0; right: 0; }
.imgCrop_marqueeSouth { bottom: 0px; left: 0; }
.imgCrop_marqueeWest { top: 0; left: 0; }
.imgCrop_handle {
position: absolute;
border: 1px solid #333;
width: 6px;
height: 6px;
background: #FFF;
opacity: 0.5;
filter:alpha(opacity=50);
z-index: 3;
}
.imgCrop_handleN {
top: -3px;
left: 0;
margin-left: 49%; /* @TODO : in JS */
cursor: n-resize;
}
.imgCrop_handleNE {
top: -3px;
right: -3px;
cursor: ne-resize;
}
.imgCrop_handleE {
top: 0;
right: -3px;
margin-top: 49%; /* @TODO : in JS */
cursor: e-resize;
}
.imgCrop_handleSE {
right: -3px;
bottom: -3px;
cursor: se-resize;
}
.imgCrop_handleS {
right: 0;
bottom: -3px;
margin-right: 49%; /* @TODO : in JS */
cursor: s-resize;
}
.imgCrop_handleSW {
left: -3px;
bottom: -3px;
cursor: sw-resize;
}
.imgCrop_handleW {
top: 0;
left: -3px;
margin-top: 49%; /* @TODO : in JS */
cursor: e-resize;
}
.imgCrop_handleNW {
top: -3px;
left: -3px;
cursor: nw-resize;
}
/**
* Create an area to click & drag around on as the default browser behaviour is to let you drag the image
*/
.imgCrop_dragArea {
width: 100%;
height: 100%;
z-index: 200;
position: absolute;
top: 0;
left: 0;
}
.imgCrop_previewWrap {
width: 200px; /* @TODO : in JS */
height: 200px; /* @TODO : in JS */
overflow: hidden;
position: relative;
}
/* @TODO : all in JS */
.imgCrop_previewWrap img {
position: absolute;
width: 500px;
height: 333px;
left: -210px;
top: -110px;
}
/**
* These are just for the static test
*/
.imgCrop_wrap {
margin: 20px 0 0 50px;
float: left;
}
#previewWrapper {
float: left;
margin-left: 20px;
}
</style>
<br /><br />
<!-- This is all attached to the image dynamically -->
<div class="imgCrop_wrap">
<img src="castle.jpg" alt="test image" id="testImage" width="500" height="333" />
<div class="imgCrop_dragArea">
<div class="imgCrop_overlay"></div>
<div class="imgCrop_selArea">
<!-- marquees -->
<!-- the inner spans are only required for IE to stop it making the divs 1px high/wide -->
<div class="imgCrop_marqueeHoriz imgCrop_marqueeNorth"><span></span></div>
<div class="imgCrop_marqueeVert imgCrop_marqueeEast"><span></span></div>
<div class="imgCrop_marqueeHoriz imgCrop_marqueeSouth"><span></span></div>
<div class="imgCrop_marqueeVert imgCrop_marqueeWest"><span></span></div>
<!-- handles -->
<div class="imgCrop_handle imgCrop_handleN"></div>
<div class="imgCrop_handle imgCrop_handleNE"></div>
<div class="imgCrop_handle imgCrop_handleE"></div>
<div class="imgCrop_handle imgCrop_handleSE"></div>
<div class="imgCrop_handle imgCrop_handleS"></div>
<div class="imgCrop_handle imgCrop_handleSW"></div>
<div class="imgCrop_handle imgCrop_handleW"></div>
<div class="imgCrop_handle imgCrop_handleNW"></div>
<div class="imgCrop_clickArea"></div>
</div>
<div class="imgCrop_clickArea"></div>
</div>
</div>
<div id="previewWrapper">
<h3>Preview:</h3>
<div class="imgCrop_previewWrap">
<img src="castle.jpg" alt="test image" id="previewImage" />
</div>
</div>
</body>
</html>

View File

@ -0,0 +1,10 @@
{
"presets": [
["env", {
"modules": false,
"targets": {
"browsers": ["last 2 versions", "ie >= 9"]
}
}]
]
}

View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true

View File

@ -0,0 +1,13 @@
{
"extends": "airbnb-base",
"env": {
"browser": true
},
"rules": {
"no-param-reassign": "off",
"no-restricted-properties": "off",
"valid-jsdoc": ["error", {
"requireReturn": false
}]
}
}

View File

@ -0,0 +1,7 @@
language: node_js
node_js:
- "node"
script:
- npm run lint
- npm run build
- npm test

View File

@ -0,0 +1,218 @@
# Changelog
## 1.2.2 (Jan 3, 2018)
- Fix incorrect image natural sizes in iOS Safari (#279).
## 1.2.1 (Dec 17, 2017)
- Add `style` field to `package.json`.
- Fix size error when load SVG image (#256).
## 1.2.0 (Dec 17, 2017)
- Allow to set the pivot of zoom (#144).
- Fixed a bug of rotation (#260).
## 1.1.3 (Oct 21, 2017)
- Fixed a bug of render when disable one of `rotatable` and `scalable` options (#241).
## 1.1.2 (Oct 18, 2017)
- Normalize related decimal numbers when crop an image with canvas.
## 1.1.1 (Oct 11, 2017)
- Supports to load in node environment (#237).
- Fixed a bug of event binding (#238).
## 1.1.0 (Oct 8, 2017)
- Added 4 new options to `getCroppedCanvas` method: `minWidth`, `minHeight`, `maxWidth` and `maxHeight`.
- Enhanced image scaling: the `scaleX` and `scaleY` values should only be `1` or `-1` before, but now they can be any numbers.
- Improved crop box resizing behaviour in the northeast, northwest, southeast and southwest directions. (#222).
## 1.0.0 (Sep 3, 2017)
- Fixed a bug of zoom out after cleared the crop box in view mode 1, 2 and 3 (#209).
- Improve crop box resizing behaviour in the east, west, south and north directions (#222).
## 1.0.0-rc.3 (Jul 7, 2017)
- Added two new options (`imageSmoothingEnabled` and `imageSmoothingQuality`) to `getCroppedCanvas` method.
- Fixed a bug of RegExp using (#195 by @arusakov).
## 1.0.0-rc.2 (May 30, 2017)
- Fixed the issue of canvas box initialization (#179).
## 1.0.0-rc.1 (Apr 30, 2017)
- Change the `main` field value from `dist/cropper.js` (UMD) to `dist/cropper.common.js` (CommonJS).
- Added `module` and `browser` fields to `package.json`.
## 1.0.0-rc (Mar 25, 2017)
- Fixed the bug of touch zoom (#161).
- Fixed the bug of window resize (#162).
- Improve the `toggleDragModeOnDblclick` option (only available when the `dragMode` option is set to `crop` or `move`)
## 1.0.0-beta.2 (Feb 25, 2017)
- Fixed the bug of rotate square image lead image shrink (#155).
- Improved RegExps for DataURL processing (#156).
## 1.0.0-beta.1 (Jan 21, 2017)
- Use CSS3 2D Transforms instead of `left` and `top` for better performance (#138).
- Set `withCredentials` attribute when read the image data by XMLHttpRequest (#141).
## 1.0.0-beta (Jan 1, 2017)
- Supports to set an element for preview (#113).
- Improved event handler for Pointer Events (#127).
## 1.0.0-alpha (Dec 4, 2016)
- Built JavaScript with Rollup.
- Build CSS with PostCSS.
- Fixed a bug of auto crop when replace the image (#83).
## 0.8.1 (Sep 3, 2016)
- Fixed the bug of cropping (#80).
- Fixed the bug of calling `ready` event twice when call `replace` method (#81).
- Fixed the bug of `getCroppedCanvas` when set `scalable` or `rotatable` to `false` (#82).
## 0.8.0 (Aug 18, 2016)
- Removed `build` event.
- Renamed `built` event to `ready`.
- Fixed the error of orientation transform.
- Ported code to ECMAScript 6.
## 0.7.2 (Jun 8, 2016)
- Fixed a bug of `data-*` attributes setting and getting.
- Fixed the calling order of `scale` and `rotate`.
## 0.7.1 (May 28, 2016)
- Improved the rotate and scale transform behaviour.
- Improved the `getCroppedCanvas` method (returns the whole canvas if it is not cropped).
- Check cross origin setting when load image by XMLHTTPRequest.
## 0.7.0 (Mar 20, 2016)
- Supports 7 custom events: `build`, `built`, `cropstart`, `cropmove`, `cropend`, `crop` and `zoom`.
- The original callback options become shortcuts of these events now.
- IE8 is no longer supported after added these custom events.
## 0.6.0 (Feb 22, 2016)
- Added a new parameter to the `replace` method for applying filters.
- Improved the image initializing for Safari.
- Fixed incorrect size limitation of the crop box (#30).
- Fixed incorrect cropped canvas when scaleX or scaleY great than 1.
## 0.5.6 (Jan 18, 2016)
- Fixed crossOriginUrl undefined error when exists the `crossOrigin` property.
- Fixed the issue in the "destroy" method (#24).
- Optimized tests.
## 0.5.5 (Jan 1, 2016)
- Fixed a dimension bug in the "getCroppedCanvas" method.
- Added an example for cropping round image.
## 0.5.4 (Dec 28, 2015)
- Supports to zoom from event triggering point.
## 0.5.3 (Dec 24, 2015)
- Limit wheel speed to prevent zoom too fast (#21)
- Improve the `setCropBoxData` method (#22)
## 0.5.2 (Dec 15, 2015)
- Fix event handlers
## 0.5.1 (Dec 12, 2015)
- Handle Data URL (avoid to use XMLHttpRequest to open a Data URL)
- Handle ajax error when load ArrayBuffer
- Not to transform the image to base64 when Orientation equals to `1`
- Fix some typos
## 0.5.0 (Dec 5, 2015)
- Added a new option: `checkOrientation`
- Added a timestamp to the url of preview image
## 0.4.0 (Dec 2, 2015)
- Added a new option: `restore`
- Fixed #12: Added vendor prefixes to CSS `transform`
## 0.3.3 (Nov 30, 2015)
- Floor the numerical parameters for `CanvasRenderingContext2D.drawImage`
## 0.3.2 (Nov 18, 2015)
- Fixed #10: improve new crop box creating
## 0.3.1 (Nov 11, 2015)
- Fixed #7: reset the `crossOrigin` when call the `replace` method
## 0.3.0 (Oct 28, 2015)
- Supports four view modes
- Supports three drag modes
- Makes the crop box's borders and handlers visible when overflow
- Added some examples
- Fixed some issues
### Options
- Added `viewMode`
- Added `dragMode`
- Renamed `touchDragZoom` to `zoomOnTouch`
- Renamed `mouseWheelZoom` to `zoomOnWheel`
- Renamed `doubleClickToggle` to `toggleDragModeOnDblclick`
- Renamed `checkImageOrigin` to `checkCrossOrigin`
- Removed `strict` (supported by `viewMode: 1`)
- Removed `dragCrop` (supported by `dragMode: 'crop'`)
## 0.2.1 (Oct 28, 2015)
- Fix the error jQuery reference on the `setCanvasData` method
- Fix typo on the `destroy` method
## 0.2.0 (Oct 25, 2015)
- Added 5 new methods: `moveTo`, `zoomTo`, `rotateTo`, `scaleX` and `scaleY`
- Improved 4 methods: `move`, `zoom`, `rotate` and `getCanvasData`
- Improved cropping
## 0.1.1 (Oct 10, 2015)
- Improved canvas limitation
- Improved crop box limitation
- Improved preview for cross origin image
## 0.1.0 (Sep 25, 2015)
- Supports touch (mobile)
- Supports zoom
- Supports rotation
- Supports scale (flip)
- Supports canvas
- Supports multiple croppers
- Cross-browser support
- Supports 37 options: `aspectRatio`, `data`, `preview`, `strict`, `responsive`, `checkImageOrigin`, `modal`, `guides`, `center`, `highlight`, `background`, `autoCrop`, `autoCropArea`, `dragCrop`, `movable`, `rotatable`, `scalable`, `zoomable`, `mouseWheelZoom`, `wheelZoomRatio`, `touchDragZoom`, `cropBoxMovable`, `cropBoxResizable`, `doubleClickToggle`, `minCanvasWidth`, `minCanvasHeight`, `minCropBoxWidth`, `minCropBoxHeight`, `minContainerWidth`, `minContainerHeight`, `build`, `built`, `cropstart`, `cropmove`, `cropend`, `crop`, `zoom`.
- Support 22 methods: `crop`, `reset`, `clear`, `replace`, `enable`, `disable`, `destroy`, `move`, `zoom`, `rotate`, `scale`, `getData`, `setData`, `getContainerData`, `getImageData`, `getCanvasData`, `setCanvasData`, `getCropBoxData`, `setCropBoxData`, `getCroppedCanvas`, `setAspectRatio`, `setDragMode`.

View File

@ -0,0 +1,35 @@
# Contributing to Cropper
## How to report bugs
### Make sure it is a Cropper bug
Most bugs reported to our bug tracker are actually bugs in user code, not in Cropper code. Keep in mind that just because your code throws an error inside of Cropper, this does *not* mean the bug is a Cropper bug.
Ask for help first in a discussion forum like [Stack Overflow](http://stackoverflow.com/). You will get much quicker support, and you will help avoid tying up the Cropper team with invalid bug reports.
### Disable browser extensions
Make sure you have reproduced the bug with all browser extensions and add-ons disabled, as these can sometimes cause things to break in interesting and unpredictable ways. Try using incognito, stealth or anonymous browsing modes.
### Try the latest version of Cropper
Bugs in old versions of Cropper may have already been fixed. In order to avoid reporting known issues, make sure you are always testing against the [latest release](https://github.com/fengyuanchen/cropperjs/releases/latest). We cannot fix bugs in older released files, if a bug has been fixed in a subsequent version of Cropper the site should upgrade.
### Simplify the test case
When experiencing a problem, [reduce your code](http://webkit.org/quality/reduction.html) to the bare minimum required to reproduce the issue. This makes it *much* easier to isolate and fix the offending code. Bugs reported without reduced test cases take on average 9001% longer to fix than bugs that are submitted with them, so you really should try to do this if at all possible.
### Search for related or duplicate issues
Go to the [Cropper issue tracker](https://github.com/fengyuanchen/cropperjs/issues) and make sure the problem hasn't already been reported. If not, create a new issue there and include your test case.
### Browser support
Remember that Cropper supports multiple browsers and their versions; any contributed code must work in all of them. You can refer to the [browser support page](https://github.com/fengyuanchen/cropperjs/blob/master/README.md#browser-support) for the current list of supported browsers.
## Notes for pull request
- Run the test suites in the `test` directory first.
- Don't modify any files in the `dist` directory.
- Follow the same code style as the library.

View File

@ -0,0 +1,21 @@
Before opening an issue:
- [Search for duplicate or closed issues](https://github.com/fengyuanchen/cropperjs/issues?utf8=%E2%9C%93&q=is%3Aissue)
- Prepare a [reduced test case](https://css-tricks.com/reduced-test-cases/) for any bugs
- Read the [contributing guidelines](https://github.com/fengyuanchen/cropperjs/blob/master/CONTRIBUTING.md)
When asking general "how to" questions:
- Please do not open an issue here
- Instead, ask for help on [StackOverflow](http://stackoverflow.com/)
When reporting a bug, include:
- Operating system and version (Windows, Mac OS X, Android, iOS, Win10 Mobile)
- Browser and version (Chrome, Firefox, Safari, IE, MS Edge, Opera 15+, Android Browser)
- Reduced test cases and potential fixes using [JS Bin](https://jsbin.com), [JSFiddle](https://jsfiddle.net/) or [CodePen](https://codepen.io/)
When suggesting a feature, include:
- As much detail as possible for what we should add and why it's important to Cropper.js
- Relevant links to prior art, screenshots, or live demos whenever possible

21
library/cropperjs/LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-present Chen Fengyuan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

1028
library/cropperjs/README.md Normal file

File diff suppressed because it is too large Load Diff

3760
library/cropperjs/dist/cropper.common.js vendored Normal file

File diff suppressed because it is too large Load Diff

306
library/cropperjs/dist/cropper.css vendored Normal file
View File

@ -0,0 +1,306 @@
/*!
* Cropper.js v1.2.2
* https://github.com/fengyuanchen/cropperjs
*
* Copyright (c) 2015-2018 Chen Fengyuan
* Released under the MIT license
*
* Date: 2018-01-03T13:26:29.610Z
*/
.cropper-container {
direction: ltr;
font-size: 0;
line-height: 0;
position: relative;
-ms-touch-action: none;
touch-action: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.cropper-container img {/*Avoid margin top issue (Occur only when margin-top <= -height)
*/
display: block;
height: 100%;
image-orientation: 0deg;
max-height: none !important;
max-width: none !important;
min-height: 0 !important;
min-width: 0 !important;
width: 100%;
}
.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.cropper-wrap-box,
.cropper-canvas {
overflow: hidden;
}
.cropper-drag-box {
background-color: #fff;
opacity: 0;
}
.cropper-modal {
background-color: #000;
opacity: .5;
}
.cropper-view-box {
display: block;
height: 100%;
outline-color: rgba(51, 153, 255, 0.75);
outline: 1px solid #39f;
overflow: hidden;
width: 100%;
}
.cropper-dashed {
border: 0 dashed #eee;
display: block;
opacity: .5;
position: absolute;
}
.cropper-dashed.dashed-h {
border-bottom-width: 1px;
border-top-width: 1px;
height: 33.33333%;
left: 0;
top: 33.33333%;
width: 100%;
}
.cropper-dashed.dashed-v {
border-left-width: 1px;
border-right-width: 1px;
height: 100%;
left: 33.33333%;
top: 0;
width: 33.33333%;
}
.cropper-center {
display: block;
height: 0;
left: 50%;
opacity: .75;
position: absolute;
top: 50%;
width: 0;
}
.cropper-center:before,
.cropper-center:after {
background-color: #eee;
content: ' ';
display: block;
position: absolute;
}
.cropper-center:before {
height: 1px;
left: -3px;
top: 0;
width: 7px;
}
.cropper-center:after {
height: 7px;
left: 0;
top: -3px;
width: 1px;
}
.cropper-face,
.cropper-line,
.cropper-point {
display: block;
height: 100%;
opacity: .1;
position: absolute;
width: 100%;
}
.cropper-face {
background-color: #fff;
left: 0;
top: 0;
}
.cropper-line {
background-color: #39f;
}
.cropper-line.line-e {
cursor: ew-resize;
right: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}
.cropper-line.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}
.cropper-point {
background-color: #39f;
height: 5px;
opacity: .75;
width: 5px;
}
.cropper-point.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}
.cropper-point.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}
.cropper-point.point-w {
cursor: ew-resize;
left: -3px;
margin-top: -3px;
top: 50%;
}
.cropper-point.point-s {
bottom: -3px;
cursor: s-resize;
left: 50%;
margin-left: -3px;
}
.cropper-point.point-ne {
cursor: nesw-resize;
right: -3px;
top: -3px;
}
.cropper-point.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}
.cropper-point.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}
.cropper-point.point-se {
bottom: -3px;
cursor: nwse-resize;
height: 20px;
opacity: 1;
right: -3px;
width: 20px;
}
@media (min-width: 768px) {
.cropper-point.point-se {
height: 15px;
width: 15px;
}
}
@media (min-width: 992px) {
.cropper-point.point-se {
height: 10px;
width: 10px;
}
}
@media (min-width: 1200px) {
.cropper-point.point-se {
height: 5px;
opacity: .75;
width: 5px;
}
}
.cropper-point.point-se:before {
background-color: #39f;
bottom: -50%;
content: ' ';
display: block;
height: 200%;
opacity: 0;
position: absolute;
right: -50%;
width: 200%;
}
.cropper-invisible {
opacity: 0;
}
.cropper-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}
.cropper-hide {
display: block;
height: 0;
position: absolute;
width: 0;
}
.cropper-hidden {
display: none !important;
}
.cropper-move {
cursor: move;
}
.cropper-crop {
cursor: crosshair;
}
.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
cursor: not-allowed;
}

3758
library/cropperjs/dist/cropper.esm.js vendored Normal file

File diff suppressed because it is too large Load Diff

3766
library/cropperjs/dist/cropper.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,9 @@
/*!
* Cropper.js v1.2.2
* https://github.com/fengyuanchen/cropperjs
*
* Copyright (c) 2015-2018 Chen Fengyuan
* Released under the MIT license
*
* Date: 2018-01-03T13:26:29.610Z
*/.cropper-container{direction:ltr;font-size:0;line-height:0;position:relative;-ms-touch-action:none;touch-action:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.cropper-container img{display:block;height:100%;image-orientation:0deg;max-height:none!important;max-width:none!important;min-height:0!important;min-width:0!important;width:100%}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{bottom:0;left:0;position:absolute;right:0;top:0}.cropper-canvas,.cropper-wrap-box{overflow:hidden}.cropper-drag-box{background-color:#fff;opacity:0}.cropper-modal{background-color:#000;opacity:.5}.cropper-view-box{display:block;height:100%;outline-color:rgba(51,153,255,.75);outline:1px solid #39f;overflow:hidden;width:100%}.cropper-dashed{border:0 dashed #eee;display:block;opacity:.5;position:absolute}.cropper-dashed.dashed-h{border-bottom-width:1px;border-top-width:1px;height:33.33333%;left:0;top:33.33333%;width:100%}.cropper-dashed.dashed-v{border-left-width:1px;border-right-width:1px;height:100%;left:33.33333%;top:0;width:33.33333%}.cropper-center{display:block;height:0;left:50%;opacity:.75;position:absolute;top:50%;width:0}.cropper-center:after,.cropper-center:before{background-color:#eee;content:" ";display:block;position:absolute}.cropper-center:before{height:1px;left:-3px;top:0;width:7px}.cropper-center:after{height:7px;left:0;top:-3px;width:1px}.cropper-face,.cropper-line,.cropper-point{display:block;height:100%;opacity:.1;position:absolute;width:100%}.cropper-face{background-color:#fff;left:0;top:0}.cropper-line{background-color:#39f}.cropper-line.line-e{cursor:ew-resize;right:-3px;top:0;width:5px}.cropper-line.line-n{cursor:ns-resize;height:5px;left:0;top:-3px}.cropper-line.line-w{cursor:ew-resize;left:-3px;top:0;width:5px}.cropper-line.line-s{bottom:-3px;cursor:ns-resize;height:5px;left:0}.cropper-point{background-color:#39f;height:5px;opacity:.75;width:5px}.cropper-point.point-e{cursor:ew-resize;margin-top:-3px;right:-3px;top:50%}.cropper-point.point-n{cursor:ns-resize;left:50%;margin-left:-3px;top:-3px}.cropper-point.point-w{cursor:ew-resize;left:-3px;margin-top:-3px;top:50%}.cropper-point.point-s{bottom:-3px;cursor:s-resize;left:50%;margin-left:-3px}.cropper-point.point-ne{cursor:nesw-resize;right:-3px;top:-3px}.cropper-point.point-nw{cursor:nwse-resize;left:-3px;top:-3px}.cropper-point.point-sw{bottom:-3px;cursor:nesw-resize;left:-3px}.cropper-point.point-se{bottom:-3px;cursor:nwse-resize;height:20px;opacity:1;right:-3px;width:20px}@media (min-width:768px){.cropper-point.point-se{height:15px;width:15px}}@media (min-width:992px){.cropper-point.point-se{height:10px;width:10px}}@media (min-width:1200px){.cropper-point.point-se{height:5px;opacity:.75;width:5px}}.cropper-point.point-se:before{background-color:#39f;bottom:-50%;content:" ";display:block;height:200%;opacity:0;position:absolute;right:-50%;width:200%}.cropper-invisible{opacity:0}.cropper-bg{background-image:url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC")}.cropper-hide{display:block;height:0;position:absolute;width:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed}

10
library/cropperjs/dist/cropper.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,306 @@
/*!
* Cropper.js v1.2.2
* https://github.com/fengyuanchen/cropperjs
*
* Copyright (c) 2015-2018 Chen Fengyuan
* Released under the MIT license
*
* Date: 2018-01-03T13:26:29.610Z
*/
.cropper-container {
direction: ltr;
font-size: 0;
line-height: 0;
position: relative;
-ms-touch-action: none;
touch-action: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.cropper-container img {/*Avoid margin top issue (Occur only when margin-top <= -height)
*/
display: block;
height: 100%;
image-orientation: 0deg;
max-height: none !important;
max-width: none !important;
min-height: 0 !important;
min-width: 0 !important;
width: 100%;
}
.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.cropper-wrap-box,
.cropper-canvas {
overflow: hidden;
}
.cropper-drag-box {
background-color: #fff;
opacity: 0;
}
.cropper-modal {
background-color: #000;
opacity: .5;
}
.cropper-view-box {
display: block;
height: 100%;
outline-color: rgba(51, 153, 255, 0.75);
outline: 1px solid #39f;
overflow: hidden;
width: 100%;
}
.cropper-dashed {
border: 0 dashed #eee;
display: block;
opacity: .5;
position: absolute;
}
.cropper-dashed.dashed-h {
border-bottom-width: 1px;
border-top-width: 1px;
height: 33.33333%;
left: 0;
top: 33.33333%;
width: 100%;
}
.cropper-dashed.dashed-v {
border-left-width: 1px;
border-right-width: 1px;
height: 100%;
left: 33.33333%;
top: 0;
width: 33.33333%;
}
.cropper-center {
display: block;
height: 0;
left: 50%;
opacity: .75;
position: absolute;
top: 50%;
width: 0;
}
.cropper-center:before,
.cropper-center:after {
background-color: #eee;
content: ' ';
display: block;
position: absolute;
}
.cropper-center:before {
height: 1px;
left: -3px;
top: 0;
width: 7px;
}
.cropper-center:after {
height: 7px;
left: 0;
top: -3px;
width: 1px;
}
.cropper-face,
.cropper-line,
.cropper-point {
display: block;
height: 100%;
opacity: .1;
position: absolute;
width: 100%;
}
.cropper-face {
background-color: #fff;
left: 0;
top: 0;
}
.cropper-line {
background-color: #39f;
}
.cropper-line.line-e {
cursor: ew-resize;
right: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}
.cropper-line.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}
.cropper-point {
background-color: #39f;
height: 5px;
opacity: .75;
width: 5px;
}
.cropper-point.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}
.cropper-point.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}
.cropper-point.point-w {
cursor: ew-resize;
left: -3px;
margin-top: -3px;
top: 50%;
}
.cropper-point.point-s {
bottom: -3px;
cursor: s-resize;
left: 50%;
margin-left: -3px;
}
.cropper-point.point-ne {
cursor: nesw-resize;
right: -3px;
top: -3px;
}
.cropper-point.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}
.cropper-point.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}
.cropper-point.point-se {
bottom: -3px;
cursor: nwse-resize;
height: 20px;
opacity: 1;
right: -3px;
width: 20px;
}
@media (min-width: 768px) {
.cropper-point.point-se {
height: 15px;
width: 15px;
}
}
@media (min-width: 992px) {
.cropper-point.point-se {
height: 10px;
width: 10px;
}
}
@media (min-width: 1200px) {
.cropper-point.point-se {
height: 5px;
opacity: .75;
width: 5px;
}
}
.cropper-point.point-se:before {
background-color: #39f;
bottom: -50%;
content: ' ';
display: block;
height: 200%;
opacity: 0;
position: absolute;
right: -50%;
width: 200%;
}
.cropper-invisible {
opacity: 0;
}
.cropper-bg {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC');
}
.cropper-hide {
display: block;
height: 0;
position: absolute;
width: 0;
}
.cropper-hidden {
display: none !important;
}
.cropper-move {
cursor: move;
}
.cropper-crop {
cursor: crosshair;
}
.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
cursor: not-allowed;
}

View File

@ -0,0 +1,252 @@
.btn {
padding-left: .75rem;
padding-right: .75rem;
}
label.btn {
margin-bottom: 0;
}
.d-flex > .btn {
flex: 1;
}
.carbonads {
border-radius: .25rem;
border: 1px solid #ccc;
font-size: .875rem;
overflow: hidden;
padding: 1rem;
}
.carbon-wrap {
overflow: hidden;
}
.carbon-img {
clear: left;
display: block;
float: left;
}
.carbon-text,
.carbon-poweredby {
display: block;
margin-left: 140px;
}
.carbon-text,
.carbon-text:hover,
.carbon-text:focus {
color: #fff;
text-decoration: none;
}
.carbon-poweredby,
.carbon-poweredby:hover,
.carbon-poweredby:focus {
color: #ddd;
text-decoration: none;
}
@media (min-width: 768px) {
.carbonads {
float: right;
margin-bottom: -1rem;
margin-top: -1rem;
max-width: 360px;
}
}
.footer {
font-size: .875rem;
}
.heart {
color: #ddd;
display: block;
height: 2rem;
line-height: 2rem;
margin-bottom: 0;
margin-top: 1rem;
position: relative;
text-align: center;
width: 100%;
}
.heart:hover {
color: #ff4136;
}
.heart::before {
border-top: 1px solid #eee;
content: " ";
display: block;
height: 0;
left: 0;
position: absolute;
right: 0;
top: 50%;
}
.heart::after {
background-color: #fff;
content: "♥";
padding-left: .5rem;
padding-right: .5rem;
position: relative;
z-index: 1;
}
.img-container,
.img-preview {
background-color: #f7f7f7;
text-align: center;
width: 100%;
}
.img-container {
margin-bottom: 1rem;
max-height: 497px;
min-height: 200px;
}
@media (min-width: 768px) {
.img-container {
min-height: 497px;
}
}
.img-container > img {
max-width: 100%;
}
.docs-preview {
margin-right: -1rem;
}
.img-preview {
float: left;
margin-bottom: .5rem;
margin-right: .5rem;
overflow: hidden;
}
.img-preview > img {
max-width: 100%;
}
.preview-lg {
height: 9rem;
width: 16rem;
}
.preview-md {
height: 4.5rem;
width: 8rem;
}
.preview-sm {
height: 2.25rem;
width: 4rem;
}
.preview-xs {
height: 1.125rem;
margin-right: 0;
width: 2rem;
}
.docs-data > .input-group {
margin-bottom: .5rem;
}
.docs-data .input-group-prepend .input-group-text {
min-width: 4rem;
}
.docs-data .input-group-append .input-group-text {
min-width: 3rem;
}
.docs-buttons > .btn,
.docs-buttons > .btn-group,
.docs-buttons > .form-control {
margin-bottom: .5rem;
margin-right: .25rem;
}
.docs-toggles > .btn,
.docs-toggles > .btn-group,
.docs-toggles > .dropdown {
margin-bottom: .5rem;
}
.docs-tooltip {
display: block;
margin: -.5rem -.75rem;
padding: .5rem .75rem;
}
.docs-tooltip > .icon {
margin: 0 -.25rem;
vertical-align: top;
}
.tooltip-inner {
white-space: normal;
}
.btn-upload .tooltip-inner,
.btn-toggle .tooltip-inner {
white-space: nowrap;
}
.btn-toggle {
padding: .5rem;
}
.btn-toggle > .docs-tooltip {
margin: -.5rem;
padding: .5rem;
}
@media (max-width: 400px) {
.btn-group-crop {
margin-right: -1rem!important;
}
.btn-group-crop > .btn {
padding-left: .5rem;
padding-right: .5rem;
}
.btn-group-crop .docs-tooltip {
margin-left: -.5rem;
margin-right: -.5rem;
padding-left: .5rem;
padding-right: .5rem;
}
}
.docs-options .dropdown-menu {
width: 100%;
}
.docs-options .dropdown-menu > li {
font-size: .875rem;
padding: .125rem 1rem;
}
.docs-options .dropdown-menu .form-check-label {
display: block;
}
.docs-cropped .modal-body {
text-align: center;
}
.docs-cropped .modal-body > img,
.docs-cropped .modal-body > canvas {
max-width: 100%;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 179 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 220 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

View File

@ -0,0 +1,578 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="description" content="JavaScript image cropper.">
<meta name="keywords" content="HTML, CSS, JS, JavaScript, image cropping, cropper, cropperjs, cropper.js, front-end, web development">
<meta name="author" content="Chen Fengyuan">
<title>Cropper.js</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/css/bootstrap.min.css">
<link rel="stylesheet" href="css/cropper.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<!--[if lt IE 9]>
<div class="alert alert-warning alert-dismissible fade show m-0 rounded-0" role="alert">
You are using an <strong>outdated</strong> browser. Please <a href="http://browsehappy.com/">upgrade your browser</a> to improve your experience.
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<![endif]-->
<!-- Header -->
<header class="navbar navbar-light navbar-expand-md">
<div class="container">
<a class="navbar-brand" href="./">Cropper.js</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbar-collapse" aria-controls="navbar-collapse" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse justify-content-end" id="navbar-collapse" role="navigation">
<nav class="nav navbar-nav">
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs/blob/master/README.md" data-toggle="tooltip" title="View the documentation">Docs</a>
<a class="nav-link" href="https://fengyuanchen.github.io/photo-editor" data-toggle="tooltip" title="An advanced example of Cropper.js">Photo Editor</a>
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs" data-toggle="tooltip" title="View the GitHub project">GitHub</a>
<a class="nav-link" href="https://fengyuanchen.github.io" data-toggle="tooltip" title="More projects">More</a>
<a class="nav-link" href="http://chenfengyuan.com" data-toggle="tooltip" title="About the author">About</a>
</nav>
</div>
</nav>
</header>
<!-- Jumbotron -->
<div class="jumbotron bg-primary text-white rounded-0">
<div class="container">
<div class="row">
<div class="col-md">
<h1>Cropper.js <small class="h6">v1.2.2</small></h1>
<p class="lead">JavaScript image cropper.</p>
</div>
<div class="col-md">
<div class="carbonads">
<script id="_carbonads_js" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=fengyuanchen" async></script>
</div>
</div>
</div>
</div>
</div>
<!-- Content -->
<div class="container">
<div class="row">
<div class="col-md-9">
<!-- <h3>Demo:</h3> -->
<div class="img-container">
<img src="images/picture.jpg" alt="Picture">
</div>
</div>
<div class="col-md-3">
<!-- <h3>Preview:</h3> -->
<div class="docs-preview clearfix">
<div class="img-preview preview-lg"></div>
<div class="img-preview preview-md"></div>
<div class="img-preview preview-sm"></div>
<div class="img-preview preview-xs"></div>
</div>
<!-- <h3>Data:</h3> -->
<!-- <h3>Data:</h3> -->
<div class="docs-data">
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataX">X</label>
</span>
<input type="text" class="form-control" id="dataX" placeholder="x">
<span class="input-group-append">
<span class="input-group-text">px</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataY">Y</label>
</span>
<input type="text" class="form-control" id="dataY" placeholder="y">
<span class="input-group-append">
<span class="input-group-text">px</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataWidth">Width</label>
</span>
<input type="text" class="form-control" id="dataWidth" placeholder="width">
<span class="input-group-append">
<span class="input-group-text">px</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataHeight">Height</label>
</span>
<input type="text" class="form-control" id="dataHeight" placeholder="height">
<span class="input-group-append">
<span class="input-group-text">px</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataRotate">Rotate</label>
</span>
<input type="text" class="form-control" id="dataRotate" placeholder="rotate">
<span class="input-group-append">
<span class="input-group-text">deg</span>
</span>
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataScaleX">ScaleX</label>
</span>
<input type="text" class="form-control" id="dataScaleX" placeholder="scaleX">
</div>
<div class="input-group input-group-sm">
<span class="input-group-prepend">
<label class="input-group-text" for="dataScaleY">ScaleY</label>
</span>
<input type="text" class="form-control" id="dataScaleY" placeholder="scaleY">
</div>
</div>
</div>
</div>
<div class="row" id="actions">
<div class="col-md-9 docs-buttons">
<!-- <h3>Toolbar:</h3> -->
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="setDragMode" data-option="move" title="Move">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setDragMode(&quot;move&quot;)">
<span class="fa fa-arrows"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="setDragMode" data-option="crop" title="Crop">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setDragMode(&quot;crop&quot;)">
<span class="fa fa-crop"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="zoom" data-option="0.1" title="Zoom In">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.zoom(0.1)">
<span class="fa fa-search-plus"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="zoom" data-option="-0.1" title="Zoom Out">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.zoom(-0.1)">
<span class="fa fa-search-minus"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="move" data-option="-10" data-second-option="0" title="Move Left">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.move(-10, 0)">
<span class="fa fa-arrow-left"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="move" data-option="10" data-second-option="0" title="Move Right">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.move(10, 0)">
<span class="fa fa-arrow-right"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="move" data-option="0" data-second-option="-10" title="Move Up">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.move(0, -10)">
<span class="fa fa-arrow-up"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="move" data-option="0" data-second-option="10" title="Move Down">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.move(0, 10)">
<span class="fa fa-arrow-down"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="rotate" data-option="-45" title="Rotate Left">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.rotate(-45)">
<span class="fa fa-rotate-left"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="rotate" data-option="45" title="Rotate Right">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.rotate(45)">
<span class="fa fa-rotate-right"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="scaleX" data-option="-1" title="Flip Horizontal">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.scaleX(-1)">
<span class="fa fa-arrows-h"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="scaleY" data-option="-1" title="Flip Vertical">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.scaleY(-1)">
<span class="fa fa-arrows-v"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="crop" title="Crop">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.crop()">
<span class="fa fa-check"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="clear" title="Clear">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.clear()">
<span class="fa fa-remove"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="disable" title="Disable">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.disable()">
<span class="fa fa-lock"></span>
</span>
</button>
<button type="button" class="btn btn-primary" data-method="enable" title="Enable">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.enable()">
<span class="fa fa-unlock"></span>
</span>
</button>
</div>
<div class="btn-group">
<button type="button" class="btn btn-primary" data-method="reset" title="Reset">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.reset()">
<span class="fa fa-refresh"></span>
</span>
</button>
<label class="btn btn-primary btn-upload" for="inputImage" title="Upload image file">
<input type="file" class="sr-only" id="inputImage" name="file" accept=".jpg,.jpeg,.png,.gif,.bmp,.tiff">
<span class="docs-tooltip" data-toggle="tooltip" title="Import image with Blob URLs">
<span class="fa fa-upload"></span>
</span>
</label>
<button type="button" class="btn btn-primary" data-method="destroy" title="Destroy">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.destroy()">
<span class="fa fa-power-off"></span>
</span>
</button>
</div>
<div class="btn-group btn-group-crop">
<button type="button" class="btn btn-success" data-method="getCroppedCanvas" data-option="{ &quot;maxWidth&quot;: 4096, &quot;maxHeight&quot;: 4096 }">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCroppedCanvas({ maxWidth: 4096, maxHeight: 4096 })">
Get Cropped Canvas
</span>
</button>
<button type="button" class="btn btn-success" data-method="getCroppedCanvas" data-option="{ &quot;width&quot;: 160, &quot;height&quot;: 90 }">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCroppedCanvas({ width: 160, height: 90 })">
160&times;90
</span>
</button>
<button type="button" class="btn btn-success" data-method="getCroppedCanvas" data-option="{ &quot;width&quot;: 320, &quot;height&quot;: 180 }">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCroppedCanvas({ width: 320, height: 180 })">
320&times;180
</span>
</button>
</div>
<!-- Show the cropped image in modal -->
<div class="modal fade docs-cropped" id="getCroppedCanvasModal" role="dialog" aria-hidden="true" aria-labelledby="getCroppedCanvasTitle" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="getCroppedCanvasTitle">Cropped</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body"></div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<a class="btn btn-primary" id="download" href="javascript:void(0);" download="cropped.jpg">Download</a>
</div>
</div>
</div>
</div><!-- /.modal -->
<button type="button" class="btn btn-secondary" data-method="getData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getData()">
Get Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="setData" data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setData(data)">
Set Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="getContainerData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getContainerData()">
Get Container Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="getImageData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getImageData()">
Get Image Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="getCanvasData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCanvasData()">
Get Canvas Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="setCanvasData" data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setCanvasData(data)">
Set Canvas Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="getCropBoxData" data-option data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.getCropBoxData()">
Get Crop Box Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="setCropBoxData" data-target="#putData">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.setCropBoxData(data)">
Set Crop Box Data
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="moveTo" data-option="0">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.moveTo(0)">
Move to [0,0]
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="zoomTo" data-option="1">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.zoomTo(1)">
Zoom to 100%
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="rotateTo" data-option="180">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.rotateTo(180)">
Rotate 180°
</span>
</button>
<button type="button" class="btn btn-secondary" data-method="scale" data-option="-2" data-second-option="-1">
<span class="docs-tooltip" data-toggle="tooltip" title="cropper.scale(-2, -1)">
Scale (-2, -1)
</span>
</button>
<textarea class="form-control" id="putData" rows="1" placeholder="Get data to here or set data with this value"></textarea>
</div><!-- /.docs-buttons -->
<div class="col-md-3 docs-toggles">
<!-- <h3>Toggles:</h3> -->
<div class="btn-group d-flex flex-nowrap" data-toggle="buttons">
<label class="btn btn-primary active">
<input type="radio" class="sr-only" id="aspectRatio1" name="aspectRatio" value="1.7777777777777777">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: 16 / 9">
16:9
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="aspectRatio2" name="aspectRatio" value="1.3333333333333333">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: 4 / 3">
4:3
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="aspectRatio3" name="aspectRatio" value="1">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: 1 / 1">
1:1
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="aspectRatio4" name="aspectRatio" value="0.6666666666666666">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: 2 / 3">
2:3
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="aspectRatio5" name="aspectRatio" value="NaN">
<span class="docs-tooltip" data-toggle="tooltip" title="aspectRatio: NaN">
Free
</span>
</label>
</div>
<div class="btn-group d-flex flex-nowrap" data-toggle="buttons">
<label class="btn btn-primary active">
<input type="radio" class="sr-only" id="viewMode0" name="viewMode" value="0" checked>
<span class="docs-tooltip" data-toggle="tooltip" title="View Mode 0">
VM0
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="viewMode1" name="viewMode" value="1">
<span class="docs-tooltip" data-toggle="tooltip" title="View Mode 1">
VM1
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="viewMode2" name="viewMode" value="2">
<span class="docs-tooltip" data-toggle="tooltip" title="View Mode 2">
VM2
</span>
</label>
<label class="btn btn-primary">
<input type="radio" class="sr-only" id="viewMode3" name="viewMode" value="3">
<span class="docs-tooltip" data-toggle="tooltip" title="View Mode 3">
VM3
</span>
</label>
</div>
<div class="dropdown dropup docs-options">
<button type="button" class="btn btn-primary btn-block dropdown-toggle" id="toggleOptions" data-toggle="dropdown" aria-expanded="true">
Toggle Options
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" aria-labelledby="toggleOptions">
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="responsive" type="checkbox" name="responsive" checked>
<label class="form-check-label" for="responsive">responsive</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="restore" type="checkbox" name="restore" checked>
<label class="form-check-label" for="restore">restore</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="checkCrossOrigin" type="checkbox" name="checkCrossOrigin" checked>
<label class="form-check-label" for="checkCrossOrigin">checkCrossOrigin</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="checkOrientation" type="checkbox" name="checkOrientation" checked>
<label class="form-check-label" for="checkOrientation">checkOrientation</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="modal" type="checkbox" name="modal" checked>
<label class="form-check-label" for="modal">modal</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="guides" type="checkbox" name="guides" checked>
<label class="form-check-label" for="guides">guides</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="center" type="checkbox" name="center" checked>
<label class="form-check-label" for="center">center</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="highlight" type="checkbox" name="highlight" checked>
<label class="form-check-label" for="highlight">highlight</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="background" type="checkbox" name="background" checked>
<label class="form-check-label" for="background">background</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="autoCrop" type="checkbox" name="autoCrop" checked>
<label class="form-check-label" for="autoCrop">autoCrop</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="movable" type="checkbox" name="movable" checked>
<label class="form-check-label" for="movable">movable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="rotatable" type="checkbox" name="rotatable" checked>
<label class="form-check-label" for="rotatable">rotatable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="scalable" type="checkbox" name="scalable" checked>
<label class="form-check-label" for="scalable">scalable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="zoomable" type="checkbox" name="zoomable" checked>
<label class="form-check-label" for="zoomable">zoomable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="zoomOnTouch" type="checkbox" name="zoomOnTouch" checked>
<label class="form-check-label" for="zoomOnTouch">zoomOnTouch</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="zoomOnWheel" type="checkbox" name="zoomOnWheel" checked>
<label class="form-check-label" for="zoomOnWheel">zoomOnWheel</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="cropBoxMovable" type="checkbox" name="cropBoxMovable" checked>
<label class="form-check-label" for="cropBoxMovable">cropBoxMovable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="cropBoxResizable" type="checkbox" name="cropBoxResizable" checked>
<label class="form-check-label" for="cropBoxResizable">cropBoxResizable</label>
</div>
</li>
<li class="dropdown-item">
<div class="form-check">
<input class="form-check-input" id="toggleDragModeOnDblclick" type="checkbox" name="toggleDragModeOnDblclick" checked>
<label class="form-check-label" for="toggleDragModeOnDblclick">toggleDragModeOnDblclick</label>
</div>
</li>
</ul>
</div><!-- /.dropdown -->
<a class="btn btn-success btn-block" data-toggle="tooltip" href="https://fengyuanchen.github.io/photo-editor" title="An advanced example of Cropper.js">Photo Editor</a>
</div><!-- /.docs-toggles -->
</div>
</div>
<!-- Footer -->
<footer class="footer">
<div class="container">
<p class="heart"></p>
<nav class="nav flex-wrap justify-content-center mb-3">
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs">GitHub</a>
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs/tree/master/examples">Examples</a>
<a class="nav-link" href="https://github.com/fengyuanchen/cropperjs/blob/master/LICENSE">License</a>
<a class="nav-link" href="http://chenfengyuan.com">About</a>
</nav>
</div>
</footer>
<!-- Scripts -->
<script src="https://code.jquery.com/jquery-3.2.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.3/js/bootstrap.bundle.min.js"></script>
<script src="https://fengyuanchen.github.io/js/common.js"></script>
<script src="js/cropper.js"></script>
<script src="js/main.js"></script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,298 @@
window.onload = function () {
'use strict';
var Cropper = window.Cropper;
var URL = window.URL || window.webkitURL;
var container = document.querySelector('.img-container');
var image = container.getElementsByTagName('img').item(0);
var download = document.getElementById('download');
var actions = document.getElementById('actions');
var dataX = document.getElementById('dataX');
var dataY = document.getElementById('dataY');
var dataHeight = document.getElementById('dataHeight');
var dataWidth = document.getElementById('dataWidth');
var dataRotate = document.getElementById('dataRotate');
var dataScaleX = document.getElementById('dataScaleX');
var dataScaleY = document.getElementById('dataScaleY');
var options = {
aspectRatio: 16 / 9,
preview: '.img-preview',
ready: function (e) {
console.log(e.type);
},
cropstart: function (e) {
console.log(e.type, e.detail.action);
},
cropmove: function (e) {
console.log(e.type, e.detail.action);
},
cropend: function (e) {
console.log(e.type, e.detail.action);
},
crop: function (e) {
var data = e.detail;
console.log(e.type);
dataX.value = Math.round(data.x);
dataY.value = Math.round(data.y);
dataHeight.value = Math.round(data.height);
dataWidth.value = Math.round(data.width);
dataRotate.value = typeof data.rotate !== 'undefined' ? data.rotate : '';
dataScaleX.value = typeof data.scaleX !== 'undefined' ? data.scaleX : '';
dataScaleY.value = typeof data.scaleY !== 'undefined' ? data.scaleY : '';
},
zoom: function (e) {
console.log(e.type, e.detail.ratio);
}
};
var cropper = new Cropper(image, options);
var originalImageURL = image.src;
var uploadedImageType = 'image/jpeg';
var uploadedImageURL;
// Tooltip
$('[data-toggle="tooltip"]').tooltip();
// Buttons
if (!document.createElement('canvas').getContext) {
$('button[data-method="getCroppedCanvas"]').prop('disabled', true);
}
if (typeof document.createElement('cropper').style.transition === 'undefined') {
$('button[data-method="rotate"]').prop('disabled', true);
$('button[data-method="scale"]').prop('disabled', true);
}
// Download
if (typeof download.download === 'undefined') {
download.className += ' disabled';
}
// Options
actions.querySelector('.docs-toggles').onchange = function (event) {
var e = event || window.event;
var target = e.target || e.srcElement;
var cropBoxData;
var canvasData;
var isCheckbox;
var isRadio;
if (!cropper) {
return;
}
if (target.tagName.toLowerCase() === 'label') {
target = target.querySelector('input');
}
isCheckbox = target.type === 'checkbox';
isRadio = target.type === 'radio';
if (isCheckbox || isRadio) {
if (isCheckbox) {
options[target.name] = target.checked;
cropBoxData = cropper.getCropBoxData();
canvasData = cropper.getCanvasData();
options.ready = function () {
console.log('ready');
cropper.setCropBoxData(cropBoxData).setCanvasData(canvasData);
};
} else {
options[target.name] = target.value;
options.ready = function () {
console.log('ready');
};
}
// Restart
cropper.destroy();
cropper = new Cropper(image, options);
}
};
// Methods
actions.querySelector('.docs-buttons').onclick = function (event) {
var e = event || window.event;
var target = e.target || e.srcElement;
var cropped;
var result;
var input;
var data;
if (!cropper) {
return;
}
while (target !== this) {
if (target.getAttribute('data-method')) {
break;
}
target = target.parentNode;
}
if (target === this || target.disabled || target.className.indexOf('disabled') > -1) {
return;
}
data = {
method: target.getAttribute('data-method'),
target: target.getAttribute('data-target'),
option: target.getAttribute('data-option') || undefined,
secondOption: target.getAttribute('data-second-option') || undefined
};
cropped = cropper.cropped;
if (data.method) {
if (typeof data.target !== 'undefined') {
input = document.querySelector(data.target);
if (!target.hasAttribute('data-option') && data.target && input) {
try {
data.option = JSON.parse(input.value);
} catch (e) {
console.log(e.message);
}
}
}
switch (data.method) {
case 'rotate':
if (cropped && options.viewMode > 0) {
cropper.clear();
}
break;
case 'getCroppedCanvas':
try {
data.option = JSON.parse(data.option);
} catch (e) {
console.log(e.message);
}
if (uploadedImageType === 'image/jpeg') {
if (!data.option) {
data.option = {};
}
data.option.fillColor = '#fff';
}
break;
}
result = cropper[data.method](data.option, data.secondOption);
switch (data.method) {
case 'rotate':
if (cropped && options.viewMode > 0) {
cropper.crop();
}
break;
case 'scaleX':
case 'scaleY':
target.setAttribute('data-option', -data.option);
break;
case 'getCroppedCanvas':
if (result) {
// Bootstrap's Modal
$('#getCroppedCanvasModal').modal().find('.modal-body').html(result);
if (!download.disabled) {
download.href = result.toDataURL(uploadedImageType);
}
}
break;
case 'destroy':
cropper = null;
if (uploadedImageURL) {
URL.revokeObjectURL(uploadedImageURL);
uploadedImageURL = '';
image.src = originalImageURL;
}
break;
}
if (typeof result === 'object' && result !== cropper && input) {
try {
input.value = JSON.stringify(result);
} catch (e) {
console.log(e.message);
}
}
}
};
document.body.onkeydown = function (event) {
var e = event || window.event;
if (!cropper || this.scrollTop > 300) {
return;
}
switch (e.keyCode) {
case 37:
e.preventDefault();
cropper.move(-1, 0);
break;
case 38:
e.preventDefault();
cropper.move(0, -1);
break;
case 39:
e.preventDefault();
cropper.move(1, 0);
break;
case 40:
e.preventDefault();
cropper.move(0, 1);
break;
}
};
// Import image
var inputImage = document.getElementById('inputImage');
if (URL) {
inputImage.onchange = function () {
var files = this.files;
var file;
if (cropper && files && files.length) {
file = files[0];
if (/^image\/\w+/.test(file.type)) {
uploadedImageType = file.type;
if (uploadedImageURL) {
URL.revokeObjectURL(uploadedImageURL);
}
image.src = uploadedImageURL = URL.createObjectURL(file);
cropper.destroy();
cropper = new Cropper(image, options);
inputImage.value = null;
} else {
window.alert('Please choose an image file.');
}
}
};
} else {
inputImage.disabled = true;
inputImage.parentNode.className += ' disabled';
}
};

View File

@ -0,0 +1,71 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper with a range of aspect ratio</h1>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var minAspectRatio = 0.5;
var maxAspectRatio = 1.5;
var cropper = new Cropper(image, {
ready: function () {
var cropper = this.cropper;
var containerData = cropper.getContainerData();
var cropBoxData = cropper.getCropBoxData();
var aspectRatio = cropBoxData.width / cropBoxData.height;
var newCropBoxWidth;
if (aspectRatio < minAspectRatio || aspectRatio > maxAspectRatio) {
newCropBoxWidth = cropBoxData.height * ((minAspectRatio + maxAspectRatio) / 2);
cropper.setCropBoxData({
left: (containerData.width - newCropBoxWidth) / 2,
width: newCropBoxWidth
});
}
},
cropmove: function () {
var cropper = this.cropper;
var cropBoxData = cropper.getCropBoxData();
var aspectRatio = cropBoxData.width / cropBoxData.height;
if (aspectRatio < minAspectRatio) {
cropper.setCropBoxData({
width: cropBoxData.height * minAspectRatio
});
} else if (aspectRatio > maxAspectRatio) {
cropper.setCropBoxData({
width: cropBoxData.height * maxAspectRatio
});
}
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
.cropper-view-box,
.cropper-face {
border-radius: 50%;
}
</style>
</head>
<body>
<div class="container">
<h1>Crop a round image</h1>
<h3>Image</h3>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
<h3>Result</h3>
<p>
<button type="button" id="button">Crop</button>
</p>
<div id="result"></div>
</div>
<script src="../dist/cropper.js"></script>
<script>
(function () {
function getRoundedCanvas(sourceCanvas) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var width = sourceCanvas.width;
var height = sourceCanvas.height;
canvas.width = width;
canvas.height = height;
context.imageSmoothingEnabled = true;
context.drawImage(sourceCanvas, 0, 0, width, height);
context.globalCompositeOperation = 'destination-in';
context.beginPath();
context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true);
context.fill();
return canvas;
}
window.addEventListener('DOMContentLoaded', function () {
var image = document.getElementById('image');
var button = document.getElementById('button');
var result = document.getElementById('result');
var croppable = false;
var cropper = new Cropper(image, {
aspectRatio: 1,
viewMode: 1,
ready: function () {
croppable = true;
}
});
button.onclick = function () {
var croppedCanvas;
var roundedCanvas;
var roundedImage;
if (!croppable) {
return;
}
// Crop
croppedCanvas = cropper.getCroppedCanvas();
// Round
roundedCanvas = getRoundedCanvas(croppedCanvas);
// Show
roundedImage = document.createElement('img');
roundedImage.src = roundedCanvas.toDataURL()
result.innerHTML = '';
result.appendChild(roundedImage);
};
});
})();
</script>
</body>
</html>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Crop a cross origin image</h1>
<p>A cross origin image with a <code>crossorigin</code> attribute and an available <code>Access-Control-Allow-Origin</code> response header can be cropped by Cropper.</p>
<h3>Image</h3>
<div>
<img id="image" src="https://raw.githubusercontent.com/fengyuanchen/cropperjs/master/docs/images/picture.jpg" crossorigin>
</div>
<h3>Result</h3>
<div id="result"></div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var result = document.querySelector('#result');
var cropper = new Cropper(image, {
ready: function () {
var image = new Image();
image.src = cropper.getCroppedCanvas().toDataURL('image/jpeg');
result.appendChild(image);
},
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Crop on canvas with Cropper</h1>
<h3>Image</h3>
<div class="img-container">
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
<h3>Canvas</h3>
<div class="img-container">
<canvas id="canvas"></canvas>
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
function start() {
var width = this.offsetWidth;
var height = this.offsetHeight;
var cropper;
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(
this,
0, 0, this.naturalWidth, this.naturalHeight,
0, 0, width, height
);
cropper = new Cropper(canvas);
}
window.addEventListener('DOMContentLoaded', function () {
var canvas = document.getElementById('canvas');
var image = document.getElementById('image');
if (image.complete) {
start.call(image);
} else {
image.onload = start;
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,76 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css">
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.img-container img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper in a Bootstrap modal</h1>
<!-- Button trigger modal -->
<button type="button" class="btn btn-primary" data-target="#modal" data-toggle="modal">
Launch the demo
</button>
<!-- Modal -->
<div class="modal fade" id="modal" role="dialog" aria-labelledby="modalLabel" tabindex="-1">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="modalLabel">Cropper</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="img-container">
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
</div>
</div>
</div>
</div>
</div>
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/js/bootstrap.min.js"></script>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.getElementById('image');
var cropBoxData;
var canvasData;
var cropper;
$('#modal').on('shown.bs.modal', function () {
cropper = new Cropper(image, {
autoCropArea: 0.5,
ready: function () {
// Strict mode: set crop box data first
cropper.setCropBoxData(cropBoxData).setCanvasData(canvasData);
}
});
}).on('hidden.bs.modal', function () {
cropBoxData = cropper.getCropBoxData();
canvasData = cropper.getCanvasData();
cropper.destroy();
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 960px;
margin: 20px auto;
}
img {
max-width: 100%;
}
.row,
.preview {
overflow: hidden;
}
.col {
float: left;
}
.col-6 {
width: 50%;
}
.col-3 {
width: 25%;
}
.col-2 {
width: 16.7%;
}
.col-1 {
width: 8.3%;
}
</style>
</head>
<body>
<div class="container">
<h1>Customize preview for Cropper</h1>
<div class="row">
<div class="col col-6">
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
<div class="col col-3">
<div class="preview"></div>
</div>
<div class="col col-2">
<div class="preview"></div>
</div>
<div class="col col-1">
<div class="preview"></div>
</div>
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
function each(arr, callback) {
var length = arr.length;
var i;
for (i = 0; i < length; i++) {
callback.call(arr, arr[i], i, arr);
}
return arr;
}
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var previews = document.querySelectorAll('.preview');
var cropper = new Cropper(image, {
ready: function () {
var clone = this.cloneNode();
clone.className = ''
clone.style.cssText = (
'display: block;' +
'width: 100%;' +
'min-width: 0;' +
'min-height: 0;' +
'max-width: none;' +
'max-height: none;'
);
each(previews, function (elem) {
elem.appendChild(clone.cloneNode());
});
},
crop: function (e) {
var data = e.detail;
var cropper = this.cropper;
var imageData = cropper.getImageData();
var previewAspectRatio = data.width / data.height;
each(previews, function (elem) {
var previewImage = elem.getElementsByTagName('img').item(0);
var previewWidth = elem.offsetWidth;
var previewHeight = previewWidth / previewAspectRatio;
var imageScaledRatio = data.width / previewWidth;
elem.style.height = previewHeight + 'px';
previewImage.style.width = imageData.naturalWidth / imageScaledRatio + 'px';
previewImage.style.height = imageData.naturalHeight / imageScaledRatio + 'px';
previewImage.style.marginLeft = -data.x / imageScaledRatio + 'px';
previewImage.style.marginTop = -data.y / imageScaledRatio + 'px';
});
}
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper with fixed crop box</h1>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var cropper = new Cropper(image, {
dragMode: 'move',
aspectRatio: 16 / 9,
autoCropArea: 0.65,
restore: false,
guides: false,
center: false,
highlight: false,
cropBoxMovable: false,
cropBoxResizable: false,
toggleDragModeOnDblclick: false,
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper with full crop box</h1>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var cropper = new Cropper(image, {
viewMode: 3,
dragMode: 'move',
autoCropArea: 1,
restore: false,
modal: false,
guides: false,
highlight: false,
cropBoxMovable: false,
cropBoxResizable: false,
toggleDragModeOnDblclick: false,
});
});
</script>
</body>
</html>

View File

@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
.cropper-view-box,
.cropper-face {
background-color: black;
opacity: 1;
}
</style>
</head>
<body>
<div class="container">
<h1>Mask an image (Redaction)</h1>
<h3>Image</h3>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
<h3>Result</h3>
<p><button type="button" id="button">Mask</button></p>
<div id="result"></div>
</div>
<script src="../dist/cropper.js"></script>
<script>
(function () {
function getMaskedCanvas(sourceCanvas, sourceImage, cropper) {
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
var maskWidth = cropper.getData().width;
var maskHeight = cropper.getData().height;
var maskTop = cropper.getData().y;
var maskLeft = cropper.getData().x;
var imageWidth = cropper.getImageData().naturalWidth;
var imageHeight = cropper.getImageData().naturalHeight;
var imageLeft = cropper.getImageData().left;
var imageTop = cropper.getImageData().top;
var imageAspect = cropper.getImageData().aspectRatio;
canvas.width = imageWidth;
canvas.height = imageHeight;
// debug
console.log('Image Width: ' + imageWidth + ' Image Height: ' + imageHeight + ' Image Aspect Ratio: ' + imageAspect );
console.log('Image Left: ' + imageLeft + ' Image Top: ' + imageTop );
console.log('Mask Width: ' + maskWidth + ' Mask Height: ' + maskHeight + ' Mask Left: ' + maskLeft + ' Mask Top: ' + maskTop);
context.imageSmoothingEnabled = true;
context.drawImage(image, 0, 0, imageWidth, imageHeight);
context.fillRect(maskLeft, maskTop, maskWidth, maskHeight);
return canvas;
}
window.addEventListener('DOMContentLoaded', function () {
var image = document.getElementById('image');
var button = document.getElementById('button');
var result = document.getElementById('result');
var croppable = false;
var cropper = new Cropper(image, {
viewMode: 0,
guides: true,
center: true,
highlight: true,
cropBoxMovable: true,
cropBoxResizable: true,
ready: function () {
croppable = true;
}
});
button.onclick = function () {
var croppedCanvas;
var roundedCanvas;
var roundedImage;
if (!croppable) {
return;
}
// Crop
croppedCanvas = cropper.getCroppedCanvas();
// Mask
maskedCanvas = getMaskedCanvas(croppedCanvas, image, cropper);
// Show
maskedImage = document.createElement('img');
maskedImage.src = maskedCanvas.toDataURL()
result.innerHTML = '';
result.appendChild(maskedImage);
};
});
})();
</script>
</body>
</html>

View File

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
max-width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Multiple Croppers</h1>
<h3>Cropper</h3>
<div>
<img src="../docs/images/picture.jpg" alt="Picture">
</div>
<h3>Another</h3>
<div>
<img src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var images = document.querySelectorAll('img');
var length = images.length;
var croppers = [];
var i;
for (i = 0; i < length; i++) {
croppers.push(new Cropper(images[i]));
}
});
</script>
</body>
</html>

View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="../dist/cropper.css">
<style>
.container {
max-width: 640px;
margin: 20px auto;
}
img {
width: 100%;
}
</style>
</head>
<body>
<div class="container">
<h1>Cropper with responsive container</h1>
<p>
<button type="button" id="replace">Replace Image</button>
</p>
<div>
<img id="image" src="../docs/images/picture.jpg" alt="Picture">
</div>
</div>
<script src="../dist/cropper.js"></script>
<script>
window.addEventListener('DOMContentLoaded', function () {
var image = document.querySelector('#image');
var cropper = new Cropper(image, {
movable: false,
zoomable: false,
rotatable: false,
scalable: false
});
document.getElementById('replace').onclick = function () {
cropper.replace('../docs/images/picture-2.jpg');
};
});
</script>
</body>
</html>

9111
library/cropperjs/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
{
"name": "cropperjs",
"description": "JavaScript image cropper.",
"version": "1.2.2",
"main": "dist/cropper.common.js",
"module": "dist/cropper.esm.js",
"browser": "dist/cropper.js",
"style": "dist/cropper.css",
"repository": "fengyuanchen/cropperjs",
"homepage": "https://fengyuanchen.github.io/cropperjs",
"license": "MIT",
"author": {
"name": "Chen Fengyuan",
"url": "http://chenfengyuan.com"
},
"files": [
"src",
"dist"
],
"keywords": [
"image",
"crop",
"cropping",
"move",
"zoom",
"rotate",
"scale",
"cropper",
"cropperjs",
"cropper.js",
"html",
"css",
"javascript",
"front-end",
"web",
"development"
],
"scripts": {
"build": "npm run build:css && npm run build:js",
"build:css": "postcss src/css/cropper.css -o dist/cropper.css --no-map",
"build:js": "rollup -c",
"clear": "del-cli dist",
"compress": "npm run compress:css && npm run compress:js",
"compress:css": "postcss dist/cropper.css -u cssnano -o dist/cropper.min.css --no-map",
"compress:js": "uglifyjs dist/cropper.js -o dist/cropper.min.js -c -m --comments /^!/",
"copy": "cpy dist/cropper.css docs/css",
"lint": "eslint src/js --fix",
"release": "npm run clear && npm run lint && npm run build && npm run compress && npm run copy && npm test",
"start": "npm-run-all --parallel watch:*",
"test": "node-qunit-phantomjs test/index.html --timeout 10",
"watch:css": "postcss src/css/cropper.css -o docs/css/cropper.css -m -w",
"watch:js": "rollup -c -m -w"
},
"devDependencies": {
"babel-core": "^6.26.0",
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-env": "^1.6.1",
"cpy-cli": "^1.0.1",
"cssnano": "^3.10.0",
"del-cli": "^1.1.0",
"eslint": "^4.14.0",
"eslint-config-airbnb-base": "^12.1.0",
"eslint-plugin-import": "^2.8.0",
"node-qunit-phantomjs": "^2.0.0",
"npm-run-all": "^4.1.2",
"postcss-cli": "^4.1.1",
"postcss-cssnext": "^3.0.2",
"postcss-header": "^1.0.0",
"postcss-url": "^7.3.0",
"rollup": "^0.53.3",
"rollup-plugin-babel": "^3.0.3",
"rollup-watch": "^4.3.1",
"stylefmt": "^6.0.0",
"uglify-js": "^3.3.4"
}
}

View File

@ -0,0 +1,14 @@
const rollupConfig = require('./rollup.config');
module.exports = {
plugins: {
'postcss-cssnext': {},
'postcss-url': {
url: 'inline',
},
'postcss-header': {
header: rollupConfig.banner,
},
stylefmt: {},
},
};

View File

@ -0,0 +1,50 @@
const babel = require('rollup-plugin-babel');
const pkg = require('./package');
const now = new Date();
const banner = `/*!
* Cropper.js v${pkg.version}
* https://github.com/${pkg.repository}
*
* Copyright (c) 2015-${now.getFullYear()} ${pkg.author.name}
* Released under the ${pkg.license} license
*
* Date: ${now.toISOString()}
*/
`;
module.exports = {
// Export banner for PostCSS
banner,
input: 'src/js/cropper.js',
output: [
{
banner,
file: 'dist/cropper.js',
format: 'umd',
name: 'Cropper',
},
{
banner,
file: 'dist/cropper.common.js',
format: 'cjs',
},
{
banner,
file: 'dist/cropper.esm.js',
format: 'es',
},
{
banner,
file: 'docs/js/cropper.js',
format: 'umd',
name: 'Cropper',
},
],
plugins: [
babel({
exclude: 'node_modules/**',
plugins: ['external-helpers'],
}),
],
};

View File

@ -0,0 +1,290 @@
:root {
--blue: #39f;
}
.cropper-container {
direction: ltr;
font-size: 0;
line-height: 0;
position: relative;
touch-action: none;
user-select: none;
& img {
/* Avoid margin top issue (Occur only when margin-top <= -height) */
display: block;
height: 100%;
image-orientation: 0deg;
max-height: none !important;
max-width: none !important;
min-height: 0 !important;
min-width: 0 !important;
width: 100%;
}
}
.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.cropper-wrap-box,
.cropper-canvas {
overflow: hidden;
}
.cropper-drag-box {
background-color: #fff;
opacity: 0;
}
.cropper-modal {
background-color: #000;
opacity: .5;
}
.cropper-view-box {
display: block;
height: 100%;
outline-color: color(var(--blue) alpha(75%));
outline: 1px solid var(--blue);
overflow: hidden;
width: 100%;
}
.cropper-dashed {
border: 0 dashed #eee;
display: block;
opacity: .5;
position: absolute;
&.dashed-h {
border-bottom-width: 1px;
border-top-width: 1px;
height: calc(100 / 3)%;
left: 0;
top: calc(100 / 3)%;
width: 100%;
}
&.dashed-v {
border-left-width: 1px;
border-right-width: 1px;
height: 100%;
left: calc(100 / 3)%;
top: 0;
width: calc(100 / 3)%;
}
}
.cropper-center {
display: block;
height: 0;
left: 50%;
opacity: .75;
position: absolute;
top: 50%;
width: 0;
&:before,
&:after {
background-color: #eee;
content: ' ';
display: block;
position: absolute;
}
&:before {
height: 1px;
left: -3px;
top: 0;
width: 7px;
}
&:after {
height: 7px;
left: 0;
top: -3px;
width: 1px;
}
}
.cropper-face,
.cropper-line,
.cropper-point {
display: block;
height: 100%;
opacity: .1;
position: absolute;
width: 100%;
}
.cropper-face {
background-color: #fff;
left: 0;
top: 0;
}
.cropper-line {
background-color: var(--blue);
&.line-e {
cursor: ew-resize;
right: -3px;
top: 0;
width: 5px;
}
&.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}
&.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}
&.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}
}
.cropper-point {
background-color: var(--blue);
height: 5px;
opacity: .75;
width: 5px;
&.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}
&.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}
&.point-w {
cursor: ew-resize;
left: -3px;
margin-top: -3px;
top: 50%;
}
&.point-s {
bottom: -3px;
cursor: s-resize;
left: 50%;
margin-left: -3px;
}
&.point-ne {
cursor: nesw-resize;
right: -3px;
top: -3px;
}
&.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}
&.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}
&.point-se {
bottom: -3px;
cursor: nwse-resize;
height: 20px;
opacity: 1;
right: -3px;
width: 20px;
@media (min-width: 768px) {
height: 15px;
width: 15px;
}
@media (min-width: 992px) {
height: 10px;
width: 10px;
}
@media (min-width: 1200px) {
height: 5px;
opacity: .75;
width: 5px;
}
}
&.point-se:before {
background-color: var(--blue);
bottom: -50%;
content: ' ';
display: block;
height: 200%;
opacity: 0;
position: absolute;
right: -50%;
width: 200%;
}
}
.cropper-invisible {
opacity: 0;
}
.cropper-bg {
background-image: url('../images/bg.png');
}
.cropper-hide {
display: block;
height: 0;
position: absolute;
width: 0;
}
.cropper-hidden {
display: none !important;
}
.cropper-move {
cursor: move;
}
.cropper-crop {
cursor: crosshair;
}
.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
cursor: not-allowed;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

View File

@ -0,0 +1,471 @@
import {
ACTION_ALL,
ACTION_CROP,
ACTION_EAST,
ACTION_MOVE,
ACTION_NORTH,
ACTION_NORTH_EAST,
ACTION_NORTH_WEST,
ACTION_SOUTH,
ACTION_SOUTH_EAST,
ACTION_SOUTH_WEST,
ACTION_WEST,
ACTION_ZOOM,
CLASS_HIDDEN,
} from './constants';
import {
each,
getMaxZoomRatio,
getOffset,
removeClass,
} from './utilities';
export default {
change(e) {
const {
options,
canvasData,
containerData,
cropBoxData,
pointers,
} = this;
let { action } = this;
let { aspectRatio } = options;
let {
left,
top,
width,
height,
} = cropBoxData;
const right = left + width;
const bottom = top + height;
let minLeft = 0;
let minTop = 0;
let maxWidth = containerData.width;
let maxHeight = containerData.height;
let renderable = true;
let offset;
// Locking aspect ratio in "free mode" by holding shift key
if (!aspectRatio && e.shiftKey) {
aspectRatio = width && height ? width / height : 1;
}
if (this.limited) {
({ minLeft, minTop } = cropBoxData);
maxWidth = minLeft + Math.min(
containerData.width,
canvasData.width,
canvasData.left + canvasData.width,
);
maxHeight = minTop + Math.min(
containerData.height,
canvasData.height,
canvasData.top + canvasData.height,
);
}
const pointer = pointers[Object.keys(pointers)[0]];
const range = {
x: pointer.endX - pointer.startX,
y: pointer.endY - pointer.startY,
};
const check = (side) => {
switch (side) {
case ACTION_EAST:
if (right + range.x > maxWidth) {
range.x = maxWidth - right;
}
break;
case ACTION_WEST:
if (left + range.x < minLeft) {
range.x = minLeft - left;
}
break;
case ACTION_NORTH:
if (top + range.y < minTop) {
range.y = minTop - top;
}
break;
case ACTION_SOUTH:
if (bottom + range.y > maxHeight) {
range.y = maxHeight - bottom;
}
break;
default:
}
};
switch (action) {
// Move crop box
case ACTION_ALL:
left += range.x;
top += range.y;
break;
// Resize crop box
case ACTION_EAST:
if (range.x >= 0 && (right >= maxWidth || (aspectRatio &&
(top <= minTop || bottom >= maxHeight)))) {
renderable = false;
break;
}
check(ACTION_EAST);
width += range.x;
if (aspectRatio) {
height = width / aspectRatio;
top -= (range.x / aspectRatio) / 2;
}
if (width < 0) {
action = ACTION_WEST;
width = 0;
}
break;
case ACTION_NORTH:
if (range.y <= 0 && (top <= minTop || (aspectRatio &&
(left <= minLeft || right >= maxWidth)))) {
renderable = false;
break;
}
check(ACTION_NORTH);
height -= range.y;
top += range.y;
if (aspectRatio) {
width = height * aspectRatio;
left += (range.y * aspectRatio) / 2;
}
if (height < 0) {
action = ACTION_SOUTH;
height = 0;
}
break;
case ACTION_WEST:
if (range.x <= 0 && (left <= minLeft || (aspectRatio &&
(top <= minTop || bottom >= maxHeight)))) {
renderable = false;
break;
}
check(ACTION_WEST);
width -= range.x;
left += range.x;
if (aspectRatio) {
height = width / aspectRatio;
top += (range.x / aspectRatio) / 2;
}
if (width < 0) {
action = ACTION_EAST;
width = 0;
}
break;
case ACTION_SOUTH:
if (range.y >= 0 && (bottom >= maxHeight || (aspectRatio &&
(left <= minLeft || right >= maxWidth)))) {
renderable = false;
break;
}
check(ACTION_SOUTH);
height += range.y;
if (aspectRatio) {
width = height * aspectRatio;
left -= (range.y * aspectRatio) / 2;
}
if (height < 0) {
action = ACTION_NORTH;
height = 0;
}
break;
case ACTION_NORTH_EAST:
if (aspectRatio) {
if (range.y <= 0 && (top <= minTop || right >= maxWidth)) {
renderable = false;
break;
}
check(ACTION_NORTH);
height -= range.y;
top += range.y;
width = height * aspectRatio;
} else {
check(ACTION_NORTH);
check(ACTION_EAST);
if (range.x >= 0) {
if (right < maxWidth) {
width += range.x;
} else if (range.y <= 0 && top <= minTop) {
renderable = false;
}
} else {
width += range.x;
}
if (range.y <= 0) {
if (top > minTop) {
height -= range.y;
top += range.y;
}
} else {
height -= range.y;
top += range.y;
}
}
if (width < 0 && height < 0) {
action = ACTION_SOUTH_WEST;
height = 0;
width = 0;
} else if (width < 0) {
action = ACTION_NORTH_WEST;
width = 0;
} else if (height < 0) {
action = ACTION_SOUTH_EAST;
height = 0;
}
break;
case ACTION_NORTH_WEST:
if (aspectRatio) {
if (range.y <= 0 && (top <= minTop || left <= minLeft)) {
renderable = false;
break;
}
check(ACTION_NORTH);
height -= range.y;
top += range.y;
width = height * aspectRatio;
left += range.y * aspectRatio;
} else {
check(ACTION_NORTH);
check(ACTION_WEST);
if (range.x <= 0) {
if (left > minLeft) {
width -= range.x;
left += range.x;
} else if (range.y <= 0 && top <= minTop) {
renderable = false;
}
} else {
width -= range.x;
left += range.x;
}
if (range.y <= 0) {
if (top > minTop) {
height -= range.y;
top += range.y;
}
} else {
height -= range.y;
top += range.y;
}
}
if (width < 0 && height < 0) {
action = ACTION_SOUTH_EAST;
height = 0;
width = 0;
} else if (width < 0) {
action = ACTION_NORTH_EAST;
width = 0;
} else if (height < 0) {
action = ACTION_SOUTH_WEST;
height = 0;
}
break;
case ACTION_SOUTH_WEST:
if (aspectRatio) {
if (range.x <= 0 && (left <= minLeft || bottom >= maxHeight)) {
renderable = false;
break;
}
check(ACTION_WEST);
width -= range.x;
left += range.x;
height = width / aspectRatio;
} else {
check(ACTION_SOUTH);
check(ACTION_WEST);
if (range.x <= 0) {
if (left > minLeft) {
width -= range.x;
left += range.x;
} else if (range.y >= 0 && bottom >= maxHeight) {
renderable = false;
}
} else {
width -= range.x;
left += range.x;
}
if (range.y >= 0) {
if (bottom < maxHeight) {
height += range.y;
}
} else {
height += range.y;
}
}
if (width < 0 && height < 0) {
action = ACTION_NORTH_EAST;
height = 0;
width = 0;
} else if (width < 0) {
action = ACTION_SOUTH_EAST;
width = 0;
} else if (height < 0) {
action = ACTION_NORTH_WEST;
height = 0;
}
break;
case ACTION_SOUTH_EAST:
if (aspectRatio) {
if (range.x >= 0 && (right >= maxWidth || bottom >= maxHeight)) {
renderable = false;
break;
}
check(ACTION_EAST);
width += range.x;
height = width / aspectRatio;
} else {
check(ACTION_SOUTH);
check(ACTION_EAST);
if (range.x >= 0) {
if (right < maxWidth) {
width += range.x;
} else if (range.y >= 0 && bottom >= maxHeight) {
renderable = false;
}
} else {
width += range.x;
}
if (range.y >= 0) {
if (bottom < maxHeight) {
height += range.y;
}
} else {
height += range.y;
}
}
if (width < 0 && height < 0) {
action = ACTION_NORTH_WEST;
height = 0;
width = 0;
} else if (width < 0) {
action = ACTION_SOUTH_WEST;
width = 0;
} else if (height < 0) {
action = ACTION_NORTH_EAST;
height = 0;
}
break;
// Move canvas
case ACTION_MOVE:
this.move(range.x, range.y);
renderable = false;
break;
// Zoom canvas
case ACTION_ZOOM:
this.zoom(getMaxZoomRatio(pointers), e);
renderable = false;
break;
// Create crop box
case ACTION_CROP:
if (!range.x || !range.y) {
renderable = false;
break;
}
offset = getOffset(this.cropper);
left = pointer.startX - offset.left;
top = pointer.startY - offset.top;
width = cropBoxData.minWidth;
height = cropBoxData.minHeight;
if (range.x > 0) {
action = range.y > 0 ? ACTION_SOUTH_EAST : ACTION_NORTH_EAST;
} else if (range.x < 0) {
left -= width;
action = range.y > 0 ? ACTION_SOUTH_WEST : ACTION_NORTH_WEST;
}
if (range.y < 0) {
top -= height;
}
// Show the crop box if is hidden
if (!this.cropped) {
removeClass(this.cropBox, CLASS_HIDDEN);
this.cropped = true;
if (this.limited) {
this.limitCropBox(true, true);
}
}
break;
default:
}
if (renderable) {
cropBoxData.width = width;
cropBoxData.height = height;
cropBoxData.left = left;
cropBoxData.top = top;
this.action = action;
this.renderCropBox();
}
// Override
each(pointers, (p) => {
p.startX = p.endX;
p.startY = p.endY;
});
},
};

View File

@ -0,0 +1,56 @@
export const WINDOW = typeof window !== 'undefined' ? window : {};
export const NAMESPACE = 'cropper';
// Actions
export const ACTION_ALL = 'all';
export const ACTION_CROP = 'crop';
export const ACTION_MOVE = 'move';
export const ACTION_ZOOM = 'zoom';
export const ACTION_EAST = 'e';
export const ACTION_WEST = 'w';
export const ACTION_SOUTH = 's';
export const ACTION_NORTH = 'n';
export const ACTION_NORTH_EAST = 'ne';
export const ACTION_NORTH_WEST = 'nw';
export const ACTION_SOUTH_EAST = 'se';
export const ACTION_SOUTH_WEST = 'sw';
// Classes
export const CLASS_CROP = `${NAMESPACE}-crop`;
export const CLASS_DISABLED = `${NAMESPACE}-disabled`;
export const CLASS_HIDDEN = `${NAMESPACE}-hidden`;
export const CLASS_HIDE = `${NAMESPACE}-hide`;
export const CLASS_INVISIBLE = `${NAMESPACE}-invisible`;
export const CLASS_MODAL = `${NAMESPACE}-modal`;
export const CLASS_MOVE = `${NAMESPACE}-move`;
// Data keys
export const DATA_ACTION = 'action';
export const DATA_PREVIEW = 'preview';
// Drag modes
export const DRAG_MODE_CROP = 'crop';
export const DRAG_MODE_MOVE = 'move';
export const DRAG_MODE_NONE = 'none';
// Events
export const EVENT_CROP = 'crop';
export const EVENT_CROP_END = 'cropend';
export const EVENT_CROP_MOVE = 'cropmove';
export const EVENT_CROP_START = 'cropstart';
export const EVENT_DBLCLICK = 'dblclick';
export const EVENT_ERROR = 'error';
export const EVENT_LOAD = 'load';
export const EVENT_POINTER_DOWN = WINDOW.PointerEvent ? 'pointerdown' : 'touchstart mousedown';
export const EVENT_POINTER_MOVE = WINDOW.PointerEvent ? 'pointermove' : 'touchmove mousemove';
export const EVENT_POINTER_UP = WINDOW.PointerEvent ? 'pointerup pointercancel' : 'touchend touchcancel mouseup';
export const EVENT_READY = 'ready';
export const EVENT_RESIZE = 'resize';
export const EVENT_WHEEL = 'wheel mousewheel DOMMouseScroll';
export const EVENT_ZOOM = 'zoom';
// RegExps
export const REGEXP_ACTIONS = /^(?:e|w|s|n|se|sw|ne|nw|all|crop|move|zoom)$/;
export const REGEXP_DATA_URL = /^data:/;
export const REGEXP_DATA_URL_JPEG = /^data:image\/jpeg;base64,/;
export const REGEXP_TAG_NAME = /^(?:img|canvas)$/i;

View File

@ -0,0 +1,421 @@
import DEFAULTS from './defaults';
import TEMPLATE from './template';
import render from './render';
import preview from './preview';
import events from './events';
import handlers from './handlers';
import change from './change';
import methods from './methods';
import {
ACTION_ALL,
CLASS_HIDDEN,
CLASS_HIDE,
CLASS_INVISIBLE,
CLASS_MODAL,
CLASS_MOVE,
DATA_ACTION,
EVENT_CROP,
EVENT_ERROR,
EVENT_LOAD,
EVENT_READY,
NAMESPACE,
REGEXP_DATA_URL,
REGEXP_DATA_URL_JPEG,
REGEXP_TAG_NAME,
WINDOW,
} from './constants';
import {
addClass,
addListener,
addTimestamp,
arrayBufferToDataURL,
dataURLToArrayBuffer,
dispatchEvent,
extend,
getData,
getImageNaturalSizes,
getOrientation,
isCrossOriginURL,
isFunction,
isPlainObject,
parseOrientation,
proxy,
removeClass,
removeListener,
setData,
} from './utilities';
const AnotherCropper = WINDOW.Cropper;
class Cropper {
/**
* Create a new Cropper.
* @param {Element} element - The target element for cropping.
* @param {Object} [options={}] - The configuration options.
*/
constructor(element, options = {}) {
if (!element || !REGEXP_TAG_NAME.test(element.tagName)) {
throw new Error('The first argument is required and must be an <img> or <canvas> element.');
}
this.element = element;
this.options = extend({}, DEFAULTS, isPlainObject(options) && options);
this.complete = false;
this.cropped = false;
this.disabled = false;
this.isImg = false;
this.limited = false;
this.loaded = false;
this.ready = false;
this.replaced = false;
this.wheeling = false;
this.originalUrl = '';
this.canvasData = null;
this.cropBoxData = null;
this.previews = null;
this.pointers = {};
this.init();
}
init() {
const { element } = this;
const tagName = element.tagName.toLowerCase();
let url;
if (getData(element, NAMESPACE)) {
return;
}
setData(element, NAMESPACE, this);
if (tagName === 'img') {
this.isImg = true;
// e.g.: "img/picture.jpg"
url = element.getAttribute('src') || '';
this.originalUrl = url;
// Stop when it's a blank image
if (!url) {
return;
}
// e.g.: "http://example.com/img/picture.jpg"
url = element.src;
} else if (tagName === 'canvas' && window.HTMLCanvasElement) {
url = element.toDataURL();
}
this.load(url);
}
load(url) {
if (!url) {
return;
}
this.url = url;
this.imageData = {};
const { element, options } = this;
if (!options.checkOrientation || !window.ArrayBuffer) {
this.clone();
return;
}
// XMLHttpRequest disallows to open a Data URL in some browsers like IE11 and Safari
if (REGEXP_DATA_URL.test(url)) {
if (REGEXP_DATA_URL_JPEG.test(url)) {
this.read(dataURLToArrayBuffer(url));
} else {
this.clone();
}
return;
}
const xhr = new XMLHttpRequest();
xhr.onerror = () => {
this.clone();
};
xhr.onload = () => {
this.read(xhr.response);
};
// Bust cache when there is a "crossOrigin" property
if (options.checkCrossOrigin && isCrossOriginURL(url) && element.crossOrigin) {
url = addTimestamp(url);
}
xhr.open('get', url);
xhr.responseType = 'arraybuffer';
xhr.withCredentials = element.crossOrigin === 'use-credentials';
xhr.send();
}
read(arrayBuffer) {
const { options, imageData } = this;
const orientation = getOrientation(arrayBuffer);
let rotate = 0;
let scaleX = 1;
let scaleY = 1;
if (orientation > 1) {
this.url = arrayBufferToDataURL(arrayBuffer, 'image/jpeg');
({ rotate, scaleX, scaleY } = parseOrientation(orientation));
}
if (options.rotatable) {
imageData.rotate = rotate;
}
if (options.scalable) {
imageData.scaleX = scaleX;
imageData.scaleY = scaleY;
}
this.clone();
}
clone() {
const { element, url } = this;
let crossOrigin;
let crossOriginUrl;
if (this.options.checkCrossOrigin && isCrossOriginURL(url)) {
({ crossOrigin } = element);
if (crossOrigin) {
crossOriginUrl = url;
} else {
crossOrigin = 'anonymous';
// Bust cache when there is not a "crossOrigin" property
crossOriginUrl = addTimestamp(url);
}
}
this.crossOrigin = crossOrigin;
this.crossOriginUrl = crossOriginUrl;
const image = document.createElement('img');
if (crossOrigin) {
image.crossOrigin = crossOrigin;
}
image.src = crossOriginUrl || url;
const start = proxy(this.start, this);
const stop = proxy(this.stop, this);
this.image = image;
this.onStart = start;
this.onStop = stop;
if (this.isImg) {
if (element.complete) {
this.start();
} else {
addListener(element, EVENT_LOAD, start);
}
} else {
addListener(image, EVENT_LOAD, start);
addListener(image, EVENT_ERROR, stop);
addClass(image, CLASS_HIDE);
element.parentNode.insertBefore(image, element.nextSibling);
}
}
start(event) {
const image = this.isImg ? this.element : this.image;
if (event) {
removeListener(image, EVENT_LOAD, this.onStart);
removeListener(image, EVENT_ERROR, this.onStop);
}
getImageNaturalSizes(image, (naturalWidth, naturalHeight) => {
extend(this.imageData, {
naturalWidth,
naturalHeight,
aspectRatio: naturalWidth / naturalHeight,
});
this.loaded = true;
this.build();
});
}
stop() {
const { image } = this;
removeListener(image, EVENT_LOAD, this.onStart);
removeListener(image, EVENT_ERROR, this.onStop);
image.parentNode.removeChild(image);
this.image = null;
}
build() {
if (!this.loaded) {
return;
}
// Unbuild first when replace
if (this.ready) {
this.unbuild();
}
const { element, options, image } = this;
// Create cropper elements
const container = element.parentNode;
const template = document.createElement('div');
template.innerHTML = TEMPLATE;
const cropper = template.querySelector(`.${NAMESPACE}-container`);
const canvas = cropper.querySelector(`.${NAMESPACE}-canvas`);
const dragBox = cropper.querySelector(`.${NAMESPACE}-drag-box`);
const cropBox = cropper.querySelector(`.${NAMESPACE}-crop-box`);
const face = cropBox.querySelector(`.${NAMESPACE}-face`);
this.container = container;
this.cropper = cropper;
this.canvas = canvas;
this.dragBox = dragBox;
this.cropBox = cropBox;
this.viewBox = cropper.querySelector(`.${NAMESPACE}-view-box`);
this.face = face;
canvas.appendChild(image);
// Hide the original image
addClass(element, CLASS_HIDDEN);
// Inserts the cropper after to the current image
container.insertBefore(cropper, element.nextSibling);
// Show the image if is hidden
if (!this.isImg) {
removeClass(image, CLASS_HIDE);
}
this.initPreview();
this.bind();
options.aspectRatio = Math.max(0, options.aspectRatio) || NaN;
options.viewMode = Math.max(0, Math.min(3, Math.round(options.viewMode))) || 0;
this.cropped = options.autoCrop;
if (options.autoCrop) {
if (options.modal) {
addClass(dragBox, CLASS_MODAL);
}
} else {
addClass(cropBox, CLASS_HIDDEN);
}
if (!options.guides) {
addClass(cropBox.getElementsByClassName(`${NAMESPACE}-dashed`), CLASS_HIDDEN);
}
if (!options.center) {
addClass(cropBox.getElementsByClassName(`${NAMESPACE}-center`), CLASS_HIDDEN);
}
if (options.background) {
addClass(cropper, `${NAMESPACE}-bg`);
}
if (!options.highlight) {
addClass(face, CLASS_INVISIBLE);
}
if (options.cropBoxMovable) {
addClass(face, CLASS_MOVE);
setData(face, DATA_ACTION, ACTION_ALL);
}
if (!options.cropBoxResizable) {
addClass(cropBox.getElementsByClassName(`${NAMESPACE}-line`), CLASS_HIDDEN);
addClass(cropBox.getElementsByClassName(`${NAMESPACE}-point`), CLASS_HIDDEN);
}
this.setDragMode(options.dragMode);
this.render();
this.ready = true;
this.setData(options.data);
// Call the "ready" option asynchronously to keep "image.cropper" is defined
this.completing = setTimeout(() => {
if (isFunction(options.ready)) {
addListener(element, EVENT_READY, options.ready, {
once: true,
});
}
dispatchEvent(element, EVENT_READY);
dispatchEvent(element, EVENT_CROP, this.getData());
this.complete = true;
}, 0);
}
unbuild() {
if (!this.ready) {
return;
}
if (!this.complete) {
clearTimeout(this.completing);
}
this.ready = false;
this.complete = false;
this.initialImageData = null;
// Clear `initialCanvasData` is necessary when replace
this.initialCanvasData = null;
this.initialCropBoxData = null;
this.containerData = null;
this.canvasData = null;
// Clear `cropBoxData` is necessary when replace
this.cropBoxData = null;
this.unbind();
this.resetPreview();
this.previews = null;
this.viewBox = null;
this.cropBox = null;
this.dragBox = null;
this.canvas = null;
this.container = null;
this.cropper.parentNode.removeChild(this.cropper);
this.cropper = null;
}
/**
* Get the no conflict cropper class.
* @returns {Cropper} The cropper class.
*/
static noConflict() {
window.Cropper = AnotherCropper;
return Cropper;
}
/**
* Change the default options.
* @param {Object} options - The new default options.
*/
static setDefaults(options) {
extend(DEFAULTS, isPlainObject(options) && options);
}
}
extend(Cropper.prototype, render, preview, events, handlers, change, methods);
export default Cropper;

View File

@ -0,0 +1,99 @@
import {
DRAG_MODE_CROP,
} from './constants';
export default {
// Define the view mode of the cropper
viewMode: 0, // 0, 1, 2, 3
// Define the dragging mode of the cropper
dragMode: DRAG_MODE_CROP, // 'crop', 'move' or 'none'
// Define the aspect ratio of the crop box
aspectRatio: NaN,
// An object with the previous cropping result data
data: null,
// A selector for adding extra containers to preview
preview: '',
// Re-render the cropper when resize the window
responsive: true,
// Restore the cropped area after resize the window
restore: true,
// Check if the current image is a cross-origin image
checkCrossOrigin: true,
// Check the current image's Exif Orientation information
checkOrientation: true,
// Show the black modal
modal: true,
// Show the dashed lines for guiding
guides: true,
// Show the center indicator for guiding
center: true,
// Show the white modal to highlight the crop box
highlight: true,
// Show the grid background
background: true,
// Enable to crop the image automatically when initialize
autoCrop: true,
// Define the percentage of automatic cropping area when initializes
autoCropArea: 0.8,
// Enable to move the image
movable: true,
// Enable to rotate the image
rotatable: true,
// Enable to scale the image
scalable: true,
// Enable to zoom the image
zoomable: true,
// Enable to zoom the image by dragging touch
zoomOnTouch: true,
// Enable to zoom the image by wheeling mouse
zoomOnWheel: true,
// Define zoom ratio when zoom the image by wheeling mouse
wheelZoomRatio: 0.1,
// Enable to move the crop box
cropBoxMovable: true,
// Enable to resize the crop box
cropBoxResizable: true,
// Toggle drag mode between "crop" and "move" when click twice on the cropper
toggleDragModeOnDblclick: true,
// Size limitation
minCanvasWidth: 0,
minCanvasHeight: 0,
minCropBoxWidth: 0,
minCropBoxHeight: 0,
minContainerWidth: 200,
minContainerHeight: 100,
// Shortcuts of events
ready: null,
cropstart: null,
cropmove: null,
cropend: null,
crop: null,
zoom: null,
};

View File

@ -0,0 +1,111 @@
import {
EVENT_CROP,
EVENT_CROP_END,
EVENT_CROP_MOVE,
EVENT_CROP_START,
EVENT_DBLCLICK,
EVENT_POINTER_DOWN,
EVENT_POINTER_MOVE,
EVENT_POINTER_UP,
EVENT_RESIZE,
EVENT_WHEEL,
EVENT_ZOOM,
} from './constants';
import {
addListener,
isFunction,
proxy,
removeListener,
} from './utilities';
export default {
bind() {
const { element, options, cropper } = this;
if (isFunction(options.cropstart)) {
addListener(element, EVENT_CROP_START, options.cropstart);
}
if (isFunction(options.cropmove)) {
addListener(element, EVENT_CROP_MOVE, options.cropmove);
}
if (isFunction(options.cropend)) {
addListener(element, EVENT_CROP_END, options.cropend);
}
if (isFunction(options.crop)) {
addListener(element, EVENT_CROP, options.crop);
}
if (isFunction(options.zoom)) {
addListener(element, EVENT_ZOOM, options.zoom);
}
addListener(cropper, EVENT_POINTER_DOWN, (this.onCropStart = proxy(this.cropStart, this)));
if (options.zoomable && options.zoomOnWheel) {
addListener(cropper, EVENT_WHEEL, (this.onWheel = proxy(this.wheel, this)));
}
if (options.toggleDragModeOnDblclick) {
addListener(cropper, EVENT_DBLCLICK, (this.onDblclick = proxy(this.dblclick, this)));
}
addListener(
element.ownerDocument,
EVENT_POINTER_MOVE,
(this.onCropMove = proxy(this.cropMove, this)),
);
addListener(
element.ownerDocument,
EVENT_POINTER_UP,
(this.onCropEnd = proxy(this.cropEnd, this)),
);
if (options.responsive) {
addListener(window, EVENT_RESIZE, (this.onResize = proxy(this.resize, this)));
}
},
unbind() {
const { element, options, cropper } = this;
if (isFunction(options.cropstart)) {
removeListener(element, EVENT_CROP_START, options.cropstart);
}
if (isFunction(options.cropmove)) {
removeListener(element, EVENT_CROP_MOVE, options.cropmove);
}
if (isFunction(options.cropend)) {
removeListener(element, EVENT_CROP_END, options.cropend);
}
if (isFunction(options.crop)) {
removeListener(element, EVENT_CROP, options.crop);
}
if (isFunction(options.zoom)) {
removeListener(element, EVENT_ZOOM, options.zoom);
}
removeListener(cropper, EVENT_POINTER_DOWN, this.onCropStart);
if (options.zoomable && options.zoomOnWheel) {
removeListener(cropper, EVENT_WHEEL, this.onWheel);
}
if (options.toggleDragModeOnDblclick) {
removeListener(cropper, EVENT_DBLCLICK, this.onDblclick);
}
removeListener(element.ownerDocument, EVENT_POINTER_MOVE, this.onCropMove);
removeListener(element.ownerDocument, EVENT_POINTER_UP, this.onCropEnd);
if (options.responsive) {
removeListener(window, EVENT_RESIZE, this.onResize);
}
},
};

View File

@ -0,0 +1,212 @@
import {
ACTION_CROP,
ACTION_ZOOM,
CLASS_CROP,
CLASS_MODAL,
DATA_ACTION,
DRAG_MODE_CROP,
DRAG_MODE_MOVE,
DRAG_MODE_NONE,
EVENT_CROP_END,
EVENT_CROP_MOVE,
EVENT_CROP_START,
REGEXP_ACTIONS,
} from './constants';
import {
addClass,
dispatchEvent,
each,
extend,
getData,
getPointer,
hasClass,
toggleClass,
} from './utilities';
export default {
resize() {
const { options, container, containerData } = this;
const minContainerWidth = Number(options.minContainerWidth) || 200;
const minContainerHeight = Number(options.minContainerHeight) || 100;
if (this.disabled || containerData.width <= minContainerWidth ||
containerData.height <= minContainerHeight) {
return;
}
const ratio = container.offsetWidth / containerData.width;
// Resize when width changed or height changed
if (ratio !== 1 || container.offsetHeight !== containerData.height) {
let canvasData;
let cropBoxData;
if (options.restore) {
canvasData = this.getCanvasData();
cropBoxData = this.getCropBoxData();
}
this.render();
if (options.restore) {
this.setCanvasData(each(canvasData, (n, i) => {
canvasData[i] = n * ratio;
}));
this.setCropBoxData(each(cropBoxData, (n, i) => {
cropBoxData[i] = n * ratio;
}));
}
}
},
dblclick() {
if (this.disabled || this.options.dragMode === DRAG_MODE_NONE) {
return;
}
this.setDragMode(hasClass(this.dragBox, CLASS_CROP) ? DRAG_MODE_MOVE : DRAG_MODE_CROP);
},
wheel(e) {
const ratio = Number(this.options.wheelZoomRatio) || 0.1;
let delta = 1;
if (this.disabled) {
return;
}
e.preventDefault();
// Limit wheel speed to prevent zoom too fast (#21)
if (this.wheeling) {
return;
}
this.wheeling = true;
setTimeout(() => {
this.wheeling = false;
}, 50);
if (e.deltaY) {
delta = e.deltaY > 0 ? 1 : -1;
} else if (e.wheelDelta) {
delta = -e.wheelDelta / 120;
} else if (e.detail) {
delta = e.detail > 0 ? 1 : -1;
}
this.zoom(-delta * ratio, e);
},
cropStart(e) {
if (this.disabled) {
return;
}
const { options, pointers } = this;
let action;
if (e.changedTouches) {
// Handle touch event
each(e.changedTouches, (touch) => {
pointers[touch.identifier] = getPointer(touch);
});
} else {
// Handle mouse event and pointer event
pointers[e.pointerId || 0] = getPointer(e);
}
if (Object.keys(pointers).length > 1 && options.zoomable && options.zoomOnTouch) {
action = ACTION_ZOOM;
} else {
action = getData(e.target, DATA_ACTION);
}
if (!REGEXP_ACTIONS.test(action)) {
return;
}
if (dispatchEvent(this.element, EVENT_CROP_START, {
originalEvent: e,
action,
}) === false) {
return;
}
e.preventDefault();
this.action = action;
this.cropping = false;
if (action === ACTION_CROP) {
this.cropping = true;
addClass(this.dragBox, CLASS_MODAL);
}
},
cropMove(e) {
const { action } = this;
if (this.disabled || !action) {
return;
}
const { pointers } = this;
e.preventDefault();
if (dispatchEvent(this.element, EVENT_CROP_MOVE, {
originalEvent: e,
action,
}) === false) {
return;
}
if (e.changedTouches) {
each(e.changedTouches, (touch) => {
extend(pointers[touch.identifier], getPointer(touch, true));
});
} else {
extend(pointers[e.pointerId || 0], getPointer(e, true));
}
this.change(e);
},
cropEnd(e) {
if (this.disabled) {
return;
}
const { action, pointers } = this;
if (e.changedTouches) {
each(e.changedTouches, (touch) => {
delete pointers[touch.identifier];
});
} else {
delete pointers[e.pointerId || 0];
}
if (!action) {
return;
}
e.preventDefault();
if (!Object.keys(pointers).length) {
this.action = '';
}
if (this.cropping) {
this.cropping = false;
toggleClass(this.dragBox, CLASS_MODAL, this.cropped && this.options.modal);
}
dispatchEvent(this.element, EVENT_CROP_END, {
originalEvent: e,
action,
});
},
};

View File

@ -0,0 +1,842 @@
import {
CLASS_CROP,
CLASS_DISABLED,
CLASS_HIDDEN,
CLASS_MODAL,
CLASS_MOVE,
DATA_ACTION,
DRAG_MODE_CROP,
DRAG_MODE_MOVE,
DRAG_MODE_NONE,
EVENT_LOAD,
EVENT_ZOOM,
NAMESPACE,
} from './constants';
import {
addClass,
dispatchEvent,
each,
extend,
getAdjustedSizes,
getOffset,
getPointersCenter,
getSourceCanvas,
isFunction,
isNumber,
isPlainObject,
isUndefined,
normalizeDecimalNumber,
removeClass,
removeData,
removeListener,
setData,
toggleClass,
} from './utilities';
export default {
// Show the crop box manually
crop() {
if (this.ready && !this.disabled) {
if (!this.cropped) {
this.cropped = true;
this.limitCropBox(true, true);
if (this.options.modal) {
addClass(this.dragBox, CLASS_MODAL);
}
removeClass(this.cropBox, CLASS_HIDDEN);
}
this.setCropBoxData(this.initialCropBoxData);
}
return this;
},
// Reset the image and crop box to their initial states
reset() {
if (this.ready && !this.disabled) {
this.imageData = extend({}, this.initialImageData);
this.canvasData = extend({}, this.initialCanvasData);
this.cropBoxData = extend({}, this.initialCropBoxData);
this.renderCanvas();
if (this.cropped) {
this.renderCropBox();
}
}
return this;
},
// Clear the crop box
clear() {
if (this.cropped && !this.disabled) {
extend(this.cropBoxData, {
left: 0,
top: 0,
width: 0,
height: 0,
});
this.cropped = false;
this.renderCropBox();
this.limitCanvas(true, true);
// Render canvas after crop box rendered
this.renderCanvas();
removeClass(this.dragBox, CLASS_MODAL);
addClass(this.cropBox, CLASS_HIDDEN);
}
return this;
},
/**
* Replace the image's src and rebuild the cropper
* @param {string} url - The new URL.
* @param {boolean} [onlyColorChanged] - Indicate if the new image only changed color.
* @returns {Object} this
*/
replace(url, onlyColorChanged = false) {
if (!this.disabled && url) {
if (this.isImg) {
this.element.src = url;
}
if (onlyColorChanged) {
this.url = url;
this.image.src = url;
if (this.ready) {
this.image2.src = url;
each(this.previews, (element) => {
element.getElementsByTagName('img')[0].src = url;
});
}
} else {
if (this.isImg) {
this.replaced = true;
}
// Clear previous data
this.options.data = null;
this.load(url);
}
}
return this;
},
// Enable (unfreeze) the cropper
enable() {
if (this.ready) {
this.disabled = false;
removeClass(this.cropper, CLASS_DISABLED);
}
return this;
},
// Disable (freeze) the cropper
disable() {
if (this.ready) {
this.disabled = true;
addClass(this.cropper, CLASS_DISABLED);
}
return this;
},
// Destroy the cropper and remove the instance from the image
destroy() {
const { element, image } = this;
if (this.loaded) {
if (this.isImg && this.replaced) {
element.src = this.originalUrl;
}
this.unbuild();
removeClass(element, CLASS_HIDDEN);
} else if (this.isImg) {
removeListener(element, EVENT_LOAD, this.onStart);
} else if (image) {
image.parentNode.removeChild(image);
}
removeData(element, NAMESPACE);
return this;
},
/**
* Move the canvas with relative offsets
* @param {number} offsetX - The relative offset distance on the x-axis.
* @param {number} offsetY - The relative offset distance on the y-axis.
* @returns {Object} this
*/
move(offsetX, offsetY) {
const { left, top } = this.canvasData;
return this.moveTo(
isUndefined(offsetX) ? offsetX : (left + Number(offsetX)),
isUndefined(offsetY) ? offsetY : (top + Number(offsetY)),
);
},
/**
* Move the canvas to an absolute point
* @param {number} x - The x-axis coordinate.
* @param {number} [y=x] - The y-axis coordinate.
* @returns {Object} this
*/
moveTo(x, y = x) {
const { canvasData } = this;
let changed = false;
x = Number(x);
y = Number(y);
if (this.ready && !this.disabled && this.options.movable) {
if (isNumber(x)) {
canvasData.left = x;
changed = true;
}
if (isNumber(y)) {
canvasData.top = y;
changed = true;
}
if (changed) {
this.renderCanvas(true);
}
}
return this;
},
/**
* Zoom the canvas with a relative ratio
* @param {number} ratio - The target ratio.
* @param {Event} _originalEvent - The original event if any.
* @returns {Object} this
*/
zoom(ratio, _originalEvent) {
const { canvasData } = this;
ratio = Number(ratio);
if (ratio < 0) {
ratio = 1 / (1 - ratio);
} else {
ratio = 1 + ratio;
}
return this.zoomTo((canvasData.width * ratio) / canvasData.naturalWidth, null, _originalEvent);
},
/**
* Zoom the canvas to an absolute ratio
* @param {number} ratio - The target ratio.
* @param {Object} pivot - The zoom pivot point coordinate.
* @param {Event} _originalEvent - The original event if any.
* @returns {Object} this
*/
zoomTo(ratio, pivot, _originalEvent) {
const { options, canvasData } = this;
const {
width,
height,
naturalWidth,
naturalHeight,
} = canvasData;
ratio = Number(ratio);
if (ratio >= 0 && this.ready && !this.disabled && options.zoomable) {
const newWidth = naturalWidth * ratio;
const newHeight = naturalHeight * ratio;
if (dispatchEvent(this.element, EVENT_ZOOM, {
originalEvent: _originalEvent,
oldRatio: width / naturalWidth,
ratio: newWidth / naturalWidth,
}) === false) {
return this;
}
if (_originalEvent) {
const { pointers } = this;
const offset = getOffset(this.cropper);
const center = pointers && Object.keys(pointers).length ? getPointersCenter(pointers) : {
pageX: _originalEvent.pageX,
pageY: _originalEvent.pageY,
};
// Zoom from the triggering point of the event
canvasData.left -= (newWidth - width) * (
((center.pageX - offset.left) - canvasData.left) / width
);
canvasData.top -= (newHeight - height) * (
((center.pageY - offset.top) - canvasData.top) / height
);
} else if (isPlainObject(pivot) && isNumber(pivot.x) && isNumber(pivot.y)) {
canvasData.left -= (newWidth - width) * (
(pivot.x - canvasData.left) / width
);
canvasData.top -= (newHeight - height) * (
(pivot.y - canvasData.top) / height
);
} else {
// Zoom from the center of the canvas
canvasData.left -= (newWidth - width) / 2;
canvasData.top -= (newHeight - height) / 2;
}
canvasData.width = newWidth;
canvasData.height = newHeight;
this.renderCanvas(true);
}
return this;
},
/**
* Rotate the canvas with a relative degree
* @param {number} degree - The rotate degree.
* @returns {Object} this
*/
rotate(degree) {
return this.rotateTo((this.imageData.rotate || 0) + Number(degree));
},
/**
* Rotate the canvas to an absolute degree
* @param {number} degree - The rotate degree.
* @returns {Object} this
*/
rotateTo(degree) {
degree = Number(degree);
if (isNumber(degree) && this.ready && !this.disabled && this.options.rotatable) {
this.imageData.rotate = degree % 360;
this.renderCanvas(true, true);
}
return this;
},
/**
* Scale the image on the x-axis.
* @param {number} scaleX - The scale ratio on the x-axis.
* @returns {Object} this
*/
scaleX(scaleX) {
const { scaleY } = this.imageData;
return this.scale(scaleX, isNumber(scaleY) ? scaleY : 1);
},
/**
* Scale the image on the y-axis.
* @param {number} scaleY - The scale ratio on the y-axis.
* @returns {Object} this
*/
scaleY(scaleY) {
const { scaleX } = this.imageData;
return this.scale(isNumber(scaleX) ? scaleX : 1, scaleY);
},
/**
* Scale the image
* @param {number} scaleX - The scale ratio on the x-axis.
* @param {number} [scaleY=scaleX] - The scale ratio on the y-axis.
* @returns {Object} this
*/
scale(scaleX, scaleY = scaleX) {
const { imageData } = this;
let transformed = false;
scaleX = Number(scaleX);
scaleY = Number(scaleY);
if (this.ready && !this.disabled && this.options.scalable) {
if (isNumber(scaleX)) {
imageData.scaleX = scaleX;
transformed = true;
}
if (isNumber(scaleY)) {
imageData.scaleY = scaleY;
transformed = true;
}
if (transformed) {
this.renderCanvas(true, true);
}
}
return this;
},
/**
* Get the cropped area position and size data (base on the original image)
* @param {boolean} [rounded=false] - Indicate if round the data values or not.
* @returns {Object} The result cropped data.
*/
getData(rounded = false) {
const {
options,
imageData,
canvasData,
cropBoxData,
} = this;
let data;
if (this.ready && this.cropped) {
data = {
x: cropBoxData.left - canvasData.left,
y: cropBoxData.top - canvasData.top,
width: cropBoxData.width,
height: cropBoxData.height,
};
const ratio = imageData.width / imageData.naturalWidth;
each(data, (n, i) => {
n /= ratio;
data[i] = rounded ? Math.round(n) : n;
});
} else {
data = {
x: 0,
y: 0,
width: 0,
height: 0,
};
}
if (options.rotatable) {
data.rotate = imageData.rotate || 0;
}
if (options.scalable) {
data.scaleX = imageData.scaleX || 1;
data.scaleY = imageData.scaleY || 1;
}
return data;
},
/**
* Set the cropped area position and size with new data
* @param {Object} data - The new data.
* @returns {Object} this
*/
setData(data) {
const { options, imageData, canvasData } = this;
const cropBoxData = {};
if (isFunction(data)) {
data = data.call(this.element);
}
if (this.ready && !this.disabled && isPlainObject(data)) {
let transformed = false;
if (options.rotatable) {
if (isNumber(data.rotate) && data.rotate !== imageData.rotate) {
imageData.rotate = data.rotate;
transformed = true;
}
}
if (options.scalable) {
if (isNumber(data.scaleX) && data.scaleX !== imageData.scaleX) {
imageData.scaleX = data.scaleX;
transformed = true;
}
if (isNumber(data.scaleY) && data.scaleY !== imageData.scaleY) {
imageData.scaleY = data.scaleY;
transformed = true;
}
}
if (transformed) {
this.renderCanvas(true, true);
}
const ratio = imageData.width / imageData.naturalWidth;
if (isNumber(data.x)) {
cropBoxData.left = (data.x * ratio) + canvasData.left;
}
if (isNumber(data.y)) {
cropBoxData.top = (data.y * ratio) + canvasData.top;
}
if (isNumber(data.width)) {
cropBoxData.width = data.width * ratio;
}
if (isNumber(data.height)) {
cropBoxData.height = data.height * ratio;
}
this.setCropBoxData(cropBoxData);
}
return this;
},
/**
* Get the container size data.
* @returns {Object} The result container data.
*/
getContainerData() {
return this.ready ? extend({}, this.containerData) : {};
},
/**
* Get the image position and size data.
* @returns {Object} The result image data.
*/
getImageData() {
return this.loaded ? extend({}, this.imageData) : {};
},
/**
* Get the canvas position and size data.
* @returns {Object} The result canvas data.
*/
getCanvasData() {
const { canvasData } = this;
const data = {};
if (this.ready) {
each([
'left',
'top',
'width',
'height',
'naturalWidth',
'naturalHeight',
], (n) => {
data[n] = canvasData[n];
});
}
return data;
},
/**
* Set the canvas position and size with new data.
* @param {Object} data - The new canvas data.
* @returns {Object} this
*/
setCanvasData(data) {
const { canvasData } = this;
const { aspectRatio } = canvasData;
if (isFunction(data)) {
data = data.call(this.element);
}
if (this.ready && !this.disabled && isPlainObject(data)) {
if (isNumber(data.left)) {
canvasData.left = data.left;
}
if (isNumber(data.top)) {
canvasData.top = data.top;
}
if (isNumber(data.width)) {
canvasData.width = data.width;
canvasData.height = data.width / aspectRatio;
} else if (isNumber(data.height)) {
canvasData.height = data.height;
canvasData.width = data.height * aspectRatio;
}
this.renderCanvas(true);
}
return this;
},
/**
* Get the crop box position and size data.
* @returns {Object} The result crop box data.
*/
getCropBoxData() {
const { cropBoxData } = this;
let data;
if (this.ready && this.cropped) {
data = {
left: cropBoxData.left,
top: cropBoxData.top,
width: cropBoxData.width,
height: cropBoxData.height,
};
}
return data || {};
},
/**
* Set the crop box position and size with new data.
* @param {Object} data - The new crop box data.
* @returns {Object} this
*/
setCropBoxData(data) {
const { cropBoxData } = this;
const { aspectRatio } = this.options;
let widthChanged;
let heightChanged;
if (isFunction(data)) {
data = data.call(this.element);
}
if (this.ready && this.cropped && !this.disabled && isPlainObject(data)) {
if (isNumber(data.left)) {
cropBoxData.left = data.left;
}
if (isNumber(data.top)) {
cropBoxData.top = data.top;
}
if (isNumber(data.width) && data.width !== cropBoxData.width) {
widthChanged = true;
cropBoxData.width = data.width;
}
if (isNumber(data.height) && data.height !== cropBoxData.height) {
heightChanged = true;
cropBoxData.height = data.height;
}
if (aspectRatio) {
if (widthChanged) {
cropBoxData.height = cropBoxData.width / aspectRatio;
} else if (heightChanged) {
cropBoxData.width = cropBoxData.height * aspectRatio;
}
}
this.renderCropBox();
}
return this;
},
/**
* Get a canvas drawn the cropped image.
* @param {Object} [options={}] - The config options.
* @returns {HTMLCanvasElement} - The result canvas.
*/
getCroppedCanvas(options = {}) {
if (!this.ready || !window.HTMLCanvasElement) {
return null;
}
const { canvasData } = this;
const source = getSourceCanvas(this.image, this.imageData, canvasData, options);
// Returns the source canvas if it is not cropped.
if (!this.cropped) {
return source;
}
let {
x: initialX,
y: initialY,
width: initialWidth,
height: initialHeight,
} = this.getData();
const ratio = source.width / Math.floor(canvasData.naturalWidth);
if (ratio !== 1) {
initialX *= ratio;
initialY *= ratio;
initialWidth *= ratio;
initialHeight *= ratio;
}
const aspectRatio = initialWidth / initialHeight;
const maxSizes = getAdjustedSizes({
aspectRatio,
width: options.maxWidth || Infinity,
height: options.maxHeight || Infinity,
});
const minSizes = getAdjustedSizes({
aspectRatio,
width: options.minWidth || 0,
height: options.minHeight || 0,
}, 'cover');
let {
width,
height,
} = getAdjustedSizes({
aspectRatio,
width: options.width || (ratio !== 1 ? source.width : initialWidth),
height: options.height || (ratio !== 1 ? source.height : initialHeight),
});
width = Math.min(maxSizes.width, Math.max(minSizes.width, width));
height = Math.min(maxSizes.height, Math.max(minSizes.height, height));
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
canvas.width = normalizeDecimalNumber(width);
canvas.height = normalizeDecimalNumber(height);
context.fillStyle = options.fillColor || 'transparent';
context.fillRect(0, 0, width, height);
const { imageSmoothingEnabled = true, imageSmoothingQuality } = options;
context.imageSmoothingEnabled = imageSmoothingEnabled;
if (imageSmoothingQuality) {
context.imageSmoothingQuality = imageSmoothingQuality;
}
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D.drawImage
const sourceWidth = source.width;
const sourceHeight = source.height;
// Source canvas parameters
let srcX = initialX;
let srcY = initialY;
let srcWidth;
let srcHeight;
// Destination canvas parameters
let dstX;
let dstY;
let dstWidth;
let dstHeight;
if (srcX <= -initialWidth || srcX > sourceWidth) {
srcX = 0;
srcWidth = 0;
dstX = 0;
dstWidth = 0;
} else if (srcX <= 0) {
dstX = -srcX;
srcX = 0;
srcWidth = Math.min(sourceWidth, initialWidth + srcX);
dstWidth = srcWidth;
} else if (srcX <= sourceWidth) {
dstX = 0;
srcWidth = Math.min(initialWidth, sourceWidth - srcX);
dstWidth = srcWidth;
}
if (srcWidth <= 0 || srcY <= -initialHeight || srcY > sourceHeight) {
srcY = 0;
srcHeight = 0;
dstY = 0;
dstHeight = 0;
} else if (srcY <= 0) {
dstY = -srcY;
srcY = 0;
srcHeight = Math.min(sourceHeight, initialHeight + srcY);
dstHeight = srcHeight;
} else if (srcY <= sourceHeight) {
dstY = 0;
srcHeight = Math.min(initialHeight, sourceHeight - srcY);
dstHeight = srcHeight;
}
// All the numerical parameters should be integer for `drawImage`
// https://github.com/fengyuanchen/cropper/issues/476
const params = [
srcX,
srcY,
srcWidth,
srcHeight,
];
// Avoid "IndexSizeError"
if (dstWidth > 0 && dstHeight > 0) {
const scale = width / initialWidth;
params.push(
dstX * scale,
dstY * scale,
dstWidth * scale,
dstHeight * scale,
);
}
context.drawImage(source, ...params.map(param => Math.floor(normalizeDecimalNumber(param))));
return canvas;
},
/**
* Change the aspect ratio of the crop box.
* @param {number} aspectRatio - The new aspect ratio.
* @returns {Object} this
*/
setAspectRatio(aspectRatio) {
const { options } = this;
if (!this.disabled && !isUndefined(aspectRatio)) {
// 0 -> NaN
options.aspectRatio = Math.max(0, aspectRatio) || NaN;
if (this.ready) {
this.initCropBox();
if (this.cropped) {
this.renderCropBox();
}
}
}
return this;
},
/**
* Change the drag mode.
* @param {string} mode - The new drag mode.
* @returns {Object} this
*/
setDragMode(mode) {
const { options, dragBox, face } = this;
if (this.loaded && !this.disabled) {
const croppable = mode === DRAG_MODE_CROP;
const movable = options.movable && mode === DRAG_MODE_MOVE;
mode = (croppable || movable) ? mode : DRAG_MODE_NONE;
setData(dragBox, DATA_ACTION, mode);
toggleClass(dragBox, CLASS_CROP, croppable);
toggleClass(dragBox, CLASS_MOVE, movable);
if (!options.cropBoxMovable) {
// Sync drag mode to crop box when it is not movable
setData(face, DATA_ACTION, mode);
toggleClass(face, CLASS_CROP, croppable);
toggleClass(face, CLASS_MOVE, movable);
}
}
return this;
},
};

View File

@ -0,0 +1,142 @@
import {
DATA_PREVIEW,
} from './constants';
import {
each,
empty,
extend,
getData,
getTransforms,
removeData,
setData,
setStyle,
} from './utilities';
export default {
initPreview() {
const { crossOrigin } = this;
const { preview } = this.options;
const url = crossOrigin ? this.crossOriginUrl : this.url;
const image = document.createElement('img');
if (crossOrigin) {
image.crossOrigin = crossOrigin;
}
image.src = url;
this.viewBox.appendChild(image);
this.image2 = image;
if (!preview) {
return;
}
const previews = preview.querySelector ? [preview] : document.querySelectorAll(preview);
this.previews = previews;
each(previews, (element) => {
const img = document.createElement('img');
// Save the original size for recover
setData(element, DATA_PREVIEW, {
width: element.offsetWidth,
height: element.offsetHeight,
html: element.innerHTML,
});
if (crossOrigin) {
img.crossOrigin = crossOrigin;
}
img.src = url;
/**
* Override img element styles
* Add `display:block` to avoid margin top issue
* Add `height:auto` to override `height` attribute on IE8
* (Occur only when margin-top <= -height)
*/
img.style.cssText = (
'display:block;' +
'width:100%;' +
'height:auto;' +
'min-width:0!important;' +
'min-height:0!important;' +
'max-width:none!important;' +
'max-height:none!important;' +
'image-orientation:0deg!important;"'
);
empty(element);
element.appendChild(img);
});
},
resetPreview() {
each(this.previews, (element) => {
const data = getData(element, DATA_PREVIEW);
setStyle(element, {
width: data.width,
height: data.height,
});
element.innerHTML = data.html;
removeData(element, DATA_PREVIEW);
});
},
preview() {
const { imageData, canvasData, cropBoxData } = this;
const { width: cropBoxWidth, height: cropBoxHeight } = cropBoxData;
const { width, height } = imageData;
const left = cropBoxData.left - canvasData.left - imageData.left;
const top = cropBoxData.top - canvasData.top - imageData.top;
if (!this.cropped || this.disabled) {
return;
}
setStyle(this.image2, extend({
width,
height,
}, getTransforms(extend({
translateX: -left,
translateY: -top,
}, imageData))));
each(this.previews, (element) => {
const data = getData(element, DATA_PREVIEW);
const originalWidth = data.width;
const originalHeight = data.height;
let newWidth = originalWidth;
let newHeight = originalHeight;
let ratio = 1;
if (cropBoxWidth) {
ratio = originalWidth / cropBoxWidth;
newHeight = cropBoxHeight * ratio;
}
if (cropBoxHeight && newHeight > originalHeight) {
ratio = originalHeight / cropBoxHeight;
newWidth = cropBoxWidth * ratio;
newHeight = originalHeight;
}
setStyle(element, {
width: newWidth,
height: newHeight,
});
setStyle(element.getElementsByTagName('img')[0], extend({
width: width * ratio,
height: height * ratio,
}, getTransforms(extend({
translateX: -left * ratio,
translateY: -top * ratio,
}, imageData))));
});
},
};

View File

@ -0,0 +1,495 @@
import {
ACTION_ALL,
ACTION_MOVE,
CLASS_HIDDEN,
DATA_ACTION,
EVENT_CROP,
} from './constants';
import {
addClass,
dispatchEvent,
extend,
getAdjustedSizes,
getRotatedSizes,
getTransforms,
removeClass,
setData,
setStyle,
} from './utilities';
export default {
render() {
this.initContainer();
this.initCanvas();
this.initCropBox();
this.renderCanvas();
if (this.cropped) {
this.renderCropBox();
}
},
initContainer() {
const {
element,
options,
container,
cropper,
} = this;
addClass(cropper, CLASS_HIDDEN);
removeClass(element, CLASS_HIDDEN);
const containerData = {
width: Math.max(
container.offsetWidth,
Number(options.minContainerWidth) || 200,
),
height: Math.max(
container.offsetHeight,
Number(options.minContainerHeight) || 100,
),
};
this.containerData = containerData;
setStyle(cropper, {
width: containerData.width,
height: containerData.height,
});
addClass(element, CLASS_HIDDEN);
removeClass(cropper, CLASS_HIDDEN);
},
// Canvas (image wrapper)
initCanvas() {
const { containerData, imageData } = this;
const { viewMode } = this.options;
const rotated = Math.abs(imageData.rotate) % 180 === 90;
const naturalWidth = rotated ? imageData.naturalHeight : imageData.naturalWidth;
const naturalHeight = rotated ? imageData.naturalWidth : imageData.naturalHeight;
const aspectRatio = naturalWidth / naturalHeight;
let canvasWidth = containerData.width;
let canvasHeight = containerData.height;
if (containerData.height * aspectRatio > containerData.width) {
if (viewMode === 3) {
canvasWidth = containerData.height * aspectRatio;
} else {
canvasHeight = containerData.width / aspectRatio;
}
} else if (viewMode === 3) {
canvasHeight = containerData.width / aspectRatio;
} else {
canvasWidth = containerData.height * aspectRatio;
}
const canvasData = {
aspectRatio,
naturalWidth,
naturalHeight,
width: canvasWidth,
height: canvasHeight,
};
canvasData.left = (containerData.width - canvasWidth) / 2;
canvasData.top = (containerData.height - canvasHeight) / 2;
canvasData.oldLeft = canvasData.left;
canvasData.oldTop = canvasData.top;
this.canvasData = canvasData;
this.limited = (viewMode === 1 || viewMode === 2);
this.limitCanvas(true, true);
this.initialImageData = extend({}, imageData);
this.initialCanvasData = extend({}, canvasData);
},
limitCanvas(sizeLimited, positionLimited) {
const {
options,
containerData,
canvasData,
cropBoxData,
} = this;
const { viewMode } = options;
const { aspectRatio } = canvasData;
const cropped = this.cropped && cropBoxData;
if (sizeLimited) {
let minCanvasWidth = Number(options.minCanvasWidth) || 0;
let minCanvasHeight = Number(options.minCanvasHeight) || 0;
if (viewMode > 1) {
minCanvasWidth = Math.max(minCanvasWidth, containerData.width);
minCanvasHeight = Math.max(minCanvasHeight, containerData.height);
if (viewMode === 3) {
if (minCanvasHeight * aspectRatio > minCanvasWidth) {
minCanvasWidth = minCanvasHeight * aspectRatio;
} else {
minCanvasHeight = minCanvasWidth / aspectRatio;
}
}
} else if (viewMode > 0) {
if (minCanvasWidth) {
minCanvasWidth = Math.max(
minCanvasWidth,
cropped ? cropBoxData.width : 0,
);
} else if (minCanvasHeight) {
minCanvasHeight = Math.max(
minCanvasHeight,
cropped ? cropBoxData.height : 0,
);
} else if (cropped) {
minCanvasWidth = cropBoxData.width;
minCanvasHeight = cropBoxData.height;
if (minCanvasHeight * aspectRatio > minCanvasWidth) {
minCanvasWidth = minCanvasHeight * aspectRatio;
} else {
minCanvasHeight = minCanvasWidth / aspectRatio;
}
}
}
({ width: minCanvasWidth, height: minCanvasHeight } = getAdjustedSizes({
aspectRatio,
width: minCanvasWidth,
height: minCanvasHeight,
}, 'cover'));
canvasData.minWidth = minCanvasWidth;
canvasData.minHeight = minCanvasHeight;
canvasData.maxWidth = Infinity;
canvasData.maxHeight = Infinity;
}
if (positionLimited) {
if (viewMode) {
const newCanvasLeft = containerData.width - canvasData.width;
const newCanvasTop = containerData.height - canvasData.height;
canvasData.minLeft = Math.min(0, newCanvasLeft);
canvasData.minTop = Math.min(0, newCanvasTop);
canvasData.maxLeft = Math.max(0, newCanvasLeft);
canvasData.maxTop = Math.max(0, newCanvasTop);
if (cropped && this.limited) {
canvasData.minLeft = Math.min(
cropBoxData.left,
cropBoxData.left + (cropBoxData.width - canvasData.width),
);
canvasData.minTop = Math.min(
cropBoxData.top,
cropBoxData.top + (cropBoxData.height - canvasData.height),
);
canvasData.maxLeft = cropBoxData.left;
canvasData.maxTop = cropBoxData.top;
if (viewMode === 2) {
if (canvasData.width >= containerData.width) {
canvasData.minLeft = Math.min(0, newCanvasLeft);
canvasData.maxLeft = Math.max(0, newCanvasLeft);
}
if (canvasData.height >= containerData.height) {
canvasData.minTop = Math.min(0, newCanvasTop);
canvasData.maxTop = Math.max(0, newCanvasTop);
}
}
}
} else {
canvasData.minLeft = -canvasData.width;
canvasData.minTop = -canvasData.height;
canvasData.maxLeft = containerData.width;
canvasData.maxTop = containerData.height;
}
}
},
renderCanvas(changed, transformed) {
const { canvasData, imageData } = this;
if (transformed) {
const { width: naturalWidth, height: naturalHeight } = getRotatedSizes({
width: imageData.naturalWidth * Math.abs(imageData.scaleX || 1),
height: imageData.naturalHeight * Math.abs(imageData.scaleY || 1),
degree: imageData.rotate || 0,
});
const width = canvasData.width * (naturalWidth / canvasData.naturalWidth);
const height = canvasData.height * (naturalHeight / canvasData.naturalHeight);
canvasData.left -= (width - canvasData.width) / 2;
canvasData.top -= (height - canvasData.height) / 2;
canvasData.width = width;
canvasData.height = height;
canvasData.aspectRatio = naturalWidth / naturalHeight;
canvasData.naturalWidth = naturalWidth;
canvasData.naturalHeight = naturalHeight;
this.limitCanvas(true, false);
}
if (canvasData.width > canvasData.maxWidth ||
canvasData.width < canvasData.minWidth) {
canvasData.left = canvasData.oldLeft;
}
if (canvasData.height > canvasData.maxHeight ||
canvasData.height < canvasData.minHeight) {
canvasData.top = canvasData.oldTop;
}
canvasData.width = Math.min(
Math.max(canvasData.width, canvasData.minWidth),
canvasData.maxWidth,
);
canvasData.height = Math.min(
Math.max(canvasData.height, canvasData.minHeight),
canvasData.maxHeight,
);
this.limitCanvas(false, true);
canvasData.left = Math.min(
Math.max(canvasData.left, canvasData.minLeft),
canvasData.maxLeft,
);
canvasData.top = Math.min(
Math.max(canvasData.top, canvasData.minTop),
canvasData.maxTop,
);
canvasData.oldLeft = canvasData.left;
canvasData.oldTop = canvasData.top;
setStyle(this.canvas, extend({
width: canvasData.width,
height: canvasData.height,
}, getTransforms({
translateX: canvasData.left,
translateY: canvasData.top,
})));
this.renderImage(changed);
if (this.cropped && this.limited) {
this.limitCropBox(true, true);
}
},
renderImage(changed) {
const { canvasData, imageData } = this;
const width = imageData.naturalWidth * (canvasData.width / canvasData.naturalWidth);
const height = imageData.naturalHeight * (canvasData.height / canvasData.naturalHeight);
extend(imageData, {
width,
height,
left: (canvasData.width - width) / 2,
top: (canvasData.height - height) / 2,
});
setStyle(this.image, extend({
width: imageData.width,
height: imageData.height,
}, getTransforms(extend({
translateX: imageData.left,
translateY: imageData.top,
}, imageData))));
if (changed) {
this.output();
}
},
initCropBox() {
const { options, canvasData } = this;
const { aspectRatio } = options;
const autoCropArea = Number(options.autoCropArea) || 0.8;
const cropBoxData = {
width: canvasData.width,
height: canvasData.height,
};
if (aspectRatio) {
if (canvasData.height * aspectRatio > canvasData.width) {
cropBoxData.height = cropBoxData.width / aspectRatio;
} else {
cropBoxData.width = cropBoxData.height * aspectRatio;
}
}
this.cropBoxData = cropBoxData;
this.limitCropBox(true, true);
// Initialize auto crop area
cropBoxData.width = Math.min(
Math.max(cropBoxData.width, cropBoxData.minWidth),
cropBoxData.maxWidth,
);
cropBoxData.height = Math.min(
Math.max(cropBoxData.height, cropBoxData.minHeight),
cropBoxData.maxHeight,
);
// The width/height of auto crop area must large than "minWidth/Height"
cropBoxData.width = Math.max(
cropBoxData.minWidth,
cropBoxData.width * autoCropArea,
);
cropBoxData.height = Math.max(
cropBoxData.minHeight,
cropBoxData.height * autoCropArea,
);
cropBoxData.left = (
canvasData.left + ((canvasData.width - cropBoxData.width) / 2)
);
cropBoxData.top = (
canvasData.top + ((canvasData.height - cropBoxData.height) / 2)
);
cropBoxData.oldLeft = cropBoxData.left;
cropBoxData.oldTop = cropBoxData.top;
this.initialCropBoxData = extend({}, cropBoxData);
},
limitCropBox(sizeLimited, positionLimited) {
const {
options,
containerData,
canvasData,
cropBoxData,
limited,
} = this;
const { aspectRatio } = options;
if (sizeLimited) {
let minCropBoxWidth = Number(options.minCropBoxWidth) || 0;
let minCropBoxHeight = Number(options.minCropBoxHeight) || 0;
let maxCropBoxWidth = Math.min(
containerData.width,
limited ? canvasData.width : containerData.width,
);
let maxCropBoxHeight = Math.min(
containerData.height,
limited ? canvasData.height : containerData.height,
);
// The min/maxCropBoxWidth/Height must be less than container's width/height
minCropBoxWidth = Math.min(minCropBoxWidth, containerData.width);
minCropBoxHeight = Math.min(minCropBoxHeight, containerData.height);
if (aspectRatio) {
if (minCropBoxWidth && minCropBoxHeight) {
if (minCropBoxHeight * aspectRatio > minCropBoxWidth) {
minCropBoxHeight = minCropBoxWidth / aspectRatio;
} else {
minCropBoxWidth = minCropBoxHeight * aspectRatio;
}
} else if (minCropBoxWidth) {
minCropBoxHeight = minCropBoxWidth / aspectRatio;
} else if (minCropBoxHeight) {
minCropBoxWidth = minCropBoxHeight * aspectRatio;
}
if (maxCropBoxHeight * aspectRatio > maxCropBoxWidth) {
maxCropBoxHeight = maxCropBoxWidth / aspectRatio;
} else {
maxCropBoxWidth = maxCropBoxHeight * aspectRatio;
}
}
// The minWidth/Height must be less than maxWidth/Height
cropBoxData.minWidth = Math.min(minCropBoxWidth, maxCropBoxWidth);
cropBoxData.minHeight = Math.min(minCropBoxHeight, maxCropBoxHeight);
cropBoxData.maxWidth = maxCropBoxWidth;
cropBoxData.maxHeight = maxCropBoxHeight;
}
if (positionLimited) {
if (limited) {
cropBoxData.minLeft = Math.max(0, canvasData.left);
cropBoxData.minTop = Math.max(0, canvasData.top);
cropBoxData.maxLeft = Math.min(
containerData.width,
canvasData.left + canvasData.width,
) - cropBoxData.width;
cropBoxData.maxTop = Math.min(
containerData.height,
canvasData.top + canvasData.height,
) - cropBoxData.height;
} else {
cropBoxData.minLeft = 0;
cropBoxData.minTop = 0;
cropBoxData.maxLeft = containerData.width - cropBoxData.width;
cropBoxData.maxTop = containerData.height - cropBoxData.height;
}
}
},
renderCropBox() {
const { options, containerData, cropBoxData } = this;
if (cropBoxData.width > cropBoxData.maxWidth ||
cropBoxData.width < cropBoxData.minWidth) {
cropBoxData.left = cropBoxData.oldLeft;
}
if (cropBoxData.height > cropBoxData.maxHeight ||
cropBoxData.height < cropBoxData.minHeight) {
cropBoxData.top = cropBoxData.oldTop;
}
cropBoxData.width = Math.min(
Math.max(cropBoxData.width, cropBoxData.minWidth),
cropBoxData.maxWidth,
);
cropBoxData.height = Math.min(
Math.max(cropBoxData.height, cropBoxData.minHeight),
cropBoxData.maxHeight,
);
this.limitCropBox(false, true);
cropBoxData.left = Math.min(
Math.max(cropBoxData.left, cropBoxData.minLeft),
cropBoxData.maxLeft,
);
cropBoxData.top = Math.min(
Math.max(cropBoxData.top, cropBoxData.minTop),
cropBoxData.maxTop,
);
cropBoxData.oldLeft = cropBoxData.left;
cropBoxData.oldTop = cropBoxData.top;
if (options.movable && options.cropBoxMovable) {
// Turn to move the canvas when the crop box is equal to the container
setData(this.face, DATA_ACTION, cropBoxData.width >= containerData.width &&
cropBoxData.height >= containerData.height ? ACTION_MOVE : ACTION_ALL);
}
setStyle(this.cropBox, extend({
width: cropBoxData.width,
height: cropBoxData.height,
}, getTransforms({
translateX: cropBoxData.left,
translateY: cropBoxData.top,
})));
if (this.cropped && this.limited) {
this.limitCanvas(true, true);
}
if (!this.disabled) {
this.output();
}
},
output() {
this.preview();
if (this.complete) {
dispatchEvent(this.element, EVENT_CROP, this.getData());
}
},
};

View File

@ -0,0 +1,27 @@
export default (
'<div class="cropper-container">' +
'<div class="cropper-wrap-box">' +
'<div class="cropper-canvas"></div>' +
'</div>' +
'<div class="cropper-drag-box"></div>' +
'<div class="cropper-crop-box">' +
'<span class="cropper-view-box"></span>' +
'<span class="cropper-dashed dashed-h"></span>' +
'<span class="cropper-dashed dashed-v"></span>' +
'<span class="cropper-center"></span>' +
'<span class="cropper-face"></span>' +
'<span class="cropper-line line-e" data-action="e"></span>' +
'<span class="cropper-line line-n" data-action="n"></span>' +
'<span class="cropper-line line-w" data-action="w"></span>' +
'<span class="cropper-line line-s" data-action="s"></span>' +
'<span class="cropper-point point-e" data-action="e"></span>' +
'<span class="cropper-point point-n" data-action="n"></span>' +
'<span class="cropper-point point-w" data-action="w"></span>' +
'<span class="cropper-point point-s" data-action="s"></span>' +
'<span class="cropper-point point-ne" data-action="ne"></span>' +
'<span class="cropper-point point-nw" data-action="nw"></span>' +
'<span class="cropper-point point-sw" data-action="sw"></span>' +
'<span class="cropper-point point-se" data-action="se"></span>' +
'</div>' +
'</div>'
);

View File

@ -0,0 +1,985 @@
import {
WINDOW,
} from './constants';
/**
* Check if the given value is not a number.
*/
export const isNaN = Number.isNaN || WINDOW.isNaN;
/**
* Check if the given value is a number.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is a number, else `false`.
*/
export function isNumber(value) {
return typeof value === 'number' && !isNaN(value);
}
/**
* Check if the given value is undefined.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is undefined, else `false`.
*/
export function isUndefined(value) {
return typeof value === 'undefined';
}
/**
* Check if the given value is an object.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is an object, else `false`.
*/
export function isObject(value) {
return typeof value === 'object' && value !== null;
}
const { hasOwnProperty } = Object.prototype;
/**
* Check if the given value is a plain object.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is a plain object, else `false`.
*/
export function isPlainObject(value) {
if (!isObject(value)) {
return false;
}
try {
const { constructor } = value;
const { prototype } = constructor;
return constructor && prototype && hasOwnProperty.call(prototype, 'isPrototypeOf');
} catch (e) {
return false;
}
}
/**
* Check if the given value is a function.
* @param {*} value - The value to check.
* @returns {boolean} Returns `true` if the given value is a function, else `false`.
*/
export function isFunction(value) {
return typeof value === 'function';
}
/**
* Iterate the given data.
* @param {*} data - The data to iterate.
* @param {Function} callback - The process function for each element.
* @returns {*} The original data.
*/
export function each(data, callback) {
if (data && isFunction(callback)) {
if (Array.isArray(data) || isNumber(data.length)/* array-like */) {
const { length } = data;
let i;
for (i = 0; i < length; i += 1) {
if (callback.call(data, data[i], i, data) === false) {
break;
}
}
} else if (isObject(data)) {
Object.keys(data).forEach((key) => {
callback.call(data, data[key], key, data);
});
}
}
return data;
}
/**
* Extend the given object.
* @param {*} obj - The object to be extended.
* @param {*} args - The rest objects which will be merged to the first object.
* @returns {Object} The extended object.
*/
export function extend(obj, ...args) {
if (isObject(obj) && args.length > 0) {
if (Object.assign) {
return Object.assign(obj, ...args);
}
args.forEach((arg) => {
if (isObject(arg)) {
Object.keys(arg).forEach((key) => {
obj[key] = arg[key];
});
}
});
}
return obj;
}
/**
* Takes a function and returns a new one that will always have a particular context.
* @param {Function} fn - The target function.
* @param {Object} context - The new context for the function.
* @returns {boolean} The new function.
*/
export function proxy(fn, context, ...args) {
return (...args2) => fn.apply(context, args.concat(args2));
}
const REGEXP_DECIMALS = /\.\d*(?:0|9){12}\d*$/i;
/**
* Normalize decimal number.
* Check out {@link http://0.30000000000000004.com/ }
* @param {number} value - The value to normalize.
* @param {number} [times=100000000000] - The times for normalizing.
* @returns {number} Returns the normalized number.
*/
export function normalizeDecimalNumber(value, times = 100000000000) {
return REGEXP_DECIMALS.test(value) ? (Math.round(value * times) / times) : value;
}
const REGEXP_SUFFIX = /^(?:width|height|left|top|marginLeft|marginTop)$/;
/**
* Apply styles to the given element.
* @param {Element} element - The target element.
* @param {Object} styles - The styles for applying.
*/
export function setStyle(element, styles) {
const { style } = element;
each(styles, (value, property) => {
if (REGEXP_SUFFIX.test(property) && isNumber(value)) {
value += 'px';
}
style[property] = value;
});
}
/**
* Check if the given element has a special class.
* @param {Element} element - The element to check.
* @param {string} value - The class to search.
* @returns {boolean} Returns `true` if the special class was found.
*/
export function hasClass(element, value) {
return element.classList ?
element.classList.contains(value) :
element.className.indexOf(value) > -1;
}
/**
* Add classes to the given element.
* @param {Element} element - The target element.
* @param {string} value - The classes to be added.
*/
export function addClass(element, value) {
if (!value) {
return;
}
if (isNumber(element.length)) {
each(element, (elem) => {
addClass(elem, value);
});
return;
}
if (element.classList) {
element.classList.add(value);
return;
}
const className = element.className.trim();
if (!className) {
element.className = value;
} else if (className.indexOf(value) < 0) {
element.className = `${className} ${value}`;
}
}
/**
* Remove classes from the given element.
* @param {Element} element - The target element.
* @param {string} value - The classes to be removed.
*/
export function removeClass(element, value) {
if (!value) {
return;
}
if (isNumber(element.length)) {
each(element, (elem) => {
removeClass(elem, value);
});
return;
}
if (element.classList) {
element.classList.remove(value);
return;
}
if (element.className.indexOf(value) >= 0) {
element.className = element.className.replace(value, '');
}
}
/**
* Add or remove classes from the given element.
* @param {Element} element - The target element.
* @param {string} value - The classes to be toggled.
* @param {boolean} added - Add only.
*/
export function toggleClass(element, value, added) {
if (!value) {
return;
}
if (isNumber(element.length)) {
each(element, (elem) => {
toggleClass(elem, value, added);
});
return;
}
// IE10-11 doesn't support the second parameter of `classList.toggle`
if (added) {
addClass(element, value);
} else {
removeClass(element, value);
}
}
const REGEXP_HYPHENATE = /([a-z\d])([A-Z])/g;
/**
* Hyphenate the given value.
* @param {string} value - The value to hyphenate.
* @returns {string} The hyphenated value.
*/
export function hyphenate(value) {
return value.replace(REGEXP_HYPHENATE, '$1-$2').toLowerCase();
}
/**
* Get data from the given element.
* @param {Element} element - The target element.
* @param {string} name - The data key to get.
* @returns {string} The data value.
*/
export function getData(element, name) {
if (isObject(element[name])) {
return element[name];
} else if (element.dataset) {
return element.dataset[name];
}
return element.getAttribute(`data-${hyphenate(name)}`);
}
/**
* Set data to the given element.
* @param {Element} element - The target element.
* @param {string} name - The data key to set.
* @param {string} data - The data value.
*/
export function setData(element, name, data) {
if (isObject(data)) {
element[name] = data;
} else if (element.dataset) {
element.dataset[name] = data;
} else {
element.setAttribute(`data-${hyphenate(name)}`, data);
}
}
/**
* Remove data from the given element.
* @param {Element} element - The target element.
* @param {string} name - The data key to remove.
*/
export function removeData(element, name) {
if (isObject(element[name])) {
try {
delete element[name];
} catch (e) {
element[name] = null;
}
} else if (element.dataset) {
// #128 Safari not allows to delete dataset property
try {
delete element.dataset[name];
} catch (e) {
element.dataset[name] = null;
}
} else {
element.removeAttribute(`data-${hyphenate(name)}`);
}
}
const REGEXP_SPACES = /\s\s*/;
/**
* Remove event listener from the target element.
* @param {Element} element - The event target.
* @param {string} type - The event type(s).
* @param {Function} listener - The event listener.
* @param {Object} options - The event options.
*/
export function removeListener(element, type, listener, options = {}) {
if (!isFunction(listener)) {
return;
}
const types = type.trim().split(REGEXP_SPACES);
if (types.length > 1) {
each(types, (t) => {
removeListener(element, t, listener, options);
});
return;
}
if (element.removeEventListener) {
element.removeEventListener(type, listener, options);
} else if (element.detachEvent) {
element.detachEvent(`on${type}`, listener);
}
}
/**
* Add event listener to the target element.
* @param {Element} element - The event target.
* @param {string} type - The event type(s).
* @param {Function} listener - The event listener.
* @param {Object} options - The event options.
*/
export function addListener(element, type, listener, options = {}) {
if (!isFunction(listener)) {
return;
}
const types = type.trim().split(REGEXP_SPACES);
if (types.length > 1) {
each(types, (t) => {
addListener(element, t, listener, options);
});
return;
}
if (options.once) {
const originalListener = listener;
listener = (...args) => {
removeListener(element, type, listener, options);
return originalListener.apply(element, args);
};
}
if (element.addEventListener) {
element.addEventListener(type, listener, options);
} else if (element.attachEvent) {
element.attachEvent(`on${type}`, listener);
}
}
/**
* Dispatch event on the target element.
* @param {Element} element - The event target.
* @param {string} type - The event type(s).
* @param {Object} data - The additional event data.
* @returns {boolean} Indicate if the event is default prevented or not.
*/
export function dispatchEvent(element, type, data) {
if (element.dispatchEvent) {
let event;
// Event and CustomEvent on IE9-11 are global objects, not constructors
if (isFunction(Event) && isFunction(CustomEvent)) {
if (isUndefined(data)) {
event = new Event(type, {
bubbles: true,
cancelable: true,
});
} else {
event = new CustomEvent(type, {
detail: data,
bubbles: true,
cancelable: true,
});
}
} else if (isUndefined(data)) {
event = document.createEvent('Event');
event.initEvent(type, true, true);
} else {
event = document.createEvent('CustomEvent');
event.initCustomEvent(type, true, true, data);
}
// IE9+
return element.dispatchEvent(event);
} else if (element.fireEvent) {
// IE6-10 (native events only)
return element.fireEvent(`on${type}`);
}
return true;
}
/**
* Get the offset base on the document.
* @param {Element} element - The target element.
* @returns {Object} The offset data.
*/
export function getOffset(element) {
const doc = document.documentElement;
const box = element.getBoundingClientRect();
return {
left: box.left + (
(window.scrollX || (doc && doc.scrollLeft) || 0) - ((doc && doc.clientLeft) || 0)
),
top: box.top + (
(window.scrollY || (doc && doc.scrollTop) || 0) - ((doc && doc.clientTop) || 0)
),
};
}
/**
* Empty an element.
* @param {Element} element - The element to empty.
*/
export function empty(element) {
while (element.firstChild) {
element.removeChild(element.firstChild);
}
}
const { location } = WINDOW;
const REGEXP_ORIGINS = /^(https?:)\/\/([^:/?#]+):?(\d*)/i;
/**
* Check if the given URL is a cross origin URL.
* @param {string} url - The target URL.
* @returns {boolean} Returns `true` if the given URL is a cross origin URL, else `false`.
*/
export function isCrossOriginURL(url) {
const parts = url.match(REGEXP_ORIGINS);
return parts && (
parts[1] !== location.protocol ||
parts[2] !== location.hostname ||
parts[3] !== location.port
);
}
/**
* Add timestamp to the given URL.
* @param {string} url - The target URL.
* @returns {string} The result URL.
*/
export function addTimestamp(url) {
const timestamp = `timestamp=${(new Date()).getTime()}`;
return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
}
/**
* Get transforms base on the given object.
* @param {Object} obj - The target object.
* @returns {string} A string contains transform values.
*/
export function getTransforms({
rotate,
scaleX,
scaleY,
translateX,
translateY,
}) {
const values = [];
if (isNumber(translateX) && translateX !== 0) {
values.push(`translateX(${translateX}px)`);
}
if (isNumber(translateY) && translateY !== 0) {
values.push(`translateY(${translateY}px)`);
}
// Rotate should come first before scale to match orientation transform
if (isNumber(rotate) && rotate !== 0) {
values.push(`rotate(${rotate}deg)`);
}
if (isNumber(scaleX) && scaleX !== 1) {
values.push(`scaleX(${scaleX})`);
}
if (isNumber(scaleY) && scaleY !== 1) {
values.push(`scaleY(${scaleY})`);
}
const transform = values.length ? values.join(' ') : 'none';
return {
WebkitTransform: transform,
msTransform: transform,
transform,
};
}
const { navigator } = WINDOW;
const IS_SAFARI_OR_UIWEBVIEW = navigator && /(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(navigator.userAgent);
/**
* Get an image's natural sizes.
* @param {string} image - The target image.
* @param {Function} callback - The callback function.
*/
export function getImageNaturalSizes(image, callback) {
// Modern browsers (except Safari)
if (image.naturalWidth && !IS_SAFARI_OR_UIWEBVIEW) {
callback(image.naturalWidth, image.naturalHeight);
return;
}
const newImage = document.createElement('img');
const body = document.body || document.documentElement;
newImage.onload = () => {
callback(newImage.width, newImage.height);
if (!IS_SAFARI_OR_UIWEBVIEW) {
body.removeChild(newImage);
}
};
newImage.src = image.src;
// iOS Safari will convert the image automatically
// with its orientation once append it into DOM (#279)
if (!IS_SAFARI_OR_UIWEBVIEW) {
newImage.style.cssText = (
'left:0;' +
'max-height:none!important;' +
'max-width:none!important;' +
'min-height:0!important;' +
'min-width:0!important;' +
'opacity:0;' +
'position:absolute;' +
'top:0;' +
'z-index:-1;'
);
body.appendChild(newImage);
}
}
/**
* Get the max ratio of a group of pointers.
* @param {string} pointers - The target pointers.
* @returns {number} The result ratio.
*/
export function getMaxZoomRatio(pointers) {
const pointers2 = extend({}, pointers);
const ratios = [];
each(pointers, (pointer, pointerId) => {
delete pointers2[pointerId];
each(pointers2, (pointer2) => {
const x1 = Math.abs(pointer.startX - pointer2.startX);
const y1 = Math.abs(pointer.startY - pointer2.startY);
const x2 = Math.abs(pointer.endX - pointer2.endX);
const y2 = Math.abs(pointer.endY - pointer2.endY);
const z1 = Math.sqrt((x1 * x1) + (y1 * y1));
const z2 = Math.sqrt((x2 * x2) + (y2 * y2));
const ratio = (z2 - z1) / z1;
ratios.push(ratio);
});
});
ratios.sort((a, b) => Math.abs(a) < Math.abs(b));
return ratios[0];
}
/**
* Get a pointer from an event object.
* @param {Object} event - The target event object.
* @param {boolean} endOnly - Indicates if only returns the end point coordinate or not.
* @returns {Object} The result pointer contains start and/or end point coordinates.
*/
export function getPointer({ pageX, pageY }, endOnly) {
const end = {
endX: pageX,
endY: pageY,
};
if (endOnly) {
return end;
}
return extend({
startX: pageX,
startY: pageY,
}, end);
}
/**
* Get the center point coordinate of a group of pointers.
* @param {Object} pointers - The target pointers.
* @returns {Object} The center point coordinate.
*/
export function getPointersCenter(pointers) {
let pageX = 0;
let pageY = 0;
let count = 0;
each(pointers, ({ startX, startY }) => {
pageX += startX;
pageY += startY;
count += 1;
});
pageX /= count;
pageY /= count;
return {
pageX,
pageY,
};
}
/**
* Check if the given value is a finite number.
*/
export const isFinite = Number.isFinite || WINDOW.isFinite;
/**
* Get the max sizes in a rectangle under the given aspect ratio.
* @param {Object} data - The original sizes.
* @param {string} [type='contain'] - The adjust type.
* @returns {Object} The result sizes.
*/
export function getAdjustedSizes(
{
aspectRatio,
height,
width,
},
type = 'contain', // or 'cover'
) {
const isValidNumber = value => isFinite(value) && value > 0;
if (isValidNumber(width) && isValidNumber(height)) {
const adjustedWidth = height * aspectRatio;
if ((type === 'contain' && adjustedWidth > width) || (type === 'cover' && adjustedWidth < width)) {
height = width / aspectRatio;
} else {
width = height * aspectRatio;
}
} else if (isValidNumber(width)) {
height = width / aspectRatio;
} else if (isValidNumber(height)) {
width = height * aspectRatio;
}
return {
width,
height,
};
}
/**
* Get the new sizes of a rectangle after rotated.
* @param {Object} data - The original sizes.
* @returns {Object} The result sizes.
*/
export function getRotatedSizes({ width, height, degree }) {
degree = Math.abs(degree) % 180;
if (degree === 90) {
return {
width: height,
height: width,
};
}
const arc = ((degree % 90) * Math.PI) / 180;
const sinArc = Math.sin(arc);
const cosArc = Math.cos(arc);
const newWidth = (width * cosArc) + (height * sinArc);
const newHeight = (width * sinArc) + (height * cosArc);
return degree > 90 ? {
width: newHeight,
height: newWidth,
} : {
width: newWidth,
height: newHeight,
};
}
/**
* Get a canvas which drew the given image.
* @param {HTMLImageElement} image - The image for drawing.
* @param {Object} imageData - The image data.
* @param {Object} canvasData - The canvas data.
* @param {Object} options - The options.
* @returns {HTMLCanvasElement} The result canvas.
*/
export function getSourceCanvas(
image,
{
rotate = 0,
scaleX = 1,
scaleY = 1,
},
{
aspectRatio,
naturalWidth,
naturalHeight,
},
{
fillColor = 'transparent',
imageSmoothingEnabled = true,
imageSmoothingQuality = 'low',
maxWidth = Infinity,
maxHeight = Infinity,
minWidth = 0,
minHeight = 0,
},
) {
const canvas = document.createElement('canvas');
const context = canvas.getContext('2d');
const maxSizes = getAdjustedSizes({
aspectRatio,
width: maxWidth,
height: maxHeight,
});
const minSizes = getAdjustedSizes({
aspectRatio,
width: minWidth,
height: minHeight,
}, 'cover');
const width = Math.min(maxSizes.width, Math.max(minSizes.width, naturalWidth));
const height = Math.min(maxSizes.height, Math.max(minSizes.height, naturalHeight));
const params = [
-width / 2,
-height / 2,
width,
height,
];
canvas.width = normalizeDecimalNumber(width);
canvas.height = normalizeDecimalNumber(height);
context.fillStyle = fillColor;
context.fillRect(0, 0, width, height);
context.save();
context.translate(width / 2, height / 2);
context.rotate((rotate * Math.PI) / 180);
context.scale(scaleX, scaleY);
context.imageSmoothingEnabled = imageSmoothingEnabled;
context.imageSmoothingQuality = imageSmoothingQuality;
context.drawImage(image, ...params.map(param => Math.floor(normalizeDecimalNumber(param))));
context.restore();
return canvas;
}
const { fromCharCode } = String;
/**
* Get string from char code in data view.
* @param {DataView} dataView - The data view for read.
* @param {number} start - The start index.
* @param {number} length - The read length.
* @returns {string} The read result.
*/
export function getStringFromCharCode(dataView, start, length) {
let str = '';
let i;
length += start;
for (i = start; i < length; i += 1) {
str += fromCharCode(dataView.getUint8(i));
}
return str;
}
const REGEXP_DATA_URL_HEAD = /^data:.*,/;
/**
* Transform Data URL to array buffer.
* @param {string} dataURL - The Data URL to transform.
* @returns {ArrayBuffer} The result array buffer.
*/
export function dataURLToArrayBuffer(dataURL) {
const base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
const binary = atob(base64);
const arrayBuffer = new ArrayBuffer(binary.length);
const uint8 = new Uint8Array(arrayBuffer);
each(uint8, (value, i) => {
uint8[i] = binary.charCodeAt(i);
});
return arrayBuffer;
}
/**
* Transform array buffer to Data URL.
* @param {ArrayBuffer} arrayBuffer - The array buffer to transform.
* @param {string} mimeType - The mime type of the Data URL.
* @returns {string} The result Data URL.
*/
export function arrayBufferToDataURL(arrayBuffer, mimeType) {
const uint8 = new Uint8Array(arrayBuffer);
let data = '';
// TypedArray.prototype.forEach is not supported in some browsers.
each(uint8, (value) => {
data += fromCharCode(value);
});
return `data:${mimeType};base64,${btoa(data)}`;
}
/**
* Get orientation value from given array buffer.
* @param {ArrayBuffer} arrayBuffer - The array buffer to read.
* @returns {number} The read orientation value.
*/
export function getOrientation(arrayBuffer) {
const dataView = new DataView(arrayBuffer);
let orientation;
let littleEndian;
let app1Start;
let ifdStart;
// Only handle JPEG image (start by 0xFFD8)
if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
const length = dataView.byteLength;
let offset = 2;
while (offset < length) {
if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
app1Start = offset;
break;
}
offset += 1;
}
}
if (app1Start) {
const exifIDCode = app1Start + 4;
const tiffOffset = app1Start + 10;
if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
const endianness = dataView.getUint16(tiffOffset);
littleEndian = endianness === 0x4949;
if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
const firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
if (firstIFDOffset >= 0x00000008) {
ifdStart = tiffOffset + firstIFDOffset;
}
}
}
}
}
if (ifdStart) {
const length = dataView.getUint16(ifdStart, littleEndian);
let offset;
let i;
for (i = 0; i < length; i += 1) {
offset = ifdStart + (i * 12) + 2;
if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
// 8 is the offset of the current tag's value
offset += 8;
// Get the original orientation value
orientation = dataView.getUint16(offset, littleEndian);
// Override the orientation with its default value
dataView.setUint16(offset, 1, littleEndian);
break;
}
}
}
return orientation;
}
/**
* Parse Exif Orientation value.
* @param {number} orientation - The orientation to parse.
* @returns {Object} The parsed result.
*/
export function parseOrientation(orientation) {
let rotate = 0;
let scaleX = 1;
let scaleY = 1;
switch (orientation) {
// Flip horizontal
case 2:
scaleX = -1;
break;
// Rotate left 180°
case 3:
rotate = -180;
break;
// Flip vertical
case 4:
scaleY = -1;
break;
// Flip vertical and rotate right 90°
case 5:
rotate = 90;
scaleY = -1;
break;
// Rotate right 90°
case 6:
rotate = 90;
break;
// Flip horizontal and rotate right 90°
case 7:
rotate = 90;
scaleX = -1;
break;
// Rotate left 90°
case 8:
rotate = -90;
break;
default:
}
return {
rotate,
scaleX,
scaleY,
};
}

View File

@ -0,0 +1,9 @@
.container {
max-width: 640px;
max-height: 360px;
margin: 20px auto;
}
.container > img {
max-width: 100%;
}

View File

@ -0,0 +1,21 @@
QUnit.test('events#crop', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(7);
image.addEventListener('crop', function (e) {
assert.ok(util.isNumber(e.detail.x));
assert.ok(util.isNumber(e.detail.y));
assert.ok(util.isNumber(e.detail.width));
assert.ok(util.isNumber(e.detail.height));
assert.ok(util.isNumber(e.detail.rotate));
assert.ok(util.isNumber(e.detail.scaleX));
assert.ok(util.isNumber(e.detail.scaleY));
done();
});
return new Cropper(image);
});

View File

@ -0,0 +1,24 @@
QUnit.test('events#cropend', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
});
image.addEventListener('cropend', function (e) {
assert.strictEqual(e.detail.action, 'crop');
});
return new Cropper(image);
});

View File

@ -0,0 +1,24 @@
QUnit.test('events#cropmove', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
});
image.addEventListener('cropmove', function (e) {
assert.strictEqual(e.detail.action, 'crop');
});
return new Cropper(image);
});

View File

@ -0,0 +1,56 @@
QUnit.test('events#cropstart', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
});
image.addEventListener('cropstart', function (e) {
assert.strictEqual(e.detail.action, 'crop');
});
return new Cropper(image);
});
QUnit.test('events#cropstart: default prevented', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(0);
image.addEventListener('ready', function () {
var PointerEvent = window.PointerEvent;
var cropper = this.cropper;
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerdown' : 'mousedown');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointermove' : 'mousemove');
util.dispatchEvent(cropper.dragBox, PointerEvent ? 'pointerup' : 'mouseup');
done();
});
image.addEventListener('cropstart', function (e) {
e.preventDefault();
});
image.addEventListener('cropmove', function () {
assert.ok(false);
});
image.addEventListener('cropend', function () {
assert.ok(false);
});
return new Cropper(image);
});

View File

@ -0,0 +1,15 @@
QUnit.test('events#ready', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
assert.ok(true);
done();
});
return new Cropper(image);
});

View File

@ -0,0 +1,46 @@
QUnit.test('events#zoom', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(3);
image.addEventListener('ready', function () {
var cropper = this.cropper;
cropper.zoom(0.1);
done();
});
image.addEventListener('zoom', function (e) {
assert.ok(e.detail.ratio > 0);
assert.ok(e.detail.oldRatio > 0);
assert.ok(e.detail.ratio > e.detail.oldRatio);
});
return new Cropper(image);
});
QUnit.test('events#zoom: default prevented', function (assert) {
var done = assert.async();
var util = window.Util;
var image = util.createImage();
assert.expect(1);
image.addEventListener('ready', function () {
var cropper = this.cropper;
var canvasData = cropper.getCanvasData();
assert.deepEqual(cropper.zoom(0.1).getCanvasData(), canvasData);
done();
});
image.addEventListener('zoom', function (e) {
e.preventDefault();
});
return new Cropper(image);
});

View File

@ -0,0 +1,90 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Cropper.js</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.4.0.css">
<link rel="stylesheet" href="../dist/cropper.css">
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.4.0.js"></script>
<script src="../dist/cropper.js"></script>
<script src="js/main.js"></script>
<!-- Options -->
<script src="options/viewMode.js"></script>
<script src="options/dragMode.js"></script>
<script src="options/aspectRatio.js"></script>
<script src="options/data.js"></script>
<!-- <script src="options/checkCrossOrigin.js"></script> -->
<script src="options/checkOrientation.js"></script>
<script src="options/modal.js"></script>
<script src="options/guides.js"></script>
<script src="options/center.js"></script>
<script src="options/highlight.js"></script>
<script src="options/background.js"></script>
<script src="options/autoCrop.js"></script>
<script src="options/movable.js"></script>
<script src="options/rotatable.js"></script>
<script src="options/scalable.js"></script>
<script src="options/zoomable.js"></script>
<script src="options/zoomOnWheel.js"></script>
<script src="options/cropBoxMovable.js"></script>
<script src="options/cropBoxResizable.js"></script>
<script src="options/toggleDragModeOnDblclick.js"></script>
<script src="options/minCanvasWidth.js"></script>
<script src="options/minCanvasHeight.js"></script>
<script src="options/minCropBoxWidth.js"></script>
<script src="options/minCropBoxHeight.js"></script>
<script src="options/minContainerWidth.js"></script>
<script src="options/minContainerHeight.js"></script>
<script src="options/ready.js"></script>
<script src="options/cropstart.js"></script>
<script src="options/cropmove.js"></script>
<script src="options/cropend.js"></script>
<script src="options/crop.js"></script>
<script src="options/zoom.js"></script>
<!-- Methods -->
<script src="methods/crop.js"></script>
<script src="methods/reset.js"></script>
<script src="methods/clear.js"></script>
<script src="methods/replace.js"></script>
<script src="methods/enable.js"></script>
<script src="methods/disable.js"></script>
<script src="methods/destroy.js"></script>
<script src="methods/move.js"></script>
<script src="methods/moveTo.js"></script>
<script src="methods/zoom.js"></script>
<script src="methods/zoomTo.js"></script>
<script src="methods/rotate.js"></script>
<script src="methods/rotateTo.js"></script>
<script src="methods/scale.js"></script>
<script src="methods/scaleX.js"></script>
<script src="methods/scaleY.js"></script>
<script src="methods/getData.js"></script>
<script src="methods/setData.js"></script>
<script src="methods/getContainerData.js"></script>
<script src="methods/getImageData.js"></script>
<script src="methods/getCanvasData.js"></script>
<script src="methods/setCanvasData.js"></script>
<script src="methods/getCropBoxData.js"></script>
<script src="methods/setCropBoxData.js"></script>
<script src="methods/getCroppedCanvas.js"></script>
<script src="methods/setAspectRatio.js"></script>
<script src="methods/setDragMode.js"></script>
<!-- Events -->
<script src="events/ready.js"></script>
<script src="events/cropstart.js"></script>
<script src="events/cropmove.js"></script>
<script src="events/cropend.js"></script>
<script src="events/crop.js"></script>
<script src="events/zoom.js"></script>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More