From c0299c5ff700dbbe6077bdf981dc5bdb8b0530c7 Mon Sep 17 00:00:00 2001 From: wanghongjun <1445693971@qq,com> Date: Thu, 12 Oct 2023 17:25:13 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=BA=8C=E6=AC=A1=E6=8E=A8=E9=80=81?= =?UTF-8?q?=E9=A1=B9=E7=9B=AE3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/.htaccess | 9 + public/shearphoto/css/ShearPhoto.css | 1 + public/shearphoto/css/ShearPhoto_f_en-us.css | 703 ++ public/shearphoto/css/ShearPhoto_f_zh-cn.css | 703 ++ public/shearphoto/file/photo/1.jpg | Bin 0 -> 31923 bytes public/shearphoto/file/photo/2.jpg | Bin 0 -> 60315 bytes public/shearphoto/file/photo/3.jpg | Bin 0 -> 62875 bytes public/shearphoto/file/photo/4.jpg | Bin 0 -> 47945 bytes public/shearphoto/images/Author.png | Bin 0 -> 8320 bytes .../shearphoto/images/Effects/cardboard.png | Bin 0 -> 12917 bytes public/shearphoto/images/Effects/e0.jpg | Bin 0 -> 2795 bytes public/shearphoto/images/Effects/e1.jpg | Bin 0 -> 2331 bytes public/shearphoto/images/Effects/e10.jpg | Bin 0 -> 2712 bytes public/shearphoto/images/Effects/e11.jpg | Bin 0 -> 1761 bytes public/shearphoto/images/Effects/e12.jpg | Bin 0 -> 2843 bytes public/shearphoto/images/Effects/e13.jpg | Bin 0 -> 2893 bytes public/shearphoto/images/Effects/e14.jpg | Bin 0 -> 2779 bytes public/shearphoto/images/Effects/e2.jpg | Bin 0 -> 2007 bytes public/shearphoto/images/Effects/e3.jpg | Bin 0 -> 2501 bytes public/shearphoto/images/Effects/e4.jpg | Bin 0 -> 3013 bytes public/shearphoto/images/Effects/e5.jpg | Bin 0 -> 2726 bytes public/shearphoto/images/Effects/e6.jpg | Bin 0 -> 2689 bytes public/shearphoto/images/Effects/e7.jpg | Bin 0 -> 2527 bytes public/shearphoto/images/Effects/e8.jpg | Bin 0 -> 2355 bytes public/shearphoto/images/Effects/e9.jpg | Bin 0 -> 2493 bytes public/shearphoto/images/Select.jpg | Bin 0 -> 14365 bytes public/shearphoto/images/Select_en-us.jpg | Bin 0 -> 47038 bytes public/shearphoto/images/Select_zh-cn.jpg | Bin 0 -> 14365 bytes public/shearphoto/images/ZoomBar.gif | Bin 0 -> 1261 bytes public/shearphoto/images/Zoomcentre.jpg | Bin 0 -> 1407 bytes public/shearphoto/images/bch.jpg | Bin 0 -> 2406 bytes public/shearphoto/images/bg.jpg | Bin 0 -> 462 bytes public/shearphoto/images/bg.png | Bin 0 -> 2817 bytes public/shearphoto/images/bg_headLine1.png | Bin 0 -> 2802 bytes public/shearphoto/images/bg_index.gif | Bin 0 -> 2779 bytes public/shearphoto/images/bg_repno.png | Bin 0 -> 2861 bytes public/shearphoto/images/border.gif | Bin 0 -> 329 bytes public/shearphoto/images/btn5.jpg | Bin 0 -> 4870 bytes public/shearphoto/images/cam.png | Bin 0 -> 3135 bytes public/shearphoto/images/cam_bg.jpg | Bin 0 -> 4700 bytes public/shearphoto/images/default.gif | Bin 0 -> 1093 bytes public/shearphoto/images/fancybox_sprite.png | Bin 0 -> 1362 bytes public/shearphoto/images/loading.gif | Bin 0 -> 5035 bytes public/shearphoto/images/logo.png | Bin 0 -> 11154 bytes public/shearphoto/images/logo2.png | Bin 0 -> 13863 bytes public/shearphoto/images/shutter.mp3 | Bin 0 -> 13624 bytes public/shearphoto/images/waterimg.png | Bin 0 -> 11069 bytes public/shearphoto/images/waterimg2.png | Bin 0 -> 4237 bytes public/shearphoto/images/zoom.png | Bin 0 -> 1275 bytes public/shearphoto/js/ShearPhoto.js | 2000 ++++ public/shearphoto/js/alloyimage.js | 723 ++ public/shearphoto/js/handle.js | 183 + public/shearphoto/js/handle_f.js | 185 + public/shearphoto/php/shearphoto.config.php | 21 + public/shearphoto/php/shearphoto.php | 264 + public/shearphoto/php/shearphoto.up.php | 59 + public/shearphoto/php/upload.php | 13 + public/shearphoto/php/zip_img.php | 76 + public/sldate/daterangepicker-bs3.css | 281 + public/sldate/daterangepicker.js | 1019 +++ public/sldate/moment.js | 2400 +++++ public/webuploader/README.md | 25 + public/webuploader/Uploader.swf | Bin 0 -> 115554 bytes public/webuploader/webuploader.css | 28 + public/webuploader/webuploader.custom.js | 6530 +++++++++++++ public/webuploader/webuploader.custom.min.js | 2 + public/webuploader/webuploader.fis.js | 8117 ++++++++++++++++ public/webuploader/webuploader.flashonly.js | 4648 ++++++++++ .../webuploader/webuploader.flashonly.min.js | 2 + .../webuploader/webuploader.html5nodepend.js | 6589 +++++++++++++ public/webuploader/webuploader.html5only.js | 6059 ++++++++++++ .../webuploader/webuploader.html5only.min.js | 2 + public/webuploader/webuploader.js | 8140 +++++++++++++++++ public/webuploader/webuploader.min.js | 3 + public/webuploader/webuploader.noimage.js | 5057 ++++++++++ public/webuploader/webuploader.noimage.min.js | 2 + public/webuploader/webuploader.nolog.js | 8046 ++++++++++++++++ public/webuploader/webuploader.nolog.min.js | 3 + .../webuploader/webuploader.withoutimage.js | 4993 ++++++++++ .../webuploader.withoutimage.min.js | 2 + public/yfcmf/dispatch_jump.html | 33 + public/yfcmf/error.html | 34 + public/yfcmf/yfcmf.css | 22 + public/yfcmf/yfcmf.js | 1244 +++ 84 files changed, 68221 insertions(+) create mode 100644 public/.htaccess create mode 100644 public/shearphoto/css/ShearPhoto.css create mode 100644 public/shearphoto/css/ShearPhoto_f_en-us.css create mode 100644 public/shearphoto/css/ShearPhoto_f_zh-cn.css create mode 100644 public/shearphoto/file/photo/1.jpg create mode 100644 public/shearphoto/file/photo/2.jpg create mode 100644 public/shearphoto/file/photo/3.jpg create mode 100644 public/shearphoto/file/photo/4.jpg create mode 100644 public/shearphoto/images/Author.png create mode 100644 public/shearphoto/images/Effects/cardboard.png create mode 100644 public/shearphoto/images/Effects/e0.jpg create mode 100644 public/shearphoto/images/Effects/e1.jpg create mode 100644 public/shearphoto/images/Effects/e10.jpg create mode 100644 public/shearphoto/images/Effects/e11.jpg create mode 100644 public/shearphoto/images/Effects/e12.jpg create mode 100644 public/shearphoto/images/Effects/e13.jpg create mode 100644 public/shearphoto/images/Effects/e14.jpg create mode 100644 public/shearphoto/images/Effects/e2.jpg create mode 100644 public/shearphoto/images/Effects/e3.jpg create mode 100644 public/shearphoto/images/Effects/e4.jpg create mode 100644 public/shearphoto/images/Effects/e5.jpg create mode 100644 public/shearphoto/images/Effects/e6.jpg create mode 100644 public/shearphoto/images/Effects/e7.jpg create mode 100644 public/shearphoto/images/Effects/e8.jpg create mode 100644 public/shearphoto/images/Effects/e9.jpg create mode 100644 public/shearphoto/images/Select.jpg create mode 100644 public/shearphoto/images/Select_en-us.jpg create mode 100644 public/shearphoto/images/Select_zh-cn.jpg create mode 100644 public/shearphoto/images/ZoomBar.gif create mode 100644 public/shearphoto/images/Zoomcentre.jpg create mode 100644 public/shearphoto/images/bch.jpg create mode 100644 public/shearphoto/images/bg.jpg create mode 100644 public/shearphoto/images/bg.png create mode 100644 public/shearphoto/images/bg_headLine1.png create mode 100644 public/shearphoto/images/bg_index.gif create mode 100644 public/shearphoto/images/bg_repno.png create mode 100644 public/shearphoto/images/border.gif create mode 100644 public/shearphoto/images/btn5.jpg create mode 100644 public/shearphoto/images/cam.png create mode 100644 public/shearphoto/images/cam_bg.jpg create mode 100644 public/shearphoto/images/default.gif create mode 100644 public/shearphoto/images/fancybox_sprite.png create mode 100644 public/shearphoto/images/loading.gif create mode 100644 public/shearphoto/images/logo.png create mode 100644 public/shearphoto/images/logo2.png create mode 100644 public/shearphoto/images/shutter.mp3 create mode 100644 public/shearphoto/images/waterimg.png create mode 100644 public/shearphoto/images/waterimg2.png create mode 100644 public/shearphoto/images/zoom.png create mode 100644 public/shearphoto/js/ShearPhoto.js create mode 100644 public/shearphoto/js/alloyimage.js create mode 100644 public/shearphoto/js/handle.js create mode 100644 public/shearphoto/js/handle_f.js create mode 100644 public/shearphoto/php/shearphoto.config.php create mode 100644 public/shearphoto/php/shearphoto.php create mode 100644 public/shearphoto/php/shearphoto.up.php create mode 100644 public/shearphoto/php/upload.php create mode 100644 public/shearphoto/php/zip_img.php create mode 100644 public/sldate/daterangepicker-bs3.css create mode 100644 public/sldate/daterangepicker.js create mode 100644 public/sldate/moment.js create mode 100644 public/webuploader/README.md create mode 100644 public/webuploader/Uploader.swf create mode 100644 public/webuploader/webuploader.css create mode 100644 public/webuploader/webuploader.custom.js create mode 100644 public/webuploader/webuploader.custom.min.js create mode 100644 public/webuploader/webuploader.fis.js create mode 100644 public/webuploader/webuploader.flashonly.js create mode 100644 public/webuploader/webuploader.flashonly.min.js create mode 100644 public/webuploader/webuploader.html5nodepend.js create mode 100644 public/webuploader/webuploader.html5only.js create mode 100644 public/webuploader/webuploader.html5only.min.js create mode 100644 public/webuploader/webuploader.js create mode 100644 public/webuploader/webuploader.min.js create mode 100644 public/webuploader/webuploader.noimage.js create mode 100644 public/webuploader/webuploader.noimage.min.js create mode 100644 public/webuploader/webuploader.nolog.js create mode 100644 public/webuploader/webuploader.nolog.min.js create mode 100644 public/webuploader/webuploader.withoutimage.js create mode 100644 public/webuploader/webuploader.withoutimage.min.js create mode 100644 public/yfcmf/dispatch_jump.html create mode 100644 public/yfcmf/error.html create mode 100644 public/yfcmf/yfcmf.css create mode 100644 public/yfcmf/yfcmf.js diff --git a/public/.htaccess b/public/.htaccess new file mode 100644 index 0000000..23ba96f --- /dev/null +++ b/public/.htaccess @@ -0,0 +1,9 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f +# RewriteRule ^(.*)$ index.php/$1 [QSA,PT,L] + RewriteRule ^(.*)$ index.php [L,E=PATH_INFO:$1] + diff --git a/public/shearphoto/css/ShearPhoto.css b/public/shearphoto/css/ShearPhoto.css new file mode 100644 index 0000000..d084685 --- /dev/null +++ b/public/shearphoto/css/ShearPhoto.css @@ -0,0 +1 @@ +@charset "utf-8";#shearphoto_main{visibility:hidden;margin:0 auto}#shearphoto_loading{width:100px;text-align:center;line-height:130px;color:#999;margin:0 auto;background:url(../images/loading.gif) no-repeat}.header{height:65px;margin-bottom:10px;width:100%;overflow:hidden;background:url(../images/bg_headLine1.png) no-repeat 290px #505153}.header A{-moz-border-radius:5px;border-radius:5px;color:#FFF;display:inline-block;float:left;font-size:15px;font-weight:700;line-height:50px;text-align:center;width:150px;margin:7px 0 0 100px;background:#3E8BFF}.header A:hover{text-decoration:underline;background-color:#0F6FFF}.header img{display:inline-block;float:left;margin-left:20px}.header strong{color:#FFF;display:block;float:right;height:60px;line-height:60px;margin-right:20px}#shearphoto_main #preview{float:left;position:relative}#shearphoto_main #preview a{display:inline;float:left;-moz-border-radius:5px;border-radius:5px;-moz-box-shadow:1px 1px 3px 1px #999;box-shadow:1px 1px 3px 1px #999;overflow:hidden;position:relative}#shearphoto_main #preview a img{position:relative}.bottom a:hover{color:#39F;text-decoration:underline}#Shearbar #LeftRotate{margin:0 10px}#Shearbar #LeftRotate em,#Shearbar #RightRotate em{display:inline-block;height:21px;vertical-align:middle;width:15px;margin:1px 3px 0 0;background:url(../images/bch.jpg) no-repeat}#Shearbar #LeftRotate,#Shearbar #RightRotate{-moz-border-radius:5px;border:1px solid #CCC;border-radius:5px;color:#666;cursor:pointer;float:left;font-size:12px;margin-top:-5px;text-align:center;width:80px;padding:5px 0}#Shearbar #LeftRotate:hover,#Shearbar #RightRotate:hover{border:1px solid #919191;color:#414141}#Shearbar #RightRotate{margin-left:10px}#Shearbar #RightRotate em{background-position:-91px 0;margin:1px 0 0 3px}#Shearbar .hint{color:#333;display:block;float:left;font-size:12px;font-style:normal;height:19px;line-height:15px;width:19px}#Shearbar .hint.L,#Shearbar .hint.R{background:url(../images/zoom.png) no-repeat -2px -1px}#Shearbar .hint.R{background-position:-43px -1px}#shearphoto_main #SelectBox{background:url(../images/bg.png);position:absolute;z-index:180}#SelectBox #selectImage input{height:74px;width:224px;background-color:#FFF;filter:alpha(opacity=0);opacity:0;position:absolute;float:left;cursor:pointer;display:block}.displayNone{display:none}#SelectBox #PhotoLoading,#SelectBox #camerasImage,#SelectBox #selectImage{-moz-border-radius:10px;border-radius:10px;-moz-box-shadow:2px 2px 7px 1px #3e4044;box-shadow:2px 2px 7px 1px #3e4044;display:block;height:74px;outline:0 none;position:absolute;width:223px;overflow:hidden;margin:-37px 0 0 -112px;background:url(../images/Select.jpg) no-repeat}#SelectBox #PhotoLoading{background-position:0 -148px;left:50%;top:70%}#SelectBox #selectImage{left:50%;top:35%}#SelectBox #camerasImage{background-position:0 -74px;left:50%;top:79%}#SelectBox #PhotoLoading:hover,#SelectBox #camerasImage:hover,#SelectBox #selectImage:hover{border:3px solid #666;margin:-40px 0 0 -115px}#shearphoto_main #Shearbar{padding-top:10px;width:442px;margin:0 auto}#Shearbar .Psava{clear:both;height:40px;padding:20px 0 0 110px}#Shearbar .Psava #againIMG,#Shearbar .Psava #saveShear{background:url(../images/btn5.jpg) no-repeat;color:#666;float:left;line-height:31px;margin-right:20px;text-align:center;width:79px;height:31px}#Shearbar .Psava #againIMG:hover{background-position:0 -31px}#Shearbar .Psava #saveShear{background-position:-79px 0;color:#FFF}#Shearbar .Psava #saveShear:hover{background-position:-79px -31px}#Shearbar #ZoomDist{float:left;height:20px;position:relative;width:160px}#Shearbar #ZoomDist #ZoomBar{background:url(../images/ZoomBar.gif) no-repeat;filter:alpha(opacity=80);opacity:.8;height:20px;left:0;position:absolute;width:15px}#ZoomDist #Zoomcentre{filter:alpha(opacity=100);height:9px;left:50%;top:25px;position:absolute;width:10px;margin-left:-4px;background:url(../images/Zoomcentre.jpg) no-repeat}#ZoomDist .progress{-moz-border-radius:15px;border-radius:15px;background:#999;display:block;float:left;height:8px;margin-top:4px;overflow:hidden;width:100%}#shearphoto_main #black{position:absolute;z-index:99}#shearphoto_main .primary{float:left}#shearphoto_main #main{-moz-user-select:none;-ms-user-select:none;-webkit-user-select:none;background:url(../images/bg.png);overflow:hidden;position:relative;z-index:50;border:1px solid #CCC;margin:0 auto}#main .BigImg{float:left;position:relative;z-index:51}#main .MoveImg{position:relative;z-index:98}#main #imgID{display:block;z-index:51}#main #movebox{position:absolute;z-index:100;float:left}#movebox #borderBottom,#movebox #borderLeft,#movebox #borderRight,#movebox #borderTop{background:url(../images/border.gif) #FFF;display:inline-block;filter:alpha(opacity=50);opacity:.5;overflow:hidden;position:absolute;z-index:104}#movebox #borderTop{height:1px;left:0;top:0;width:100%}#movebox #borderLeft{height:100%;left:0;top:0;width:1px}#movebox #borderRight{height:100%;right:0;top:0;width:1px}#movebox #borderBottom{bottom:0;height:1px;left:0;width:100%}#main #movebox #BottomRight,#main #movebox #Bottomleft,#main #movebox #Bottommiddle,#main #movebox #Rightmiddle,#main #movebox #TopRight,#main #movebox #Topleft,#main #movebox #Topmiddle,#main #movebox #leftmiddle{background:#000;border:1px solid #FFF;bottom:-5px;cursor:nw-resize;display:block;filter:alpha(opacity=50);height:8px;opacity:.5;overflow:hidden;position:absolute;right:-5px;width:8px;z-index:105}#main #movebox #Bottomleft{bottom:-5px;cursor:ne-resize;left:-5px}#main #movebox #Bottommiddle{bottom:-5px;cursor:n-resize;left:50%;margin-left:-5px}#main #movebox #Rightmiddle{cursor:e-resize;margin-top:-5px;right:-5px;top:50%}#main #movebox #TopRight{cursor:ne-resize;right:-5px;top:-5px}#main #movebox #Topleft{cursor:nw-resize;left:-5px;top:-5px}#main #movebox #Topmiddle{cursor:n-resize;left:50%;margin-left:-5px;top:-5px}#main #movebox #leftmiddle{cursor:e-resize;left:-5px;margin-top:-5px;top:50%}#main #relat{position:relative;z-index:60}#main #smallbox{background:url();overflow:hidden;position:relative;z-index:100}#main .point{width:100%;line-height:35px;font-size:12px;position:absolute;z-index:200;filter:alpha(opacity=70);opacity:.7}.point i{display:inline-block;height:16px;width:15px;float:left;margin:10px 10px 0 20px;background:url(../images/bg_repno.png) no-repeat}#shearphoto_main .complete{float:left;overflow:hidden;position:absolute;width:100%;z-index:250;padding:10px 0 0 5px;background:url(../images/bg.jpg) #CCC}.complete .completeTxt{-moz-border-radius:10px;-moz-box-shadow:0 0 15px 0 #666;border-radius:10px;bottom:30px;box-shadow:0 0 15px 0 #666;position:absolute;right:30px;text-align:center;width:280px;padding:30px 0;background:#FBFDFF}.complete .completeTxt a{color:#FFF;display:block;font-size:14px;line-height:40px;width:128px;margin:0 auto;background:url(../images/bg_index.gif) no-repeat 0 -2px}.complete .completeTxt a:hover{background-position:-137px -2px}.complete .completeTxt p{color:#666;font-size:12px;font-weight:700;margin:10px 0}.complete .completeTxt strong{font-size:14px}.complete img{border:1px solid #CCC;display:inline-block;float:left;margin:2px 90px;padding:2px}.complete .completeTxt strong i{display:inline-block;height:16px;width:15px;vertical-align:middle;margin:-3px 5px 0 0;background:url(../images/bg_repno.png) no-repeat}#photoalbum{-moz-border-radius:10px;-moz-box-shadow:3px 3px 10px 0 #000;border:8px solid #999;border-radius:10px;box-shadow:3px 3px 10px 0 #000;display:none;height:400px;position:relative;width:420px;z-index:210;margin:0 auto;background:#DFEFFF;margin-top:-400px}#photoalbum h1{background:#999;color:#FFF;font-size:15px;height:35px;line-height:30px;padding-left:10px;width:410px;position:absolute;top:-10px}#photoalbum i{background:url(../images/fancybox_sprite.png) no-repeat;display:block;float:left;height:37px;cursor:pointer;width:36px;position:absolute;left:370px;top:10px}#photoalbum ul{height:auto;width:100%;position:absolute;top:50px;margin:0 auto}#photoalbum ul li{border:1px solid #CCC;cursor:pointer;display:inline;float:left;height:155px;margin:5px;width:47%;overflow:hidden}#photoalbum ul li img{height:100%}#photoalbum ul li:hover{border:1px solid #09F}#CamBox{-moz-border-radius:10px;border-radius:10px;left:50%;position:absolute;-moz-box-shadow:5px 5px 10px 0 #3b3b3f;box-shadow:5px 5px 10px 0 #3b3b3f;width:550px;z-index:250;display:none;top:50%;margin:-285px 0 0 -276px;background:url(../images/cam_bg.jpg) #d5d5d5}#CamBox #CamFlash{height:450px;width:450px;margin:0 auto;background:#fff}#CamBox #timing{color:#F60;display:none;font-size:36px;font-weight:700;height:200px;left:50%;line-height:200px;position:absolute;text-align:center;top:50%;width:200px;margin:-100px 0 0 -100px}#CamBox .cambar{height:50px;padding-top:20px;width:300px;margin:0 auto}#CamBox .cambar a{background:url(../images/btn5.jpg) no-repeat;float:left;height:31px;line-height:31px;text-align:center;width:79px}#CamBox .lens{background:url(../images/cam.png) no-repeat 50%;height:50px;width:100%}#CamBox .cambar #camClose,#CamBox .cambar #setCam{color:#333}#CamBox .cambar #setCam{margin-right:30px}#CamBox .cambar #camClose:hover,#CamBox .cambar #setCam:hover{background-position:0 -31px}#CamBox .cambar #CamOk{background-position:-79px 0;color:#fff;margin-right:30px}#CamBox .cambar #CamOk:hover{background-position:-79px -31px}#shearphoto_main .Effects{color:#FFF;overflow-x:hidden;width:155px;display:none;float:left;border-color:#CCC;border-style:solid none;border-width:1px;background:url(../images/Effects/cardboard.png)}#shearphoto_main .Effects .EffectsStrong{border-bottom:solid 1px #676767;display:block;font-size:14px;height:30px;line-height:30px;text-align:center;width:100%}#shearphoto_main .Effects a{border-bottom:solid 1px #676767;color:#FFF;display:block;vertical-align:middle;text-shadow:#000 0 2px 0;width:100%;padding:5px 0 7px}#shearphoto_main .Effects .Aclick,#shearphoto_main .Effects a:hover{background-color:#A7A7A7}#shearphoto_main .Effects a img{-moz-border-radius:3px;border-radius:3px;display:inline-block;height:42px;vertical-align:middle;-moz-box-shadow:0 0 5px 0 #000;box-shadow:0 0 5px 0 #000;width:70px;margin:2px 7px 0}::-webkit-scrollbar{height:13px;width:13px}::-webkit-scrollbar-thumb{background:padding-box #c2c2c2;border:1px solid #979797;min-height:28px}::-webkit-scrollbar-thumb:hover{background:#929292;border:1px solid #636363}::-webkit-scrollbar-track-piece{background:#f5f5f5;border-left:1px solid #d2d2d2}.bottom{width:1000px;left:2px;color:#666;clear:both;text-align:center;margin:0 auto;padding:0 0 10px 5px}.bottom span{vertical-align:middle;display:inline-block;margin:0 5px 0 2px}.bottom a{color:#666}.header h1 a{font-size:14px;color:#fff}.bottom span img{border-radius:30px;-moz-border-radius:30px} \ No newline at end of file diff --git a/public/shearphoto/css/ShearPhoto_f_en-us.css b/public/shearphoto/css/ShearPhoto_f_en-us.css new file mode 100644 index 0000000..4f79799 --- /dev/null +++ b/public/shearphoto/css/ShearPhoto_f_en-us.css @@ -0,0 +1,703 @@ + @charset "utf-8"; + /*公用开始*/ + img,div,i,em,a,body,p,H1,ul,li { + border-style:none; + list-style-type:none; + margin:0; + padding:0; + text-decoration:none +} +/*公用结束*/ + /*缓冲效果开始 2.3加入的*/ + #shearphoto_main { + visibility:hidden; + margin:0 auto; +} +#shearphoto_loading { + width:100px; + text-align:center; + line-height:130px; + color:#999; + margin:0 auto; + background:url(../images/loading.gif) no-repeat +} +/*缓冲效果结束*/ + /*头部部份开始*/ + .header { + height:65px; + margin-bottom:10px; + width:100%; + overflow:hidden; + background:url(../images/bg_headLine1.png) no-repeat 290px #505153; +} +.header A { + -moz-border-radius:5px; + border-radius:5px; + color:#FFF; + display:inline-block; + float:left; + font-size:15px; + font-weight:700; + line-height:50px; + text-align:center; + width:150px; + margin:7px 0 0 100px; + background:#3E8BFF; +} +.header A:hover { + text-decoration: underline; + background-color: #0F6FFF; +} +.header img { + display:inline-block; + float:left; + margin-left:20px +} +.header strong { + color:#FFF; + display:block; + float:right; + height:60px; + line-height:60px; + margin-right:20px +} +/*头部部份结束*/ + /*预览开始*/ + #shearphoto_main #preview { + float:left; + position: relative; +} +#shearphoto_main #preview a { + display:inline; + float:left; + -moz-border-radius:5px; + border-radius:5px; + -moz-box-shadow:1px 1px 3px 1px #999; + box-shadow:1px 1px 3px 1px #999; + overflow: hidden; + position: relative; +} +#shearphoto_main #preview a img { + position: relative; +} +/*预览结束*/ + .bottom a:hover { + color: #39F; + text-decoration: underline; +} +/*旋转开始*/ + #Shearbar #LeftRotate { + margin:0 10px; +} +#Shearbar #LeftRotate em,#Shearbar #RightRotate em { + display:inline-block; + height:21px; + vertical-align:middle; + width:15px; + margin:1px 3px 0 0; + background:url(../images/bch.jpg) no-repeat; +} +#Shearbar #LeftRotate,#Shearbar #RightRotate { + -moz-border-radius:5px; + border:1px solid #CCC; + border-radius:5px; + color:#666; + cursor:pointer; + float:left; + font-size:12px; + margin-top:-5px; + text-align:center; + width:50px; +} +#Shearbar #LeftRotate:hover,#Shearbar #RightRotate:hover { + border:1px solid #919191; + color:#414141 +} +#Shearbar #RightRotate { + margin-left:10px +} +#Shearbar #RightRotate em { + background-position:-91px 0; + margin:1px 0 0 3px; +} +#Shearbar .hint { + color:#333; + display:block; + float:left; + font-size:12px; + font-style:normal; + height:19px; + line-height:15px; + width:19px +} +#Shearbar .hint.L,#Shearbar .hint.R { + background:url(../images/zoom.png) no-repeat -2px -1px +} +#Shearbar .hint.R { + background-position: -43px -1px +} +/*旋转结束*/ + /*选择图片方式开始*/ + #shearphoto_main #SelectBox { + background:url(../images/bg.png); + position:absolute; + z-index:180 +} +#SelectBox #selectImage input { + height: 50px; + width: 224px; + background-color: #FFF; + filter:alpha(opacity=0); + opacity:0; + position: absolute; + float: left; + cursor: pointer; + display: block; +} +.displayNone { + display: none; +} +#SelectBox #selectImage,#SelectBox #PhotoLoading,#SelectBox #camerasImage { + -moz-border-radius:10px; + border-radius:10px; + -moz-box-shadow:2px 2px 7px 1px #3e4044; + box-shadow:2px 2px 7px 1px #3e4044; + display:block; + height:50px; + outline:0 none; + position:absolute; + width:223px; + overflow:hidden; + margin:-37px 0 0 -112px; + background:url(../images/Select_en-us.jpg) no-repeat; +} +#SelectBox #PhotoLoading { + background-position:0 -160px; + left:50%; + top:70% +} +#SelectBox #selectImage { + background-position:0 -15px; + left:50%; + top:35% +} +#SelectBox #camerasImage { + background-position:0 -85px; + left:50%; + top:79% +} +#SelectBox #selectImage:hover,#SelectBox #PhotoLoading:hover,#SelectBox #camerasImage:hover { + border:3px solid #666; + margin:-37px 0 0 -112px; +} +/*选择图片方式结束*/ + /*工具条开始*/ + #shearphoto_main #Shearbar { + padding-top:10px; + width:442px; + margin:0 auto; +} +#Shearbar .Psava { + clear:both; + height:40px; + padding:20px 0 0 65px +} +#Shearbar .Psava #againIMG ,#Shearbar .Psava #saveShear { + background:url(../images/btn5.jpg) no-repeat; + color:#666; + float:left; + line-height:31px; + margin-right:20px; + text-align:center; + width:79px; + height: 31px; +} +#Shearbar .Psava #againIMG:hover { + background-position: 0px -31px; +} +#Shearbar .Psava #saveShear { + background-position: -79px 0px; + color:#FFF; +} +#Shearbar .Psava #saveShear:hover { + background-position:-79px -31px +} +#Shearbar #ZoomDist { + float:left; + height:20px; + position:relative; + width:120px +} +#Shearbar #ZoomDist #ZoomBar { + background:url(../images/ZoomBar.gif) no-repeat; + filter:alpha(opacity=80); + opacity:0.8; + height:20px; + left:0; + position:absolute; + width:15px +} +#ZoomDist #Zoomcentre { + filter:alpha(opacity=100); + height:9px; + left:50%; + top:25px; + position:absolute; + width:10px; + margin-left:-4px; + background:url(../images/Zoomcentre.jpg) no-repeat +} +#ZoomDist .progress { + -moz-border-radius:15px; + border-radius:15px; + background:#999; + display:block; + float:left; + height:8px; + margin-top:4px; + overflow:hidden; + width:100% +} +/*工具条结束*/ + /*主功能界面开始*/ + #shearphoto_main #black { + position:absolute; + z-index:99 +} +#shearphoto_main .primary { + float: left; +} +#shearphoto_main #main { + -moz-user-select:none; + -ms-user-select:none; + -webkit-user-select:none; + background:url(../images/bg.png); + overflow:hidden; + position:relative; + z-index:50; + border: 1px solid #CCC; + float: left; +} +#main .BigImg { + float:left; + position:relative; + z-index:51 +} +.MoveImg { + position:relative; + z-index:98; + max-width:300%; +} +#preview_img{ + max-width:300%; +} +#main #imgID { + display:block; + z-index:51 +} +#main #movebox { + position:absolute; + z-index:100; + float: left; +} +/*动态边框开始*/ + #movebox #borderTop,#movebox #borderLeft,#movebox #borderRight,#movebox #borderBottom { + background:url(../images/border.gif) #FFF; + display:inline-block; + filter:alpha(opacity=50); + opacity:0.5; + overflow:hidden; + position:absolute; + z-index:104; +} +#movebox #borderTop { + height:1px; + left:0; + top:0; + width:100%; +} +#movebox #borderLeft { + height:100%; + left:0; + top:0; + width:1px; +} +#movebox #borderRight { + height:100%; + right:0; + top:0; + width:1px; +} +#movebox #borderBottom { + bottom:0; + height:1px; + left:0; + width:100%; +} +/*动态边框结束*/ + /*拉伸截框的八个点开始*/ + #main #movebox #BottomRight,#main #movebox #TopRight,#main #movebox #Topleft,#main #movebox #Bottomleft,#main #movebox #Topmiddle,#main #movebox #leftmiddle,#main #movebox #Rightmiddle,#main #movebox #Bottommiddle { + background:#000; + border:1px solid #FFF; + bottom:-5px; + cursor:nw-resize; + display:block; + filter:alpha(opacity=50); + height:8px; + opacity:0.5; + overflow:hidden; + position:absolute; + right:-5px; + width:8px; + z-index:105; +} +#main #movebox #Bottomleft { + bottom:-5px; + cursor:ne-resize; + left:-5px +} +#main #movebox #Bottommiddle { + bottom:-5px; + cursor:n-resize; + left:50%; + margin-left:-5px +} +#main #movebox #Rightmiddle { + cursor:e-resize; + margin-top:-5px; + right:-5px; + top:50% +} +#main #movebox #TopRight { + cursor:ne-resize; + right:-5px; + top:-5px +} +#main #movebox #Topleft { + cursor:nw-resize; + left:-5px; + top:-5px +} +#main #movebox #Topmiddle { + cursor:n-resize; + left:50%; + margin-left:-5px; + top:-5px +} +#main #movebox #leftmiddle { + cursor:e-resize; + left:-5px; + margin-top:-5px; + top:50% +} +/*拉伸截框的八个点结束*/ + #main #relat { + position:relative; + z-index:60 +} +#main #smallbox { + background:url(); + overflow:hidden; + position:relative; + z-index:100 +} +/*主功能界面结束*/ + /*上滚下滚提示的CSS*/ + #main .point { + width:100%; + line-height:35px; + font-size:12px; + position:absolute; + z-index:200; + filter:alpha(opacity=70); + opacity:0.7; +} +.point i { + display:inline-block; + height:16px; + width:15px; + float:left; + margin:10px 10px 0 20px; + background:url(../images/bg_repno.png) no-repeat; +} +/*上滚下滚提示的CSS结束*/ + /*截图完成后的CSS开始*/ + #shearphoto_main .complete { + float:left; + overflow:hidden; + position:absolute; + width:100%; + z-index:250; + padding:10px 0 0 40px; + background:url(../images/bg.jpg) #CCC; +} +.complete .completeTxt { + -moz-border-radius:10px; + -moz-box-shadow:0 0 15px 0 #666; + border-radius:10px; + bottom:30px; + box-shadow:0 0 15px 0 #666; + position:absolute; + right:30px; + text-align:center; + width:280px; + padding:30px 0; + background:#FBFDFF; +} +.complete .completeTxt a { + color:#FFF; + display:block; + font-size:14px; + line-height:40px; + width:128px; + margin:0 auto; + background:url(../images/bg_index.gif) no-repeat 0 -2px; +} +.complete .completeTxt a:hover { + background-position: -137px -2px +} +.complete .completeTxt p { + color:#666; + font-size:12px; + font-weight:700; + margin:10px 0; +} +.complete .completeTxt strong { + font-size:14px +} +.complete img { + border:1px solid #CCC; + display:inline-block; + float:left; + margin:5px 5px; + padding:2px; +} +.complete .completeTxt strong i { + display:inline-block; + height:16px; + width:15px; + vertical-align:middle; + margin:-3px 5px 0 0; + background:url(../images/bg_repno.png) no-repeat; +} +/*截图完成后的CSS结束*/ + /*弹出相册开始*/ + #photoalbum { + -moz-border-radius:10px; + -moz-box-shadow:3px 3px 10px 0 #000; + border:8px solid #999; + border-radius:10px; + box-shadow:3px 3px 10px 0 #000; + display:none; + height:300px; + left:50%; + position:absolute; + top:59%; + width:465px; + z-index:210; + margin:-178px 0 0 -240.5px; + background:#DFEFFF; +} +#photoalbum h1 { + background:#999; + color:#FFF; + font-size:15px; + height:40px; + line-height:30px; + padding-left:10px; + width:455px +} +#photoalbum i { + background:url(../images/fancybox_sprite.png) no-repeat; + display:block; + float:left; + height:37px; + left:429px; + position:absolute; + top:0; + cursor:pointer; + width:36px +} +#photoalbum ul { + height:auto; + width:100% +} +#photoalbum ul li { + border:1px solid #CCC; + cursor:pointer; + display:inline; + float:left; + height:133px; + margin:5px; + padding:2px; + width:47%; + overflow: hidden; +} +#photoalbum ul li img { + width:100% +} +#photoalbum ul li:hover { + border:1px solid #09F +} +/*弹出相册结束*/ + /*弹出拍照开始*/ + #CamBox { + -moz-border-radius:10px; + border-radius:10px; + left:67%; + position:absolute; + -moz-box-shadow:5px 5px 10px 0 #3b3b3f; + box-shadow:5px 5px 10px 0 #3b3b3f; + width:350px; + z-index:250; + display:none; + top:95%; + margin:-285px 0 0 -276px; + background:url(../images/cam_bg.jpg) #d5d5d5; +} +#CamBox #CamFlash { + height:300px; + width:300px; + margin:0 auto; + background:#fff; +} +#CamBox #timing { + color:#F60; + display:none; + font-size:36px; + font-weight:700; + height:220px; + left:50%; + line-height:200px; + position:absolute; + text-align:center; + top:50%; + width:200px; + margin:-100px 0 0 -100px; +} +#CamBox .cambar { + height:60px; + padding-top:20px; + width:300px; + margin:0 auto; +} +#CamBox .cambar a { + background:url(../images/btn5.jpg) no-repeat; + float:left; + height:31px; + line-height:31px; + text-align:center; + width:79px +} +#CamBox .lens { + background:url(../images/cam.png) no-repeat 50%; + height:40px; + width:100% +} +#CamBox .cambar #camClose,#CamBox .cambar #setCam { + color: #333; +} +#CamBox .cambar #setCam { + margin-right: 30px; +} +#CamBox .cambar #camClose:hover,#CamBox .cambar #setCam:hover { + background-position: 0 -31px; +} +#CamBox .cambar #CamOk { + background-position: -79px 0; + color: #fff; + margin-right: 30px; +} +#CamBox .cambar #CamOk:hover { + background-position: -79px -31px; +} +/*弹出拍照结束*/ + /*图片特效*/ + #shearphoto_main .Effects { + color:#FFF; + overflow-x:hidden; + width:155px; + display:none; + float:left; + border-color:#CCC; + border-style:solid none; + border-width:1px; + background:url(../images/Effects/cardboard.png); +} +#shearphoto_main .Effects .EffectsStrong { + border-bottom:solid 1px #676767; + display:block; + font-size:14px; + height:30px; + line-height:30px; + text-align:center; + width:100% +} +#shearphoto_main .Effects a { + border-bottom:solid 1px #676767; + color:#FFF; + display:block; + vertical-align:middle; + text-shadow:#000 0 2px 0; + width:100%; + padding:5px 0 7px; +} +#shearphoto_main .Effects a:hover, #shearphoto_main .Effects .Aclick { + background-color: #A7A7A7; +} +#shearphoto_main .Effects a img { + -moz-border-radius:3px; + border-radius:3px; + display:inline-block; + height:42px; + vertical-align:middle; + -moz-box-shadow:0 0 5px 0 #000; + box-shadow:0 0 5px 0 #000; + width:70px; + margin:2px 7px 0; +} +::-webkit-scrollbar { + height:13px; + width:13px +} +::-webkit-scrollbar-thumb { + background:padding-box #c2c2c2; + border:1px solid #979797; + min-height:28px +} +::-webkit-scrollbar-thumb:hover { + background:#929292; + border:1px solid #636363 +} +::-webkit-scrollbar-track-piece { + background:#f5f5f5; + border-left:1px solid #d2d2d2 +} +/*图片特效结束*/ + /*底部开始*/ + .bottom { + width:1000px; + left:2px; + color:#666; + clear:both; + text-align:center; + margin:0 auto; + padding:0 0 10px 5px +} +.bottom span { + vertical-align:middle; + display:inline-block; + margin:0 5px 0 2px +} +.bottom a { + color: #666; +} +.header h1 a { + font-size: 14px; + color: #fff; +} +.bottom span img { + border-radius:30px; + -moz-border-radius:30px; +} +/*底部结束*/ \ No newline at end of file diff --git a/public/shearphoto/css/ShearPhoto_f_zh-cn.css b/public/shearphoto/css/ShearPhoto_f_zh-cn.css new file mode 100644 index 0000000..fe5a7f3 --- /dev/null +++ b/public/shearphoto/css/ShearPhoto_f_zh-cn.css @@ -0,0 +1,703 @@ + @charset "utf-8"; + /*公用开始*/ + img,div,i,em,a,body,p,H1,ul,li { + border-style:none; + list-style-type:none; + margin:0; + padding:0; + text-decoration:none +} +/*公用结束*/ + /*缓冲效果开始 2.3加入的*/ + #shearphoto_main { + visibility:hidden; + margin:0 auto; +} +#shearphoto_loading { + width:100px; + text-align:center; + line-height:130px; + color:#999; + margin:0 auto; + background:url(../images/loading.gif) no-repeat +} +/*缓冲效果结束*/ + /*头部部份开始*/ + .header { + height:65px; + margin-bottom:10px; + width:100%; + overflow:hidden; + background:url(../images/bg_headLine1.png) no-repeat 290px #505153; +} +.header A { + -moz-border-radius:5px; + border-radius:5px; + color:#FFF; + display:inline-block; + float:left; + font-size:15px; + font-weight:700; + line-height:50px; + text-align:center; + width:150px; + margin:7px 0 0 100px; + background:#3E8BFF; +} +.header A:hover { + text-decoration: underline; + background-color: #0F6FFF; +} +.header img { + display:inline-block; + float:left; + margin-left:20px +} +.header strong { + color:#FFF; + display:block; + float:right; + height:60px; + line-height:60px; + margin-right:20px +} +/*头部部份结束*/ + /*预览开始*/ + #shearphoto_main #preview { + float:left; + position: relative; +} +#shearphoto_main #preview a { + display:inline; + float:left; + -moz-border-radius:5px; + border-radius:5px; + -moz-box-shadow:1px 1px 3px 1px #999; + box-shadow:1px 1px 3px 1px #999; + overflow: hidden; + position: relative; +} +#shearphoto_main #preview a img { + position: relative; +} +/*预览结束*/ + .bottom a:hover { + color: #39F; + text-decoration: underline; +} +/*旋转开始*/ + #Shearbar #LeftRotate { + margin:0 10px; +} +#Shearbar #LeftRotate em,#Shearbar #RightRotate em { + display:inline-block; + height:21px; + vertical-align:middle; + width:15px; + margin:1px 3px 0 0; + background:url(../images/bch.jpg) no-repeat; +} +#Shearbar #LeftRotate,#Shearbar #RightRotate { + -moz-border-radius:5px; + border:1px solid #CCC; + border-radius:5px; + color:#666; + cursor:pointer; + float:left; + font-size:12px; + margin-top:-5px; + text-align:center; + width:50px; +} +#Shearbar #LeftRotate:hover,#Shearbar #RightRotate:hover { + border:1px solid #919191; + color:#414141 +} +#Shearbar #RightRotate { + margin-left:10px +} +#Shearbar #RightRotate em { + background-position:-91px 0; + margin:1px 0 0 3px; +} +#Shearbar .hint { + color:#333; + display:block; + float:left; + font-size:12px; + font-style:normal; + height:19px; + line-height:15px; + width:19px +} +#Shearbar .hint.L,#Shearbar .hint.R { + background:url(../images/zoom.png) no-repeat -2px -1px +} +#Shearbar .hint.R { + background-position: -43px -1px +} +/*旋转结束*/ + /*选择图片方式开始*/ + #shearphoto_main #SelectBox { + background:url(../images/bg.png); + position:absolute; + z-index:180 +} +#SelectBox #selectImage input { + height: 50px; + width: 224px; + background-color: #FFF; + filter:alpha(opacity=0); + opacity:0; + position: absolute; + float: left; + cursor: pointer; + display: block; +} +.displayNone { + display: none; +} +#SelectBox #selectImage,#SelectBox #PhotoLoading,#SelectBox #camerasImage { + -moz-border-radius:10px; + border-radius:10px; + -moz-box-shadow:2px 2px 7px 1px #3e4044; + box-shadow:2px 2px 7px 1px #3e4044; + display:block; + height:50px; + outline:0 none; + position:absolute; + width:223px; + overflow:hidden; + margin:-37px 0 0 -112px; + background:url(../images/Select_zh-cn.jpg) no-repeat; +} +#SelectBox #PhotoLoading { + background-position:0 -160px; + left:50%; + top:70% +} +#SelectBox #selectImage { + background-position:0 -15px; + left:50%; + top:35% +} +#SelectBox #camerasImage { + background-position:0 -85px; + left:50%; + top:79% +} +#SelectBox #selectImage:hover,#SelectBox #PhotoLoading:hover,#SelectBox #camerasImage:hover { + border:3px solid #666; + margin:-37px 0 0 -112px; +} +/*选择图片方式结束*/ + /*工具条开始*/ + #shearphoto_main #Shearbar { + padding-top:10px; + width:442px; + margin:0 auto; +} +#Shearbar .Psava { + clear:both; + height:40px; + padding:20px 0 0 65px +} +#Shearbar .Psava #againIMG ,#Shearbar .Psava #saveShear { + background:url(../images/btn5.jpg) no-repeat; + color:#666; + float:left; + line-height:31px; + margin-right:20px; + text-align:center; + width:79px; + height: 31px; +} +#Shearbar .Psava #againIMG:hover { + background-position: 0px -31px; +} +#Shearbar .Psava #saveShear { + background-position: -79px 0px; + color:#FFF; +} +#Shearbar .Psava #saveShear:hover { + background-position:-79px -31px +} +#Shearbar #ZoomDist { + float:left; + height:20px; + position:relative; + width:120px +} +#Shearbar #ZoomDist #ZoomBar { + background:url(../images/ZoomBar.gif) no-repeat; + filter:alpha(opacity=80); + opacity:0.8; + height:20px; + left:0; + position:absolute; + width:15px +} +#ZoomDist #Zoomcentre { + filter:alpha(opacity=100); + height:9px; + left:50%; + top:25px; + position:absolute; + width:10px; + margin-left:-4px; + background:url(../images/Zoomcentre.jpg) no-repeat +} +#ZoomDist .progress { + -moz-border-radius:15px; + border-radius:15px; + background:#999; + display:block; + float:left; + height:8px; + margin-top:4px; + overflow:hidden; + width:100% +} +/*工具条结束*/ + /*主功能界面开始*/ + #shearphoto_main #black { + position:absolute; + z-index:99 +} +#shearphoto_main .primary { + float: left; +} +#shearphoto_main #main { + -moz-user-select:none; + -ms-user-select:none; + -webkit-user-select:none; + background:url(../images/bg.png); + overflow:hidden; + position:relative; + z-index:50; + border: 1px solid #CCC; + float: left; +} +#main .BigImg { + float:left; + position:relative; + z-index:51 +} +.MoveImg { + position:relative; + z-index:98; + max-width:300%; +} +#preview_img{ + max-width:300%; +} +#main #imgID { + display:block; + z-index:51 +} +#main #movebox { + position:absolute; + z-index:100; + float: left; +} +/*动态边框开始*/ + #movebox #borderTop,#movebox #borderLeft,#movebox #borderRight,#movebox #borderBottom { + background:url(../images/border.gif) #FFF; + display:inline-block; + filter:alpha(opacity=50); + opacity:0.5; + overflow:hidden; + position:absolute; + z-index:104; +} +#movebox #borderTop { + height:1px; + left:0; + top:0; + width:100%; +} +#movebox #borderLeft { + height:100%; + left:0; + top:0; + width:1px; +} +#movebox #borderRight { + height:100%; + right:0; + top:0; + width:1px; +} +#movebox #borderBottom { + bottom:0; + height:1px; + left:0; + width:100%; +} +/*动态边框结束*/ + /*拉伸截框的八个点开始*/ + #main #movebox #BottomRight,#main #movebox #TopRight,#main #movebox #Topleft,#main #movebox #Bottomleft,#main #movebox #Topmiddle,#main #movebox #leftmiddle,#main #movebox #Rightmiddle,#main #movebox #Bottommiddle { + background:#000; + border:1px solid #FFF; + bottom:-5px; + cursor:nw-resize; + display:block; + filter:alpha(opacity=50); + height:8px; + opacity:0.5; + overflow:hidden; + position:absolute; + right:-5px; + width:8px; + z-index:105; +} +#main #movebox #Bottomleft { + bottom:-5px; + cursor:ne-resize; + left:-5px +} +#main #movebox #Bottommiddle { + bottom:-5px; + cursor:n-resize; + left:50%; + margin-left:-5px +} +#main #movebox #Rightmiddle { + cursor:e-resize; + margin-top:-5px; + right:-5px; + top:50% +} +#main #movebox #TopRight { + cursor:ne-resize; + right:-5px; + top:-5px +} +#main #movebox #Topleft { + cursor:nw-resize; + left:-5px; + top:-5px +} +#main #movebox #Topmiddle { + cursor:n-resize; + left:50%; + margin-left:-5px; + top:-5px +} +#main #movebox #leftmiddle { + cursor:e-resize; + left:-5px; + margin-top:-5px; + top:50% +} +/*拉伸截框的八个点结束*/ + #main #relat { + position:relative; + z-index:60 +} +#main #smallbox { + background:url(); + overflow:hidden; + position:relative; + z-index:100 +} +/*主功能界面结束*/ + /*上滚下滚提示的CSS*/ + #main .point { + width:100%; + line-height:35px; + font-size:12px; + position:absolute; + z-index:200; + filter:alpha(opacity=70); + opacity:0.7; +} +.point i { + display:inline-block; + height:16px; + width:15px; + float:left; + margin:10px 10px 0 20px; + background:url(../images/bg_repno.png) no-repeat; +} +/*上滚下滚提示的CSS结束*/ + /*截图完成后的CSS开始*/ + #shearphoto_main .complete { + float:left; + overflow:hidden; + position:absolute; + width:100%; + z-index:250; + padding:10px 0 0 40px; + background:url(../images/bg.jpg) #CCC; +} +.complete .completeTxt { + -moz-border-radius:10px; + -moz-box-shadow:0 0 15px 0 #666; + border-radius:10px; + bottom:30px; + box-shadow:0 0 15px 0 #666; + position:absolute; + right:30px; + text-align:center; + width:280px; + padding:30px 0; + background:#FBFDFF; +} +.complete .completeTxt a { + color:#FFF; + display:block; + font-size:14px; + line-height:40px; + width:128px; + margin:0 auto; + background:url(../images/bg_index.gif) no-repeat 0 -2px; +} +.complete .completeTxt a:hover { + background-position: -137px -2px +} +.complete .completeTxt p { + color:#666; + font-size:12px; + font-weight:700; + margin:10px 0; +} +.complete .completeTxt strong { + font-size:14px +} +.complete img { + border:1px solid #CCC; + display:inline-block; + float:left; + margin:5px 5px; + padding:2px; +} +.complete .completeTxt strong i { + display:inline-block; + height:16px; + width:15px; + vertical-align:middle; + margin:-3px 5px 0 0; + background:url(../images/bg_repno.png) no-repeat; +} +/*截图完成后的CSS结束*/ + /*弹出相册开始*/ + #photoalbum { + -moz-border-radius:10px; + -moz-box-shadow:3px 3px 10px 0 #000; + border:8px solid #999; + border-radius:10px; + box-shadow:3px 3px 10px 0 #000; + display:none; + height:300px; + left:50%; + position:absolute; + top:59%; + width:465px; + z-index:210; + margin:-178px 0 0 -240.5px; + background:#DFEFFF; +} +#photoalbum h1 { + background:#999; + color:#FFF; + font-size:15px; + height:40px; + line-height:30px; + padding-left:10px; + width:455px +} +#photoalbum i { + background:url(../images/fancybox_sprite.png) no-repeat; + display:block; + float:left; + height:37px; + left:429px; + position:absolute; + top:0; + cursor:pointer; + width:36px +} +#photoalbum ul { + height:auto; + width:100% +} +#photoalbum ul li { + border:1px solid #CCC; + cursor:pointer; + display:inline; + float:left; + height:133px; + margin:5px; + padding:2px; + width:47%; + overflow: hidden; +} +#photoalbum ul li img { + width:100% +} +#photoalbum ul li:hover { + border:1px solid #09F +} +/*弹出相册结束*/ + /*弹出拍照开始*/ + #CamBox { + -moz-border-radius:10px; + border-radius:10px; + left:67%; + position:absolute; + -moz-box-shadow:5px 5px 10px 0 #3b3b3f; + box-shadow:5px 5px 10px 0 #3b3b3f; + width:350px; + z-index:250; + display:none; + top:95%; + margin:-285px 0 0 -276px; + background:url(../images/cam_bg.jpg) #d5d5d5; +} +#CamBox #CamFlash { + height:300px; + width:300px; + margin:0 auto; + background:#fff; +} +#CamBox #timing { + color:#F60; + display:none; + font-size:36px; + font-weight:700; + height:220px; + left:50%; + line-height:200px; + position:absolute; + text-align:center; + top:50%; + width:200px; + margin:-100px 0 0 -100px; +} +#CamBox .cambar { + height:60px; + padding-top:20px; + width:300px; + margin:0 auto; +} +#CamBox .cambar a { + background:url(../images/btn5.jpg) no-repeat; + float:left; + height:31px; + line-height:31px; + text-align:center; + width:79px +} +#CamBox .lens { + background:url(../images/cam.png) no-repeat 50%; + height:40px; + width:100% +} +#CamBox .cambar #camClose,#CamBox .cambar #setCam { + color: #333; +} +#CamBox .cambar #setCam { + margin-right: 30px; +} +#CamBox .cambar #camClose:hover,#CamBox .cambar #setCam:hover { + background-position: 0 -31px; +} +#CamBox .cambar #CamOk { + background-position: -79px 0; + color: #fff; + margin-right: 30px; +} +#CamBox .cambar #CamOk:hover { + background-position: -79px -31px; +} +/*弹出拍照结束*/ + /*图片特效*/ + #shearphoto_main .Effects { + color:#FFF; + overflow-x:hidden; + width:155px; + display:none; + float:left; + border-color:#CCC; + border-style:solid none; + border-width:1px; + background:url(../images/Effects/cardboard.png); +} +#shearphoto_main .Effects .EffectsStrong { + border-bottom:solid 1px #676767; + display:block; + font-size:14px; + height:30px; + line-height:30px; + text-align:center; + width:100% +} +#shearphoto_main .Effects a { + border-bottom:solid 1px #676767; + color:#FFF; + display:block; + vertical-align:middle; + text-shadow:#000 0 2px 0; + width:100%; + padding:5px 0 7px; +} +#shearphoto_main .Effects a:hover, #shearphoto_main .Effects .Aclick { + background-color: #A7A7A7; +} +#shearphoto_main .Effects a img { + -moz-border-radius:3px; + border-radius:3px; + display:inline-block; + height:42px; + vertical-align:middle; + -moz-box-shadow:0 0 5px 0 #000; + box-shadow:0 0 5px 0 #000; + width:70px; + margin:2px 7px 0; +} +::-webkit-scrollbar { + height:13px; + width:13px +} +::-webkit-scrollbar-thumb { + background:padding-box #c2c2c2; + border:1px solid #979797; + min-height:28px +} +::-webkit-scrollbar-thumb:hover { + background:#929292; + border:1px solid #636363 +} +::-webkit-scrollbar-track-piece { + background:#f5f5f5; + border-left:1px solid #d2d2d2 +} +/*图片特效结束*/ + /*底部开始*/ + .bottom { + width:1000px; + left:2px; + color:#666; + clear:both; + text-align:center; + margin:0 auto; + padding:0 0 10px 5px +} +.bottom span { + vertical-align:middle; + display:inline-block; + margin:0 5px 0 2px +} +.bottom a { + color: #666; +} +.header h1 a { + font-size: 14px; + color: #fff; +} +.bottom span img { + border-radius:30px; + -moz-border-radius:30px; +} +/*底部结束*/ \ No newline at end of file diff --git a/public/shearphoto/file/photo/1.jpg b/public/shearphoto/file/photo/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f1fc24dba85776aacc0c482dd458553c289e7f9a GIT binary patch literal 31923 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 Yt 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 z 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 MoONmg>s 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 CG&E5F literal 0 HcmV?d00001 diff --git a/public/shearphoto/file/photo/4.jpg b/public/shearphoto/file/photo/4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..902e428761177eb31ea879af91425153f059e0a9 GIT binary patch literal 47945 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 f^H@)()Bg`X&-uPT-}8Q*5BD>70@!!K3~dI0 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 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e1.jpg b/public/shearphoto/images/Effects/e1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a1b7d3829916052007d82b4fc8e63a3a84f6e537 GIT binary patch literal 2331 zcmbW0do%Fe+K z0D(ZjMg9SJUw{*U5C|lQKLr*63nVHm3>JZiLLh<^gNj4N#9(3&2uuePeTr^W&$03j~1wh4mNNe{doATtfUE2tx#1 zAfey*2}THu9Mlt)v37#^Meb47j~A1*NiTTS3RN+1UPby}9e~NH8cwOP1Ze*x`|rSR z{a<8%f&H6{4oHAO{N;fW02*NOfMDIv({3E>)M>9`mJzj&=o8Z*ZKW*7xMaNSHfB?; z$Q?gwzuuJ)Ys=vQMIpqatOMBzMpR>8$q8)uwbJkIZczQAvD4MIwP!>cvTS6Rf3Yjc z@W{|M?>KJ4Dp@*baU8UYN~GLvQXwVQ9`kOB_j?_Pj`>^Tn2OUxx4W6y7$J!oJUPKd zL^!f!v+7&qjkfQH%1`wAN?l@8s9ER+!%3YCO&&mu6nl96N8?D(V6tIE?}z3sHV-f< zDceDGjD^0Wl&?9Qt*otf2#I-tJ3JCtb(-AY-aDU&dK0 zaI-<0_R0#!)ORyv72PV{e|rt@-nJi;yxto`-?oZ2{9-Ni#n9QyA|e6REj;bRnh!low9xTh5~+NQ-;&JYJvNzKFl?$^yplzV}4~aetsRz zLt9EnYGiBDrLw%tv#(~zi>fLTutd4lz5+(7b`0hAV%=7HA(PE*(FWSZM+&O)S|$sd zk*{}NwfJNA#F}y9D)E;seHn`W+PLo(N!vPdwCbYXFEuu4UeX-{_euBJr0(9Bl|>{= z+l6y?TakOXnI#L2U>vg0^Mety5DHcutf4yKc!1yr% z+L^9M9TcXyV=`n4!gWZ@DqqG;_S1W&prO$b#~B zslOK|uM#jreL)TpzEvyvv)m-e`~~;LEym}nSWM}q>e!E=jy-4151MM-NS6PZyL9|8 z_KF2U^=S8icy+GhVT&A5yHl1Ur{h5SGa!YmJ z?d|XoK3uG>#2gI`;@rchOf8WV0)<3R*10Vt2if_PxTT`jX0n7})U9lk)by|+52$$? z7VfEVNh3#;DEkRd>-WDLIq|~Rdu{?p<^-jZJIbQIp7sd@1 zdS9?g*+;Sia577$?Y$2an(nUE_3(h#X!ywF<#<$rg(R-9lilTCYy{?9Ija?}>_Vg+ z+Ow6?1NmSU#@Y$Bl4lzEgA>Jdiy9`;waXWc_q;GRJ&{sz)FId9Ty8lO~kGBtU^u?}MYD^A!1(A)DtR&5P z+Am^M4pg8Rs+HGW^U&0~bRTuaFtwYeTqx?kqaqHP66oq_k|7Vbn=EQE=nYfH)zOu|1Y{o##x9{7Iq$V5)1ZnArIdmO_I$x@@e(a0U0-Sm1}@dHF}Sh-6lx5vf~ zh2I;DBTm>l1f1cP_v?*KoO?ykBHHeUP0&ihLK+z4nTf9yF4mP6g&exxesw;SbUAXN zf1}ET6T(VtiUWtr*rNxZ)bf9`#iys`yWov}^7Wsc-1d(1j>H`rJwXOKeG%3?tW!cv zKn!0T>GG{6Qt`PS^?PNajkgS1_w#@#nP6Mgt($1rELlUzlblyS9uTG^XQbk+8x@pa wL70i_e}*HyxZ!#Hg2Y<F%N%FU0(=*Y&5QvM z2n1YbEr2-*7y@h{&{6y)Fgy5291sYY9m)ZP9wjFiHy0--j1vll@xWl*aF(E42womI z;%I+#$x-`J7c1bLP|l-@|4qzRfS(gk0F1#P8Gwx+1m*`ZI{_2`fY@1Xj|~4M5E~f6 z&cU(-<7O3*_*nMAU^bRnb_j%39nLxjApGnC(n|Uqf|fU+GQL8}QE3I7vIbRc!d4Wz zoQkVoG#5-nR80KXiIb<~Pyd9*sH&-JXd0e3GBz3#cd zaA{aXhpI$NHS)LM^M9i`S&bF=DoCnZR!D*1dwcTNKRt)eeWdk2#9x zE=d=vcwU6Xa8x~5I!mgxKPx>{nA4~2nSB2p(fl@J`WzF8c2Zl%6|R2Ab`S7ha}I%; zWf+s_r{Xt%t*&WIEIgNh;bvrTm7g6EQO?Lnv!9f{BA2rI^nxDtDHC8QTz(YryTl16(m^HX_xG;HKlqpbs?DWj?#2eh!bYdjm_ovs`-CRyMH@>U^y5HZ8L&n-f}d z*w5em_jOI?m_WR)1id+t_6ozx_KUgxP+S3h-0Qt+$U+iz3Q84UPbookLs1^?E;&3i zWvI%JZ6D|7`9?n=A4dr8;M=)%l>+;*MdA9{Bq2zDfL!_V*DJ zkc`A*@M73m0v6c}W_a~XpB^S-fAe4h^bey{d}0NLNA-`o?C^7`7FlZwS_A{GYLoi3 zq|jH#Gz^+)i}(_(Km-LTPQlI9kPkmLU-9*MnOb|D36P|IQ1PwLQF@n!=TUTFi~gx|T4LzHXr6HvZBKHX%*^OCpvIAd-Bed)1t@z}?A=p?z-Lk43S|bW zFl4(OTaE9)@`XIE%`6w~2n)zstnR1oTDn|PbRvyL<0XZ^@tyYbC z`Dq6EwdGByRe0{m#`=6j@Q2s+5M+_Qq7Do^e1*fOGnH{Nu4jF-^gv~#S@zSkObwk0 zbT9#Nd&ZI$4)=JsEYSUUdk4p|+k&HLCa%|cqlEO3oCqDu<7qxlJy6TGBvnVOHXdp5}$ltwkmkI3*4p1VewxyDk?b`8Wv$nh$hq?>F>!J1Nxepyolkcnr!h9Z%& z!+j2ej0%fvYj3oRiSVa~ud^%rO$YjNd4)i{DH{%xhVe#lJk8COj0noE*Z;wFpwN%^ zxD0VzK?+ee@2;QEoA@?#?@|np(C7DZ>S7WITK(69OJQ#aC!)tXg6-6FUD91&tP?V+ z+ArkZww{V^m_-%*cHFMh7R!H#T6=%}Mnl$!qml^E;)R?@)82vO`cgZPXfA*;rF-1T zywp>W>foZ7RbyIZamu+2rexK>P_kEvFa<-UL&d)SBi=;-)3*tK`RLcx-O)zpSHxQ_ zDK^ek#s~gqp6rG+=x(QYn#vSK_#}=lKSnHWVI`yX0v%n$JMaK z!(Z~-kgudpOw~JvD2Ms2W=bw>xTj@9h#D`*GloN$R78j;#dT-{d`)E_-YL%I>1lg9 z+s4NIaVm%121O6Kvv1@P=CjVfuell_O@F^r-cWo=^4MXzzx(~ih+;3f6B(v6TDW84 z+O7I~(S*Q?+=4C{qGZP)F?z2>l-fVm83fIHN_o5a^DG7Nk< zMjBhozMhX$*G>uN?u-x^dT6x8Q}8<}g(eX$)FpJ+q7x@4 z-Rd;I1k3*(mBRXQTEAeI6@t=J-nlV1{uy>%aaVtC@A{) zQ;ohCWJyN3=q4{W^0|gr5VGUAdU;<5QlyF=LCkOnZ**)Y=OCObIhJ%j04P1Eo&uDH zA%_K}o-DqXj3{smhTQ)UgGiM7V#X@VeAw2mdS-#me5Hv-!k~O`n4TGo=eKFcAX$2{Zb?b;dD|7i?$@yixgF^EbPv0a? RLAq@kDuwJBO#+w`e*&AG-Fg53 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e11.jpg b/public/shearphoto/images/Effects/e11.jpg new file mode 100644 index 0000000000000000000000000000000000000000..5bc7369c420baa541bf4f256f62e964f05a75857 GIT binary patch literal 1761 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 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e13.jpg b/public/shearphoto/images/Effects/e13.jpg new file mode 100644 index 0000000000000000000000000000000000000000..118ee7d4e5a957fad66faff110a3abe12965827f GIT binary patch literal 2893 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 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e14.jpg b/public/shearphoto/images/Effects/e14.jpg new file mode 100644 index 0000000000000000000000000000000000000000..44d6f184022e28e37797f11a44c40c59139c5b6a GIT binary patch literal 2779 zcmbV}c~H~W7RP@fK*BC4A(16)qLqCIjnGO>Se~WIE}M#MVxk}@M0t%IcLLibdl9E!=LO~>C zFZf$ll}H|@3q@=!n*K!ZV=fQUQ@EDzf40+0Xz5)-D~3;dTr zB4AN5abYA$DWQR~PZ%Ez77>OO6BQM@)qVY)jU=h-*0yGHJPyQqb92wc%EmMQUo35c1@@F zcd<=4!OKz!$fO&dyq6_6tAaYNbFTRJKun z#pXy@e(WvdF4f2r2g7ArO>7*h0gphiz^k+N$d?QLmgsb+!U*adKX&1d+U*(z&a==T zTz|6(#m2W7V%>Lv$z4D;V<&%r!NT;3x%DE*}WPM0x7 zx*p1Uq6W%)_>BgYBURc^pXEIgG-B-$L4MX`Kko#haE){B`&rE`@#)+u8xeSV(e|fh zenN+>md#E6jZT=jadZTR7{qO{h784bP-EUsQ(h9@p2K;&+dBn81y^drrZLg8= z$Uk*V6y?kDV>34Br?JPKhf7mua4XuRBb_FxqK0go92}0i&Tb9c1(JViAEOK|5o&f& zy!Z-UR1`8zzR7#c#}`MTJgZBfuL)Mbbsn?LA@I)c!|X@h2*WGOb+vUtqFd&u~m3J!(ig z>Uv0?WnN{Jw1Ez7PB~E>*_Wv}>~*fXp!J-9iwF8rid)B?9{74lKk%#ORmD16OM&#w z0j7=m@Ri77snfp?tsh|ry2un{K@+tt^AC=jMy*e)g)JD~ZmWHn5}B62?Wp?{d-s4G z#XjKYl=<3al`UiQ>9OBex}++1fpjG{{4VuWuwGM&fO4lE0~c;7307T?-NEPL;f?rIqJ z!k$WrC{KzaRg1u24oariFq!Y|%G zel2ogI$llo;e)3#bmp7&gz0Os&YjRWrz&ciq5kp}oW_u$8P?P}8LWc;@s(IdZf(xm zD=kdN+-3l(NY0Z_j0{|LriDamldKrs_}|=gXYO>7HyRofDlY)E49mkg;pP~r6Y1?1Y!UgfomMl9eaGPh~$CkXV8Xk71NL3lR0Vrf~1_l z{u-eFERKBtRSuJE=Aj!}g(+JI)AX^Ew%p(A9$H}1j|P2sq!O%!nhGvG;Nekc#jmin zvOSMv8T2rx7$v{6^;m?i;}Vng#rgs-PjI+OVg^Z4FR>&gdcfEp7foCrQ|95ePd8IY z8-r&XxO3lUR7|}1EPz4oVAK=QM!Kt1feig!DKlaGp$)38*>sc@ZA&{=vtf4jhoz>3 zt*D{|^})?ccdp-WWj|?)Fl!NK_#O=o5#rT7CuZ47Gj06U9M#?|W`^gx#cY-6ux{3@ zj&(Z(s*F&Utr=JY-bt7~j#<{g%~&4o?M66{8Yq9ZP;TSJPaQ`d^cnT8Kdp1WQm

rjnRWH^W2t#XCH+8q z`I753hCieHYBMJ^(xI&lQ(iNE1CsIO@mn*oVQu|`Wp&++c6oeupEYu0B)g{>Ibpag z5yPxUY#=7(s=qFQgCPa|EeHzoqILgkfe0)Mn#uSU>vkN#3g z9VI?BM_ALt+=3I_2ZxZUZSsZhPThIA@x=SWfgsOUP#64Ug+#k(QOU8lJ{Z~w3;95# hLIN4decRG9U%h3O{x&_s?{=oQ%t;rVlgsYMKLLnw7RLYp literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e2.jpg b/public/shearphoto/images/Effects/e2.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6a84e943bc24ddf86168345cc1b9d3c41b174fe9 GIT binary patch literal 2007 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 QgIsIgT8!4}eJbMq39p-uf&c&j literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e4.jpg b/public/shearphoto/images/Effects/e4.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d4a102ba305805411be041a345a10677ee52bd49 GIT binary patch literal 3013 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 Ds;Qwa literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e5.jpg b/public/shearphoto/images/Effects/e5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cb46d33c077507abc4df51f22ab471c92192d7c1 GIT binary patch literal 2726 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 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e6.jpg b/public/shearphoto/images/Effects/e6.jpg new file mode 100644 index 0000000000000000000000000000000000000000..27d072be8b51f5424d8bd2c1e8d9c57698e424e8 GIT binary patch literal 2689 zcmbW1c{JPG7RP@P#1Irk%|)~|l$xiOqK)aI#v)Wrt=Tn4uUQEfH@FpaQuB37iJ`PK zl!#edv!v#t=4go+Dlgu<>%O(#U+=x|*?X=T6A{HqF?0Y`zPh^;i zdq^TH8^6FgK_Tf27ca@ks;a4LXliL2-na=jGBz=_v9+_m?cnI-;puhnzPFEWXjphe zBr@tj(!=BwRBGC$X`i92WP0erKc68#q2;DuseZwQ8 zW8)K(Q^fg&#VXFcD{Yz-J|SN4-QYcKmhn(SbvlK2NyS;i-Fz)M#w1_h#``m zU~Wbx2_>})YRw)&0GM{_MCpLbV>b&&MDcV2D{yVV5|BLKz zuzzzA0Vo(mZyuN%&;vGcY{z1wI58hglPft3(eLpVu_}3m z$8u8urprW5NqXsw^nd$WSct9h)dIG>q5h8D2Gge=hG(H&&}qW}j&c2ZE(&fxh%=xRiywZkI; z8Q8~dw7d<_cArR-`O}XCy>(@=6rz8kh8L|9mzLB2bf5tpO*u1^ zwQoeOUG--4#^$Bp?Aj%-44KJz!LrU}k>W}$QQ{2Ih#hz0&|$y%@8;N1j8185%YYTk zUqgmNvXI3@kJ=Z~F-$61y= zxK(jZPG7{jQbsT8>Gp3|^~$J6mS{^H7C}OK@or|#F$3{&7gTTCd3e6Hz2lT1-3jf_ zcvdm8SB$bybd-?I^hOW1CF<3CO{50-H@Gl&aIkk`F*lIa<2uN8O^>sF`}vEctPsD| z8k8Oul@umdb~23E3@<%jL%K&2(E5tqoLecnSREz4A+f`=ZbQxeWgd|^DEbIpiRhp3 zbc@esrMQ=h>5XeR?adMw?Qu<1NQ|G)&*Rux-&?EuzgH4J5luHl0ykb65MoNDnroyu z*kb#MYN5k;(~1U)&m{HegdIV?-QL#tMV$SZdw%x9jCcx8f(AUq9LA`BvL;1NQM!C{ z6uFw~jJv?l^Dgi6H@_E7b# zdp^di>{zjJhyIwAB+KKxl*NuS@dp&oU8xrM=KJ;6md0xVLGuEXJDHSahsCBuDd`V+ zoWgexQuQ`cKy7ncT^>%nl?stfX2Ld)WM0|5!LQD;qJE7wxtO-?meRK-%Cl}fTqR*% zOc1>M3R)GV*NQO|GvtYjQ0Z;or2$jIM)Z&6=AYq`M4mfM2Hu=g#E{a#-4=LTu`}VL zo7GzqdftdiJLYzZG(cD_UHFd~VHYQz7=zw^(DL|b;I1!Y?k7=tR~1bV11mD{j&Gx z>L93&#KSCU@vwx9xh$28J;Cka)|82bhIHYtq3r$00N(pgD0l5WVOh3YjL%JX!LojJ zP1O3*uJTFJ=V`Ufv9UMEMQ`d&sLhgIa73=u@_NR?I|tje<-R96embK9Jcfb%mWr6} z>7NdFzwE7<^9_*vxP63N^YgCgb9CYJAG@>l!9hf3%^`RcVbeMo7z(;l3SMaP< zSLjYIhJ-cbLwqUZwXAIb3|AyshgQ2P=7 zoSVTyg!-7Go`9tDt=@Y1*>0|$a$&-PR@O4CXOpqkXB;!i!CtVgD7Z-ib=DW%&iTYF&#eYn}zu3%SB{yL6 zK87U=>m~#nN@b2Y3p(6e#^P@W=lBJ*G5WxN4UW_^(iMrmbp+WyCp|OCXeF2sC!@hH r`{L;(*cY^u+yG9%)m%MKs3Bvv<%8bM;+D$sJOz~eYWEWx+SES)!CmH4 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Effects/e7.jpg b/public/shearphoto/images/Effects/e7.jpg new file mode 100644 index 0000000000000000000000000000000000000000..16be6d1fe6117329297b14a4461bb7aa35f9edc9 GIT binary patch literal 2527 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 F>>uDHiC+K! literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Select.jpg b/public/shearphoto/images/Select.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4284a88c68d17ab3e691ca15f000dd1bd90adf3b GIT binary patch literal 14365 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�~IM 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 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/Select_zh-cn.jpg b/public/shearphoto/images/Select_zh-cn.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4284a88c68d17ab3e691ca15f000dd1bd90adf3b GIT binary patch literal 14365 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 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/bg.jpg b/public/shearphoto/images/bg.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a464e6595f62230fef381ac4dac514e171bac7eb GIT binary patch literal 462 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 TjheQO00000NkvXXu0mjfz&cz% literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/bg_headLine1.png b/public/shearphoto/images/bg_headLine1.png new file mode 100644 index 0000000000000000000000000000000000000000..a386fc91fbfe2e7298cd5d117d63e7dfb6396cf8 GIT binary patch literal 2802 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 h4!5{hqHS)6PARtvmWGEsc(go>^CD=&<&&o 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 bLA~ou{o$W!&%I+-e_$PKU2N`K`JMSU$j9@K literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/border.gif b/public/shearphoto/images/border.gif new file mode 100644 index 0000000000000000000000000000000000000000..72ea7ccb5321d5384d70437cfaac73011237901e GIT binary patch literal 329 zcmZ?wbhEHb9b#5NV>2k zBC~b@b~P=nNfWAe-b%_i6tS^-1y(h@EsB~1TqDA_h@fkxG$bHgvj}VxE1JLgr!*!^ ILUxTc0Q$^Q5C8xG literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/btn5.jpg b/public/shearphoto/images/btn5.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6743bd5294cec24e80f754281c797cee412a5f73 GIT binary patch literal 4870 zcmdT_c{r49+rP(*jA$`JJRVI$%aCP^Z7gMMWi7^*q9!vKnx&bsCgPzok%Y1gA-nLX zL6#_@vSu$ONywIvY{@%(EzciM@9})c_Z`Q3yytP>$8lZ1>-_!B^SrL}y6@HJs{_Dx zLm!e002mv~0#M+mtxfC z;i?K~d1oaQ3a+N6fKpLaQ$y{8D=VRt6_vP;nmkHHQ&mkfg<8ho!Ei9(=yGmz{4=H{Bl6b2Jd zaV8oY=peWX3M3LiQw5{0rmuv;qA>dUD3l>uPeV`H$WRGwfH6dA7^rD{=NeF*J;_80 z^E;RDldJbj?piC5Y23&LL^{cfNHC&P$?)}(HA%nDMdO!x-?)Td=YshqSCN~H;@ZLf z<)FVq-0oQ${%Bk7`ZNo?ywd? zS>n%J6T>2`+J&9(cG`G~7AbFIa(1clMSTpCQQf`T3kY)4<8p+613;p)oT2t`?O{xL z2WR|lUcTm|iQ(aiN8Mv@${xPRkiJ&8QS{y|>l7b%5dnyXJ#X*>edGqK7$JfeC32W7 z-X_7Y!3w*)|LW3~B$TCUEu{AU0W(`C^xn*!?F@8KErx~_MCFd=st(n~&Lok@E>2PQ zIB3|G@lqKRbP!w;`2-vn6pGwfzf~VY{g}UxHDT%1W}~wIy5~79sg5VZRSbzYeo&dc zpXKwiQ))_+Jf$zoruWUEw+}S*+%bLCv-zs-PJ^z;`R+x2bC<0p8qSQxuzfT0GDdIg zG7@}vNl)S;pOI&QsResvRKmAD0W<#y*}`DkH|1Rmw$5xk87J`!;30H}vzt9?9W*5X zDImKhZ&_WUv$HAtoJYRSyPL41L#QE!K;%)GoBXn^obrS|%(Gm%>Gu-@+ng#KwcILK z2Ay>ld&`cgOe$oSS}W5hRO)@9b zyd%QrN8E=_cl;Eju@oH(k~kLAMaf?#kLM?N-M}5o(iAZa(Ag|xI9}XQ682gR-Jg^f zd)K0U%!4_imtFWcwPx>quX3+8t@`@2v&HlhQm>;qh5g<}WZ{#Bp;$qWMeiP;=dA%= z1-8vvraAc?NgLCur7l9#=6Xk88ch8rKGo9I@FG9;WEuv$iN;qEfB%BQ=VnJ{B&y8< zZg3QTe^D2`*>LuY5w1JCVY;y179Q`Ux*#JgS6>YYCi?WexoTD@@dd^oJ9r`(t6b^v z@z8F&hyZ@xYX-Ru>R|(Z*_Uo!{MrlzmTUIUzvvFADeci+DgX54@`0m=G7H)Rcn_Ft zXbld(rR_Cp>-#eHULNt2L?Jz~bpDw^1Es$oD~zlNJO?q$oLu=Nr2O|VbIT-lA+SJ) zd7<@dz{(&e+0GSqTI5T^`^6;1((7r@2N!InXUePbDE%1s71aDn>akLdJGyjPkTi1K zddS_ZEx551 zc_(zq>zr+8$Mop+lvRM~S-CJdegJ>$WqLrLUx4Aw+vzy0qrDOiVQ8UqkFdFOS?f39 zVEitJOss7~OX%%c0Uel)aT9#jvWeKDfA>5Re+Y3$+@@wpz*Z;IGJ&Sep2|BJ)=L!6 z0zYk8DuE{b-$2gmJGjqXU4{0J4o74+dgkONU>3_N|2Tivk0v3*Cjm;%coY%}0gGEH z*rT`pc~Rl>ruatB3*+gIKCIx`+{l5Pae}!|tiFt6)l20HIxW{%xISL+?xW4fiz z`dMqtlQ;p~E}e+5kI*Psn$&bx1g&Y}U3*!k;XaB(1}ts3xaz5EmIhIR{{?O$9DP|j zvoIq!i)ePyAsUt1tb~AH3J*E>C0C>1YkvJFn`pOTE0v~LRI`jlH1rR_s5nvelVg+H zr6l|6ohv#TMw;xREBr}i5zL6R)4x030@o(qYtqp!@UPPTHv5aa^1?|D|-f($3AYm4NJFt z3K4HVWjapMy%;HYRpwFTOlGsOyf(`yir>UcdaRw(TbphiEH^GkBkGm8->PEQ&TW&k zO>xM0nED<;^ei$@urzB4fcSq z*@+PD6}N4X`%N?^{aLPV;huPL6a93>Z1yUU;`1;>yuk;-t`ue;`p7J?sOz!LNzud= z%AUkFK+$nf>9{L4;`PzHjUV=KMh!|lC=F@QCs>#imX9r&dXPaG(_00?SrJ1>-X%z; z(Qq-sV2H1h2e-?@m#_!^=X@{#*(sQD%Bn~drjS=IPB7WSE>2Irb|_5y-H^4bI_xd~ zT}M<`v2NL;cL|>(KZ72CngBpSZ7FBO(Xf%??~bnd(D(WsB$ipu0#l|BDGOL9b!6)WK*msHXHof4^ARfUN{7SfPV+;@Isg!Pdd(!{{*=v@!KVRI)-}UImUVjmfs=cP&r8m67vC91gCIcie%__&n2T zC;9k&;$ZaGwcnRz{O{P&$d1pUla5Kxi_X7dg}nNbm5<|StO8=mqPFsx&j!CZ`5ZMR zeM78z$(s+_`wkHQjmJIVVafB_iZkZ?$)yV{?YzLTgRuVXfrUJMeHN=g zkZ*wb&Abe&#Kh|s!ED*4p&+9p-S=}b;U~J~ATxPi-KOmtwFWIdiS6C(U7z$`MtE%k z<4>B-^5ciJEOgw51iSi^UI$~*RVq_hcznNvcgj^q1hr6+o~5x}T@TO$5GGjEwDC=4b8G&pwA*V$6jOR%jHxdBqAgok=b>dN$bVg(jZj z7OFjuU$3v#OLVR7e=yB^S9p(fJoMs$0W;&|9lWtmO;!Of4NMiK_(t#r8R-y}Cb~Pu_m~R!^b{@iOPwjmob4l&JCM4CT6B`Nr4o-Omoq z4Y$XiniL(HQuT0?@tY-?6?t8z&nFafU^>2>O#2GMva`(&z5G&@y}0jskVq#o^14DB z#c9^IB=}&VTI{X4v?$>bn-zkZ^_2?IN4XbrOc^tk`>vT{C}Ut)wIo6kdZZR`;msvZ_isdXUql_{!!)b%)h<_x$k?oo>wP}#99ZNm0!u1ovkgPw=| zs@n29WQN<>yZ?9_g=H!pIu*md-g<3k}WRBI%B7ayHBTEX2iY0m`f&t zd!_j`fVd0r3jq=d3GtrHnGD?h8K-%^N=D$vUhr^1^Z^h!1S)7*`Op4Aus0)`%lv;Y7A literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/cam.png b/public/shearphoto/images/cam.png new file mode 100644 index 0000000000000000000000000000000000000000..79b86aa491ee6769e9cafe449a89bcd6c19ca86d GIT binary patch literal 3135 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 Ee}v^uK;1(=s2*F(w+=5$zh2Rn-xckiG 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 literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/shutter.mp3 b/public/shearphoto/images/shutter.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..16df2b3b56dfc67e0dfa46f84c8c5f176afd76e7 GIT binary patch literal 13624 zcmeI1WmFtpv#1*l?hptN+}%C6y9~h{1`kdGAq)g}hrtJe2MF#?2=1;4L4qe}2r?J) zp7Wh`?p^EtzGvO9e)Q~B-95FRs@k=smF4&lfd|1;($nJw05X7wPf$SE++0o9+}zsI z#n!{p+}hc}+6j==RDXC1uyqAH=y|(pDN1Vr+yI~_uK@tqz@y66liEj!Q{bNm?f>&Z z4NV<&fSUVJcz6~5X*{(5X$UI_{BIg=5!wH);Z+de|L+=ZZeiYkY5uVm{GZlz)wN{) z!`i=$@bdhJH5p#vf4%qEA9-p1|5E(D!z(E8zjyIpfAfEDJi74T@^26P+XMghz`s54 zZx8%`>;d?VPB#Fc{hq-g;sem3N^D?cHvmvyn@|jtle>{q@D~p;o-s$|WRV3ozExQR0 zyUhorqOR^-_hCGH*K_0)C4yb>)iL;`b>6~XlmBsfJ#7yOLNJr|@o`8)egt{~V6tC1 zwBtk2EpplU@nhX2$JPmxGj-v{gp$scgJlMq1ReZL60Eu^Z*OlWbD?5%H4G8gBJEuF z&zLDlYth%>cgj>YHWK8L@OdcQsMtolDV{Een-w5IG`6BPu>}D7GCm7I0AeHE$CnPB zljWlwofOG#z~SI8j_W}wE72Ep)c&zpXqC7}r}CrOdS|5eJY@`PI9+l-!76{|J*zP* zYmb`=S~{}{D#g>*bg~~+_xVXe*#Lw<#bia3>+7Jx}3Xky8y@h%@oPeAVbcJJG^L-r>e+`3(X)*4li1Mc4dL!>lTw5Q=rbW;|-R zWLO&a*Ypd>?Lz_)xK06pLL%t_^9*NOnp`7^93-*3ubDiUi97fa2oJ#cxvQy%4?*}k z$Z>_N1+etVG4Zq4Wz;rs6)-o^%cm`0E66e5-yuB(S2Wi-J-6(YK~<5#CPM#=sg`f* zQx;xyR9*z1&+>NFk3a?x&`}d3!Eky0Vp&cdN;ylKO7sH8S%^@e9m*A~UeC@I>i2C~TO?3?Mq zZS}x&Og`3~<_Bv>XVw3ebiXTv3;+naY9Pk5>X(l|r~t+kig`N^1i`$JrJkS6jqDQZ zt&T|wbE@6$03wDo1Ojz&TGd5-v04qs{XIek?HVpGza|$V%jA0y7I$c-b8m(&F_xDN zB_p%E%vbvql}~!a*r}HA`wB{qhhaoe3F{9gRx4zD)#c=}oN0aAeO}6Ags^L?i@G#8^bAMm zPAZ^;5C9N8-bGNZZtDni=;y{eYQJzWFp-)2{Lk1Rz15oT);J2nm$|BTRAjsiFWnVsNlp2ERR0Mij^f3Uupt9Z4UcdS#g}liN|;P}P3`9)WjUzUOhgjU zz#nhk!3Bpio;mn05|ASX1@-*C>9TnQdII1COZzs{00?Oh60Q+f6Hxix^5kj^u z3##;m$Tueu(^VOwFrNPU0n#Iun3`2BE8-mD_fTgr%`^RIEBhX8_Z;@K*7g@~qP)

ZYF8xmYdcJD&k@W+q5Q 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 X_dmb?W3P`uaL7aV{|)s2Xbk@sDL>R` literal 0 HcmV?d00001 diff --git a/public/shearphoto/images/waterimg.png b/public/shearphoto/images/waterimg.png new file mode 100644 index 0000000000000000000000000000000000000000..d6715c83f740ad55243741438c0519621a106553 GIT binary patch literal 11069 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 maxs[0]) { + width = maxs[0]; + height = Math.round(maxs[0] / P); + } + if (height > maxs[0]) { + height = maxs[0]; + width = Math.round(maxs[0] * P); + if (width > maxs[0]) { + width = maxs[0]; + height = Math.round(maxs[0] / P); + } + } + } + newcanvas.width = width; + newcanvas.height = height; + ctx.fillStyle = "#FFFFFF"; + ctx.fillRect(0, 0, width, height); + ctx.drawImage(Image64, 0, 0, this.width, this.height, 0, 0, width, height); + var DATA64 = newcanvas.toDataURL(type, maxs ? maxs[1] :.85); + if (this_.URL) { + this_.BOLBID && this_.URL.revokeObjectURL(this_.BOLBID); + this_.BOLBID = this_.URL.createObjectURL(this_.FormBlob(DATA64)); + typeof functions === "function" && functions(this_.BOLBID); + } else { + typeof functions === "function" && functions(DATA64); + } + ctx.clearRect(0, 0, width, height); + bodys.removeChild(newcanvas); + delete DATA64; + delete Image64; + }; + Image64.src = DataUrl; + delete DataUrl; + }, + CtxDrawImage:function(ctx, SendArry, newcanvas, this_) { + var MyR = SendArry.R, arg = this_.arg; + if (this.HTML5MAX) { + var W = SendArry.IW, WW = W, H = SendArry.IH, p = W / H; + W > this.HTML5MAX ? (W = this.HTML5MAX, H = Math.round(W / p), H > this.HTML5MAX && (H = this.HTML5MAX, + W = Math.round(H * p))) :H > this.HTML5MAX && (H = this.HTML5MAX, W = Math.round(H * p), + W > this.HTML5MAX && (W = this.HTML5MAX, H = Math.round(W / p))); + p = W / WW; + if (MyR === 0) { + var twx = this_.ImgOWidth - SendArry.X, twy = this_.ImgOHeight - SendArry.Y, IMGWx = twx * p, IMGHy = twy * p; + newcanvas.width = SendArry.IW = W; + newcanvas.height = SendArry.IH = H; + ctx.fillStyle = "#FFFFFF"; + ctx.fillRect(0, 0, IMGWx, IMGHy); + ctx.drawImage(arg.ImgMain, SendArry.X, SendArry.Y, twx, twy, 0, 0, IMGWx, IMGHy); + } else { + this.HandleRotation(this_, MyR, SendArry, newcanvas, ctx, p, W, H); + } + } else { + if (MyR === 0) { + newcanvas.width = SendArry.IW; + newcanvas.height = SendArry.IH; + ctx.fillStyle = "#FFFFFF"; + ctx.fillRect(0, 0, SendArry.IW, SendArry.IH); + ctx.drawImage(arg.ImgMain, -SendArry.X, -SendArry.Y); + } else { + this.HandleRotation(this_, MyR, SendArry, newcanvas, ctx, 1, SendArry.IW, SendArry.IH); + } + } + }, + lock:false, + PhotoHTML5True:false, + SetSrc:function(newsrc, ImgMain, ImgDom, domimg) { + ImgMain.src = ImgDom.src = newsrc; + for (var i = 0; i < domimg[1]; i++) { + domimg[0][i].src = newsrc; + } + delete newsrc; + }, + BOLBID:false, + Aclick:false, + artwork:false, + EffectsReturn:function() { + this.Aclick && (this.Aclick.className = ""); + this.Aclick = this.artwork; + this.artwork && (this.artwork.className = "Aclick"); + }, + Effects:function(StrEvent, HTML5) { + var ImgMain = this.arg.ImgMain, ImgDom = this.arg.ImgDom, AP; + var preview = this.preview; + var this_ = this; + return function() { + if (HTML5.lock) return; + if (HTML5.Aclick === this) { + this_.pointhandle(1500, 1, "亲!现在已经是" + StrEvent + "效果了!", 2, "#307ff6", "#fff"); + return; + } + HTML5.lock = true; + AP = window.ShearPhoto.psLib(HTML5.Images); + HTML5.Aclick && (HTML5.Aclick.className = ""); + HTML5.Aclick = this; + this.className = "Aclick"; + this_.pointhandle(0, 1, "正在加载" + StrEvent + "效果!稍等....,不要动鼠标,可能有点卡", 2, "#fbeb61", "#3a414c", function() { + setTimeout(function() { + var domimg = preview.domimg, DATA64; + if (StrEvent === "原图") { + DATA64 = AP.save(false, HTML5.ImagesType); + } else { + DATA64 = AP.ps(StrEvent).save(false, HTML5.ImagesType); + } + if (HTML5.URL) { + HTML5.BOLBID && HTML5.URL.revokeObjectURL(HTML5.BOLBID); + HTML5.BOLBID = HTML5.URL.createObjectURL(HTML5.FormBlob(DATA64)); + HTML5.SetSrc(HTML5.BOLBID, ImgMain, ImgDom, domimg); + this_.runImgUrl = [ HTML5.BOLBID, true, true ]; + } else { + HTML5.SetSrc(DATA64, ImgMain, ImgDom, domimg); + this_.runImgUrl = [ DATA64, true, true ]; + } + delete DATA64; + this_.pointhandle(1500, 1, StrEvent + "效果加载成功!提示:如果机器配置差,效果加载时间会更长哦", 1, "#307ff6", "#fff"); + HTML5.lock = false; + HTML5.PhotoHTML5True = true; + }, 1); + }); + }; + }, + BlobRegExp:new RegExp("^data:.*base64,"), + FormBlob:function(dataURI) { + var byteString, splits = false, splits1 = dataURI.replace(this.BlobRegExp, function() { + splits = true; + return ""; + }); + if (splits) byteString = atob(splits1); else byteString = unescape(splits1); + var byteStringlength = byteString.length, ia = new Uint8Array(byteStringlength); + for (var i = 0; i < byteStringlength; i++) { + ia[i] = byteString.charCodeAt(i); + } + return new Blob([ ia ], { + type:this.ImagesType + }); + }, + IfHTML5:function(transform, H5True, HTML5MAX) { + try { + new Blob([ "1" ], { + type:"text/plain" + }); + } catch (e) { + H5True = false; + } + transform && H5True && (this.canvas = true, this.HTML5MAX = HTML5MAX); + }, + CanvasImg:function(SendArry, postArgs, this_) { + var newcanvas = document.createElement("canvas"), bodys = document.body; + newcanvas.style.display = "none"; + bodys.appendChild(newcanvas); + var ctx = newcanvas.getContext("2d"); + this.CtxDrawImage(ctx, SendArry, newcanvas, this_); + var blob = this.FormBlob(newcanvas.toDataURL(this.ImagesType, this_.arg.HTML5Quality)); + ctx.clearRect(0, 0, SendArry.IW, SendArry.IH); + bodys.removeChild(newcanvas); + var readerForm = new FormData(); + readerForm.append("ShearPhotoIW", SendArry.IW); + readerForm.append("ShearPhotoIH", SendArry.IH); + readerForm.append("ShearPhotoFW", SendArry.FW); + readerForm.append("ShearPhotoFH", SendArry.FH); + readerForm.append("ShearPhotoP", this_.arg.proportional[0]); + if (Object.prototype.toString.call(postArgs) === "[object Object]") { + for (var key in postArgs) { + readerForm.append(key, postArgs[key]); + } + } + readerForm.append("UpFile", blob); + return readerForm; + } + }, + _ieexchange_:function() { + var this_ = this; + function e(a, b) { + var exchange = new Array(this_[a], this_[b]); + this_[a] = exchange[1]; + this_[b] = exchange[0]; + } + e("ImgWidth", "ImgHeight"); + }, + SetRote:{ + ROReg:new RegExp("rotate\\((.*?)\\)", "i"), + SLReg:new RegExp("translate\\((.*?)\\)", "i"), + run:function(DOM, Transform, RO, SL) { + var domstyle = DOM.style, RegTrue = true, domstyleTransform = domstyle[Transform], ROstr = domstyleTransform.replace(this.ROReg, function() { + RegTrue = false; + return RO; + }), txt = RegTrue ? domstyleTransform + " " + RO :ROstr, RegTrue = true, SLstr = txt.replace(this.SLReg, function() { + RegTrue = false; + return SL; + }), txt = RegTrue ? txt + " " + SL :SLstr; + return txt; + }, + runSL:function(DOM, Transform, SL) { + var domstyle = DOM.style, RegTrue = true, domstyleTransform = domstyle[Transform], SLstr = domstyleTransform.replace(this.SLReg, function() { + RegTrue = false; + return SL; + }); + return RegTrue ? domstyleTransform + " " + SL :SLstr; + } + }, + _exchange_:function() { + var this_ = this; + this._ieexchange_(); + var IfRotate = this.rotate; + if (IfRotate === 90 || IfRotate === 270) { + var consts = { + 90:-1, + 270:1 + }[IfRotate]; + this.ImgRotateFun = function(W, H) { + var ImgRotateLT = consts * Math.round((H - W) * .5) + "px", arg = this.arg, str = "translate(" + ImgRotateLT + "," + ImgRotateLT + ")"; + arg.ImgMain.style[this_.transform] = this_.SetRote.runSL(arg.ImgMain, this_.transform, str); + arg.ImgDom.style[this_.transform] = this_.SetRote.runSL(arg.ImgDom, this_.transform, str); + return [ H, W ]; + }; + return function(ImgMain, ImgDom, rotate) { + var slate = consts * Math.round((this_.ImgWidth - this_.ImgHeight) * .5), str = "translate(" + slate + "px," + slate + "px)"; + ImgMain.style[this_.transform] = this_.SetRote.run(ImgMain, this_.transform, rotate, str); + ImgDom.style[this_.transform] = this_.SetRote.run(ImgDom, this_.transform, rotate, str); + return slate; + }; + this.preview.WH = [ this_.ImgHeight, this_.ImgWidth ]; + } else { + this.preview.WH = [ this_.ImgWidth, this_.ImgHeight ]; + this.ImgRotateFun = function(W, H) { + return [ W, H ]; + }; + return function(ImgMain, ImgDom, rotate) { + ImgMain.style[this_.transform] = this_.SetRote.run(ImgMain, this_.transform, rotate, "translate(0,0)"); + ImgDom.style[this_.transform] = this_.SetRote.run(ImgDom, this_.transform, rotate, "translate(0,0)"); + return 0; + }; + } + }, + preview:{ + isW:new Array(), + isH:new Array(), + run:function(arg, thisMain) { + var _this = this; + if (Object.prototype.toString.call(arg.preview) === "[object Array]") { + var leng = arg.preview.length, EmptyFun = function() {}, srcDefault = arg.relativeUrl + "images/default.gif"; + if (leng > 0) { + arg.scope.parentNode.insertAdjacentHTML("afterEnd", '

'); + var HTML = "", proportionFun = EmptyFun, p = false; + arg.proportional[0] ? p = arg.proportional[0] :proportionFun = function(d, w, i, pro) { + d[1][i].style.height = Math.round(w[i] / pro[0] * pro[1]) + "px"; + }; + var margin_right = 10, borderW = 5, MBall = margin_right + borderW * 2; + for (var i = 0; i < leng; i++) { + this.domWidth += arg.preview[i] + MBall; + HTML += '
'; + } + this.dom = document.getElementById("preview"); + this.dom.innerHTML = HTML; + this.dom.parentNode.style.width = arg.scopeWidth + 2 + "px"; + var domimg = [ this.dom.getElementsByTagName("img"), this.dom.getElementsByTagName("a") ], imgUrlFun = function(d, u) { + d.src = u; + }, imgWHFun = function(d, WH, pro, i) { + var W = Math.round(WH[0] * pro), H = Math.round(WH[1] * pro), True = false; + _this.isW[i] === W || (d.style.width = W + "px", _this.isW[i] = W, True = true); + _this.isH[i] === H || (d.style.height = H + "px", _this.isH[i] = H, True = true); + if (True && thisMain.rotate > 10 && thisMain.rotate !== 180) { + var mylt = (_this.isW[i] - _this.isH[i]) / (thisMain.rotate === 270 ? -2 :2) + "px"; + d.style[thisMain.transform] = thisMain.SetRote.runSL(d, thisMain.transform, "translate(" + mylt + "," + mylt + ")"); + } + }, RFun = function(d, styleR, R, pro) { + if (thisMain.transform) { + var SL = R[1] * pro + "px"; + d.style[styleR] = thisMain.SetRote.run(d, styleR, R[0], "translate(" + SL + "," + SL + ")"); + } else { + d.style[styleR] = R; + } + }, funone, funtwo, funthree, domimgA = domimg[0], pro, leftBorder, topBorder, domimgi; + this.domimg = [ domimg[0], leng ], this.close_ = function() { + for (var i = 0; i < leng; i++) { + domimgi = domimgA[i]; + domimgi.src = srcDefault; + if ("cssText" in domimgi.style) domimgi.style.cssText = ""; else domimgi.setAttribute("style", ""); + } + this.dom.style.display = "none"; + arg.Effects && (arg.Effects.style.display = "none"); + this.dom.parentNode.style.width = arg.scopeWidth + 2 + "px"; + }; + this.SetPreview = function(p) { + if (p) { + for (var i = 0; i < leng; i++) domimg[1][i].style.height = Math.round(arg.preview[i] / p) + "px"; + proportionFun = EmptyFun; + } else proportionFun = function(d, w, i, pro) { + d[1][i].style.height = Math.round(w[i] / pro[0] * pro[1]) + "px"; + }; + }; + this.handle = function(argarr, True, TrueHTML5, args) { + True && this.open_(args || arg, TrueHTML5); + var left = argarr.left, top = argarr.top, formAllW = argarr.formAllW, imgUrl = argarr.imgUrl, TF = argarr.TF, styleR = argarr.styleR, R = argarr.R, formAllH = argarr.formAllH, HTML3D = argarr.HTML3D; + typeof imgUrl === "boolean" ? funone = EmptyFun :funone = imgUrlFun; + TF ? funtwo = imgWHFun :funtwo = EmptyFun; + typeof R === "boolean" ? funthree = EmptyFun :funthree = RFun; + typeof left === "boolean" ? left = EmptyFun :(leftBorder = left + arg.Border, topBorder = top + arg.Border, + left = function(pro) { + HTML3D.setLT(domimgi, Math.round(leftBorder * pro) + "px", Math.round(topBorder * pro) + "px"); + }); + for (var i = 0; i < leng; i++) { + domimgi = domimgA[i]; + pro = arg.preview[i] / formAllW; + left(pro); + proportionFun(domimg, arg.preview, i, [ formAllW, formAllH ]); + funone(domimgi, imgUrl); + funtwo(domimgi, this.WH, pro, i); + funthree(domimgi, styleR, R, pro); + } + }; + } + } + }, + dom:false, + domWidth:0, + domimg:false, + WH:new Array(2), + parentNodes:false, + SetPreview:function() {}, + EffTrue:false, + handle:function(argarr, True, arg) { + True && this.open_(arg); + }, + close_:function() { + this.arg.Effects && (this.arg.Effects.style.display = "none"); + this.parentNodes && (this.parentNodes.style.width = this.arg.scopeWidth + 2 + "px"); + }, + open_:function(arg) { + var efffwidth = 0; + this.arg = arg; + if (this.EffTrue) { + arg.Effects.style.display = "block"; + arg.Effects.scrollTop = 0; + arg.Effects.style.height = arg.scopeHeight + "px"; + this.parentNodes = arg.Effects.parentNode; + efffwidth = arg.Effects.offsetWidth; + } else { + arg.Effects && (arg.Effects.parentNode.removeChild(arg.Effects), arg.Effects = false); + } + if (this.dom) { + var this_ = this; + this.dom.style.display = "block"; + this.dom.parentNode.style.width = arg.scopeWidth + this.domWidth + efffwidth + 10 + "px"; + } else { + arg.Effects && (arg.Effects.parentNode.style.width = arg.scopeWidth + this.domWidth + efffwidth + 2 + "px"); + } + } + }, + Rotate:function(arg) { + var rotate; + this.saveL = this.formLeft + this.relatL; + this.saveT = this.formTop + this.relatT; + if (this.transform) { + arg === "left" ? this.rotate -= 90 :this.rotate += 90; + this.rotate = { + "-90":270, + 0:0, + "-270":90, + 360:0, + 180:180, + 90:90, + 270:270, + "-360":0, + "-180":180 + }[this.rotate] || 0; + rotate = "rotate(" + this.rotate + "deg)"; + var fun = this._exchange_(); + this.setinitial(this.arg, true); + var SL = fun(this.arg.ImgMain, this.arg.ImgDom, rotate); + this.preview.handle({ + left:this.ImgDomL, + top:this.ImgDomT, + formAllW:this.formAllW, + formAllH:this.formAllH, + imgUrl:false, + styleR:this.transform, + R:[ rotate, SL ], + HTML3D:this.HTML5 + }); + } else { + arg === "left" ? this.rotate -= 1 :this.rotate += 1; + this.rotate = this.rotate > 3 ? 0 :this.rotate < 0 ? 3 :this.rotate; + if (this.rotate === 1 || this.rotate === 3) { + this.ImgRotateFun = function(W, H) { + return [ H, W ]; + }; + } else { + this.ImgRotateFun = function(W, H) { + return [ W, H ]; + }; + } + this.arg.ImgMain.style.filter = this.arg.ImgDom.style.filter = rotate = "progid:DXImageTransform.Microsoft.BasicImage(rotation=" + this.rotate + ")"; + this._ieexchange_(); + this.preview.handle({ + left:false, + top:false, + formAllW:this.formAllW, + formAllH:this.formAllH, + imgUrl:false, + styleR:"filter", + R:rotate, + HTML3D:this.HTML5 + }); + this.setinitial(this.arg, true); + } + }, + pointhandle:function(residencetime, speed, txt, Position, backgroundColor, color, functions) { + var point = this.arg.scope.children[0]; + point.className === "point" && this.arg.scope.removeChild(point); + if (residencetime === -1) return; + point = document.createElement("div"); + point.className = "point"; + this.arg.scope.insertBefore(point, this.arg.scope.childNodes[0]); + var this_ = this, num = -35, HTML53D = this.HTML5; + function show(begin, end, speed, cmd) { + begin += cmd; + if (begin > end && 0 < cmd) { + HTML53D.setT(point, end + "px"); + if (residencetime) { + setTimeout(function() { + show(end, num, speed, -1); + }, residencetime); + } else { + typeof functions === "function" && functions(); + } + return; + } else { + if (end > begin && 0 > cmd) { + try { + this_.arg.scope.removeChild(point); + } catch (e) {} + return; + } + } + HTML53D.setT(point, begin + "px"); + setTimeout(function() { + show(begin, end, speed, cmd); + }, speed); + } + point.style.color = color; + point.style.backgroundColor = backgroundColor; + point.innerHTML = "" + txt; + var pointI = point.getElementsByTagName("i")[0]; + switch (Position) { + case 0: + pointI.style.backgroundPosition = "-16px 0"; + break; + + case 1: + pointI.style.backgroundPosition = "0 0"; + break; + + case 2: + pointI.style.backgroundPosition = "-31px 0"; + break; + } + show(num, 0, speed, 1); + }, + setinitial:function(arg, TF) { + var cl = 0, ct = 0, TrueTraverse = !arg.traverse, HTML53D = this.HTML5, RL, BL, RT, BT; + if (this.BoxW > this.ImgWidth) { + this.relatW = this.ImgWidth; + arg.relat.style.width = this.ImgWidth + "px"; + var BiW = Math.round((this.BoxW - this.ImgWidth) * .5); + RL = (this.relatL = BiW) + "px"; + BL = -BiW + "px"; + } else { + arg.relat.style.width = this.BoxW + "px"; + this.relatW = this.BoxW; + RL = BL = this.relatL = 0; + TrueTraverse && (cl = (this.BoxW - this.ImgWidth) * .5); + } + if (this.BoxH > this.ImgHeight) { + this.relatH = this.ImgHeight; + arg.relat.style.height = this.ImgHeight + "px"; + var BiH = Math.round((this.BoxH - this.ImgHeight) * .5); + this.relatT = BiH; + RT = BiH + "px"; + BT = -BiH + "px"; + } else { + this.relatH = this.BoxH; + arg.relat.style.height = this.BoxH + "px"; + RT = BT = this.relatT = 0; + TrueTraverse && (ct = (this.BoxH - this.ImgHeight) * .5); + } + HTML53D.setLT(arg.relat, RL, RT); + HTML53D.setLT(arg.black, BL, BT); + this.AmendOffset(); + this.MovePhoto(TF, cl, ct, TrueTraverse); + }, + MovePhoto:function(TF, cl, ct, True) { + var arg = this.arg, ImgDom = arg.ImgDom, ImgMain = arg.ImgMain, also, scale, ImgMainL, ImgMainT, ImgDomL, ImgDomT, HTML3D = this.HTML5; + if (arg.traverse && this.BoxW < this.ImgWidth) { + var MinusImgWBoxW = this.ImgWidth - this.BoxW; + also = this.BoxW - this.formAllW; + scale = also && MinusImgWBoxW / also; + var L = Math.round(-this.formLeft * scale); + ImgMainL = L; + ImgDomL = L - this.formLeft - this.Border; + } else { + ImgMainL = cl = arg.traverse ? 0 :True ? cl :this.ImgMainL; + ImgDomL = cl - this.formLeft - this.Border; + } + if (arg.traverse && this.BoxH < this.ImgHeight) { + var MinusImgHBoxH = this.ImgHeight - this.BoxH; + also = this.BoxH - this.formAllH; + scale = also && MinusImgHBoxH / also; + var T = Math.round(-this.formTop * scale); + ImgMainT = T; + ImgDomT = T - this.formTop - this.Border; + } else { + ImgMainT = ct = arg.traverse ? 0 :True ? ct :this.ImgMainT; + ImgDomT = ct - this.formTop - this.Border; + } + this.ImgMainT = ImgMainT, this.ImgMainL = ImgMainL; + this.ImgDomL = ImgDomL, this.ImgDomT = ImgDomT; + HTML3D.setLT(ImgMain, ImgMainL + "px", ImgMainT + "px"); + HTML3D.setLT(ImgDom, ImgDomL + "px", ImgDomT + "px"); + this.preview.handle({ + left:ImgDomL, + top:ImgDomT, + formAllW:this.formAllW, + formAllH:this.formAllH, + imgUrl:false, + TF:TF, + styleR:false, + R:false, + HTML3D:HTML3D + }); + }, + AmendOffset:function() { + var HTML53D = this.HTML5, LT3D; + if (this.saveL) { + this.formLeft = this.saveL - this.relatL; + this.formTop = this.saveT - this.relatT; + } else { + LT3D = HTML53D.getLT(this.formParent); + typeof this.formLeft === "boolean" && (this.formLeft = parseFloat(LT3D[0])); + typeof this.formTop === "boolean" && (this.formTop = parseFloat(LT3D[1])); + this.formLeft -= this.relatL; + this.formTop -= this.relatT; + } + this.formLeft < 0 && (this.formLeft = 0, this.saveL = this.relatL); + this.formTop < 0 && (this.formTop = 0, this.saveT = this.relatT); + var formWL = this.formLeft + this.formAllW, formHT = this.formTop + this.formAllH; + if (formWL > this.relatW) { + var FrL = formWL - this.relatW; + if (FrL > this.formLeft) { + this.formW = this.formW - (FrL - this.formLeft); + this.formLeft = 0; + this.saveL = this.relatL; + this.formAllW = this.formW + this.Mdouble; + } else { + this.formLeft -= FrL; + this.saveL = this.formLeft + this.relatL; + } + } + if (formHT > this.relatH) { + var FrT = formHT - this.relatH; + if (FrT > this.formTop) { + this.formH = this.formH - (FrT - this.formTop); + this.formTop = 0; + this.saveT = this.relatT; + this.formAllH = this.formH + this.Mdouble; + } else { + this.formTop -= FrT; + this.saveT = this.formTop + this.relatT; + } + } + HTML53D.setLT(this.formParent, this.formLeft + "px", this.formTop + "px"); + if (this.arg.proportional[0]) { + var H = Math.round(this.formAllW / this.arg.proportional[0]); + if (H > this.formAllH) { + this.formAllW = Math.round(this.formAllH * this.arg.proportional[0]); + this.formW = this.formAllW - this.Mdouble; + } else { + this.formAllH = H; + this.formH = H - this.Mdouble; + } + } + this.arg.form.style.width = this.formW + "px", this.arg.form.style.height = this.formH + "px"; + this.ie6(this.formParent, this.formAllW, this.formAllH); + }, + MaxMinLimit:function(this_) { + this_.ImgWidth = this_.ImgOWidth = this.width; + this_.ImgHeight = this_.ImgOHeight = this.height; + if (typeof this_.arg.Max === "number") { + this_.ImgWidth > this_.arg.Max && (this_.ImgWidth = this_.arg.Max, this_.ImgHeight = Math.round(this_.ImgWidth / this_.ImgScales)); + this_.ImgHeight > this_.arg.Max && (this_.ImgHeight = this_.arg.Max, this_.ImgWidth = Math.round(this_.ImgHeight * this_.ImgScales)); + } + var MH, MW; + if (this_.arg.proportional[0]) { + MH = this_.Min; + MW = Math.round(MH * this_.arg.proportional[0]); + if (MW < this_.Min) { + MW = this_.Min; + MH = Math.round(MW / this_.arg.proportional[0]); + } + } else { + MW = MH = this_.Min; + } + this_.ImgWidth < MW && (this_.ImgWidth = MW, this_.ImgHeight = Math.round(this_.ImgWidth / this_.ImgScales)); + this_.ImgHeight < MH && (this_.ImgHeight = MH, this_.ImgWidth = Math.round(this_.ImgHeight * this_.ImgScales)); + this_.artworkW = this_.ImgWidth, this_.artworkH = this_.ImgHeight; + }, + SetProportional:function(pro) { + if (pro != this.arg.proportional[0]) { + this.arg.proportional[0] = pro; + if (!this.runImgUrl) { + return; + } + var Type = this.HTML5.ImagesType, this_ = this, runImgUrl = this.runImgUrl; + this.preview.close_(); + this.pointhandle(0, 1, "正在更换截框比例......", 2, "#fbeb61", "#3a414c", function() { + this_.again(); + this_.run.apply(this_, runImgUrl); + this_.HTML5.ImagesType = Type; + this_.preview.SetPreview(pro); + }); + } + }, + run:function(ImgUrl, Trues, SetPro) { + this.runImgUrl = [ ImgUrl, Trues, true ]; + this.HTML5.HTML5PHP = Trues; + var dir_=(ImgUrl.indexOf('file/photo/') > -1) ? this.arg.relativeUrl : this.arg.avatarUrl; + var this_ = this, arg = this.arg, relatImgUrl = this.HTML5.canvas && Trues ? ImgUrl : dir_ +"/" + ImgUrl, image = new Image(); + SetPro || (this.HTML5.Images = image); + this.defaultShear(); + this.arg = arg; + this.HTML5.canvas && Trues || (this.ImgUrl = ImgUrl); + image.onload = function() { + if (!(this.width = Math.round(this.width)) > 0 || !(this.height = Math.round(this.height)) > 0) { + this_.pointhandle(3e3, 10, "请选择正确图片", 0, "#f82373", "#fff"); + return; + } + arg.ImgMain.src = arg.ImgDom.src = relatImgUrl; + arg.black.style.zIndex = 99; + this_.ImgScales = this.width / this.height; + this_.Min = arg.Min; + this_.MaxMinLimit.call(this, this_); + arg.ImgMain.style.width = arg.ImgDom.style.width = this_.artworkW + "px", arg.ImgMain.style.height = arg.ImgDom.style.height = this_.artworkH + "px"; + this_.BoxW = arg.scope.offsetWidth - 2; + this_.BoxH = arg.scope.offsetHeight - 2; + this_.Border = arg.Border; + this_.Mdouble = arg.Border * 2; + var W, H; + if (arg.proportional[0]) { + W = arg.proportional[1] - this_.Mdouble; + H = arg.proportional[1] / arg.proportional[0] - this_.Mdouble; + } else { + W = arg.proportional[1] - this_.Mdouble; + H = arg.proportional[2] - this_.Mdouble; + } + this_.formW = W = Math.round(W); + this_.formH = H = Math.round(H); + this_.formAllW = W + this_.Mdouble; + this_.formAllH = H + this_.Mdouble; + this_.preview.WH = [ this_.artworkW, this_.artworkH ]; + this_.formParent = arg.form.offsetParent; + this_.et(); + this_.setinitial(arg); + this_.preview.handle({ + left:false, + top:false, + formAllW:this_.formAllW, + formAllH:this_.formAllH, + TF:true, + imgUrl:relatImgUrl, + styleR:false, + R:false, + HTML3D:this_.HTML5 + }, true, arg); + var str = this_.MoveDiv = new window.ShearPhoto.MoveDiv(); + str.reckon(arg.relat, false); + str.selectionempty = this_.selectionempty; + str.addevent = this_.addevent; + str.HTML5 = this_.HTML5; + str.run({ + to:new Array(arg.form), + form:this_.formParent, + MoveWidth:this_.relatW, + MoveHeight:this_.relatH, + shifting:new Array(), + center:1, + centerFront:function() { + return [ this_.relatW, this_.relatH ]; + }, + DivDownFun:function(t) { + t.arg.MoveWidth = this_.relatW; + t.arg.MoveHeight = this_.relatH; + t.DivW = this_.formW + this_.Mdouble; + t.DivH = this_.formH + this_.Mdouble; + }, + centerfun:function(l, t, tt) { + this_.formLeft = l; + this_.formTop = t; + this_.MovePhoto(false, 0, 0); + tt.arg.MoveWidth = this_.relatW; + tt.arg.MoveHeight = this_.relatH; + tt.DivW = this_.formW + this_.Mdouble; + tt.DivH = this_.formH + this_.Mdouble; + }, + zIndex:100, + MoveFun:function(iL, iT, MoveScale) { + this_.formLeft = iL; + this_.formTop = iT; + this_.MovePhoto(true, 0, 0); + } + }); + this_.MoveDivEve = function() { + str.delDownEve(); + }; + arg.Shearbar.style.display = "block"; + arg.SelectBox.style.visibility = "hidden"; + this_.zoom(); + this_.pointhandle(2e3, 10, "可以拖动或拉伸蓝边框进行截图", 1, "#fbeb61", "#3a414c"); + delete relatImgUrl; + delete ImgUrl; + }; + image.onerror = function() { + this_.pointhandle(0, 10, "无法读取图片。图片类型或路径不正确 或 relativeUrl参数是否存在问题", 0, "#f82373", "#fff"); + }; + this.pointhandle(0, 1, "图片已加载,正在创建截图环境,请稍等........", 2, "#fbeb61", "#3a414c", function() { + image.src = relatImgUrl; + }); + }, + config:function(arg) { + this.arg = arg; + arg.Shearbar.style.display = "none"; + arg.scope.style.width = arg.black.style.width = arg.SelectBox.style.width = arg.scopeWidth + "px"; + arg.scope.style.height = arg.black.style.height = arg.SelectBox.style.height = arg.scopeHeight + "px"; + var scopeparentNode = arg.scope.parentNode; + scopeparentNode.style.width = scopeparentNode.parentNode.style.width = arg.scopeWidth + 2 + "px"; + var opacityFun; + this.HTML5.IfHTML5(this.transform, arg.HTML5, arg.HTML5MAX); + this.HTML5.HTML5LT(arg.translate3d && this.transform); + if (this.transform) opacityFun = function(t, n) { + t.style.opacity = n; + }; else opacityFun = function(t, n) { + t.style.filter = "alpha(opacity=" + n * 100 + ")"; + }; + if (arg.Border > 0) { + arg.DynamicBorder[0].style.display = arg.DynamicBorder[1].style.display = arg.DynamicBorder[2].style.display = arg.DynamicBorder[3].style.display = "none"; + arg.DynamicBorder[0].style.background = arg.DynamicBorder[1].style.background = arg.DynamicBorder[2].style.background = arg.DynamicBorder[3].style.background = "#FFF"; + for (var a in arg.to) { + arg.to[a].style.border = "1px solid" + " " + arg.BorderColor; + arg.to[a].style.background = arg.BorderColor; + opacityFun(arg.to[a], 1); + } + arg.form.style.border = arg.Border + "px" + " " + arg.BorderStyle + " " + arg.BorderColor; + } + arg.black.style.background = arg.backgroundColor; + opacityFun(arg.black, arg.backgroundOpacity); + this.preview.run(arg, this); + arg.scope.ondragstart = function() { + return false; + }; + if (navigator.userAgent.indexOf("MSIE 6.0") > 0 && arg.Border === 0) this.ie6 = function(a, b, c) { + a.style.height = arg.DynamicBorder[1].style.height = arg.DynamicBorder[2].style.height = c + "px"; + a.style.width = b + "px"; + }; else this.ie6 = function() {}; + if (this.preview.EffTrue = arg.HTML5Effects && this.HTML5.canvas && arg.Effects) { + var EffectsA = arg.Effects.getElementsByTagName("a"), EffectsAi, EffectsAiclick = this.HTML5.Effects; + this.HTML5.artwork = this.HTML5.Aclick = EffectsA[0]; + for (var i = 0; i < EffectsA.length; i++) { + EffectsAi = EffectsA[i]; + EffectsAi.onclick = EffectsAiclick.call(this, EffectsAi.getAttribute("StrEvent"), this.HTML5); + } + } + this.pointhandle(3e3, 10, "请选择本地照片、相册,进行截取头像", 2, "#307ff6", "#fff"); + }, + zoom:function() { + var this_ = this, zoom = new window.ShearPhoto.MoveDiv(); + zoom.reckon(this_.arg.ZoomDist, false); + zoom.selectionempty = this_.selectionempty; + zoom.addevent = this_.addevent; + zoom.HTML5 = this.HTML5; + var Draggable = this_.arg.ZoomBar, MH, MW, thePro; + function SetMHW(PRO) { + if (PRO) { + MH = this_.Min; + MW = Math.round(MH * PRO); + if (MW < this_.Min) { + MW = this_.Min; + MH = Math.round(MW / PRO); + } + } else { + MW = MH = this_.Min; + } + } + SetMHW(this_.arg.proportional[0]); + zoom.run({ + to:[ Draggable ], + form:Draggable, + MoveWidth:zoom.ReckonWH.W, + MoveHeight:zoom.ReckonWH.H, + shifting:new Array(), + center:1, + zIndex:100, + DivDownFun:function() { + this_.saveL = this_.formLeft + this_.relatL; + this_.saveT = this_.formTop + this_.relatT; + }, + cursor:"pointer", + MoveFun:function(L) { + var schedule; + if (L < bisect) schedule = Math.round(Zoomout * L + 10) / 100; else schedule = Math.round(L * magnify - 100) / 100; + var W = Math.round(this_.artworkW * schedule), H = Math.round(this_.artworkH * schedule); + W < MW && (W = MW, H = Math.round(W / this_.ImgScales)); + H < MH && (H = MH, W = Math.round(H * this_.ImgScales)); + var IMGWH = this_.ImgRotateFun(W, H); + this_.ImgWidth = IMGWH[0]; + this_.ImgHeight = IMGWH[1]; + this_.arg.ImgMain.style.width = this_.arg.ImgDom.style.width = W + "px"; + this_.arg.ImgMain.style.height = this_.arg.ImgDom.style.height = H + "px"; + this_.preview.WH = [ W, H ]; + this_.setinitial(this_.arg, true); + } + }); + this_.zoomEve = function() { + zoom.delDownEve(); + }; + var zoomMAx = zoom.ReckonWH.W - zoom.DivW, bisect = zoomMAx * .5, magnify = 200 / bisect, Zoomout = 90 / bisect; + }, + PointerShape:function(Shape) { + this.arg.scope.style.cursor = this.arg.form.style.cursor = Shape; + }, + DelPointerShape:function() { + this.arg.scope.style.cursor = ""; + this.arg.form.style.cursor = "move"; + }, + ShearPhotoDown:function(obj, fun) { + this.addEvent(obj, "mousedown", fun); + this.addEvent(obj, "touchstart", fun); + }, + delShearPhotoDown:function(obj, fun) { + this.delEvent(obj, "mousedown", fun); + this.delEvent(obj, "touchstart", fun); + }, + et:function() { + for (var a in this.arg.to) { + if (this.addevent === "add") { + if (typeof this.DivDownEVe[a] !== "function") { + this.DivDownEVe[a] = this.DivDown(a); + } else { + this.delShearPhotoDown(this.arg.to[a], this.DivDownEVe[a]); + } + this.ShearPhotoDown(this.arg.to[a], this.DivDownEVe[a]); + } else { + this.arg.to[a].onmousedown = this.DivDown(a); + } + } + }, + addEvent:function(obj, evetype, fun) { + var addevent = { + add:function() { + obj.addEventListener(evetype, fun, false); + }, + att:function() { + obj.attachEvent("on" + evetype, fun); + } + }; + addevent[this.addevent] && addevent[this.addevent](); + }, + delEvent:function(obj, evetype, fun) { + var delevent = { + add:function() { + obj.removeEventListener(evetype, fun, false); + }, + att:function() { + obj.detachEvent("on" + evetype, fun); + } + }; + delevent[this.addevent] && delevent[this.addevent](); + }, + DomUp:function(dom) { + var this_ = this; + return function() { + typeof this_.arg.UpFun === "function" && this_.arg.UpFun(); + dom.releaseCapture && dom.releaseCapture(); + this_.DelPointerShape(); + typeof this_.DomMoveEve === "function" && this_.delEvent(document, this_.eveMold[1], this_.DomMoveEve); + if (typeof this_.DomUpEve === "function") { + this_.delEvent(document, this_.eveMold[2], this_.DomUpEve); + this_.delEvent(window, this_.eveMold[2], this_.DomUpEve); + this_.delEvent(window, "blur", this_.DomUpEve); + this_.delEvent(dom, "losecapture", this_.DomUpEve); + } + return false; + }; + }, + setWHfalse:function(argform, iW, iH, proportional, MaxW, MaxH) { + iW > MaxW && (iW = MaxW); + iH > MaxH && (iH = MaxH); + iW < this.Min && (iW = this.Min); + iH < this.Min && (iH = this.Min); + return [ iW, iH ]; + }, + setWfalse:function(argform, iW, iH, proportional, MaxW, MaxH) { + iW > MaxW && (iW = MaxW); + iW < this.Min && (iW = this.Min); + return [ iW, this.formAllH ]; + }, + setHfalse:function(argform, iW, iH, proportional, MaxW, MaxH) { + iH > MaxH && (iH = MaxH); + iH < this.Min && (iH = this.Min); + return [ this.formAllW, iH ]; + }, + CycleCalculation:function(iW, iH, proportional, MaxW, MaxH) { + if (iH > MaxH) { + iH = MaxH; + iW = Math.round(iH * proportional); + return this.CycleCalculation.apply(this, arguments); + } + if (iW > MaxW) { + iW = MaxW; + iH = Math.round(iW / proportional); + return this.CycleCalculation.apply(this, arguments); + } + if (iH < this.Min) { + iH = this.Min; + iW = Math.round(iH * proportional); + return this.CycleCalculation.apply(this, arguments); + } + if (iW < this.Min) { + iW = this.Min; + iH = Math.round(iW / proportional); + return this.CycleCalculation.apply(this, arguments); + } + return [ iW, iH ]; + }, + setHtrue:function(argform, iW, iH, proportional, MaxW, MaxH) { + iW = Math.round(iH * proportional); + return this.CycleCalculation(iW, iH, proportional, MaxW, MaxH); + }, + setWtrue:function(argform, iW, iH, proportional, MaxW, MaxH) { + iH = Math.round(iW / proportional); + return this.CycleCalculation(iW, iH, proportional, MaxW, MaxH); + }, + amend:function(iW, iH, formParent, strLL, strTT) { + var L = iW - this.formAllW, T = iH - this.formAllH, Left, Top, ImgLeft, ImgTop, this_ = this, HTML5 = this.HTML5, fun = { + LL:function() { + Left = Math.round(this_.formLeft - L); + this_.formLeft = Left; + HTML5.setL(formParent, Left + "px"); + }, + TT:function() { + Top = Math.round(this_.formTop - T); + this_.formTop = Top; + HTML5.setT(formParent, Top + "px"); + }, + ML:function() { + L *= .5; + Left = this_.formLeft - L; + this_.formLeft = Left; + HTML5.setL(formParent, Left + "px"); + }, + MT:function() { + T *= .5; + Top = this_.formTop - T; + this_.formTop = Top; + HTML5.setT(formParent, Top + "px"); + }, + NO:function() {} + }; + fun[strLL](), fun[strTT](); + }, + DomMove:function(this_, dom, disX, disY, PNW, PNH, formParent, MaxW, MaxH, strLL, strTT) { + var eveclientX, eveclientY, drawWH, iW, iH, argform, iHH, iWW, ImgMain = this_.arg.ImgMain, ImgDom = this_.arg.ImgDom; + if (typeof this_.DomUpEve === "function") { + this_.delEvent(document, this_.eveMold[2], this_.DomUpEve); + this_.delEvent(window, this_.eveMold[2], this_.DomUpEve); + this_.delEvent(window, "blur", this_.DomUpEve); + this_.delEvent(dom, "losecapture", this_.DomUpEve); + } + this_.DomUpEve = this_.DomUp(dom); + this_.addEvent(document, this_.eveMold[2], this_.DomUpEve); + this_.addEvent(window, this_.eveMold[2], this_.DomUpEve); + this_.addEvent(window, "blur", this_.DomUpEve); + this_.addEvent(dom, "losecapture", this_.DomUpEve); + return function(eve) { + eve = eve || window.event; + if (eve.button > 1) { + this_.DomUp(this)(); + return false; + } + eveclientX = this_.eveMold[3](eve, "clientX"), eveclientY = this_.eveMold[3](eve, "clientY"), + argform = this_.arg.form; + setTimeout(function() { + iW = PNW * (eveclientX - disX); + iH = PNH * (eveclientY - disY); + this_.selectionempty(); + drawWH = this_.drawfun(argform, iW, iH, this_.arg.proportional[0], MaxW, MaxH); + iW = drawWH[0]; + iH = drawWH[1]; + this_.amend(iW, iH, formParent, strLL, strTT); + this_.formAllW = iW; + this_.formAllH = iH; + iW = this_.formW = iW - this_.Mdouble; + iH = this_.formH = iH - this_.Mdouble; + argform.style.width = iW + "px", argform.style.height = iH + "px"; + this_.ie6(formParent, iW, iH); + this_.MovePhoto(true, 0, 0); + }, 1); + return false; + }; + }, + defaultShear:function() { + this.arg.Shearbar.style.display = "none"; + typeof this.MoveDivEve === "function" && this.MoveDivEve(); + typeof this.zoomEve === "function" && this.zoomEve(); + if ("cssText" in this.arg.ImgMain.style) this.arg.ImgMain.style.cssText = this.arg.ImgDom.style.cssText = ""; else { + this.arg.ImgMain.setAttribute("style", ""); + this.arg.ImgDom.setAttribute("style", ""); + } + this.arg = this.ImgUrl = this.formW = this.formH = this.formAllW = this.formAllH = this.drawfun = this.formParent = this.ImgWidth = this.ImgHeight = this.artworkW = this.artworkH = this.BoxW = this.BoxH = this.Border = this.Mdouble = this.ImgScales = this.Min = this.formLeft = this.formTop = this.relatL = this.relatT = this.relatW = this.relatH = this.saveL = this.ImgOWidth = this.ImgOHeight = this.saveT = this.HTML5.lock = this.HTML5.PhotoHTML5True = false; + this.rotate = this.ImgMainT = this.ImgDomT = this.ImgMainL = this.ImgDomL = 0; + this.preview.isW = []; + this.preview.isH = []; + this.ImgRotateFun = function(W, H) { + return [ W, H ]; + }; + }, + SendUserMsg:function(msg, n, p, b, c, i, k, f) { + this.arg.black.style.zIndex = i ? 199 :99; + this.pointhandle(n, 5, msg, p, b, c, f); + k ? this.arg.Shearbar.style.display = "none" :this.arg.Shearbar.style.display = "block"; + }, + again:function() { + this.arg.SelectBox.style.visibility = "visible"; + this.arg.Shearbar.style.display = "none"; + this.runImgUrl = false; + this.arg.ImgDom.src = this.arg.ImgMain.src = this.arg.relativeUrl + "images/default.gif"; + }, + CoordinateData:function(True) { + var SendPHPSmaller = function(W, H, P) { + if (W < 1) { + W = 1; + H = Math.round(1 / P); + } + if (H < 1) { + H = 1; + W = Math.round(P); + } + return [ W, H ]; + }, SendArry = {}; + True || (SendArry.url = "../" + this.ImgUrl); + var R = { + 1:270, + 2:180, + 3:90, + "90":270, + "180":180, + "270":90 + }[this.rotate] || (R = this.rotate), LT = this.ImgWidth, TL = this.ImgHeight, XYWH = { + 0:LT, + 90:TL, + 180:LT, + 270:TL + }, XYWHP = this.ImgOWidth / XYWH[R]; + SendArry.R = R; + SendArry.X = Math.round((Math.abs(this.ImgDomL) - this.Border) * XYWHP); + SendArry.Y = Math.round((Math.abs(this.ImgDomT) - this.Border) * XYWHP); + SendArry.P = this.arg.proportional[0]; + var P = this.formAllW / this.formAllH, Smaller = SendPHPSmaller(Math.round(this.formAllW * XYWHP), Math.round(this.formAllH * XYWHP), P); + SendArry.IW = Smaller[0]; + SendArry.IH = Smaller[1]; + Smaller = SendPHPSmaller(this.formAllW, this.formAllH, P); + SendArry.FW = Smaller[0]; + SendArry.FH = Smaller[1]; + return SendArry; + }, + SendPHP:function(postArgs) { + var this_ = this, SendArry, HTML5 = this.HTML5, ResultData; + this.SendUserMsg("正在为你处理截图,稍等...", 0, 2, "#fbeb61", "#3a414c", true, true, function() { + if ((HTML5.HTML5PHP || HTML5.PhotoHTML5True) && HTML5.canvas) { + try { + HTML5.BOLBID && HTML5.URL.revokeObjectURL(HTML5.BOLBID); + SendArry = this_.CoordinateData(true); + ResultData = HTML5.CanvasImg(SendArry, postArgs, this_); + } catch (e) { + this_.SendUserMsg("错误:切割图片时严重报错,请更换浏览器试试,或者换张图片", 5e3, 0, "#f4102b", "#fff", false); + return; + } + } else { + var POSTHTML = ""; + if (Object.prototype.toString.call(postArgs) === "[object Object]") { + for (var key in postArgs) { + POSTHTML += "&" + key + "=" + postArgs[key]; + } + } + SendArry = this_.CoordinateData(); + ResultData = "JSdate=" + window.ShearPhoto.JsonString.JsonToString(SendArry) + POSTHTML; + } + this_.MyAjax.carry({ + url:this_.arg.url, + data:ResultData, + type:"POST", + timeout:9e4, + async:true, + lock:true, + complete:false, + success:function(serverdata) { + serverdata = window.ShearPhoto.JsonString.StringToJson(serverdata); + if (serverdata === false) { + this_.SendUserMsg("错误:请保证后端环境运行正常", 5e3, 0, "#f4102b", "#fff", false); + return; + } + if (serverdata["erro"]) { + this_.SendUserMsg("错误:" + serverdata["erro"], 5e3, 0, "#f4102b", "#fff", false); + return; + } + this_.runImgUrl = false; + typeof this_.complete === "function" && this_.complete(serverdata); + delete this_.HTML5.Images; + }, + error:function(ErroMsg) { + this_.SendUserMsg("错误:连接后端失败,可能原因,超时!或者后端环境无法运行", 5e3, 0, "#f4102b", "#fff", false); + }, + cache:false + }); + }); + }, + DivDown:function(a) { + var this_ = this, PNW = 1, PNH = 1, strLL = "NO", strTT = "NO", MaxW, MaxH, W, H, formParentoffsetLeft, formParent, formParentoffsetTop; + return function(event) { + var event = event || window.event, eventbutton = event.button, typebutton = typeof eventbutton, clientX, clientY; + event.preventDefault && event.preventDefault(); + if (typebutton !== "number") { + this_.eveMold = [ "touchstart", "touchmove", "touchend", function(events, clientXY) { + return events.touches[0][clientXY]; + } ]; + clientX = event.touches[0].clientX; + clientY = event.touches[0].clientY; + } else { + this_.eveMold = [ "mousedown", "mousemove", "mouseup", function(events, clientXY) { + return events[clientXY]; + } ]; + clientX = event.clientX; + clientY = event.clientY; + } + if (eventbutton < 2 || typebutton !== "number") { + W = this_.formAllW, H = this_.formAllH, formParent = this_.formParent, formParentoffsetLeft = this_.formLeft, + formParentoffsetTop = this_.formTop; + switch (a) { + case "BottomRight": + MaxW = this_.relatW - formParentoffsetLeft, MaxH = this_.relatH - formParentoffsetTop; + this_.arg.proportional[0] ? this_.drawfun = this_.setHtrue :this_.drawfun = this_.setWHfalse; + this_.PointerShape("nw-resize"); + break; + + case "TopRight": + PNH = -1; + strTT = "TT"; + MaxW = this_.relatW - formParentoffsetLeft, MaxH = formParentoffsetTop + this_.formAllH; + this_.arg.proportional[0] ? this_.drawfun = this_.setHtrue :this_.drawfun = this_.setWHfalse; + this_.PointerShape("ne-resize"); + break; + + case "Bottomleft": + PNW = -1; + strLL = "LL"; + MaxW = formParentoffsetLeft + this_.formAllW; + MaxH = this_.relatH - formParentoffsetTop; + this_.arg.proportional[0] ? this_.drawfun = this_.setHtrue :this_.drawfun = this_.setWHfalse; + this_.PointerShape("ne-resize"); + break; + + case "Topleft": + PNH = PNW = -1; + strLL = "LL"; + strTT = "TT"; + MaxW = formParentoffsetLeft + this_.formAllW; + MaxH = formParentoffsetTop + this_.formAllH; + this_.arg.proportional[0] ? this_.drawfun = this_.setHtrue :this_.drawfun = this_.setWHfalse; + this_.PointerShape("nw-resize"); + break; + + case "Topmiddle": + strLL = "ML"; + var MaxWA = formParentoffsetLeft, MaxWB = this_.relatW - MaxWA - this_.formAllW; + MaxW = Math.min(MaxWA, MaxWB) * 2 + this_.formAllW; + strTT = "TT"; + MaxH = formParentoffsetTop + this_.formAllH; + PNH = -1; + this_.arg.proportional[0] ? this_.drawfun = this_.setHtrue :this_.drawfun = this_.setHfalse; + this_.PointerShape("n-resize"); + break; + + case "leftmiddle": + PNH = PNW = -1; + MaxW = formParentoffsetLeft + this_.formAllW; + var MaxHA = formParentoffsetTop, MaxHB = this_.relatH - MaxHA - this_.formAllH; + MaxH = Math.min(MaxHA, MaxHB) * 2 + this_.formAllH; + strTT = "MT"; + strLL = "LL"; + this_.arg.proportional[0] ? this_.drawfun = this_.setWtrue :this_.drawfun = this_.setWfalse; + this_.PointerShape("e-resize"); + break; + + case "Rightmiddle": + MaxW = this_.relatW - formParentoffsetLeft; + var MaxHA = formParentoffsetTop, MaxHB = this_.relatH - MaxHA - this_.formAllH; + MaxH = Math.min(MaxHA, MaxHB) * 2 + this_.formAllH; + strTT = "MT"; + this_.arg.proportional[0] ? this_.drawfun = this_.setWtrue :this_.drawfun = this_.setWfalse; + this_.PointerShape("e-resize"); + break; + + case "Bottommiddle": + var MaxWA = formParentoffsetLeft, MaxWB = this_.relatW - MaxWA - this_.formAllW; + MaxW = Math.min(MaxWA, MaxWB) * 2 + this_.formAllW; + MaxH = this_.relatH - formParentoffsetTop; + this_.arg.proportional[0] ? this_.drawfun = this_.setHtrue :this_.drawfun = this_.setHfalse; + strLL = "ML"; + this_.PointerShape("n-resize"); + break; + + default: + break; + } + var disX = clientX - PNW * W, disY = clientY - PNH * H; + this.setCapture && this.setCapture(); + typeof this_.DomMoveEve === "function" && this_.delEvent(document, this_.eveMold[1], this_.DomMoveEve); + this_.DomMoveEve = this_.DomMove(this_, this, disX, disY, PNW, PNH, formParent, MaxW, MaxH, strLL, strTT); + this_.addEvent(document, this_.eveMold[1], this_.DomMoveEve); + } else { + this_.DomUp(this)(); + } + return false; + }; + } +}; + +window.ShearPhoto.MINGGE = function(a) { + function b() { + try { + var c = function() { + "complete" === document.readyState && (document.detachEvent("onreadystatechange", c), + a()); + }, d = window.frameElement; + } catch (e) { + return document.attachEvent("onreadystatechange", c), void 0; + } + if (null != d) return document.attachEvent("onreadystatechange", c), void 0; + try { + document.documentElement.doScroll("left"); + } catch (c) { + return setTimeout(b, 13), void 0; + } + a(); + } + var c; + "function" == typeof a && (document.addEventListener ? (c = function() { + document.removeEventListener("DOMContentLoaded", c, !1), a(); + }, document.addEventListener("DOMContentLoaded", c, !1)) :b()); +}; +/*--------------------------拉伸,截图.HTML5的压缩,剪图的处理核心部份结束-----------------------------------------------------------------------------*/ + + +/*--------------------------拖拽移动处理开始-----------------------------------------------------------------------------*/ + +window.ShearPhoto.MoveDiv = function() { + this.arg = new Array(), this.ReckonWH = this.DivW = this.DivH = this.selectionempty = this.addevent = this.DivDownEVe = this.DomMoveEve = this.DomUpEve = this.eveMold = false; +}; + +window.ShearPhoto.MoveDiv.prototype = { + ZeroSetting:function() { + var getLT = this.HTML5.getLT(this.arg.form), + left = parseFloat(getLT[0]), top = parseFloat(getLT[1]), + size = this._size_(window, true), + leftfun = function() {}, topfun = function() {}, tf = false; + if (!isNaN(left)) { + tf = true; + this.HTML5.setL(this.arg.form, 0); + leftfun = function(a, b) { + a < 0 && (a = 0); + this_.HTML5.setL(b, left - a + "px"); + }; + } + if (!isNaN(top)) { + tf = true; + this.HTML5.setT(this.arg.form, 0); + topfun = function(a, b) { + a < 0 && (a = 0); + this_.HTML5.setT(b, top - a + "px"); + }; + } + if (tf === true) { + var size2 = this._size_(window, true); + leftfun(size.W - size2.W, this.arg.form); + topfun(size.H - size2.H, this.arg.form); + } + }, + reckon:function(obj, se) { + this._size_(obj); + var this_ = this; + if (se === true) { + var addfun = function() { + this_.ZeroSetting(); + this_._size_(obj); + this_.arg.MoveWidth = this_.ReckonWH.W; + this_.arg.MoveHeight = this_.ReckonWH.H; + this_.SetCenter(this_.arg); + }; + this.addEvent(window, "resize", addfun); + } + }, + _size_:function(obj, t) { + var w, h, ReckonWH; + if (obj === window) { + var DE = { + add:document.documentElement, + att:document.body + }[this.addevent]; + w = DE.clientWidth; + h = DE.clientHeight; + ReckonWH = { + W:Math.max(DE.scrollWidth, w), + H:Math.max(DE.scrollHeight, h), + CW:w, + CH:h + }; + } else { + w = obj.offsetWidth; + h = obj.offsetHeight; + ReckonWH = { + W:w, + H:h, + CW:w, + CH:h + }; + } + if (t === true) return ReckonWH; + this.ReckonWH = ReckonWH; + }, + DomUp:function(dom) { + var this_ = this; + return function() { + dom.releaseCapture && dom.releaseCapture(); + typeof this_.DomMoveEve === "function" && this_.delEvent(document, this_.eveMold[1], this_.DomMoveEve); + if (typeof this_.DomUpEve === "function") { + this_.delEvent(document, this_.eveMold[2], this_.DomUpEve); + this_.delEvent(window, this_.eveMold[2], this_.DomUpEve); + this_.delEvent(window, "blur", this_.DomUpEve); + this_.delEvent(dom, "losecapture", this_.DomUpEve); + } + return false; + }; + }, + DivWHFun:function() { + this.DivW = this.arg.form.offsetWidth; + this.DivH = this.arg.form.offsetHeight; + }, + DomMove:function(this_, dom, disX, disY, formLeft, formTop) { + var argform = this_.arg.form, DivW = this_.DivW, DivH = this_.DivH, MoveScale, MoveFun = function() {}, + shifting = this_.arg.shifting = Object.prototype.toString.call(this_.arg.shifting) === "[object Array]" && this_.arg.shifting.length > 1 ? this_.arg.shifting :new Array(0, 0), + argMoveWidth = this_.arg.MoveWidth - shifting[0], + argMoveHeight = this_.arg.MoveHeight - shifting[1]; + if (typeof this_.DomUpEve === "function") { + this_.delEvent(document, this_.eveMold[2], this_.DomUpEve); + this_.delEvent(window, this_.eveMold[2], this_.DomUpEve); + this_.delEvent(window, "blur", this_.DomUpEve); + this_.delEvent(dom, "losecapture", this_.DomUpEve); + } + this_.DomUpEve = this_.DomUp(dom); + this_.addEvent(document, this_.eveMold[2], this_.DomUpEve); + this_.addEvent(window, this_.eveMold[2], this_.DomUpEve); + this_.addEvent(window, "blur", this_.DomUpEve); + this_.addEvent(dom, "losecapture", this_.DomUpEve); + var maxL = argMoveWidth - DivW, maxT = argMoveHeight - DivH, iL, iT, eveclientX, eveclientY; + typeof this_.arg.MoveFun === "function" && (MoveFun = this_.arg.MoveFun); + MoveScale = [ maxL, maxT ]; + return function(eve) { + eve = eve || window.event; + if (eve.button > 1) { + this_.DomUp(this)(); + return false; + } + eveclientX = this_.eveMold[3](eve, "clientX"), eveclientY = this_.eveMold[3](eve, "clientY"); + setTimeout(function() { + iL = eveclientX - disX; + iT = eveclientY - disY; + this_.selectionempty(); + iL = iL < -shifting[0] ? -shifting[0] :iL; + iL = iL > maxL ? maxL :iL; + iT = iT < -shifting[1] ? -shifting[1] :iT; + iT = iT > maxT ? maxT :iT; + this_.HTML5.setLT(argform, iL + "px", iT + "px"); + MoveFun(iL, iT, MoveScale); + }, 1); + return false; + }; + }, + DivDown:function() { + var this_ = this; + return function(event) { + var event = event || window.event, eventbutton = event.button, typebutton = typeof eventbutton, clientX, clientY; + event.preventDefault && event.preventDefault(); + if (typebutton !== "number") { + this_.eveMold = [ "touchstart", "touchmove", "touchend", function(events, clientXY) { + return events.touches[0][clientXY]; + } ]; + clientX = event.touches[0].clientX; + clientY = event.touches[0].clientY; + } else { + this_.eveMold = [ "mousedown", "mousemove", "mouseup", function(events, clientXY) { + return events[clientXY]; + } ]; + clientX = event.clientX; + clientY = event.clientY; + } + if (eventbutton < 2 || typebutton !== "number") { + var getLT = this_.HTML5.getLT(this_.arg.form), formLeft = parseFloat(getLT[0]), formTop = parseFloat(getLT[1]), disX = clientX - formLeft, disY = clientY - formTop; + this.setCapture && this.setCapture(); + typeof this_.arg.DivDownFun === "function" && this_.arg.DivDownFun(this_); + typeof this_.DomMoveEve === "function" && this_.delEvent(document, this_.eveMold[1], this_.DomMoveEve); + this_.DomMoveEve = this_.DomMove(this_, this, disX, disY, formLeft, formTop); + this_.addEvent(document, this_.eveMold[1], this_.DomMoveEve); + } else { + this_.DomUp(this)(); + } + return false; + }; + }, + ShearPhotoDown:function(obj, fun) { + this.addEvent(obj, "mousedown", fun); + this.addEvent(obj, "touchstart", fun); + }, + delShearPhotoDown:function(obj, fun) { + this.delEvent(obj, "mousedown", fun); + this.delEvent(obj, "touchstart", fun); + }, + et:function() { + var this_ = this; + var cursor = this.arg.cursor || "move"; + this_ = this; + for (var i = 0; i < this.arg.to.length; i++) { + if (this.addevent === "add") { + if (typeof this.DivDownEVe !== "function") { + this.DivDownEVe = this.DivDown(); + } else { + this.delShearPhotoDown(this.arg.to[i], this.DivDownEVe); + } + this.ShearPhotoDown(this.arg.to[i], this.DivDownEVe); + } else { + this.arg.to[i].onmousedown = this.DivDown(); + } + this.arg.to[i].style["cursor"] = cursor; + } + }, + delDownEve:function() { + for (var i = 0; i < this.arg.to.length; i++) { + if (this.addevent === "add") { + if (typeof this.DivDownEVe === "function") { + this.delShearPhotoDown(this.arg.to[i], this.DivDownEVe); + } + } + } + }, + setdiv:function(argform, CW, CH, arg) { + if (typeof arg.centerFront === "function") { + var CWCH = arg.centerFront(); + CW = CWCH[0]; + CH = CWCH[1]; + } + var DivT = Math.round((CH - this.DivH) *.5); + DivT = DivT < 0 ? 0 :DivT; + var DivL = Math.round((CW - this.DivW) *.5); + DivL = DivL < 0 ? 0 :DivL; + this.HTML5.setLT(argform, DivL + "px", DivT + "px"); + typeof arg.centerfun === "function" && arg.centerfun(DivL, DivT, this); + }, + addEvent:function(obj, evetype, fun) { + var addevent = { + add:function() { + obj.addEventListener(evetype, fun, false); + }, + att:function() { + obj.attachEvent("on" + evetype, fun); + } + }; + addevent[this.addevent] && addevent[this.addevent](); + }, + delEvent:function(obj, evetype, fun) { + var delevent = { + add:function() { + obj.removeEventListener(evetype, fun, false); + }, + att:function() { + obj.detachEvent("on" + evetype, fun); + } + }; + delevent[this.addevent] && delevent[this.addevent](); + }, + SetCenter:function(arg) { + if (arg.center) { + if (arg.center === 1) { + var CW = this.ReckonWH.CW, CH = this.ReckonWH.CH; + } else { + var ReckonWH = this._size_(arg.center, true); + var CW = ReckonWH.CW, CH = ReckonWH.CH; + } + this.setdiv(arg.form, CW, CH, arg); + } + }, + run:function(arg) { + this.arg = arg; + this.DivW = arg.form.offsetWidth; + this.DivH = arg.form.offsetHeight; + this.SetCenter(arg); + typeof arg.zIndex === "number" && (arg.form.style.zIndex = arg.zIndex); + this.et(); + } +}; + +/*--------------------------拖拽移动处理结束-----------------------------------------*/ + +/*--------------------------与服务器交互数据JS部份开始------------------------------*/ + +window.ShearPhoto.JsonString = { + _json_:null, + JsonToString:function(arr) { + try { + this._json_ = new Array(); + this._read_(arr, true); + var JsonJoin = this._json_.join(""); + JsonJoin = JsonJoin.replace(/,([\}\]])/g, function(a, b) { + return b; + }); + this._json_ = null; + return JsonJoin; + } catch (e) { + alert("发生错误,错误代码--" + e); + return ""; + } + }, + StringToJson:function(arrtxt) { + if (typeof arrtxt !== "string") { + alert("错误,请传入JSON字串符"); + return; + } + try { + var array = new Function("return " + "(" + arrtxt + ")")(); + var type = this._type_(array); + if (type !== "[object Object]" && type !== "[object Array]") { + alert("严重报错:后端没返回到JSON,而是一串无效字符串。\n\n你是在调试吗?\n\n那么按确定,查看那串无效字符串吧"); + alert(arrtxt); + return false; + } + return array; + } catch (e) { + alert("严重报错:后端没返回到JSON,而是一串无效字符串。\n\n你是在调试吗?\n\n那么按确定,查看那串无效字符串吧"); + alert(arrtxt); + return false; + } + }, + _type_:function(arr) { + if (typeof arr.nodeType === "number") return "[object document]"; + var Types = Object.prototype.toString.call(arr); + return Types; + }, + _addjson_:function(types, txt, txt2, TrueFalse) { + var run = { + "[object Object]":txt, + "[object Array]":txt2 + }; + this._json_.push(run[types]); + }, + _addstring_:function(parameter) { + var of = typeof parameter; + if (of === "string") return '"' + parameter + '"'; else if (of === "number") return parameter; + var types = this._type_(parameter); + if (types === "[object Object]" || types === "[object Array]") { + return false; + } else return '""'; + }, + _read_:function(arr, TrueFalse) { + var types = this._type_(arr); + if (TrueFalse && types !== "[object Object]" && types !== "[object Array]") { + alert("你传入不是数组或JSON"); + this._json_ = null; + return false; + } + this._addjson_(types, "{", "[", TrueFalse); + for (var a in arr) { + if (typeof arr.constructor.prototype[a] === "undefined") { + var ArrA = this._addstring_(arr[a]); + if (typeof ArrA !== "boolean") { + this._addjson_(types, '"' + a + '":' + ArrA + ",", ArrA + ","); + } else { + this._addjson_(types, '"' + a + '":', ""); + this._read_(arr[a], false); + } + } + } + TrueFalse = TrueFalse ? "" :","; + this._addjson_(types, "}" + TrueFalse, "]" + TrueFalse); + + } +}; + +window.ShearPhoto.MyAjax = function() { + this.serverdata = this.erromsg = this.timeout = this.stop = this.xmlhttp = false; + this.transit = true; +}; + +window.ShearPhoto.MyAjax.prototype.handle = function(xmlhttp, arg) { + if (4 == xmlhttp.readyState) { + if (this.stop === true) return; + this.transit = true; + if (arg.timeout && arg.async) { + clearTimeout(this.timeout); + this.timeout = false; + } + if (200 == xmlhttp.status) { + var responseText = this.serverdata = xmlhttp.responseText.replace(/(^\s*)|(\s*$)/g, ""); + typeof arg.success === "function" && arg.success(responseText); + } else { + this.erromsg = xmlhttp.status; + typeof arg.error === "function" && arg.error(xmlhttp.status); + } + } else { + if (0 == xmlhttp.readyState) { + if (this.stop === true) return; + if (arg.timeout && arg.async) { + clearTimeout(this.timeout); + this.timeout = false; + } + this.erromsg = xmlhttp.readyState; + this.transit = true; + typeof arg.error === "function" && arg.error(xmlhttp.readyState); + } + } +}; + +window.ShearPhoto.MyAjax.prototype.out = function(arg) { + this.transit = true; + this.erromsg = 504; + this.stop = true; + typeof arg.error === "function" && arg.error(504); +}; + +window.ShearPhoto.MyAjax.prototype.carry = function(arg) { + if (arg.lock && !this.transit) return false; + this.transit = false; + this.stop = this.erromsg = false; + var xmlhttp; + if (window.XMLHttpRequest) { + xmlhttp = new XMLHttpRequest(); + } else { + xmlhttp = new ActiveXObject("Microsoft.XMLHTTP"); + } + arg.type = arg.type.toUpperCase(); + var ContentType = function() {}; + if (typeof arg.data === "string") { + arg.data = arg.data.replace(/(^\s*)|(\s*$)/g, ""); + ContentType = function() { + xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + }; + } else { + if (Object.prototype.toString.call(arg.data) !== "[object FormData]") { + arg.data = ""; + ContentType = function() { + xmlhttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); + }; + } else { + typeof arg.progress === "function" && xmlhttp.upload.addEventListener("progress", arg.progress, false); + arg.type = "POST"; + } + } + var SendArg = arg.data == "" ? [ null, "" ] :[ arg.data, "?" + arg.data ]; + var self = this; + typeof arg.complete === "function" && arg.complete(); + arg.timeout && arg.async && (this.timeout = setTimeout(function() { + self.out(arg); + }, arg.timeout)); + if (arg.async === true) { + xmlhttp.onreadystatechange = function() { + self.handle(xmlhttp, arg); + }; + } + try { + switch (arg.type) { + case "POST": + xmlhttp.open("POST", arg.url, arg.async); + ContentType(); + break; + + default: + xmlhttp.open("GET", arg.url + SendArg[1], arg.async); + arg.cache === true || xmlhttp.setRequestHeader("If-Modified-Since", "0"); + break; + } + } catch (e2) { + this.erromsg = 505; + if (arg.timeout && arg.async) { + clearTimeout(this.timeout); + this.timeout = false; + } + this.transit = true; + typeof arg.error === "function" && arg.error(505); + return; + } + xmlhttp.send(SendArg[0]); + if (arg.async === false) { + self.handle(xmlhttp, arg); + } +}; + +/*--------------------------与服务器交互数据JS部份结束----------------------------------*/ + + + + + +/*--------------------------选择上传截图的JS部份开始-------------------------------------*/ +window.ShearPhoto.frameUpImg = function(config) { + this.BodyDom = document.body; + this.FORM = config.FORM; + this.upfile = this.FORM.UpFile; + this.config = config; + this.upfileclick = false; + typeof config.erro !== "function" && (config.erro = function() {}); + this.FORM.action = config["url"]; + var this_ = this; + this.parentNodes = this.upfile.parentNode; + if (window.attachEvent) { + this.parentNodes.onmousemove = function() { + var offsetX = window.event.offsetX + 5; + offsetX < 0 && (offsetX = 0); + offsetX > 224 && (offsetX = 224); + this_.upfile.style.width = offsetX + "px"; + }; + } +}; + +window.ShearPhoto.frameUpImg.prototype = { + createframe:function() { + this.BodyDom.insertAdjacentHTML("afterBegin", ''); + this.iframe = document.getElementsByName("POSTiframe")[0]; + this.createEve(this.iframe, window.frames["POSTiframe"]); + }, + createEve:function(iframe, windowframes) { + var this_ = this; + if (this.upfile.files) { + iframe.onload = function() { + iframe.onload = null; + var windowframesbody = windowframes.document.getElementsByTagName("BODY")[0]; + var html = windowframesbody.innerHTML; + this_.Evebubbling(html); + }; + } else { + iframe.onreadystatechange = function() { + if (iframe.readyState === "complete") { + iframe.onreadystatechange = null; + var windowframesbody = windowframes.document.getElementsByTagName("BODY")[0]; + var html = windowframesbody.innerHTML; + this_.Evebubbling(html); + } + }; + } + }, + delframe:function() { + this.BodyDom.removeChild(this.iframe); + this.DelCreateUpfile(); + }, + Evebubbling:function(html) { + if (html != "") { + this.upfile.onclick = this.upfileclick; + typeof this.fun === "function" && this.fun(html); + this.delframe(); + } + }, + inArray:function(elem, array) { + if (array.indexOf) { + return array.indexOf(elem); + } + for (var i = 0, length = array.length; i < length; i++) { + if (array[i] === elem) { + return i; + } + } + return -1; + }, + DelCreateUpfile:function() { + var change = this.upfile.onchange; + this.upfile.onchange = this.upfile.onclick= null; + this.parentNodes.removeChild(this.upfile); + var inputfile = document.createElement("input"); + inputfile.setAttribute("type", "file"); + inputfile.setAttribute("name", "UpFile"); + this.parentNodes.appendChild(inputfile); + this.upfile = inputfile; + this.upfile.onchange = change; + this.upfile.onclick = this.upfileclick; + }, + Upsubmit:function(upfile) { + try { + var filestype = upfile.value.split("."); + filestype = Object.prototype.toString.call(filestype) === "[object Array]" ? filestype[filestype.length - 1] :""; + if (this.inArray(filestype.toLowerCase(), this.config.UpType) == -1) { + this.DelCreateUpfile(); + this.config.erro("请选择正确图片类型"); + return; + } + var files = upfile.files,type; + if (files) { + files = files[0]; + files.type && (type= files.type==="image/gif" ? "image/jpeg":files.type,this.config.HTML5.ImagesType = type); + if (files.size <= 0) { + this.DelCreateUpfile(); + this.config.erro("错误:不能选择空字节文件"); + return; + } + if (this.config.HTML5.canvas) { + if (files.size > this.config.HTML5FilesSize * 1024 * 1024) { + this.DelCreateUpfile(); + this.config.erro("错误:HTML5上传不能大于" + this.config.HTML5FilesSize + "M"); + return; + } + } else if (files.size > this.config.FilesSize * 1024 * 1024) { + this.DelCreateUpfile(); + this.config.erro("错误:文件不能大于" + this.config.FilesSize + "M"); + return; + } + } + var this_ =this; + typeof this.config.preced === "function" + && this.config.preced(function(){ + if (this_.config.HTML5.canvas) { + var reader = new FileReader(); + reader.onload = function() { + this_.DelCreateUpfile(); + this_.config.HTML5.zipImg(this.result, this_.config.HTML5ZIP, type, function(DataUrl) { + typeof this_.fun === "function" && this_.fun({ + success:DataUrl + }, true); + delete (reader); + }); + }; + reader.readAsDataURL(files); + return; + } + this_.createframe(); + this_.FORM.submit(); + }); + + } catch (e) { + this.DelCreateUpfile(); + this.config.erro("你选择了非图片类型,或 图片路径有误"); + return; + } + }, + run:function(fun) { + var this_ = this; + this.fun = fun; + this.upfile.onclick = this.upfileclick = function() { + typeof this_.config.fileClick === "function" && this_.config.fileClick(); + }; + this.upfile.onchange = function() { + if (this.value == "") return false; + this.onclick = function() { + return false; + }; + this_.Upsubmit(this); + }; + } +}; +/*--------------------------选择上传截图的JS部份结束-----------------------------------*/ \ No newline at end of file diff --git a/public/shearphoto/js/alloyimage.js b/public/shearphoto/js/alloyimage.js new file mode 100644 index 0000000..be7d3f2 --- /dev/null +++ b/public/shearphoto/js/alloyimage.js @@ -0,0 +1,723 @@ +try{ +Array.prototype.del = function(a) { + a.sort(); + for (var b = this.concat([]), c = a.length - 1; c >= 0; c--) b = b.slice(0, a[c]).concat(b.slice(a[c] + 1)); + return b; +}, + + HTMLImageElement.prototype.loadOnce = function(a) { + var b = 0; + this.onload = function() { + b || a.call(this, null), b++; + }; +}, function(a) { + var b = { + lib:[], + init:function() { + this.require("config"); + }, + module:function(a, b) { + this.lib[a] = b.call(null, this); + }, + require:function(a) { + var b = this, c = document.createElement("script"); + document.body.appendChild(c), c.src = "./js/module/" + a + ".js", c.onload = c.onerror = function(a) { + b.handlerror(a); + }; + }, + handlerror:function() {}, + destroySelf:function(b) { + throw delete ShearPhoto[a], Error(b); + }, + reflect:function(a, b, c) { + return a = this.lib.config.getModuleName(a), this.lib[a].process(b, c); + }, + reflectEasy:function(a) { + return a = this.lib.config.getEasyFun(a), this.lib.easy.getFun(a); + }, + add:function(a, b, c, d, e, f, g, h) { + return this.lib.addLayer.add(a, b, c, d, e, f, g, h); + }, + applyMatrix:function() {} + }; + ShearPhoto[a] = function(b, c, d) { + if (!(this instanceof ShearPhoto[a])) return new ShearPhoto[a](b, c, d); + this.startTime = +new Date(); + var e = document.createElement("canvas"), f = e.getContext("2d") , imgWidth , imgHeight; + isNaN(b) ? (e.width = parseInt(b.width), e.height = parseInt(b.height), c = getComputedStyle(b), + imgWidth = parseInt(c.getPropertyValue("width")), imgHeight = parseInt(c.getPropertyValue("height")), + isNaN(imgWidth) ? f.drawImage(b, 0, 0) :f.drawImage(b, 0, 0, imgWidth, imgHeight)) :(e.width = b, + e.height = c, f.fillStyle = d || "#fff", f.fillRect(0, 0, b, c)), this.canvas = e, + this.context = f, this.imgData = f.getImageData(0, 0, e.width, e.height), this.name = a + "_" + Math.random(), + this.canvas.id = this.name, this.layers = [], b = document.createElement("canvas"), + b.width = e.width, b.height = e.height; + }, ShearPhoto[a].module = function(a, c) { + b.module(a, c); + }, ShearPhoto[a].dorsyMath = function() { + return b.lib.dorsyMath; + }, ShearPhoto[a].prototype = { + act:function(a) { + var c = [], c = Array.prototype.slice.call(arguments, 1); + return b.reflect(a, this.imgData, c), this; + }, + view:function(a, b, c, d, e) { + var f = this.clone(); + return f.type = 1, this.addLayer(f, "正常", 0, 0), f.act(a, b, c, d, e), this; + }, + excute:function() { + var a = this.layers, b = a.length; + a[b - 1] && 1 == a[b - 1][0].type && (this.imgData = a[b - 1][0].imgData, delete a[b - 1]); + }, + cancel:function() { + var a = this.layers, b = a.length; + a[b - 1] && 1 == a[b - 1][0].type && delete a[b - 1]; + }, + show:function(b, c) { + var d, e, f, g, h = new ShearPhoto[a](this.canvas.width, this.canvas.height); + for (h.add(this, "正常", 0, 0, c), this.tempPsLib = h, d = 0; d < this.layers.length; d++) e = this.layers[d], + f = e[0].layers, g = e[0], f[f.length - 1] && 1 == f[f.length - 1][0].type && (g = f[f.length - 1][0]), + h.add(g, e[1], e[2], e[3], c); + return this.context.clearRect(0, 0, this.canvas.width, this.canvas.height), this.context.putImageData(h.imgData, 0, 0), + b ? document.querySelector(b).appendChild(this.canvas) :document.body.appendChild(this.canvas), + this; + }, + replace:function(a) { + return a && (a.onload = function() {}, a.src = this.save()), this; + }, + add:function() { + var a, c, d, e, f, g = []; + for (d = 0; d < arguments.length; d++) if (d) switch (typeof arguments[d]) { + case "string": + /\d+%/.test(arguments[d]) ? c = arguments[d].replace("%", "") :/[RGB]+/.test(arguments[d]) ? f = arguments[d] :a = arguments[d]; + break; + + case "number": + g.push(arguments[d]); + break; + + case "boolean": + e = arguments[d]; + } + return d = g[0] || 0, g = g[1] || 0, this.imgData = b.add(this.imgData, arguments[0].imgData, a || "正常", c / 100 || 1, d, g, e || !1, f || "RGB"), + this; + }, + addLayer:function(a, b, c, d) { + return this.layers.push([ a, b, c, d ]), this; + }, + clone:function() { + var b = new ShearPhoto[a](this.canvas.width, this.canvas.height); + return b.context.putImageData(this.imgData, 0, 0), b.imgData = b.context.getImageData(0, 0, this.canvas.width, this.canvas.height), + b; + }, + swap:function(a, b) { + var c = this.layers[a]; + return this.layers[a] = this.layers[b], this.layers[b] = c, this; + }, + deleteLayers:function(a) { + this.layers = this.layers.del(a); + }, + save:function(b, c) { + var d, e, f, g, h; + if (!this.layers.length) return c ? (this.context.putImageData(this.imgData, 0, 0), + this.canvas.toDataURL(c, .9)) :(this.context.putImageData(this.imgData, 0, 0), this.canvas.toDataURL()); + for (d = new ShearPhoto[a](this.canvas.width, this.canvas.height), d.add(this, "正常", 0, 0, b), + this.tempPsLib = d, e = 0; e < this.layers.length; e++) f = this.layers[e], g = f[0].layers, + h = f[0], g[g.length - 1] && 1 == g[g.length - 1][0].type && (h = g[g.length - 1][0]), + d.add(h, f[1], f[2], f[3], b); + return this.context.clearRect(0, 0, this.canvas.width, this.canvas.height), this.context.putImageData(d.imgData, 0, 0), + c ? this.canvas.toDataURL(c, .9) :this.canvas.toDataURL(); + }, + drawRect:function() { + var a, b, c, d, e, f; + for ((a = document.getElementById("imgRect")) || (a = document.createElement("canvas"), + a.id = "imgRect", document.body.appendChild(a), a.width = parseInt(this.canvas.width), + a.height = parseInt(this.canvas.height)), b = a.getContext("2d"), b.clearRect(0, 0, a.width, a.height), + c = [], d = this.tempPsLib.imgData.data, e = 0, f = d.length; f > e; e++) c[d[e]] ? c[d[e]]++ :c[d[e]] = 1; + for (b.beginPath(), b.moveTo(0, a.height), e = d = 0; 255 > e; e++) c[e] > d && (d = c[e]); + for (e = 0; 255 > e; e++) f = c[e] || 0, f = a.height - .8 * (f / d) * a.height, + b.lineTo(e / 256 * a.width, f, 1, 1); + b.lineTo(a.width + 10, a.height), b.fill(); + }, + ps:function(a) { + var c; + return c = b.reflectEasy(a).call(this), this.logTime("组合效果" + a), c; + }, + logTime:function(a) { + console.log(a + ": " + (+new Date() - this.startTime) / 1e3 + "s"); + }, + ctx:function(a) { + var b = this.ctxContext; + return b.putImageData(this.imgData, 0, 0), a.call(b), this.imgData = b.getImageData(0, 0, this.canvas.width, this.canvas.height), + this; + } + }; +}("psLib"), function(a) { + ShearPhoto[a].module("ImageEnhance", function() { + return { + process:function(a) { + for (var b = a.data, c = 0, d = b.length; d > c; c += 4) ; + return a.data = b, a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("addLayer", function() { + return { + add:function(a, b, c, d, e, f, g, h) { + var i, j, k, l, m, n, o, p; + for (g = a.data, i = b.data, e = e || 0, f = f || 0, d = d || 1, h = h || "RGB", + /[RGB]+/.test(h) || (h = "RGB"), k = h.replace("R", "0").replace("G", "1").replace("B", "2"), + h = a.width, l = i.length, b = b.width, k = [ -1 < k.indexOf("0"), -1 < k.indexOf("1"), -1 < k.indexOf("2") ], + o = 0, p = g.length; p > o; o += 4) if (m = o / 4, n = parseInt(m / h), m %= h, + n -= f, m -= e, n = n * b + m, n *= 4, n >= 0 && l - 4 > n && b > m && m >= 0) for (m = 0; 3 > m && 0 != i[n + 3]; m++) switch (g[o + 3] = i[n + 3], + c) { + case "颜色减淡": + k[m] && (j = g[o + m] + g[o + m] * i[n + m] / (255 - i[n + m]), g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "变暗": + k[m] && (j = g[o + m] < i[n + m] ? g[o + m] :i[n + m], g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "变亮": + k[m] && (j = g[o + m] > i[n + m] ? g[o + m] :i[n + m], g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "正片叠底": + k[m] && (j = parseInt(g[o + m] * i[n + m] / 255), g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "滤色": + k[m] && (j = parseInt(255 - (255 - g[o + m]) * (255 - i[n + m]) / 255), g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "叠加": + k[m] && (j = 127.5 >= g[o + m] ? g[o + m] * i[n + m] / 127.5 :255 - (255 - g[o + m]) * (255 - i[n + m]) / 127.5, + g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "强光": + k[m] && (j = 127.5 >= i[n + m] ? g[o + m] * i[n + m] / 127.5 :g[o + m] + (255 - g[o + m]) * (i[n + m] - 127.5) / 127.5, + g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "差值": + k[m] && (j = g[o + m] > i[n + m] ? g[o + m] - i[n + m] :i[n + m] - g[o + m], g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "排除": + k[m] && (j = g[o + m] + i[n + m] - g[o + m] * i[n + m] / 127.5, g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "点光": + k[m] && (j = g[o + m] < 2 * i[n + m] - 255 ? 2 * i[n + m] - 255 :g[o + m] < 2 * i[n + m] ? g[o + m] :2 * i[n + m], + g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "颜色加深": + k[m] && (j = 255 - 255 * (255 - g[o + m]) / i[n + m], g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "线性加深": + k[m] && (j = g[o + m] + i[n + m], j = j > 255 ? j - 255 :0, g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "线性减淡": + k[m] && (j = g[o + m] + i[n + m], j = j > 255 ? 255 :j, g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "柔光": + k[m] && (j = 127.5 > i[n + m] ? ((2 * i[n + m] - 255) * (255 - g[o + m]) / 65025 + 1) * g[o + m] :(2 * i[n + m] - 255) * (Math.sqrt(g[o + m] / 255) - g[o + m] / 255) + g[o + m], + g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "亮光": + k[m] && (j = 127.5 > i[n + m] ? 255 * (1 - (255 - g[o + m]) / (2 * i[n + m])) :g[o + m] / (2 * (1 - i[n + m] / 255)), + g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "线性光": + k[m] && (j = g[o + m] + 2 * i[n + m] - 255, j = j > 255 ? 255 :j, g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + case "实色混合": + k[m] && (j = i[n + m] < 255 - g[o + m] ? 0 :255, g[o + m] = (1 - d) * g[o + m] + d * j); + break; + + default: + k[m] && (j = i[n + m], g[o + m] = (1 - d) * g[o + m] + d * j); + } + return a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("brightness", function() { + return { + process:function(a, b) { + var c, d, e, f, g, h; + for (c = a.data, d = b[0] / 50, e = Math.tan((45 + 44 * ((b[1] || 0) / 50)) * Math.PI / 180), + f = 0, g = c.length; g > f; f += 4) for (h = 0; 3 > h; h++) c[f + h] = (c[f + h] - 127.5 * (1 - d)) * e + 127.5 * (1 + d); + return a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("applyMatrix", function(a) { + return { + process:function(b) { + var c, d, e, f, g, h, i, j, k, l, m, n, o, p; + for (c = b.data, d = b.width, e = new a.lib.dorsyMath.Matrix([ -2, -4, -4, -4, -2, -4, 0, 8, 0, -4, -4, 8, 24, 8, -4, -4, 0, 8, 0, -4, -2, -4, -4, -4, -2 ], 25, 1), + f = [], g = 0, h = c.length; h > g; g += 4) if (i = g / 4, j = parseInt(i / d), + k = i % d, 0 != j && 0 != k) { + for (l = [ [], [], [] ], m = -2; 3 > m; m++) for (n = j + m, o = -2; 3 > o; o++) for (p = 4 * (n * d + (k + o)), + i = 0; 3 > i; i++) l[i].push(c[p + i]); + for (j = new a.lib.dorsyMath.Matrix(l, 3, matrixSize).mutiply(e), i = 0; 3 > i; i++) f[g + i] = j.data[i]; + f[g + 4] = c[g + 4]; + } + for (g = 0, h = c.length; h > g; g++) c[g] = f[g] || c[g]; + return b; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("config", function() { + var a = { + "灰度处理":"toGray", + "反色":"toReverse", + "灰度阈值":"toThresh", + "高斯模糊":"gaussBlur", + "亮度":"brightness", + "浮雕效果":"embossment", + "查找边缘":"borderline", + "色相/饱和度调节":"setHSI", + "马赛克":"mosaic", + "油画":"oilPainting", + "腐蚀":"corrode", + "锐化":"sharp", + "添加杂色":"noise", + "曲线":"curve", + "暗角":"darkCorner", + "喷点":"dotted" + }, b = { + "美肤":"softenFace", + "素描":"sketch", + "自然增强":"softEnhancement", + "紫调":"purpleStyle", + "柔焦":"soften", + "复古":"vintage", + "黑白":"gray", + "仿lomo":"lomo", + "亮白增强":"strongEnhancement", + "灰白":"strongGray", + "灰色":"lightGray", + "暖秋":"warmAutumn", + "木雕":"carveStyle", + "粗糙":"rough" + }; + return { + getModuleName:function(b) { + return a[b] || b; + }, + getEasyFun:function(a) { + return b[a] || a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("corrode", function() { + return { + process:function(a, b) { + var c, d, e, f, g, h, i, j, k; + for (c = parseInt(b[0]) || 3, d = a.data, e = a.width, f = a.height, g = 0; e > g; g++) for (h = 0; f > h; h++) for (i = parseInt(2 * Math.random() * c) - c, + j = parseInt(2 * Math.random() * c) - c, k = h * e + g, i = (h + i) * e + g + j, + j = 0; 3 > j; j++) d[4 * k + j] = d[4 * i + j]; + return a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("curve", function(a) { + return { + process:function(b, c) { + var d, e, f, g, h, i, j, k; + for (d = a.lib.dorsyMath.lagrange(c[0], c[1]), e = b.data, f = b.width, g = b.height, + h = 0; f > h; h++) for (i = 0; g > i; i++) for (j = i * f + h, k = 0; 3 > k; k++) e[4 * j + k] = d(e[4 * j + k]); + return b; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("darkCorner", function(a) { + return { + process:function(b, c) { + var d, e, f, g, h, i, j, k, l, m, n, o, p, q; + for (d = parseInt(c[0]) || 3, e = c[1] || 30, f = b.data, g = b.width, h = b.height, + i = 2 * g / 3, j = 1 * h / 2, k = a.lib.dorsyMath.distance([ i, j ]), d = k * (1 - d / 10), + l = 0; g > l; l++) for (m = 0; h > m; m++) for (n = m * g + l, o = 0; 3 > o; o++) p = f[4 * n + o], + q = (a.lib.dorsyMath.distance([ l, m ], [ i, j ]) - d) / (k - d), 0 > q && (q = 0), + p = (0 * Math.pow(1 - q, 3) + .06 * q * Math.pow(1 - q, 2) + 3 * .3 * q * q * (1 - q) + 1 * Math.pow(q, 3)) * p * e / 255, + f[4 * n + o] -= p; + return b; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("dorsyMath", function(a) { + var b = { + FFT1:function(a) { + function c() { + var g, h, i, j, k, l, m, n, o, p, q; + for (e++, g = d / Math.pow(2, e), h = d / g, i = 0; g > i; i++) for (j = i * h, + k = (i + 1) * h - 1, l = e, m = Math.pow(2, l - 1), n = 0; k - m >= j; j++) o = j + m, + p = n * d / Math.pow(2, l), q = p + d / 4, a[j] instanceof b.C || (a[j] = new b.C(a[j])), + a[o] instanceof b.C || (a[o] = new b.C(a[o])), p = a[j].plus(a[o].mutiply(f[p])), + q = a[j].plus(a[o].mutiply(f[q])), a[j] = p, a[o] = q, n++; + g > 1 && c(); + } + for (var d = a.length, e = 0, f = [], g = 0; d > g; g++) f[g] = this.exp(-2 * Math.PI * g / d); + return c(), a; + }, + DFT:function() {}, + Matrix:function(a, b, c) { + var d, e, f = []; + if (b) { + if (isNaN(b) ? (d = /(\d+)\*/.exec(b)[1], b = /\*(\d+)/.exec(b)[1]) :(d = b, b = c), + a[0] && a[0][0]) for (c = 0; d > c; c++) for (f[c] = [], e = 0; b > e; e++) f[c][e] = a[c][e] || 0; else for (c = 0; d > c; c++) for (f[c] = [], + e = 0; b > e; e++) f[c][e] = a[c * b + e] || 0; + this.m = d, this.n = b; + } else this.m = a.length, this.n = a[0].length; + this.data = f; + }, + C:function(a, b) { + this.r = a || 0, this.i = b || 0; + }, + exp:function(a, c) { + a = a || 0, c = c || 1; + var d = new b.C(); + return d.r = c * Math.cos(a), d.i = c * Math.sin(a), d; + }, + lagrange:function(a, b) { + var c = a.length; + return function(d) { + var e, f, g, h, i; + for (e = 0, f = 0; c > f; f++) { + for (g = 1, h = 1, i = 0; c > i; i++) i != f && (g *= a[f] - a[i], h *= d - a[i]); + e += b[f] * (h / g); + } + return e; + }; + }, + applyMatrix:function(c, d, e) { + var f, g, h, i, j, k, l, m, n, o, p, q, r, s, t; + for (e = e || 0, f = c.data, g = c.width, h = d.length, d = new b.Matrix(d, h, 1), + i = [], j = -(Math.sqrt(h) - 1) / 2, k = 0, l = f.length; l > k; k += 4) if (m = k / 4, + n = parseInt(m / g), o = m % g, 0 != n && 0 != o) { + for (p = [ [], [], [] ], q = j; -j >= q; q++) for (r = n + q, s = j; -j >= s; s++) for (t = 4 * (r * g + (o + s)), + m = 0; 3 > m; m++) p[m].push(f[t + m]); + for (n = new a.lib.dorsyMath.Matrix(p, 3, h).mutiply(d), m = 0; 3 > m; m++) i[k + m] = n.data[m]; + i[k + 4] = f[k + 4]; + } + for (k = 0, l = f.length; l > k; k++) i[k] && (f[k] = i[k] < e ? i[k] :f[k]); + return c; + }, + RGBToHSI:function(a, b, c) { + var d = (a - b + a - c) / 2 / Math.sqrt((a - b) * (a - b) + (a - c) * (b - c)) || 0, d = Math.acos(d), d = c > b ? 2 * Math.PI - d :d, e = 1 - 3 * Math.min(a, b, c) / (a + b + c); + return d > 2 * Math.PI && (d = 2 * Math.PI), 0 > d && (d = 0), { + H:d, + S:e, + I:(a + b + c) / 3 + }; + }, + HSIToRGB:function(a, b, c) { + if (0 > a ? (a %= 2 * Math.PI, a += 2 * Math.PI) :a %= 2 * Math.PI, a <= 2 * Math.PI / 3) var d = c * (1 - b), e = c * (1 + b * Math.cos(a) / Math.cos(Math.PI / 3 - a)), f = 3 * c - (e + d); else a <= 4 * Math.PI / 3 ? (a -= 2 * Math.PI / 3, + e = c * (1 - b), f = c * (1 + b * Math.cos(a) / Math.cos(Math.PI / 3 - a)), d = 3 * c - (f + e)) :(a -= 4 * Math.PI / 3, + f = c * (1 - b), d = c * (1 + b * Math.cos(a) / Math.cos(Math.PI / 3 - a)), e = 3 * c - (f + d)); + return { + R:e, + G:f, + B:d + }; + }, + applyInHSI:function(a, b) { + var c, d, e, f; + for (c = a.data, d = 0, e = c.length; e > d; d += 4) f = this.RGBToHSI(c[d], c[d + 1], c[d + 2]), + b(f), 1 < f.S && (f.S = 1), 0 > f.S && (f.S = 0), f = this.HSIToRGB(f.H, f.S, f.I), + c[d] = f.R, c[d + 1] = f.G, c[d + 2] = f.B; + }, + applyInCoordinate:function() {}, + distance:function(a, c) { + return c = c || [ 0, 0 ], a = new b.C(a[0], a[1]), c = new b.C(c[0], c[1]), a.minus(c).distance(); + }, + xyToIFun:function(a) { + return function(b, c, d) { + return 4 * (c * a + b) + (d || 0); + }; + }, + xyCal:function(a, b, c, d, e) { + var f, g, h; + for (f = this.xyToIFun(a.width), g = 0; 3 > g; g++) h = f(b, c, g), a[h] = d(a[h]); + e && (a[h + 1] = e(a[h + 1])); + } + }; + return b.Matrix.prototype = { + plus:function(a) { + var c, d, e; + if (this.m != a.m || this.n != a.n) throw Error("矩阵加法行列不匹配"); + for (c = new b.Matrix([], this.m, this.n), d = 0; d < this.m; d++) for (e = 0; e < this.n; e++) c.data[d][e] = this.data[d][e] + a.data[d][e]; + return c; + }, + minus:function(a) { + var c, d, e; + if (this.m != a.m || this.n != a.n) throw Error("矩阵减法法行列不匹配"); + for (c = new b.Matrix([], this.m, this.n), d = 0; d < this.m; d++) for (e = 0; e < this.n; e++) c.data[d][e] = this.data[d][e] - a.data[d][e]; + return c; + }, + mutiply:function(a) { + var c, d, e, f, g; + if (this.n != a.m) throw Error("矩阵乘法行列不匹配"); + for (c = new b.Matrix([], this.m, a.n), d = 0; d < this.m; d++) for (e = 0; e < a.n; e++) { + for (f = 0, g = 0; g < this.n; g++) f += this.data[d][g] * a.data[g][e]; + c.data[d][e] = f; + } + return c; + } + }, b.C.prototype = { + plus:function(a) { + var c = new b.C(); + return c.r = this.r + a.r, c.i = this.i + a.i, c; + }, + minus:function(a) { + var c = new b.C(); + return c.r = this.r - a.r, c.i = this.i - a.i, c; + }, + mutiply:function(a) { + var c = new b.C(); + return c.r = this.r * a.r - this.i * a.i, c.i = this.r * a.i + this.i * a.r, c; + }, + divide:function(a) { + var c = new b.C(), d = a.mutiply(a.conjugated()); + return a = this.mutiply(a.conjugated()), c.r = a.r / d.r, c.i = a.i / d.r, c; + }, + conjugated:function() { + return new b.C(this.r, -this.i); + }, + distance:function() { + return Math.sqrt(this.r * this.r + this.i * this.i); + } + }, b; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("dotted", function(a) { + return { + process:function(b, c) { + var d, e, f, g, h, i, j, k, l, m, n, o, p, q; + for (d = parseInt(c[0]) || 1, e = parseInt(c[1]) || 1, f = b.data, g = b.width, + h = b.height, i = 2 * d + 1, j = [], k = e * e, e = -d; d > e; e++) for (l = -d; d > l; l++) e * e + l * l > k && j.push([ e, l ]); + for (d = a.lib.dorsyMath.xyToIFun(g), e = 0, g = parseInt(g / i); g > e; e++) for (l = 0, + k = parseInt(h / i); k > l; l++) for (m = parseInt((e + .5) * i), n = parseInt((l + .5) * i), + o = 0; o < j.length; o++) p = m + j[o][0], q = n + j[o][1], f[d(p, q, 3)] = 225, + f[d(p, q, 2)] = 225, f[d(p, q, 0)] = 225, f[d(p, q, 1)] = 225; + return b; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("easy", function() { + return { + getFun:function(b) { + return { + softenFace:function() { + return this.clone().add(this.act("高斯模糊", 10), "滤色").act("亮度", -10, 5); + }, + sketch:function() { + var a = this.act("灰度处理").clone(); + return this.add(a.act("反色").act("高斯模糊", 8), "颜色减淡").act("锐化", 1); + }, + softEnhancement:function() { + return this.act("曲线", [ 0, 190, 255 ], [ 0, 229, 255 ]); + }, + purpleStyle:function() { + var a = this.clone(); + return this.add(a.act("高斯模糊", 3), "正片叠底", "RG"); + }, + soften:function() { + var a = this.clone(); + return this.add(a.act("高斯模糊", 6), "变暗"); + }, + vintage:function() { + return this.clone(), this.act("灰度处理").add(ShearPhoto[a](this.canvas.width, this.canvas.height, "#808080").act("添加杂色").act("高斯模糊", 4).act("色相/饱和度调节", 32, 19, 0, !0), "叠加"); + }, + gray:function() { + return this.act("灰度处理"); + }, + lomo:function() { + return this.clone().add(this.clone(), "滤色").add(this.clone(), "柔光").add(this.clone().act("反色"), "正常", "20%", "B").act("暗角", 6, 200); + }, + strongEnhancement:function() { + return this.clone().add(this.clone().act("曲线", [ 0, 50, 255 ], [ 0, 234, 255 ]), "柔光"); + }, + strongGray:function() { + return this.act("灰度处理").act("曲线", [ 0, 61, 69, 212, 255 ], [ 0, 111, 176, 237, 255 ]); + }, + lightGray:function() { + return this.act("灰度处理").act("曲线", [ 0, 60, 142, 194, 255 ], [ 0, 194, 240, 247, 255 ]); + }, + warmAutumn:function() { + var a = this.clone().act("色相/饱和度调节", 36, 47, 8, !0).act("暗角", 6, 150); + return this.add(a, "叠加"); + }, + carveStyle:function() { + var a = this.clone().act("马赛克").act("查找边缘").act("浮雕效果"); + return this.add(a, "线性光"); + }, + rough:function() { + return this.add(ShearPhoto[a](this.canvas.width, this.canvas.height, "#000").act("喷点").act("反色").act("浮雕效果"), "叠加"); + } + }[b]; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("embossment", function() { + return { + process:function(a) { + var b, c, d, e, f, g, h, i, j; + for (b = a.data, c = a.width, d = [], e = 0, f = b.length; f > e; e += 4) if (g = e / 4, + h = parseInt(g / c), i = g % c, g = 4 * ((h - 1) * c + (i - 1)), j = 4 * (h + 1) * c + 4 * (i + 1), + 0 != h && 0 != i) { + for (h = 0; 3 > h; h++) d[e + h] = b[g + h] - b[j + h] + 127.5; + d[e + 4] = b[e + 4]; + } + for (e = 0, f = b.length; f > e; e++) b[e] = d[e] || b[e]; + return a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("gaussBlur", function() { + return { + process:function(a, b, c) { + var d, e, f, g, h, i, j = a.data, k = a.width, l = a.height, m = [], n = 0; + for (b = Math.floor(b) || 3, c = c || b / 3, d = 1 / (Math.sqrt(2 * Math.PI) * c), + g = -1 / (2 * c * c), h = 0, c = -b; b >= c; c++, h++) f = d * Math.exp(g * c * c), + m[h] = f, n += f; + for (h = 0, c = m.length; c > h; h++) m[h] /= n; + for (d = 0; l > d; d++) for (c = 0; k > c; c++) { + for (n = e = f = g = 0, i = -b; b >= i; i++) h = c + i, h >= 0 && k > h && (h = 4 * (d * k + h), + e += j[h] * m[i + b], f += j[h + 1] * m[i + b], g += j[h + 2] * m[i + b], n += m[i + b]); + h = 4 * (d * k + c), j[h] = e / n, j[h + 1] = f / n, j[h + 2] = g / n; + } + for (c = 0; k > c; c++) for (d = 0; l > d; d++) { + for (n = e = f = g = 0, i = -b; b >= i; i++) h = d + i, h >= 0 && l > h && (h = 4 * (h * k + c), + e += j[h] * m[i + b], f += j[h + 1] * m[i + b], g += j[h + 2] * m[i + b], n += m[i + b]); + h = 4 * (d * k + c), j[h] = e / n, j[h + 1] = f / n, j[h + 2] = g / n; + } + return a.data = j, a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("borderline", function(a) { + return { + process:function(b) { + return a.lib.dorsyMath.applyMatrix(b, [ 0, 1, 0, 1, -4, 1, 0, 1, 0 ], 250); + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("mosaic", function() { + return { + process:function(a, b) { + var c, d, e, f, g, h, i, j, k, l, m, n, o; + for (c = parseInt(b[0]) || 3, d = a.data, e = a.width, f = a.height, c = 2 * c + 1, + g = 0, h = parseInt(e / c); h > g; g++) for (i = 0, j = parseInt(f / c); j > i; i++) { + for (k = [], l = [ 0, 0, 0 ], m = 0; c > m; m++) for (n = 0; c > n; n++) o = (i * c + m) * e + g * c + n, + l[0] += d[4 * o], l[1] += d[4 * o + 1], l[2] += d[4 * o + 2]; + for (k[0] = l[0] / (c * c), k[1] = l[1] / (c * c), k[2] = l[2] / (c * c), m = 0; c > m; m++) for (n = 0; c > n; n++) o = (i * c + m) * e + g * c + n, + d[4 * o] = k[0], d[4 * o + 1] = k[1], d[4 * o + 2] = k[2]; + } + return a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("noise", function() { + return { + process:function(a, b) { + var c, d, e, f, g, h, i, j, k; + for (c = parseInt(b[0]) || 100, d = a.data, e = a.width, f = a.height, g = 0; e > g; g++) for (h = 0; f > h; h++) for (i = h * e + g, + j = 0; 3 > j; j++) k = parseInt(2 * Math.random() * c) - c, d[4 * i + j] += k; + return a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("oilPainting", function() { + return { + process:function(a, b) { + var c, d, e, f, g, h, i, j, k; + for (c = parseInt(b[0]) || 16, d = a.data, e = a.width, f = a.height, g = 0; e > g; g++) for (h = 0; f > h; h++) { + for (i = h * e + g, j = 0, k = 0; 3 > k; k++) j += d[4 * i + k]; + for (j /= 3, j = parseInt(j / c) * c, k = 0; 3 > k; k++) d[4 * i + k] = j; + } + return a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("setHSI", function(a) { + return { + process:function(b, c) { + return c[0] = c[0] / 180 * Math.PI, c[1] = c[1] / 100 || 0, c[2] = 255 * (c[2] / 100) || 0, + c[3] = c[3] || !1, a.lib.dorsyMath.applyInHSI(b, function(a) { + c[3] ? (a.H = c[0], a.S = c[1]) :(a.H += c[0], a.S += c[1]), a.I += c[2]; + }), b; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("sharp", function() { + return { + process:function(a, b) { + var c, d, e, f, g, h, i, j, k; + for (c = b[0] || .6, d = a.data, e = a.width, f = 0, g = d.length; g > f; f += 4) if (h = f / 4, + i = parseInt(h / e), j = h % e, 0 != i && 0 != j) for (k = 4 * ((i - 1) * e + (j - 1)), + i = 4 * ((i - 1) * e + j), h = 4 * (h - 1), j = 0; 3 > j; j++) d[f + j] += (d[f + j] - (d[i + j] + d[h + j] + d[k + j]) / 3) * c; + return a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("toGray", function() { + return { + process:function(a) { + var b, c, d, e; + for (b = a.data, c = 0, d = b.length; d > c; c += 4) e = parseInt((b[c] + b[c + 1] + b[c + 2]) / 3), + b[c + 2] = b[c + 1] = b[c] = e; + return a.data = b, a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("toReverse", function() { + return { + process:function(a) { + for (var b = a.data, c = 0, d = b.length; d > c; c += 4) b[c] = 255 - b[c], b[c + 1] = 255 - b[c + 1], + b[c + 2] = 255 - b[c + 2]; + return a.data = b, a; + } + }; + }); +}("psLib"), function(a) { + ShearPhoto[a].module("toThresh", function(a) { + return { + process:function(b, c) { + var d, e, f; + for (b = a.lib.toGray.process(b), d = b.data, c = c[0] || 128, e = 0, f = d.length; f > e; e++) (e + 1) % 4 && (d[e] = d[e] > c ? 255 :0); + return b.data = d, b; + } + }; + }); +}("psLib"); +}catch(e){} \ No newline at end of file diff --git a/public/shearphoto/js/handle.js b/public/shearphoto/js/handle.js new file mode 100644 index 0000000..41f1535 --- /dev/null +++ b/public/shearphoto/js/handle.js @@ -0,0 +1,183 @@ +window.ShearPhoto.MINGGE(function() { + var relativeUrl= SHEAR.PATH_RES+"/shearphoto";//by rainfer,相对路径 + var avatarUrl= SHEAR.PATH_AVATAR;//by rainfer,头像路径 + relativeUrl = relativeUrl.replace(/(^\s*)|(\s*$)/g, "");//去掉相对路径的所有空格 + relativeUrl === "" || (relativeUrl += "/");//在相对地址后面加斜框,不需要用户自己加 + var publicRelat= document.getElementById("relat"); //"relat"对像 + var publicRelatImg=publicRelat.getElementsByTagName("img"); //"relat"下的两张图片对像 + var Shear = new ShearPhoto; + Shear.config({ + relativeUrl:relativeUrl, //取回相对路径 + avatarUrl:avatarUrl,//by rainfer,头像路径 + traverse:true,//可选 true,false 。 是否在拖动或拉伸时允许历遍全图(是否让大图动呢), + translate3d:false, //是否开启3D移动,CPU加速。可选true false。默认关闭的 + HTML5:true,//可选 true,false 是否使用HTML5进行切图 + HTML5MAX:500, //默认请设0 (最大尺寸做事), HTML上传截图最大宽度, 宽度越大,HTML5截出来的图片容量越大,服务器压力就大,截图就更清淅 + HTML5Quality:0.9, //截好的截图 0至1范围可选(可填小数) HTML5切图的质量 为1时 最高 + HTML5FilesSize:50, //如果是HTML5切图时,选择的图片不能超过多少,单位M + HTML5Effects:true,//是否开启图片特效功能给用户 可选true false, 提示:有HTML5浏览器才会开启的!当然开启HTML5切图,该设置才有效 + HTML5ZIP:[900,0.9],//HTML5截图前载入的大图 是否压缩图片(数组成员 是数字) + preview:[150],// 开启动态预览图片 (数组成员整数型,禁止含小数点 可选false 和数组) + url:relativeUrl+"php/shearphoto.php", //后端处理地址 + scopeWidth:400, //可拖动范围宽 也就是"main"对象的初始大小(整数型,禁止含小数点) 宽和高的值最好能一致 + scopeHeight:400, //可拖动范围高 也就是"main"对象的初始大小(整数型,禁止含小数点) 宽和高的值最好能一致 + proportional:[1/1,100,133], + Min:50, //截框拉伸或拖拽不能少于多少PX(整数型,禁止含小数点) + Max:500, //一开始启动时,图片的宽和高,有时候图片会很大的,必须要设置一下(整数型,禁止含小数点) + backgroundColor:"#000", //遮层色 + backgroundOpacity:0.6, //遮层透明度-数字0-1 可选 + Border:0, //截框的边框大小 0代表动态边框。大于0表示静态边框,大于0时也代表静态边框的粗细值 + BorderStyle:"solid", //只作用于静态边框,截框的边框类型,其实是引入CSS的border属性,和CSS2的border属性是一样的 + BorderColor:"#09F", //只作用于静态边框,截框的边框色彩 + relat:publicRelat, //请查看 id:"relat"对象 + scope:document.getElementById("main"),//main范围对象 + ImgDom:publicRelatImg[0], //截图图片对象(小) + ImgMain:publicRelatImg[1], //截图图片对象(大) + black:document.getElementById("black"),//黑色遮层对象 + form:document.getElementById("smallbox"),//截框对象 + ZoomDist:document.getElementById("ZoomDist"),//放大工具条,可从HTML查看此对象,不作详细解释了 + ZoomBar:document.getElementById("ZoomBar"), //放大工具条,可从HTML查看此对象 + to:{ + BottomRight:document.getElementById("BottomRight"),//拉伸点中右 + TopRight:document.getElementById("TopRight"),//拉伸点上右,下面如此类推,一共8点进行拉伸,下面不再作解释 + Bottomleft:document.getElementById("Bottomleft"), + Topleft:document.getElementById("Topleft"), + Topmiddle:document.getElementById("Topmiddle"), + leftmiddle:document.getElementById("leftmiddle"), + Rightmiddle:document.getElementById("Rightmiddle"), + Bottommiddle:document.getElementById("Bottommiddle") + }, + Effects:document.getElementById("shearphoto_Effects") || false, + DynamicBorder:[document.getElementById("borderTop"),document.getElementById("borderLeft"),document.getElementById("borderRight"),document.getElementById("borderBottom")], + SelectBox:document.getElementById("SelectBox"), //选择图片方式的对象 + Shearbar:document.getElementById("Shearbar"), //截图工具条对象 + UpFun:function() { //鼠标健松开时执行函数 + Shear.MoveDiv.DivWHFun(); //把截框现时的宽高告诉JS + } + }); + Shear.complete=function(serverdata) {//截图成功完成时,由shearphoto.php返回数据过来的成功包 + //alert(SHEAR.URL);//你可以调试一下这个返回包 + var point = this.arg.scope.childNodes[0]; + point.className === "point" && this.arg.scope.removeChild(point); + var complete = document.createElement("div"); + complete.className = "complete"; + complete.style.height = this.arg.scopeHeight + "px"; + this.arg.scope.insertBefore(complete, this.arg.scope.childNodes[0]); + var length = serverdata.length,creatImg; + for (var i = 0; i < length; i++) + { + creatImg = document.createElement("img"); + complete.appendChild(creatImg); + creatImg.src=this.arg.avatarUrl +"/"+ serverdata[i]["ImgUrl"]; + } + this.HTML5.EffectsReturn(); + this.HTML5.BOLBID && this.HTML5.URL.revokeObjectURL(this.HTML5.BOLBID); + var this_ = this; + this_.preview.close_(); + ////by rainfer,增加ajax开始 + $.post(SHEAR.URL, + {'imgurl':serverdata[0]['ImgUrl']}, + function(data){ + if(data.code==1){ + layer.alert(data.msg, {icon: 6}, function(index){ + layer.close(index); + window.location.href=data.url; + }); + }else{ + layer.alert(data.msg, {icon: 5}, function(index){ + layer.close(index); + }); + } + }); + } + + var photoalbum = document.getElementById("photoalbum");//相册对象 + var ShearPhotoForm = document.getElementById("ShearPhotoForm");//FORM对象 + ShearPhotoForm.UpFile.onclick=function(){return false}//一开始时先不让用户点免得事件阻塞 + var up = new ShearPhoto.frameUpImg({ + url:relativeUrl+"php/upload.php", //HTML5切图时,不会用到该文件,后端处理地址 + FORM:ShearPhotoForm, //FORM对象传到设置 + UpType:new Array("jpg", "jpeg", "png", "gif"),//图片类限制,上传的一定是图片,你就不要更改了 + FilesSize:2, //选择的图片不能超过 单位M(注意:是非HTML5时哦) + HTML5:Shear.HTML5, + HTML5FilesSize:Shear.arg.HTML5FilesSize, + HTML5ZIP:Shear.arg.HTML5ZIP, + erro:function(msg) { + Shear.pointhandle(3e3, 10, msg, 0, "#f82373", "#fff"); + }, + fileClick:function(){//先择图片被点击时,触发的事件 + Shear.pointhandle(-1);//关闭提示,防止线程阻塞事件冒泡 + }, + preced:function(fun) { //点击选择图,载入图片时的事件 + try{ + photoalbum.style.display = "none"; //什么情况下都关了相册 + camClose.onclick(); //什么情况下都关了视频 + }catch (e){console.log("在加载图片时,发现相册的对象检测不到,错误代码:"+e);} + Shear.pointhandle(0, 10, "正在为你加载图片,请你稍等哦......", 2, "#307ff6", "#fff",fun); + } + }); + + up.run(function(data,True) {//upload.php成功返回数据后 + True || (data = ShearPhoto.JsonString.StringToJson(data)); + if (data === false) { + Shear.SendUserMsg("错误:请保证后端环境运行正常", 5e3, 0, "#f4102b", "#fff", true,true); + return; + } + if (data["erro"]) { + Shear.SendUserMsg("错误:" + data["erro"], 5e3, 0, "#f4102b", "#fff", true,true); + return; + } + Shear.run(data["success"],true); + }); + + try{ + var AllType= {".jpg":"image/jpeg", ".jpeg":"image/jpeg", ".gif":"image/jpeg", ".png":"image/png"}; + var URLType =function(url){ + return AllType[/\.[^.]+$/.exec(url)] || "image/jpeg";//取后缀 + } + var DE = document.documentElement; + var PhotoLoading = document.getElementById("PhotoLoading"); + var photoalbumLi = photoalbum.getElementsByTagName("li"); + var photoalbumLifun = function() { + var serveUrl= this.getElementsByTagName("img")[0].getAttribute("serveUrl"); + Shear.HTML5.ImagesType=URLType(serveUrl);//告诉HTML5,图片的类型 + Shear.run(serveUrl); //通过API 接口,加载图片 + photoalbum.style.display = "none"; + }; + + for (var i = 0; i < photoalbumLi.length; i++) photoalbumLi[i].onclick = photoalbumLifun;//为相册的每张照加入一个点击事件 + PhotoLoading.onclick = function() { //从相册选取事件 + photoalbum.style.display = "block"; + + + }; + document.getElementById("close").onclick = function() { //关闭相册事件 + photoalbum.style.display = "none"; + }; + }catch (e){console.log("相册对象检测有误,错误代码:"+e );} + + Shear.addEvent(document.getElementById("saveShear"), "click", function() { //按下截图事件,提交到后端的shearphoto.php接收 + Shear.SendPHP({shearphoto:"",mingge:""});//我们示例截图并且传参数,后端文件shearphoto.php用 示例:$_POST["shearphoto"] 接收参数,不需要传参数请清空Shear.SendPHP里面的参数示例 Shear.SendPHP(); + + }); + + Shear.addEvent(document.getElementById("LeftRotate"), "click", function() {//向左旋转事件 + Shear.Rotate("left"); + }); + + Shear.addEvent(document.getElementById("RightRotate"), "click", function() { //向右旋转事件 + Shear.Rotate("right"); + }); + + Shear.addEvent(document.getElementById("againIMG"), "click", function() { //重新选择事件 + Shear.preview.close_(); + Shear.again(); + Shear.HTML5.EffectsReturn(); + Shear.HTML5.BOLBID && Shear.HTML5.URL.revokeObjectURL(Shear.HTML5.BOLBID); + Shear.pointhandle(3e3, 10, "已取消!重新选择", 2, "#fbeb61", "#3a414c"); + }); + var shearphoto_loading=document.getElementById("shearphoto_loading"); + var shearphoto_main=document.getElementById("shearphoto_main"); + shearphoto_loading && shearphoto_loading.parentNode.removeChild(shearphoto_loading); + shearphoto_main.style.visibility="visible"; +}); diff --git a/public/shearphoto/js/handle_f.js b/public/shearphoto/js/handle_f.js new file mode 100644 index 0000000..3acf931 --- /dev/null +++ b/public/shearphoto/js/handle_f.js @@ -0,0 +1,185 @@ +window.ShearPhoto.MINGGE(function() { + var relativeUrl= SHEAR.PATH_RES+"/shearphoto";//by rainfer,相对路径 + var avatarUrl= SHEAR.PATH_AVATAR;//by rainfer,头像路径 + relativeUrl = relativeUrl.replace(/(^\s*)|(\s*$)/g, "");//去掉相对路径的所有空格 + relativeUrl === "" || (relativeUrl += "/");//在相对地址后面加斜框,不需要用户自己加 + var publicRelat= document.getElementById("relat"); //"relat"对像 + var publicRelatImg=publicRelat.getElementsByTagName("img"); //"relat"下的两张图片对像 + var Shear = new ShearPhoto; + Shear.config({ + relativeUrl:relativeUrl, //取回相对路径 + avatarUrl:avatarUrl,//by rainfer,头像路径 + traverse:true,//可选 true,false 。 是否在拖动或拉伸时允许历遍全图(是否让大图动呢), + translate3d:false, //是否开启3D移动,CPU加速。可选true false。默认关闭的 + HTML5:true,//可选 true,false 是否使用HTML5进行切图 + HTML5MAX:500, //默认请设0 (最大尺寸做事), HTML上传截图最大宽度, 宽度越大,HTML5截出来的图片容量越大,服务器压力就大,截图就更清淅 + HTML5Quality:0.9, //截好的截图 0至1范围可选(可填小数) HTML5切图的质量 为1时 最高 + HTML5FilesSize:50, //如果是HTML5切图时,选择的图片不能超过多少,单位M + HTML5Effects:true,//是否开启图片特效功能给用户 可选true false, 提示:有HTML5浏览器才会开启的!当然开启HTML5切图,该设置才有效 + HTML5ZIP:[900,0.9],//HTML5截图前载入的大图 是否压缩图片(数组成员 是数字) + preview:[100],// 开启动态预览图片 (数组成员整数型,禁止含小数点 可选false 和数组) + url:relativeUrl+"php/shearphoto.php", //后端处理地址 + scopeWidth:300, //可拖动范围宽 也就是"main"对象的初始大小(整数型,禁止含小数点) 宽和高的值最好能一致 + scopeHeight:300, //可拖动范围高 也就是"main"对象的初始大小(整数型,禁止含小数点) 宽和高的值最好能一致 + proportional:[1/1,100,133], + Min:50, //截框拉伸或拖拽不能少于多少PX(整数型,禁止含小数点) + Max:300, //一开始启动时,图片的宽和高,有时候图片会很大的,必须要设置一下(整数型,禁止含小数点) + backgroundColor:"#000", //遮层色 + backgroundOpacity:0.6, //遮层透明度-数字0-1 可选 + Border:0, //截框的边框大小 0代表动态边框。大于0表示静态边框,大于0时也代表静态边框的粗细值 + BorderStyle:"solid", //只作用于静态边框,截框的边框类型,其实是引入CSS的border属性,和CSS2的border属性是一样的 + BorderColor:"#09F", //只作用于静态边框,截框的边框色彩 + relat:publicRelat, //请查看 id:"relat"对象 + scope:document.getElementById("main"),//main范围对象 + ImgDom:publicRelatImg[0], //截图图片对象(小) + ImgMain:publicRelatImg[1], //截图图片对象(大) + black:document.getElementById("black"),//黑色遮层对象 + form:document.getElementById("smallbox"),//截框对象 + ZoomDist:document.getElementById("ZoomDist"),//放大工具条,可从HTML查看此对象,不作详细解释了 + ZoomBar:document.getElementById("ZoomBar"), //放大工具条,可从HTML查看此对象 + to:{ + BottomRight:document.getElementById("BottomRight"),//拉伸点中右 + TopRight:document.getElementById("TopRight"),//拉伸点上右,下面如此类推,一共8点进行拉伸,下面不再作解释 + Bottomleft:document.getElementById("Bottomleft"), + Topleft:document.getElementById("Topleft"), + Topmiddle:document.getElementById("Topmiddle"), + leftmiddle:document.getElementById("leftmiddle"), + Rightmiddle:document.getElementById("Rightmiddle"), + Bottommiddle:document.getElementById("Bottommiddle") + }, + Effects:document.getElementById("shearphoto_Effects") || false, + DynamicBorder:[document.getElementById("borderTop"),document.getElementById("borderLeft"),document.getElementById("borderRight"),document.getElementById("borderBottom")], + SelectBox:document.getElementById("SelectBox"), //选择图片方式的对象 + Shearbar:document.getElementById("Shearbar"), //截图工具条对象 + UpFun:function() { //鼠标健松开时执行函数 + Shear.MoveDiv.DivWHFun(); //把截框现时的宽高告诉JS + } + }); + Shear.complete=function(serverdata) {//截图成功完成时,由shearphoto.php返回数据过来的成功包 + //alert(SHEAR.URL);//你可以调试一下这个返回包 + var point = this.arg.scope.childNodes[0]; + point.className === "point" && this.arg.scope.removeChild(point); + var complete = document.createElement("div"); + complete.className = "complete"; + complete.style.height = this.arg.scopeHeight + "px"; + this.arg.scope.insertBefore(complete, this.arg.scope.childNodes[0]); + var length = serverdata.length,creatImg; + for (var i = 0; i < length; i++) + { + creatImg = document.createElement("img"); + complete.appendChild(creatImg); + creatImg.src=this.arg.avatarUrl +"/"+ serverdata[i]["ImgUrl"]; + } + this.HTML5.EffectsReturn(); + this.HTML5.BOLBID && this.HTML5.URL.revokeObjectURL(this.HTML5.BOLBID); + var this_ = this; + this_.preview.close_(); + ////by rainfer,增加ajax开始 + $.post(SHEAR.URL, + {'imgurl':serverdata[0]['ImgUrl']}, + function(data){ + if(data.code==1){ + layer.alert(data.msg, {icon: 6}, function(index){ + layer.close(index); + window.location.href=data.url; + }); + }else{ + layer.alert(data.msg, {icon: 5}, function(index){ + layer.close(index); + }); + } + }); + } + + var photoalbum = document.getElementById("photoalbum");//相册对象 + var ShearPhotoForm = document.getElementById("ShearPhotoForm");//FORM对象 + ShearPhotoForm.UpFile.onclick=function(){return false}//一开始时先不让用户点免得事件阻塞 + var up = new ShearPhoto.frameUpImg({ + url:relativeUrl+"php/upload.php", //HTML5切图时,不会用到该文件,后端处理地址 + FORM:ShearPhotoForm, //FORM对象传到设置 + UpType:new Array("jpg", "jpeg", "png", "gif"),//图片类限制,上传的一定是图片,你就不要更改了 + FilesSize:2, //选择的图片不能超过 单位M(注意:是非HTML5时哦) + HTML5:Shear.HTML5, + HTML5FilesSize:Shear.arg.HTML5FilesSize, + HTML5ZIP:Shear.arg.HTML5ZIP, + erro:function(msg) { + Shear.pointhandle(3e3, 10, msg, 0, "#f82373", "#fff"); + }, + fileClick:function(){//先择图片被点击时,触发的事件 + Shear.pointhandle(-1);//关闭提示,防止线程阻塞事件冒泡 + }, + preced:function(fun) { //点击选择图,载入图片时的事件 + try{ + photoalbum.style.display = "none"; //什么情况下都关了相册 + camClose.onclick(); //什么情况下都关了视频 + }catch (e){console.log("在加载图片时,发现相册的对象检测不到,错误代码:"+e);} + Shear.pointhandle(0, 10, "正在为你加载图片,请你稍等哦......", 2, "#307ff6", "#fff",fun); + } + }); + + up.run(function(data,True) {//upload.php成功返回数据后 + True || (data = ShearPhoto.JsonString.StringToJson(data)); + if (data === false) { + Shear.SendUserMsg("错误:请保证后端环境运行正常", 5e3, 0, "#f4102b", "#fff", true,true); + return; + } + if (data["erro"]) { + Shear.SendUserMsg("错误:" + data["erro"], 5e3, 0, "#f4102b", "#fff", true,true); + return; + } + Shear.run(data["success"],true); + }); + + try{ + var AllType= {".jpg":"image/jpeg", ".jpeg":"image/jpeg", ".gif":"image/jpeg", ".png":"image/png"}; + var URLType =function(url){ + return AllType[/\.[^.]+$/.exec(url)] || "image/jpeg";//取后缀 + } + var DE = document.documentElement; + var PhotoLoading = document.getElementById("PhotoLoading"); + var photoalbumLi = photoalbum.getElementsByTagName("li"); + var photoalbumLifun = function() { + var serveUrl= this.getElementsByTagName("img")[0].getAttribute("serveUrl"); + Shear.HTML5.ImagesType=URLType(serveUrl);//告诉HTML5,图片的类型 + Shear.run(serveUrl); //通过API 接口,加载图片 + photoalbum.style.display = "none"; + }; + + for (var i = 0; i < photoalbumLi.length; i++) photoalbumLi[i].onclick = photoalbumLifun;//为相册的每张照加入一个点击事件 + PhotoLoading.onclick = function() { //从相册选取事件 + photoalbum.style.display = "block"; + + + }; + document.getElementById("close").onclick = function() { //关闭相册事件 + photoalbum.style.display = "none"; + }; + }catch (e){console.log("相册对象检测有误,错误代码:"+e );} + + Shear.addEvent(document.getElementById("saveShear"), "click", function() { //按下截图事件,提交到后端的shearphoto.php接收 + Shear.SendPHP({shearphoto:"",mingge:""});//我们示例截图并且传参数,后端文件shearphoto.php用 示例:$_POST["shearphoto"] 接收参数,不需要传参数请清空Shear.SendPHP里面的参数示例 Shear.SendPHP(); + + }); + + Shear.addEvent(document.getElementById("LeftRotate"), "click", function() {//向左旋转事件 + Shear.Rotate("left"); + }); + + Shear.addEvent(document.getElementById("RightRotate"), "click", function() { //向右旋转事件 + Shear.Rotate("right"); + }); + + Shear.addEvent(document.getElementById("againIMG"), "click", function() { //重新选择事件 + Shear.preview.close_(); + Shear.again(); + Shear.HTML5.EffectsReturn(); + Shear.HTML5.BOLBID && Shear.HTML5.URL.revokeObjectURL(Shear.HTML5.BOLBID); + Shear.pointhandle(3e3, 10, "已取消!重新选择", 2, "#fbeb61", "#3a414c"); + }); + + + var shearphoto_loading=document.getElementById("shearphoto_loading"); + var shearphoto_main=document.getElementById("shearphoto_main"); + shearphoto_loading && shearphoto_loading.parentNode.removeChild(shearphoto_loading); + shearphoto_main.style.visibility="visible"; +}); diff --git a/public/shearphoto/php/shearphoto.config.php b/public/shearphoto/php/shearphoto.config.php new file mode 100644 index 0000000..d41ea1f --- /dev/null +++ b/public/shearphoto/php/shearphoto.config.php @@ -0,0 +1,21 @@ + 1 / 1,//比例截图 + "quality" => 85,// 截图质量,0为一般质量(质量大概75左右), 0-100可选 + "force_jpg" => true,// 是否强制截好的图片是JPG格式 可选 true false + "width" => array(//自定义设置生成截图的张数,大小,在这设,看好下面! + //array(0,true,"name0"),//此时的0 代表以用户取当时截取框的所截的大小为宽 + //array(-1,true,"name1"),//此时的-1 代表以原图为基准,获得截图 + array(200, true, ""),//@参数1要生成的宽 (高度不用设,系统会按比例做事), @参数2:是否为该图加水印,water参数要有水印地址才有效true或false @参数3:图片后面添加字符串 + ), + "water" => false,//只接受PNG水印,当然你对PHP熟练,你可以对主程序进行修改支持其他类型水印,不设就"water"=>false + "water_scope" => 100, //图片少于多少不添加水印!没填水印地址,这里不起任何作用 + "temp" => ShearURL . "temp", //等待截图的大图文件。就是上传图片的临时目录 + "tempSaveTime" => 600,//临时图片(也就是temp内的图片)保存时间,需要永久保存请设为0。单位秒 + "saveURL" => ShearURL,//截好后的图片。储存的目录位置,后面不要加斜杠 + "filename" => uniqid("")//截好后的图片的文件名字定义!要生成多个文件时 系统会自动在后面补上 +); +?> \ No newline at end of file diff --git a/public/shearphoto/php/shearphoto.php b/public/shearphoto/php/shearphoto.php new file mode 100644 index 0000000..0a1d88a --- /dev/null +++ b/public/shearphoto/php/shearphoto.php @@ -0,0 +1,264 @@ +erro = "JS设置的比例和PHP设置不一致!"; + return false; + } + require("shearphoto.up.php"); + $tempurl = $PHPconfig["temp"] . DIRECTORY_SEPARATOR . "shearphoto.lock"; + !file_exists($tempurl) && file_put_contents($tempurl, "ShearPhoto Please don't delete"); + $this->delTempImg($PHPconfig["temp"], $PHPconfig["tempSaveTime"]); + $imagecreatefrom = $this->imagecreatefrom($_FILES['UpFile']['tmp_name'], $int_type[2]); + if (!$imagecreatefrom) return false; + list($src, $GdFun) = $imagecreatefrom; + return $this->compression($src, $PHPconfig, $JSconfig, $int_type[2], $GdFun); + } + + protected function delTempImg($temp, $deltime) + { + if ($deltime == 0) return; + $dir = opendir($temp); + $time = time(); + while (($file = readdir($dir)) !== false) { + if ($file != "." and $file != ".." and $file != "shearphoto.lock") { + $fileUrl = $temp . DIRECTORY_SEPARATOR . $file; + $pastTime = $time - filemtime($fileUrl); + if ($pastTime < 0 || $pastTime > $deltime) @unlink($fileUrl); + } + } + closedir($dir); + } + + public function run(&$JSconfig, &$PHPconfig) + { + $tempurl = $PHPconfig["temp"] . DIRECTORY_SEPARATOR . "shearphoto.lock"; + !file_exists($tempurl) && file_put_contents($tempurl, "ShearPhoto Please don't delete"); + $this->delTempImg($PHPconfig["temp"], $PHPconfig["tempSaveTime"]); + //var_dump($JSconfig["url"]); + if (!isset($JSconfig["url"]) || !isset($JSconfig["R"]) || !isset($JSconfig["X"]) || !isset($JSconfig["Y"]) || !isset($JSconfig["IW"]) || !isset($JSconfig["IH"]) || !isset($JSconfig["P"]) || !isset($JSconfig["FW"]) || !isset($JSconfig["FH"])) { + $this->erro = "服务端接收到的数据缺少参数"; + return false; + } + //var_dump($JSconfig["url"]); + if (!file_exists($JSconfig["url"])) { + $this->erro = "此图片路径有误"; + return false; + } + foreach ($JSconfig as $k => $v) { + if ($k !== "url") { //验证是否为数字除了url参数之外 + if (!is_numeric($v)) { + $this->erro = "传递的参数有误"; + return false; + } + } + } + if (is_bool($PHPconfig["proportional"])) { + $PHPconfig["proportional"] = $JSconfig["P"]; + } elseif ($PHPconfig["proportional"] != $JSconfig["P"]) { + $this->erro = "JS设置的比例和PHP设置不一致!"; + return false; + } + list($w, $h, $type) = getimagesize($JSconfig["url"]); + $strtype = image_type_to_extension($type); + $type_array = array( + ".jpeg", + ".gif", + ".png", + ".jpg" + ); + if (!in_array(strtolower($strtype), $type_array)) { + $this->erro = "无法读取图片"; + return false; + } + + if ($JSconfig["R"] == 90 || $JSconfig["R"] == 270) { + list($w, $h) = array( + $h, + $w + ); + } + return $this->createshear($PHPconfig, $w, $h, $type, $JSconfig); + } + + protected function imagecreatefrom($url, $type) + { + switch ($type) { + case 1: + $src = @imagecreatefromgif($url); + $GdFun = array( + "imagegif", + ".gif" + ); + break; + + case 2: + $src = @imagecreatefromjpeg($url); + $GdFun = array( + "imagejpeg", + ".jpg" + ); + break; + + case 3: + $src = @imagecreatefrompng($url); + $GdFun = array( + "imagepng", + ".png" + ); + break; + + default: + $this->erro = "不支持的类型"; + return false; + break; + } + return array($src, $GdFun); + } + + protected function createshear(&$PHPconfig, $w, $h, $type, &$JSconfig) + { + $imagecreatefrom = $this->imagecreatefrom($JSconfig["url"], $type); + if (!$imagecreatefrom) return false; + list($src, $GdFun) = $imagecreatefrom; + $src = $this->rotate($src, $JSconfig["R"]); + $dest = imagecreatetruecolor($JSconfig["IW"], $JSconfig["IH"]); + $white = imagecolorallocate($dest, 255, 255, 255); + imagefill($dest, 0, 0, $white); + imagecopy($dest, $src, 0, 0, $JSconfig["X"], $JSconfig["Y"], $w, $h); + imagedestroy($src); + return $this->compression($dest, $PHPconfig, $JSconfig, $type, $GdFun); + } + + protected function CreateArray(&$PHPconfig, &$JSconfig) + { + $arr = array(); + if ($PHPconfig["proportional"] > 0) { + $proportion = $PHPconfig["proportional"]; + } else { + $proportion = $JSconfig["IW"] / $JSconfig["IH"]; + } + $water_or = isset($PHPconfig["water"]) && $PHPconfig["water"] && file_exists($PHPconfig["water"]) && is_numeric($PHPconfig["water_scope"]); + if (!file_exists($PHPconfig["saveURL"])) { + if (!mkdir($PHPconfig["saveURL"], 0777, true)) { + $this->erro = "目录权限有问题"; + return false; + } + } + $file_url = $PHPconfig["saveURL"] . $PHPconfig["filename"]; + foreach ($PHPconfig["width"] as $k => $v) { + ($v[0] == 0) ? ($v[0] = $JSconfig["FW"]) : ($v[0] == -1) and ($v[0] = $JSconfig["IW"]); + $height = $v[0] / $proportion; + $suffix = isset($v[2]) ? $v[2] : "0"; + $arr[$k] = array( + $v[0], + $height, + $file_url . $suffix, + ($v[1] === true and $water_or === true and $v[0] > $PHPconfig["water_scope"] and $height > $PHPconfig["water_scope"]) + ); + } + return array( + $water_or, + $arr + ); + } + + protected function compression($DigShear, &$PHPconfig, &$JSconfig, $type, $GdFun) + { + require 'zip_img.php'; + $arrimg = $this->CreateArray($PHPconfig, $JSconfig); + if (count($arrimg[1]) < 1) { + $this->erro = "系统没有检测到处理截图的命令!"; + return false; + } + $arrimg[0] and $arrimg[0] = $PHPconfig["water"]; + $zip_photo = new zip_img(array( + "dest" => $DigShear, + "GdFun" => $GdFun, + "quality" => isset($PHPconfig["quality"]) ? $PHPconfig["quality"] : false, + "force_jpg" => isset($PHPconfig["force_jpg"]) && $PHPconfig["force_jpg"], + "water" => $arrimg[0], + "water_scope" => $PHPconfig["water_scope"], + "w" => $JSconfig["IW"], + "h" => $JSconfig["IH"], + "type" => $type, + "zip_array" => $arrimg[1] + )); + return $zip_photo->run($this); + } +} + +/*........................普通截取时开始..........................*/ + +if (isset($_POST["JSdate"])) {//普通截取时 + $ShearPhoto["JSdate"] = json_decode(trim(stripslashes($_POST["JSdate"])), true); + $Shear = new ShearPhoto; //类实例开始 + if(stripos($ShearPhoto['JSdate']['url'],'file/photo/')){ + $ShearPhoto['JSdate']['url']=IOURL.'/file/photo/' . basename($ShearPhoto['JSdate']['url']); + }else{ + $ShearPhoto['JSdate']['url']=$ShearPhoto['config']['temp'] . DIRECTORY_SEPARATOR . basename($ShearPhoto['JSdate']['url']); + } + $result = $Shear->run($ShearPhoto["JSdate"], $ShearPhoto["config"]); //传入参数运行 + if ($result === false) { //切图失败时 + echo '{"erro":"' . $Shear->erro . '"}'; //把错误发给JS /请匆随意更改"erro"的编写方式,否则JS出错 + exit; + } else { + $dirname = pathinfo($ShearPhoto["JSdate"]["url"]); + $ShearPhotodirname = $dirname["dirname"] . DIRECTORY_SEPARATOR . "shearphoto.lock"; //认证删除的密钥 + file_exists($ShearPhotodirname) && @unlink($ShearPhoto["JSdate"]["url"]); //密钥存在,当然就删掉原图 + } + +} + +/*........................普通截取时结束..........................*/ + + + elseif (isset($_POST["ShearPhotoIW"]) && + isset($_POST["ShearPhotoIH"]) && + isset($_POST["ShearPhotoFW"]) && + isset($_POST["ShearPhotoFH"]) && + isset($_POST["ShearPhotoP"]) && + is_numeric($JSconfig["P"]=trim($_POST["ShearPhotoP"]))&& + is_numeric($JSconfig["IW"]=trim($_POST["ShearPhotoIW"]))&& + is_numeric($JSconfig["IH"]=trim($_POST["ShearPhotoIH"]))&& + is_numeric($JSconfig["FW"]=trim($_POST["ShearPhotoFW"]))&& + is_numeric($JSconfig["FH"]=trim($_POST["ShearPhotoFH"]))){ + $Shear = new ShearPhoto; //类实例开始 + $result =$Shear->html5_run($ShearPhoto["config"],$JSconfig);//加载HTML5已切好的图片独有方法 + if ($result === false) { //切图失败时 + echo '{"erro":"' . $Shear->erro . '"}'; //把错误发给JS /请匆随意更改"erro"的编写方式,否则JS出错 + exit; + } + } + /*........................HTML5截取时结束..........................*/ + + /*........错误的操作................*/ + else {die('{"erro":"错误的操作!或缺少参数或错误参数"}');} + /*........错误的操作................*/ + + + $str_result = json_encode($result); + echo str_replace("\/", "/", $str_result); //去掉无用的字符修正URL地址,再把数据传弟给JS +?> diff --git a/public/shearphoto/php/shearphoto.up.php b/public/shearphoto/php/shearphoto.up.php new file mode 100644 index 0000000..8ae135b --- /dev/null +++ b/public/shearphoto/php/shearphoto.up.php @@ -0,0 +1,59 @@ + 2 * 1024 * 1024, //文件大小限制设置 M单位 + 'out_time' => 60, //上传超时设置 + 'list' => $ShearPhoto["config"]["temp"].DIRECTORY_SEPARATOR, //上传路径 + 'whitelist' => array( + ".jpeg", + ".gif", + ".png", + ".jpg")//上传的文件后缀 + ); +/*设置部份结束*/ +ini_set('max_execution_time', $ini_set['out_time']); +function HandleError($erro = '系统错误') { + die('{"erro":"'.$erro.'"}'); +} +if (!isset($_FILES['UpFile'])) { + HandleError(); +} +if (isset($_FILES['UpFile']['error']) && $_FILES['UpFile']['error'] != 0) { + $uploadErrors = array( + 0 => '文件上传成功', + 1 => '上传的文件超过了 php.ini 文件中的 upload_max_filesize directive 里的设置', + 2 => '上传的文件超过了 HTML form 文件中的 MAX_FILE_SIZE directive 里的设置', + 3 => '上传的文件仅为部分文件', + 4 => '没有文件上传', + 6 => '缺少临时文件夹' + ); + HandleError($uploadErrors[$_FILES['UpFile']['error']]); +} +if (!isset($_FILES['UpFile']['tmp_name']) || !@is_uploaded_file($_FILES['UpFile']['tmp_name'])) { + HandleError('无法找到上传的文件,上传失败'); + } +if (!isset($_FILES['UpFile']['name'])) { + HandleError('上传空名字文件名'); +} +$POST_MAX_SIZE = ini_get('post_max_size'); +$unit = strtoupper(substr($POST_MAX_SIZE, -1)); +$multiplier = $unit == 'M' ? 1048576 : ($unit == 'K' ? 1024 : ($unit == 'G' ? 1073741824 : 1)); +if ((int)$_SERVER['CONTENT_LENGTH'] > $multiplier * (int)$POST_MAX_SIZE && $POST_MAX_SIZE) { + HandleError('超过POST_MAX_SIZE的设置值,请查看PHP.ini的设置'); +} +$file_size = @filesize($_FILES['UpFile']['tmp_name']); +if (!$file_size || $file_size > $ini_set['max_size']) { + HandleError('零字节文件 或 上传的文件已经超过所设置最大值'); +} +$UpFile = array(); +$int_type = getimagesize($_FILES['UpFile']['tmp_name']); +$str_type = image_type_to_extension($int_type[2]); +if (!in_array(strtolower($str_type) , $ini_set['whitelist'])) { + HandleError('不允许上传此类型文件'); +} +$str_type==".jpeg" && ($str_type=".jpg"); +$UpFile['filename']=uniqid("temp_")."_".mt_rand(100,999).$str_type; + +$UpFile['file_url'] = $ini_set['list'] . $UpFile['filename']; + +file_exists($ini_set['list']) or @mkdir($ini_set['list'], 511,true); +?> \ No newline at end of file diff --git a/public/shearphoto/php/upload.php b/public/shearphoto/php/upload.php new file mode 100644 index 0000000..35d219a --- /dev/null +++ b/public/shearphoto/php/upload.php @@ -0,0 +1,13 @@ + \ No newline at end of file diff --git a/public/shearphoto/php/zip_img.php b/public/shearphoto/php/zip_img.php new file mode 100644 index 0000000..5e60183 --- /dev/null +++ b/public/shearphoto/php/zip_img.php @@ -0,0 +1,76 @@ +arg = $arg; + if ($arg["force_jpg"]) { + $this->quality = $arg["quality"]; + $this->GDfun = array( + "imagejpeg", + ".jpg" + ); + } else { + $this->GDfun = $arg["GdFun"]; + $this->quality =0; + } + if ($arg["water"]) { + list($W, $H, $type) = @getimagesize($arg["water"]); + if ($type == 3) { + $this->waterimg = array( + imagecreatefrompng($arg["water"]) , + $W, + $H + ); + } + } + } + protected function zip_img($dest, $width, $height, $save_url, $water) { + $createsrc = imagecreatetruecolor($width, $height); + imagecopyresampled($createsrc, $dest, 0, 0, 0, 0, $width, $height, $this->arg["w"], $this->arg["h"]); + $water and $createsrc = $this->add_water($createsrc, $width, $height); + return $this->saveimg($createsrc, $save_url, $width, $height); + } + protected function add_water($src, $width, $height) { + imagecopy($src, $this->waterimg[0], $width - $this->waterimg[1] - 10, $height - $this->waterimg[2] - 10, 0, 0, $this->waterimg[1], $this->waterimg[2]); + return $src; + } + protected function saveimg($createsrc, $save_url, $width, $height) { + $save_url.= $this->GDfun[1]; + $GDW = $this->quality ? @call_user_func($this->GDfun[0], $createsrc, $save_url, $this->quality) : @call_user_func($this->GDfun[0], $createsrc, $save_url); + imagedestroy($createsrc); + array_push($this->result, array( + "ImgUrl" => str_replace(array( + ShearURL, + "\\" + ) , array( + "", + "/" + ) , $save_url) , + "ImgName" => basename($save_url) , + "ImgWidth" => $width, + "ImgHeight" => $height + )); + return $GDW; + } + final function __destruct() { + @imagedestroy($this->arg["dest"]); + $this->waterimg[0] and @imagedestroy($this->waterimg[0]); + } + public function run($this_) { + $dest = $this->arg["dest"]; + $zip_array = $this->arg["zip_array"]; + foreach ($zip_array as $k => $v) { + list($width, $height, $save_url, $water) = $v; + if (!$this->zip_img($dest, $width, $height, $save_url, $this->waterimg and $water)) { + $this_->erro = "后端获取不到文件写入权限。原因:" . $this->GDfun[0] . "()函数-无法写入文件"; + return false; + } + } + return $this->result; + } +} +?> diff --git a/public/sldate/daterangepicker-bs3.css b/public/sldate/daterangepicker-bs3.css new file mode 100644 index 0000000..22433b4 --- /dev/null +++ b/public/sldate/daterangepicker-bs3.css @@ -0,0 +1,281 @@ +/*! + * Stylesheet for the Date Range Picker, for use with Bootstrap 3.x + * + * Copyright 2013 Dan Grossman ( http://www.dangrossman.info ) + * Licensed under the Apache License v2.0 + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Built for http://www.improvely.com + */ +.clear{ clear:both;} + .daterangepicker.dropdown-menu { + max-width: none; + z-index: 3000; +} + +.daterangepicker.opensleft .ranges, .daterangepicker.opensleft .calendar { + float: left; + margin: 4px; +} + +/*.daterangepicker.opensright .ranges, .daterangepicker.opensright .calendar {*/ +.daterangepicker.opensright .calendar { + float: right; + margin: 4px; +} + +.daterangepicker .ranges { + margin:4px; + text-align: left; + padding:0px 20px; +} + +.daterangepicker .ranges .range_inputs>div { + float: left; +} + +.daterangepicker .ranges .range_inputs>div:nth-child(2) { + padding-left: 11px; +} + +.daterangepicker .ranges .range_inputs .applyBtn{ + float:right; +} +.daterangepicker .ranges .range_inputs .cancelBtn{ + float:right;margin-right:10px; +} + + +.daterangepicker .calendar { + display: none; + max-width: 290px; +} + +.daterangepicker.show-calendar .calendar { + display: block; +} + +.daterangepicker .calendar.single .calendar-date { + border: none; +} + +.daterangepicker .calendar th, .daterangepicker .calendar td { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; + white-space: nowrap; + text-align: center; + min-width: 32px; +} + +.daterangepicker .daterangepicker_start_input label, +.daterangepicker .daterangepicker_end_input label { + color: #333; + display: block; + font-size: 11px; + font-weight: normal; + height: 20px; + line-height: 20px; + margin-bottom: 2px; + text-shadow: #fff 1px 1px 0px; + text-transform: uppercase; + padding-right:10px; + float:left; + height:34px; + line-height:34px; +} + +.daterangepicker .ranges input { + font-size: 11px; +} + +.daterangepicker .ranges .input-mini { + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; + color: #555; + display: block; + font-size: 11px; + height: 34px; + line-height: 34px; + vertical-align: middle; + margin: 0 0 10px 0; + padding: 0 6px; + width: 84px; + float:left; +} + +.daterangepicker .ranges ul { + list-style: none; + margin: 0; + padding: 0; +} + +.daterangepicker .ranges li { + font-size: 13px; + background: #f5f5f5; + border: 1px solid #f5f5f5; + color: #08c; + padding: 3px 12px; + margin-bottom: 8px; + -webkit-border-radius: 5px; + -moz-border-radius: 5px; + border-radius: 5px; + cursor: pointer; +} + +.daterangepicker .ranges li.active, .daterangepicker .ranges li:hover { + background: #08c; + border: 1px solid #08c; + color: #fff; +} + +.daterangepicker .calendar-date { + border: 1px solid #ddd; + padding: 4px; + border-radius: 4px; + background: #fff; +} + +.daterangepicker .calendar-time { + text-align: center; + margin: 8px auto 0 auto; + line-height: 30px; +} + +.daterangepicker { + position: absolute; + background: #fff; + top: 100px; + left: 20px; + padding: 4px; + margin-top: 1px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; +} + +.daterangepicker.opensleft:before { + position: absolute; + top: -7px; + right: 9px; + display: inline-block; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-left: 7px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.daterangepicker.opensleft:after { + position: absolute; + top: -6px; + right: 10px; + display: inline-block; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-left: 6px solid transparent; + content: ''; +} + +.daterangepicker.opensright:before { + position: absolute; + top: -7px; + left: 9px; + display: inline-block; + border-right: 7px solid transparent; + border-bottom: 7px solid #ccc; + border-left: 7px solid transparent; + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; +} + +.daterangepicker.opensright:after { + position: absolute; + top: -6px; + left: 10px; + display: inline-block; + border-right: 6px solid transparent; + border-bottom: 6px solid #fff; + border-left: 6px solid transparent; + content: ''; +} + +.daterangepicker table { + width: 100%; + margin: 0; +} + +.daterangepicker td, .daterangepicker th { + text-align: center; + width: 20px; + height: 20px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + cursor: pointer; + white-space: nowrap; +} + +.daterangepicker td.off { + color: #999; +} + +.daterangepicker td.disabled { + color: #999; +} + +.daterangepicker td.available:hover, .daterangepicker th.available:hover { + background: #eee; +} + +.daterangepicker td.in-range { + background: #ebf4f8; + -webkit-border-radius: 0; + -moz-border-radius: 0; + border-radius: 0; +} + +.daterangepicker td.active, .daterangepicker td.active:hover { + background-color: #357ebd; + border-color: #3071a9; + color: #fff; +} + +.daterangepicker td.week, .daterangepicker th.week { + font-size: 80%; + color: #ccc; +} + +.daterangepicker select.monthselect, .daterangepicker select.yearselect { + font-size: 12px; + padding: 1px; + height: auto; + margin: 0; + cursor: default; +} + +.daterangepicker select.monthselect { + margin-right: 2%; + width: 56%; +} + +.daterangepicker select.yearselect { + width: 40%; +} + +.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.ampmselect { + width: 50px; + margin-bottom: 0; +} + +.daterangepicker_start_input { + float: left; +} + +.daterangepicker_end_input { + float: left; + padding-left: 11px +} + +.daterangepicker th.month { + width: auto; +} diff --git a/public/sldate/daterangepicker.js b/public/sldate/daterangepicker.js new file mode 100644 index 0000000..fc2e5df --- /dev/null +++ b/public/sldate/daterangepicker.js @@ -0,0 +1,1019 @@ +/** +* @version: 1.3.6 +* @author: Dan Grossman http://www.dangrossman.info/ +* @date: 2014-04-29 +* @copyright: Copyright (c) 2012-2014 Dan Grossman. All rights reserved. +* @license: Licensed under Apache License v2.0. See http://www.apache.org/licenses/LICENSE-2.0 +* @website: http://www.improvely.com/ +*/ +!function ($, moment) { + + var DateRangePicker = function (element, options, cb) { + + // by default, the daterangepicker element is placed at the bottom of HTML body + this.parentEl = 'body'; + + //element that triggered the date range picker + this.element = $(element); + + //create the picker HTML object + var DRPTemplate = ''; + + //custom options + if (typeof options !== 'object' || options === null) + options = {}; + + this.parentEl = (typeof options === 'object' && options.parentEl && $(options.parentEl).length) ? $(options.parentEl) : $(this.parentEl); + this.container = $(DRPTemplate).appendTo(this.parentEl); + + this.setOptions(options, cb); + + //apply CSS classes and labels to buttons + var c = this.container; + $.each(this.buttonClasses, function (idx, val) { + c.find('button').addClass(val); + }); + this.container.find('.daterangepicker_start_input label').html(this.locale.fromLabel); + this.container.find('.daterangepicker_end_input label').html(this.locale.toLabel); + if (this.applyClass.length) + this.container.find('.applyBtn').addClass(this.applyClass); + if (this.cancelClass.length) + this.container.find('.cancelBtn').addClass(this.cancelClass); + this.container.find('.applyBtn').html(this.locale.applyLabel); + this.container.find('.cancelBtn').html(this.locale.cancelLabel); + + //event listeners + + this.container.find('.calendar') + .on('click.daterangepicker', '.prev', $.proxy(this.clickPrev, this)) + .on('click.daterangepicker', '.next', $.proxy(this.clickNext, this)) + .on('click.daterangepicker', 'td.available', $.proxy(this.clickDate, this)) + .on('mouseenter.daterangepicker', 'td.available', $.proxy(this.enterDate, this)) + .on('mouseleave.daterangepicker', 'td.available', $.proxy(this.updateFormInputs, this)) + .on('change.daterangepicker', 'select.yearselect', $.proxy(this.updateMonthYear, this)) + .on('change.daterangepicker', 'select.monthselect', $.proxy(this.updateMonthYear, this)) + .on('change.daterangepicker', 'select.hourselect,select.minuteselect,select.ampmselect', $.proxy(this.updateTime, this)); + + this.container.find('.ranges') + .on('click.daterangepicker', 'button.applyBtn', $.proxy(this.clickApply, this)) + .on('click.daterangepicker', 'button.cancelBtn', $.proxy(this.clickCancel, this)) + .on('click.daterangepicker', '.daterangepicker_start_input,.daterangepicker_end_input', $.proxy(this.showCalendars, this)) + .on('click.daterangepicker', 'li', $.proxy(this.clickRange, this)) + .on('mouseenter.daterangepicker', 'li', $.proxy(this.enterRange, this)) + .on('mouseleave.daterangepicker', 'li', $.proxy(this.updateFormInputs, this)); + + if (this.element.is('input')) { + this.element.on({ + 'click.daterangepicker': $.proxy(this.show, this), + 'focus.daterangepicker': $.proxy(this.show, this), + 'keyup.daterangepicker': $.proxy(this.updateFromControl, this) + }); + } else { + this.element.on('click.daterangepicker', $.proxy(this.toggle, this)); + } + + }; + + DateRangePicker.prototype = { + + constructor: DateRangePicker, + + setOptions: function(options, callback) { + + this.startDate = moment().startOf('day'); + this.endDate = moment().endOf('day'); + this.minDate = false; + this.maxDate = false; + this.dateLimit = false; + + this.showDropdowns = false; + this.showWeekNumbers = false; + this.timePicker = false; + this.timePickerIncrement = 30; + this.timePicker12Hour = true; + this.singleDatePicker = false; + this.ranges = {}; + + this.opens = 'right'; + if (this.element.hasClass('pull-right')) + this.opens = 'left'; + + this.buttonClasses = ['btn', 'btn-small']; + this.applyClass = 'btn-success'; + this.cancelClass = 'btn-default'; + + this.format = 'YYYY-MM-DD'; + this.separator = ' - '; + + this.locale = { + applyLabel: '确定', + cancelLabel: '取消', + fromLabel: '从', + toLabel: '到', + weekLabel: 'W', + customRangeLabel: 'Custom Range', + daysOfWeek: moment()._lang._weekdaysMin.slice(), + monthNames: moment()._lang._monthsShort.slice(), + firstDay: 0 + }; + + this.cb = function () { }; + + if (typeof options.format === 'string') + this.format = options.format; + + if (typeof options.separator === 'string') + this.separator = options.separator; + + if (typeof options.startDate === 'string') + this.startDate = moment(options.startDate, this.format); + + if (typeof options.endDate === 'string') + this.endDate = moment(options.endDate, this.format); + + if (typeof options.minDate === 'string') + this.minDate = moment(options.minDate, this.format); + + if (typeof options.maxDate === 'string') + this.maxDate = moment(options.maxDate, this.format); + + if (typeof options.startDate === 'object') + this.startDate = moment(options.startDate); + + if (typeof options.endDate === 'object') + this.endDate = moment(options.endDate); + + if (typeof options.minDate === 'object') + this.minDate = moment(options.minDate); + + if (typeof options.maxDate === 'object') + this.maxDate = moment(options.maxDate); + + if (typeof options.applyClass === 'string') + this.applyClass = options.applyClass; + + if (typeof options.cancelClass === 'string') + this.cancelClass = options.cancelClass; + + if (typeof options.dateLimit === 'object') + this.dateLimit = options.dateLimit; + + // update day names order to firstDay + if (typeof options.locale === 'object') { + + if (typeof options.locale.daysOfWeek === 'object') { + // Create a copy of daysOfWeek to avoid modification of original + // options object for reusability in multiple daterangepicker instances + this.locale.daysOfWeek = options.locale.daysOfWeek.slice(); + } + + if (typeof options.locale.monthNames === 'object') { + this.locale.monthNames = options.locale.monthNames.slice(); + } + + if (typeof options.locale.firstDay === 'number') { + this.locale.firstDay = options.locale.firstDay; + var iterator = options.locale.firstDay; + while (iterator > 0) { + this.locale.daysOfWeek.push(this.locale.daysOfWeek.shift()); + iterator--; + } + } + + if (typeof options.locale.applyLabel === 'string') { + this.locale.applyLabel = options.locale.applyLabel; + } + + if (typeof options.locale.cancelLabel === 'string') { + this.locale.cancelLabel = options.locale.cancelLabel; + } + + if (typeof options.locale.fromLabel === 'string') { + this.locale.fromLabel = options.locale.fromLabel; + } + + if (typeof options.locale.toLabel === 'string') { + this.locale.toLabel = options.locale.toLabel; + } + + if (typeof options.locale.weekLabel === 'string') { + this.locale.weekLabel = options.locale.weekLabel; + } + + if (typeof options.locale.customRangeLabel === 'string') { + this.locale.customRangeLabel = options.locale.customRangeLabel; + } + } + + if (typeof options.opens === 'string') + this.opens = options.opens; + + if (typeof options.showWeekNumbers === 'boolean') { + this.showWeekNumbers = options.showWeekNumbers; + } + + if (typeof options.buttonClasses === 'string') { + this.buttonClasses = [options.buttonClasses]; + } + + if (typeof options.buttonClasses === 'object') { + this.buttonClasses = options.buttonClasses; + } + + if (typeof options.showDropdowns === 'boolean') { + this.showDropdowns = options.showDropdowns; + } + + if (typeof options.singleDatePicker === 'boolean') { + this.singleDatePicker = options.singleDatePicker; + } + + if (typeof options.timePicker === 'boolean') { + this.timePicker = options.timePicker; + } + + if (typeof options.timePickerIncrement === 'number') { + this.timePickerIncrement = options.timePickerIncrement; + } + + if (typeof options.timePicker12Hour === 'boolean') { + this.timePicker12Hour = options.timePicker12Hour; + } + + var start, end, range; + + //if no start/end dates set, check if an input element contains initial values + if (typeof options.startDate === 'undefined' && typeof options.endDate === 'undefined') { + if ($(this.element).is('input[type=text]')) { + var val = $(this.element).val(); + var split = val.split(this.separator); + start = end = null; + if (split.length == 2) { + start = moment(split[0], this.format); + end = moment(split[1], this.format); + } else if (this.singleDatePicker) { + start = moment(val, this.format); + end = moment(val, this.format); + } + if (start !== null && end !== null) { + this.startDate = start; + this.endDate = end; + } + } + } + + if (typeof options.ranges === 'object') { + for (range in options.ranges) { + + start = moment(options.ranges[range][0]); + end = moment(options.ranges[range][1]); + + // If we have a min/max date set, bound this range + // to it, but only if it would otherwise fall + // outside of the min/max. + if (this.minDate && start.isBefore(this.minDate)) + start = moment(this.minDate); + + if (this.maxDate && end.isAfter(this.maxDate)) + end = moment(this.maxDate); + + // If the end of the range is before the minimum (if min is set) OR + // the start of the range is after the max (also if set) don't display this + // range option. + if ((this.minDate && end.isBefore(this.minDate)) || (this.maxDate && start.isAfter(this.maxDate))) { + continue; + } + + this.ranges[range] = [start, end]; + } + + var list = '
    '; + for (range in this.ranges) { + list += '
  • ' + range + '
  • '; + } + list += '
  • ' + this.locale.customRangeLabel + '
  • '; + list += '
'; + this.container.find('.ranges ul').remove(); + this.container.find('.ranges').prepend(list); + } + + if (typeof callback === 'function') { + this.cb = callback; + } + + if (!this.timePicker) { + this.startDate = this.startDate.startOf('day'); + this.endDate = this.endDate.endOf('day'); + } + + if (this.singleDatePicker) { + this.opens = 'right'; + this.container.find('.calendar.right').show(); + this.container.find('.calendar.left').hide(); + this.container.find('.ranges').hide(); + if (!this.container.find('.calendar.right').hasClass('single')) + this.container.find('.calendar.right').addClass('single'); + } else { + this.container.find('.calendar.right').removeClass('single'); + this.container.find('.ranges').show(); + } + + this.oldStartDate = this.startDate.clone(); + this.oldEndDate = this.endDate.clone(); + this.oldChosenLabel = this.chosenLabel; + + this.leftCalendar = { + month: moment([this.startDate.year(), this.startDate.month(), 1, this.startDate.hour(), this.startDate.minute()]), + calendar: [] + }; + + this.rightCalendar = { + month: moment([this.endDate.year(), this.endDate.month(), 1, this.endDate.hour(), this.endDate.minute()]), + calendar: [] + }; + + if (this.opens == 'right') { + //swap calendar positions + var left = this.container.find('.calendar.left'); + var right = this.container.find('.calendar.right'); + left.removeClass('left').addClass('right'); + right.removeClass('right').addClass('left'); + } + + if (typeof options.ranges === 'undefined' && !this.singleDatePicker) { + this.container.addClass('show-calendar'); + } + + this.container.addClass('opens' + this.opens); + + this.updateView(); + this.updateCalendars(); + + }, + + setStartDate: function(startDate) { + if (typeof startDate === 'string') + this.startDate = moment(startDate, this.format); + + if (typeof startDate === 'object') + this.startDate = moment(startDate); + + if (!this.timePicker) + this.startDate = this.startDate.startOf('day'); + + this.oldStartDate = this.startDate.clone(); + + this.updateView(); + this.updateCalendars(); + }, + + setEndDate: function(endDate) { + if (typeof endDate === 'string') + this.endDate = moment(endDate, this.format); + + if (typeof endDate === 'object') + this.endDate = moment(endDate); + + if (!this.timePicker) + this.endDate = this.endDate.endOf('day'); + + this.oldEndDate = this.endDate.clone(); + + this.updateView(); + this.updateCalendars(); + }, + + updateView: function () { + this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()); + this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()); + this.updateFormInputs(); + }, + + updateFormInputs: function () { + this.container.find('input[name=daterangepicker_start]').val(this.startDate.format(this.format)); + this.container.find('input[name=daterangepicker_end]').val(this.endDate.format(this.format)); + + if (this.startDate.isSame(this.endDate) || this.startDate.isBefore(this.endDate)) { + this.container.find('button.applyBtn').removeAttr('disabled'); + } else { + this.container.find('button.applyBtn').attr('disabled', 'disabled'); + } + }, + + updateFromControl: function () { + if (!this.element.is('input')) return; + if (!this.element.val().length) return; + + var dateString = this.element.val().split(this.separator), + start = null, + end = null; + + if(dateString.length === 2) { + start = moment(dateString[0], this.format); + end = moment(dateString[1], this.format); + } + + if (this.singleDatePicker || start === null || end === null) { + start = moment(this.element.val(), this.format); + end = start; + } + + if (end.isBefore(start)) return; + + this.oldStartDate = this.startDate.clone(); + this.oldEndDate = this.endDate.clone(); + + this.startDate = start; + this.endDate = end; + + if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate)) + this.notify(); + + this.updateCalendars(); + }, + + notify: function () { + this.updateView(); + this.cb(this.startDate, this.endDate, this.chosenLabel); + }, + + move: function () { + var parentOffset = { top: 0, left: 0 }; + if (!this.parentEl.is('body')) { + parentOffset = { + top: this.parentEl.offset().top - this.parentEl.scrollTop(), + left: this.parentEl.offset().left - this.parentEl.scrollLeft() + }; + } + + if (this.opens == 'left') { + this.container.css({ + top: this.element.offset().top + this.element.outerHeight() - parentOffset.top, + right: $(window).width() - this.element.offset().left - this.element.outerWidth() - parentOffset.left, + left: 'auto' + }); + if (this.container.offset().left < 0) { + this.container.css({ + right: 'auto', + left: 9 + }); + } + } else { + this.container.css({ + top: this.element.offset().top + this.element.outerHeight() - parentOffset.top, + left: this.element.offset().left - parentOffset.left, + right: 'auto' + }); + if (this.container.offset().left + this.container.outerWidth() > $(window).width()) { + this.container.css({ + left: 'auto', + right: 0 + }); + } + } + }, + + toggle: function (e) { + if (this.element.hasClass('active')) { + this.hide(); + } else { + this.show(); + } + }, + + show: function (e) { + this.element.addClass('active'); + this.container.show(); + this.move(); + + $(document).on('click.daterangepicker', $.proxy(this.outsideClick, this)); + // also explicitly play nice with Bootstrap dropdowns, which stopPropagation when clicking them + $(document).on('click.daterangepicker', '[data-toggle=dropdown]', $.proxy(this.outsideClick, this)); + + this.element.trigger('show.daterangepicker', this); + }, + + outsideClick: function (e) { + var target = $(e.target); + // if the page is clicked anywhere except within the daterangerpicker/button + // itself then call this.hide() + if ( + target.closest(this.element).length || + target.closest(this.container).length || + target.closest('.calendar-date').length + ) return; + this.hide(); + }, + + hide: function (e) { + $(document).off('click.daterangepicker', this.outsideClick); + + this.element.removeClass('active'); + this.container.hide(); + + if (!this.startDate.isSame(this.oldStartDate) || !this.endDate.isSame(this.oldEndDate)) + this.notify(); + + this.oldStartDate = this.startDate.clone(); + this.oldEndDate = this.endDate.clone(); + + this.element.trigger('hide.daterangepicker', this); + }, + + enterRange: function (e) { + // mouse pointer has entered a range label + var label = e.target.innerHTML; + if (label == this.locale.customRangeLabel) { + this.updateView(); + } else { + var dates = this.ranges[label]; + this.container.find('input[name=daterangepicker_start]').val(dates[0].format(this.format)); + this.container.find('input[name=daterangepicker_end]').val(dates[1].format(this.format)); + } + }, + + showCalendars: function() { + this.container.addClass('show-calendar'); + this.move(); + }, + + hideCalendars: function() { + this.container.removeClass('show-calendar'); + }, + + updateInputText: function() { + if (this.element.is('input') && !this.singleDatePicker) { + this.element.val(this.startDate.format(this.format) + this.separator + this.endDate.format(this.format)); + } else if (this.element.is('input')) { + this.element.val(this.startDate.format(this.format)); + } + }, + + clickRange: function (e) { + var label = e.target.innerHTML; + this.chosenLabel = label; + if (label == this.locale.customRangeLabel) { + this.showCalendars(); + } else { + var dates = this.ranges[label]; + + this.startDate = dates[0]; + this.endDate = dates[1]; + + if (!this.timePicker) { + this.startDate.startOf('day'); + this.endDate.endOf('day'); + } + + this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()).hour(this.startDate.hour()).minute(this.startDate.minute()); + this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()).hour(this.endDate.hour()).minute(this.endDate.minute()); + this.updateCalendars(); + + this.updateInputText(); + + this.hideCalendars(); + this.hide(); + this.element.trigger('apply.daterangepicker', this); + } + }, + + clickPrev: function (e) { + var cal = $(e.target).parents('.calendar'); + if (cal.hasClass('left')) { + this.leftCalendar.month.subtract('month', 1); + } else { + this.rightCalendar.month.subtract('month', 1); + } + this.updateCalendars(); + }, + + clickNext: function (e) { + var cal = $(e.target).parents('.calendar'); + if (cal.hasClass('left')) { + this.leftCalendar.month.add('month', 1); + } else { + this.rightCalendar.month.add('month', 1); + } + this.updateCalendars(); + }, + + enterDate: function (e) { + + var title = $(e.target).attr('data-title'); + var row = title.substr(1, 1); + var col = title.substr(3, 1); + var cal = $(e.target).parents('.calendar'); + + if (cal.hasClass('left')) { + this.container.find('input[name=daterangepicker_start]').val(this.leftCalendar.calendar[row][col].format(this.format)); + } else { + this.container.find('input[name=daterangepicker_end]').val(this.rightCalendar.calendar[row][col].format(this.format)); + } + + }, + + clickDate: function (e) { + var title = $(e.target).attr('data-title'); + var row = title.substr(1, 1); + var col = title.substr(3, 1); + var cal = $(e.target).parents('.calendar'); + + var startDate, endDate; + if (cal.hasClass('left')) { + startDate = this.leftCalendar.calendar[row][col]; + endDate = this.endDate; + if (typeof this.dateLimit === 'object') { + var maxDate = moment(startDate).add(this.dateLimit).startOf('day'); + if (endDate.isAfter(maxDate)) { + endDate = maxDate; + } + } + } else { + startDate = this.startDate; + endDate = this.rightCalendar.calendar[row][col]; + if (typeof this.dateLimit === 'object') { + var minDate = moment(endDate).subtract(this.dateLimit).startOf('day'); + if (startDate.isBefore(minDate)) { + startDate = minDate; + } + } + } + + if (this.singleDatePicker && cal.hasClass('left')) { + endDate = startDate.clone(); + } else if (this.singleDatePicker && cal.hasClass('right')) { + startDate = endDate.clone(); + } + + cal.find('td').removeClass('active'); + + if (startDate.isSame(endDate) || startDate.isBefore(endDate)) { + $(e.target).addClass('active'); + this.startDate = startDate; + this.endDate = endDate; + this.chosenLabel = this.locale.customRangeLabel; + } else if (startDate.isAfter(endDate)) { + $(e.target).addClass('active'); + var difference = this.endDate.diff(this.startDate); + this.startDate = startDate; + this.endDate = moment(startDate).add('ms', difference); + this.chosenLabel = this.locale.customRangeLabel; + } + + this.leftCalendar.month.month(this.startDate.month()).year(this.startDate.year()); + this.rightCalendar.month.month(this.endDate.month()).year(this.endDate.year()); + this.updateCalendars(); + + if (!this.timePicker) + endDate.endOf('day'); + + if (this.singleDatePicker) + this.clickApply(); + }, + + clickApply: function (e) { + this.updateInputText(); + this.hide(); + this.element.trigger('apply.daterangepicker', this); + }, + + clickCancel: function (e) { + this.startDate = this.oldStartDate; + this.endDate = this.oldEndDate; + this.chosenLabel = this.oldChosenLabel; + this.updateView(); + this.updateCalendars(); + this.hide(); + this.element.trigger('cancel.daterangepicker', this); + }, + + updateMonthYear: function (e) { + var isLeft = $(e.target).closest('.calendar').hasClass('left'), + leftOrRight = isLeft ? 'left' : 'right', + cal = this.container.find('.calendar.'+leftOrRight); + + // Month must be Number for new moment versions + var month = parseInt(cal.find('.monthselect').val(), 10); + var year = cal.find('.yearselect').val(); + + this[leftOrRight+'Calendar'].month.month(month).year(year); + this.updateCalendars(); + }, + + updateTime: function(e) { + var isLeft = $(e.target).closest('.calendar').hasClass('left'), + leftOrRight = isLeft ? 'left' : 'right', + cal = this.container.find('.calendar.'+leftOrRight); + + var hour = parseInt(cal.find('.hourselect').val(), 10); + var minute = parseInt(cal.find('.minuteselect').val(), 10); + + if (this.timePicker12Hour) { + var ampm = cal.find('.ampmselect').val(); + if (ampm === 'PM' && hour < 12) + hour += 12; + if (ampm === 'AM' && hour === 12) + hour = 0; + } + + if (isLeft) { + var start = this.startDate.clone(); + start.hour(hour); + start.minute(minute); + this.startDate = start; + this.leftCalendar.month.hour(hour).minute(minute); + } else { + var end = this.endDate.clone(); + end.hour(hour); + end.minute(minute); + this.endDate = end; + this.rightCalendar.month.hour(hour).minute(minute); + } + + this.updateCalendars(); + }, + + updateCalendars: function () { + this.leftCalendar.calendar = this.buildCalendar(this.leftCalendar.month.month(), this.leftCalendar.month.year(), this.leftCalendar.month.hour(), this.leftCalendar.month.minute(), 'left'); + this.rightCalendar.calendar = this.buildCalendar(this.rightCalendar.month.month(), this.rightCalendar.month.year(), this.rightCalendar.month.hour(), this.rightCalendar.month.minute(), 'right'); + this.container.find('.calendar.left').empty().html(this.renderCalendar(this.leftCalendar.calendar, this.startDate, this.minDate, this.maxDate)); + this.container.find('.calendar.right').empty().html(this.renderCalendar(this.rightCalendar.calendar, this.endDate, this.startDate, this.maxDate)); + + this.container.find('.ranges li').removeClass('active'); + var customRange = true; + var i = 0; + for (var range in this.ranges) { + if (this.timePicker) { + if (this.startDate.isSame(this.ranges[range][0]) && this.endDate.isSame(this.ranges[range][1])) { + customRange = false; + this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')') + .addClass('active').html(); + } + } else { + //ignore times when comparing dates if time picker is not enabled + if (this.startDate.format('YYYY-MM-DD') == this.ranges[range][0].format('YYYY-MM-DD') && this.endDate.format('YYYY-MM-DD') == this.ranges[range][1].format('YYYY-MM-DD')) { + customRange = false; + this.chosenLabel = this.container.find('.ranges li:eq(' + i + ')') + .addClass('active').html(); + } + } + i++; + } + if (customRange) { + this.chosenLabel = this.container.find('.ranges li:last') + .addClass('active').html(); + } + }, + + buildCalendar: function (month, year, hour, minute, side) { + var firstDay = moment([year, month, 1]); + var lastMonth = moment(firstDay).subtract('month', 1).month(); + var lastYear = moment(firstDay).subtract('month', 1).year(); + + var daysInLastMonth = moment([lastYear, lastMonth]).daysInMonth(); + + var dayOfWeek = firstDay.day(); + + var i; + + //initialize a 6 rows x 7 columns array for the calendar + var calendar = []; + for (i = 0; i < 6; i++) { + calendar[i] = []; + } + + //populate the calendar with date objects + var startDay = daysInLastMonth - dayOfWeek + this.locale.firstDay + 1; + if (startDay > daysInLastMonth) + startDay -= 7; + + if (dayOfWeek == this.locale.firstDay) + startDay = daysInLastMonth - 6; + + var curDate = moment([lastYear, lastMonth, startDay, 12, minute]); + var col, row; + for (i = 0, col = 0, row = 0; i < 42; i++, col++, curDate = moment(curDate).add('hour', 24)) { + if (i > 0 && col % 7 === 0) { + col = 0; + row++; + } + calendar[row][col] = curDate.clone().hour(hour); + curDate.hour(12); + } + + return calendar; + }, + + renderDropdowns: function (selected, minDate, maxDate) { + var currentMonth = selected.month(); + var monthHtml = '"; + + var currentYear = selected.year(); + var maxYear = (maxDate && maxDate.year()) || (currentYear + 5); + var minYear = (minDate && minDate.year()) || (currentYear - 50); + var yearHtml = ''; + + return monthHtml + yearHtml; + }, + + renderCalendar: function (calendar, selected, minDate, maxDate) { + + var html = '
'; + html += ''; + html += ''; + html += ''; + + // add empty cell for week number + if (this.showWeekNumbers) + html += ''; + + if (!minDate || minDate.isBefore(calendar[1][1])) { + html += ''; + } else { + html += ''; + } + + var dateHtml = this.locale.monthNames[calendar[1][1].month()] + calendar[1][1].format(" YYYY"); + + if (this.showDropdowns) { + dateHtml = this.renderDropdowns(calendar[1][1], minDate, maxDate); + } + + html += ''; + if (!maxDate || maxDate.isAfter(calendar[1][1])) { + html += ''; + } else { + html += ''; + } + + html += ''; + html += ''; + + // add week number label + if (this.showWeekNumbers) + html += ''; + + $.each(this.locale.daysOfWeek, function (index, dayOfWeek) { + html += ''; + }); + + html += ''; + html += ''; + html += ''; + + for (var row = 0; row < 6; row++) { + html += ''; + + // add week number + if (this.showWeekNumbers) + html += ''; + + for (var col = 0; col < 7; col++) { + var cname = 'available '; + cname += (calendar[row][col].month() == calendar[1][1].month()) ? '' : 'off'; + + if ((minDate && calendar[row][col].isBefore(minDate)) || (maxDate && calendar[row][col].isAfter(maxDate))) { + cname = ' off disabled '; + } else if (calendar[row][col].format('YYYY-MM-DD') == selected.format('YYYY-MM-DD')) { + cname += ' active '; + if (calendar[row][col].format('YYYY-MM-DD') == this.startDate.format('YYYY-MM-DD')) { + cname += ' start-date '; + } + if (calendar[row][col].format('YYYY-MM-DD') == this.endDate.format('YYYY-MM-DD')) { + cname += ' end-date '; + } + } else if (calendar[row][col] >= this.startDate && calendar[row][col] <= this.endDate) { + cname += ' in-range '; + if (calendar[row][col].isSame(this.startDate)) { cname += ' start-date '; } + if (calendar[row][col].isSame(this.endDate)) { cname += ' end-date '; } + } + + var title = 'r' + row + 'c' + col; + html += ''; + } + html += ''; + } + + html += ''; + html += '
' + dateHtml + '
' + this.locale.weekLabel + '' + dayOfWeek + '
' + calendar[row][0].week() + '' + calendar[row][col].date() + '
'; + html += '
'; + + var i; + if (this.timePicker) { + + html += '
'; + html += ' : '; + + html += ' '; + + if (this.timePicker12Hour) { + html += ''; + } + + html += '
'; + + } + + return html; + + }, + + remove: function() { + + this.container.remove(); + this.element.off('.daterangepicker'); + this.element.removeData('daterangepicker'); + + } + + }; + + $.fn.daterangepicker = function (options, cb) { + this.each(function () { + var el = $(this); + if (el.data('daterangepicker')) + el.data('daterangepicker').remove(); + el.data('daterangepicker', new DateRangePicker(el, options, cb)); + }); + return this; + }; + +}(window.jQuery, window.moment); diff --git a/public/sldate/moment.js b/public/sldate/moment.js new file mode 100644 index 0000000..8536c37 --- /dev/null +++ b/public/sldate/moment.js @@ -0,0 +1,2400 @@ +//! moment.js +//! version : 2.5.1 +//! authors : Tim Wood, Iskren Chernev, Moment.js contributors +//! license : MIT +//! momentjs.com + +(function (undefined) { + + /************************************ + Constants + ************************************/ + + var moment, + VERSION = "2.5.1", + global = this, + round = Math.round, + i, + + YEAR = 0, + MONTH = 1, + DATE = 2, + HOUR = 3, + MINUTE = 4, + SECOND = 5, + MILLISECOND = 6, + + // internal storage for language config files + languages = {}, + + // moment internal properties + momentProperties = { + _isAMomentObject: null, + _i : null, + _f : null, + _l : null, + _strict : null, + _isUTC : null, + _offset : null, // optional. Combine with _isUTC + _pf : null, + _lang : null // optional + }, + + // check for nodeJS + hasModule = (typeof module !== 'undefined' && module.exports && typeof require !== 'undefined'), + + // ASP.NET json date format regex + aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, + aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/, + + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/, + + // format tokens + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, + + // parsing token regexes + parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 + parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 + parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999 + parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 + parseTokenDigits = /\d+/, // nonzero number of digits + parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic. + parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z + parseTokenT = /T/i, // T (ISO separator) + parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + + //strict parsing regexes + parseTokenOneDigit = /\d/, // 0 - 9 + parseTokenTwoDigits = /\d\d/, // 00 - 99 + parseTokenThreeDigits = /\d{3}/, // 000 - 999 + parseTokenFourDigits = /\d{4}/, // 0000 - 9999 + parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999 + parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/, + + isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', + + isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/], + ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/], + ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/], + ['GGGG-[W]WW', /\d{4}-W\d{2}/], + ['YYYY-DDD', /\d{4}-\d{3}/] + ], + + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] + ], + + // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] + parseTimezoneChunker = /([\+\-]|\d\d)/gi, + + // getter and setter names + proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), + unitMillisecondFactors = { + 'Milliseconds' : 1, + 'Seconds' : 1e3, + 'Minutes' : 6e4, + 'Hours' : 36e5, + 'Days' : 864e5, + 'Months' : 2592e6, + 'Years' : 31536e6 + }, + + unitAliases = { + ms : 'millisecond', + s : 'second', + m : 'minute', + h : 'hour', + d : 'day', + D : 'date', + w : 'week', + W : 'isoWeek', + M : 'month', + y : 'year', + DDD : 'dayOfYear', + e : 'weekday', + E : 'isoWeekday', + gg: 'weekYear', + GG: 'isoWeekYear' + }, + + camelFunctions = { + dayofyear : 'dayOfYear', + isoweekday : 'isoWeekday', + isoweek : 'isoWeek', + weekyear : 'weekYear', + isoweekyear : 'isoWeekYear' + }, + + // format function strings + formatFunctions = {}, + + // tokens to ordinalize and pad + ordinalizeTokens = 'DDD w W M D d'.split(' '), + paddedTokens = 'M D H h m s w W'.split(' '), + + formatTokenFunctions = { + M : function () { + return this.month() + 1; + }, + MMM : function (format) { + return this.lang().monthsShort(this, format); + }, + MMMM : function (format) { + return this.lang().months(this, format); + }, + D : function () { + return this.date(); + }, + DDD : function () { + return this.dayOfYear(); + }, + d : function () { + return this.day(); + }, + dd : function (format) { + return this.lang().weekdaysMin(this, format); + }, + ddd : function (format) { + return this.lang().weekdaysShort(this, format); + }, + dddd : function (format) { + return this.lang().weekdays(this, format); + }, + w : function () { + return this.week(); + }, + W : function () { + return this.isoWeek(); + }, + YY : function () { + return leftZeroFill(this.year() % 100, 2); + }, + YYYY : function () { + return leftZeroFill(this.year(), 4); + }, + YYYYY : function () { + return leftZeroFill(this.year(), 5); + }, + YYYYYY : function () { + var y = this.year(), sign = y >= 0 ? '+' : '-'; + return sign + leftZeroFill(Math.abs(y), 6); + }, + gg : function () { + return leftZeroFill(this.weekYear() % 100, 2); + }, + gggg : function () { + return leftZeroFill(this.weekYear(), 4); + }, + ggggg : function () { + return leftZeroFill(this.weekYear(), 5); + }, + GG : function () { + return leftZeroFill(this.isoWeekYear() % 100, 2); + }, + GGGG : function () { + return leftZeroFill(this.isoWeekYear(), 4); + }, + GGGGG : function () { + return leftZeroFill(this.isoWeekYear(), 5); + }, + e : function () { + return this.weekday(); + }, + E : function () { + return this.isoWeekday(); + }, + a : function () { + return this.lang().meridiem(this.hours(), this.minutes(), true); + }, + A : function () { + return this.lang().meridiem(this.hours(), this.minutes(), false); + }, + H : function () { + return this.hours(); + }, + h : function () { + return this.hours() % 12 || 12; + }, + m : function () { + return this.minutes(); + }, + s : function () { + return this.seconds(); + }, + S : function () { + return toInt(this.milliseconds() / 100); + }, + SS : function () { + return leftZeroFill(toInt(this.milliseconds() / 10), 2); + }, + SSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + SSSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + Z : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(toInt(a / 60), 2) + ":" + leftZeroFill(toInt(a) % 60, 2); + }, + ZZ : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2); + }, + z : function () { + return this.zoneAbbr(); + }, + zz : function () { + return this.zoneName(); + }, + X : function () { + return this.unix(); + }, + Q : function () { + return this.quarter(); + } + }, + + lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin']; + + function defaultParsingFlags() { + // We need to deep clone this object, and es5 standard is not very + // helpful. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso: false + }; + } + + function padToken(func, count) { + return function (a) { + return leftZeroFill(func.call(this, a), count); + }; + } + function ordinalizeToken(func, period) { + return function (a) { + return this.lang().ordinal(func.call(this, a), period); + }; + } + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i); + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + } + formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + + + /************************************ + Constructors + ************************************/ + + function Language() { + + } + + // Moment prototype object + function Moment(config) { + checkOverflow(config); + extend(this, config); + } + + // Duration Constructor + function Duration(duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; + + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + years * 12; + + this._data = {}; + + this._bubble(); + } + + /************************************ + Helpers + ************************************/ + + + function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } + + if (b.hasOwnProperty("toString")) { + a.toString = b.toString; + } + + if (b.hasOwnProperty("valueOf")) { + a.valueOf = b.valueOf; + } + + return a; + } + + function cloneMoment(m) { + var result = {}, i; + for (i in m) { + if (m.hasOwnProperty(i) && momentProperties.hasOwnProperty(i)) { + result[i] = m[i]; + } + } + + return result; + } + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + // left zero fill a number + // see http://jsperf.com/left-zero-filling for performance comparison + function leftZeroFill(number, targetLength, forceSign) { + var output = '' + Math.abs(number), + sign = number >= 0; + + while (output.length < targetLength) { + output = '0' + output; + } + return (sign ? (forceSign ? '+' : '') : '-') + output; + } + + // helper function for _.addTime and _.subtractTime + function addOrSubtractDurationFromMoment(mom, duration, isAdding, ignoreUpdateOffset) { + var milliseconds = duration._milliseconds, + days = duration._days, + months = duration._months, + minutes, + hours; + + if (milliseconds) { + mom._d.setTime(+mom._d + milliseconds * isAdding); + } + // store the minutes and hours so we can restore them + if (days || months) { + minutes = mom.minute(); + hours = mom.hour(); + } + if (days) { + mom.date(mom.date() + days * isAdding); + } + if (months) { + mom.month(mom.month() + months * isAdding); + } + if (milliseconds && !ignoreUpdateOffset) { + moment.updateOffset(mom); + } + // restore the minutes and hours after possibly changing dst + if (days || months) { + mom.minute(minutes); + mom.hour(hours); + } + } + + // check if is an array + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + function isDate(input) { + return Object.prototype.toString.call(input) === '[object Date]' || + input instanceof Date; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } + + function normalizeUnits(units) { + if (units) { + var lowered = units.toLowerCase().replace(/(.)s$/, '$1'); + units = unitAliases[units] || camelFunctions[lowered] || lowered; + } + return units; + } + + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; + + for (prop in inputObject) { + if (inputObject.hasOwnProperty(prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; + } + + function makeList(field) { + var count, setter; + + if (field.indexOf('week') === 0) { + count = 7; + setter = 'day'; + } + else if (field.indexOf('month') === 0) { + count = 12; + setter = 'month'; + } + else { + return; + } + + moment[field] = function (format, index) { + var i, getter, + method = moment.fn._lang[field], + results = []; + + if (typeof format === 'number') { + index = format; + format = undefined; + } + + getter = function (i) { + var m = moment().utc().set(setter, i); + return method.call(moment.fn._lang, m, format || ''); + }; + + if (index != null) { + return getter(index); + } + else { + for (i = 0; i < count; i++) { + results.push(getter(i)); + } + return results; + } + }; + } + + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; + + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + if (coercedNumber >= 0) { + value = Math.floor(coercedNumber); + } else { + value = Math.ceil(coercedNumber); + } + } + + return value; + } + + function daysInMonth(year, month) { + return new Date(Date.UTC(year, month + 1, 0)).getUTCDate(); + } + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; + } + + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } + + function checkOverflow(m) { + var overflow; + if (m._a && m._pf.overflow === -2) { + overflow = + m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH : + m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE : + m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR : + m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE : + m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND : + m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND : + -1; + + if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + + m._pf.overflow = overflow; + } + } + + function isValid(m) { + if (m._isValid == null) { + m._isValid = !isNaN(m._d.getTime()) && + m._pf.overflow < 0 && + !m._pf.empty && + !m._pf.invalidMonth && + !m._pf.nullInput && + !m._pf.invalidFormat && + !m._pf.userInvalidated; + + if (m._strict) { + m._isValid = m._isValid && + m._pf.charsLeftOver === 0 && + m._pf.unusedTokens.length === 0; + } + } + return m._isValid; + } + + function normalizeLanguage(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } + + // Return a moment from input, that is local/utc/zone equivalent to model. + function makeAs(input, model) { + return model._isUTC ? moment(input).zone(model._offset || 0) : + moment(input).local(); + } + + /************************************ + Languages + ************************************/ + + + extend(Language.prototype, { + + set : function (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + }, + + _months: "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"), + months : function (m) { + return this._months[m.month()]; + }, + + _monthsShort : "一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"), + monthsShort : function (m) { + return this._monthsShort[m.month()]; + }, + + monthsParse : function (monthName) { + var i, mom, regex; + + if (!this._monthsParse) { + this._monthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + if (!this._monthsParse[i]) { + mom = moment.utc([2000, i]); + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._monthsParse[i].test(monthName)) { + return i; + } + } + }, + + _weekdays: "星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"), + weekdays : function (m) { + return this._weekdays[m.day()]; + }, + + _weekdaysShort: "星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"), + weekdaysShort : function (m) { + return this._weekdaysShort[m.day()]; + }, + + _weekdaysMin: "周日_周一_周二_周三_周四_周五_周六".split("_"), + weekdaysMin : function (m) { + return this._weekdaysMin[m.day()]; + }, + + weekdaysParse : function (weekdayName) { + var i, mom, regex; + + if (!this._weekdaysParse) { + this._weekdaysParse = []; + } + + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + if (!this._weekdaysParse[i]) { + mom = moment([2000, 1]).day(i); + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + }, + + _longDateFormat : { + LT : "h:mm A", + L: "YYYY-MM-DD", + LL : "MMMM D YYYY", + LLL : "MMMM D YYYY LT", + LLLL : "dddd, MMMM D YYYY LT" + }, + longDateFormat : function (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; + } + return output; + }, + + isPM : function (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); + }, + + _meridiemParse : /[ap]\.?m?\.?/i, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, + + _calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.apply(mom) : output; + }, + + _relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + relativeTime : function (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + }, + pastFuture : function (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + }, + + ordinal : function (number) { + return this._ordinal.replace("%d", number); + }, + _ordinal : "%d", + + preparse : function (string) { + return string; + }, + + postformat : function (string) { + return string; + }, + + week : function (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; + }, + + _week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + }, + + _invalidDate: 'Invalid date', + invalidDate: function () { + return this._invalidDate; + } + }); + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + values.abbr = key; + if (!languages[key]) { + languages[key] = new Language(); + } + languages[key].set(values); + return languages[key]; + } + + // Remove a language from the `languages` cache. Mostly useful in tests. + function unloadLang(key) { + delete languages[key]; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. + function getLangDefinition(key) { + var i = 0, j, lang, next, split, + get = function (k) { + if (!languages[k] && hasModule) { + try { + require('./lang/' + k); + } catch (e) { } + } + return languages[k]; + }; + + if (!key) { + return moment.fn._lang; + } + + if (!isArray(key)) { + //short-circuit everything else + lang = get(key); + if (lang) { + return lang; + } + key = [key]; + } + + //pick the language from the array + //try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + //substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + while (i < key.length) { + split = normalizeLanguage(key[i]).split('-'); + j = split.length; + next = normalizeLanguage(key[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + lang = get(split.slice(0, j).join('-')); + if (lang) { + return lang; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return moment.fn._lang; + } + + /************************************ + Formatting + ************************************/ + + + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ""); + } + return input.replace(/\\/g, ""); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ""; + for (i = 0; i < length; i++) { + output += array[i] instanceof Function ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + + if (!m.isValid()) { + return m.lang().invalidDate(); + } + + format = expandFormat(format, m.lang()); + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + function expandFormat(format, lang) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return lang.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; + } + + + /************************************ + Parsing + ************************************/ + + + // get the regex to find the next token + function getParseRegexForToken(token, config) { + var a, strict = config._strict; + switch (token) { + case 'DDDD': + return parseTokenThreeDigits; + case 'YYYY': + case 'GGGG': + case 'gggg': + return strict ? parseTokenFourDigits : parseTokenOneToFourDigits; + case 'Y': + case 'G': + case 'g': + return parseTokenSignedNumber; + case 'YYYYYY': + case 'YYYYY': + case 'GGGGG': + case 'ggggg': + return strict ? parseTokenSixDigits : parseTokenOneToSixDigits; + case 'S': + if (strict) { return parseTokenOneDigit; } + /* falls through */ + case 'SS': + if (strict) { return parseTokenTwoDigits; } + /* falls through */ + case 'SSS': + if (strict) { return parseTokenThreeDigits; } + /* falls through */ + case 'DDD': + return parseTokenOneToThreeDigits; + case 'MMM': + case 'MMMM': + case 'dd': + case 'ddd': + case 'dddd': + return parseTokenWord; + case 'a': + case 'A': + return getLangDefinition(config._l)._meridiemParse; + case 'X': + return parseTokenTimestampMs; + case 'Z': + case 'ZZ': + return parseTokenTimezone; + case 'T': + return parseTokenT; + case 'SSSS': + return parseTokenDigits; + case 'MM': + case 'DD': + case 'YY': + case 'GG': + case 'gg': + case 'HH': + case 'hh': + case 'mm': + case 'ss': + case 'ww': + case 'WW': + return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits; + case 'M': + case 'D': + case 'd': + case 'H': + case 'h': + case 'm': + case 's': + case 'w': + case 'W': + case 'e': + case 'E': + return parseTokenOneOrTwoDigits; + default : + a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), "i")); + return a; + } + } + + function timezoneMinutesFromString(string) { + string = string || ""; + var possibleTzMatches = (string.match(parseTokenTimezone) || []), + tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [], + parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0], + minutes = +(parts[1] * 60) + toInt(parts[2]); + + return parts[0] === '+' ? -minutes : minutes; + } + + // function to convert string input to date + function addTimeToArrayFromToken(token, input, config) { + var a, datePartArray = config._a; + + switch (token) { + // MONTH + case 'M' : // fall through to MM + case 'MM' : + if (input != null) { + datePartArray[MONTH] = toInt(input) - 1; + } + break; + case 'MMM' : // fall through to MMMM + case 'MMMM' : + a = getLangDefinition(config._l).monthsParse(input); + // if we didn't find a month name, mark the date as invalid. + if (a != null) { + datePartArray[MONTH] = a; + } else { + config._pf.invalidMonth = input; + } + break; + // DAY OF MONTH + case 'D' : // fall through to DD + case 'DD' : + if (input != null) { + datePartArray[DATE] = toInt(input); + } + break; + // DAY OF YEAR + case 'DDD' : // fall through to DDDD + case 'DDDD' : + if (input != null) { + config._dayOfYear = toInt(input); + } + + break; + // YEAR + case 'YY' : + datePartArray[YEAR] = toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + break; + case 'YYYY' : + case 'YYYYY' : + case 'YYYYYY' : + datePartArray[YEAR] = toInt(input); + break; + // AM / PM + case 'a' : // fall through to A + case 'A' : + config._isPm = getLangDefinition(config._l).isPM(input); + break; + // 24 HOUR + case 'H' : // fall through to hh + case 'HH' : // fall through to hh + case 'h' : // fall through to hh + case 'hh' : + datePartArray[HOUR] = toInt(input); + break; + // MINUTE + case 'm' : // fall through to mm + case 'mm' : + datePartArray[MINUTE] = toInt(input); + break; + // SECOND + case 's' : // fall through to ss + case 'ss' : + datePartArray[SECOND] = toInt(input); + break; + // MILLISECOND + case 'S' : + case 'SS' : + case 'SSS' : + case 'SSSS' : + datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000); + break; + // UNIX TIMESTAMP WITH MS + case 'X': + config._d = new Date(parseFloat(input) * 1000); + break; + // TIMEZONE + case 'Z' : // fall through to ZZ + case 'ZZ' : + config._useUTC = true; + config._tzm = timezoneMinutesFromString(input); + break; + case 'w': + case 'ww': + case 'W': + case 'WW': + case 'd': + case 'dd': + case 'ddd': + case 'dddd': + case 'e': + case 'E': + token = token.substr(0, 1); + /* falls through */ + case 'gg': + case 'gggg': + case 'GG': + case 'GGGG': + case 'GGGGG': + token = token.substr(0, 2); + if (input) { + config._w = config._w || {}; + config._w[token] = input; + } + break; + } + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function dateFromConfig(config) { + var i, date, input = [], currentDate, + yearToUse, fixYear, w, temp, lang, weekday, week; + + if (config._d) { + return; + } + + currentDate = currentDateArray(config); + + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + fixYear = function (val) { + var int_val = parseInt(val, 10); + return val ? + (val.length < 3 ? (int_val > 68 ? 1900 + int_val : 2000 + int_val) : int_val) : + (config._a[YEAR] == null ? moment().weekYear() : config._a[YEAR]); + }; + + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + temp = dayOfYearFromWeeks(fixYear(w.GG), w.W || 1, w.E, 4, 1); + } + else { + lang = getLangDefinition(config._l); + weekday = w.d != null ? parseWeekday(w.d, lang) : + (w.e != null ? parseInt(w.e, 10) + lang._week.dow : 0); + + week = parseInt(w.w, 10) || 1; + + //if we're parsing 'd', then the low day numbers may be next week + if (w.d != null && weekday < lang._week.dow) { + week++; + } + + temp = dayOfYearFromWeeks(fixYear(w.gg), week, weekday, lang._week.doy, lang._week.dow); + } + + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + + //if the day of the year is set, figure out what it is + if (config._dayOfYear) { + yearToUse = config._a[YEAR] == null ? currentDate[YEAR] : config._a[YEAR]; + + if (config._dayOfYear > daysInYear(yearToUse)) { + config._pf._overflowDayOfYear = true; + } + + date = makeUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } + + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } + + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // add the offsets to the time to be parsed so that we can have a clean array for checking isValid + input[HOUR] += toInt((config._tzm || 0) / 60); + input[MINUTE] += toInt((config._tzm || 0) % 60); + + config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input); + } + + function dateFromObject(config) { + var normalizedInput; + + if (config._d) { + return; + } + + normalizedInput = normalizeObjectUnits(config._i); + config._a = [ + normalizedInput.year, + normalizedInput.month, + normalizedInput.day, + normalizedInput.hour, + normalizedInput.minute, + normalizedInput.second, + normalizedInput.millisecond + ]; + + dateFromConfig(config); + } + + function currentDateArray(config) { + var now = new Date(); + if (config._useUTC) { + return [ + now.getUTCFullYear(), + now.getUTCMonth(), + now.getUTCDate() + ]; + } else { + return [now.getFullYear(), now.getMonth(), now.getDate()]; + } + } + + // date from string and format string + function makeDateFromStringAndFormat(config) { + + config._a = []; + config._pf.empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var lang = getLangDefinition(config._l), + string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, lang).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + config._pf.unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + config._pf.empty = false; + } + else { + config._pf.unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + config._pf.unusedTokens.push(token); + } + } + + // add remaining unparsed input length to the string + config._pf.charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + config._pf.unusedInput.push(string); + } + + // handle am pm + if (config._isPm && config._a[HOUR] < 12) { + config._a[HOUR] += 12; + } + // if is 12 am, change hours to 0 + if (config._isPm === false && config._a[HOUR] === 12) { + config._a[HOUR] = 0; + } + + dateFromConfig(config); + checkOverflow(config); + } + + function unescapeFormat(s) { + return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + }); + } + + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function regexpEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } + + // date from string and array of format strings + function makeDateFromStringAndArray(config) { + var tempConfig, + bestMoment, + + scoreToBeat, + i, + currentScore; + + if (config._f.length === 0) { + config._pf.invalidFormat = true; + config._d = new Date(NaN); + return; + } + + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = extend({}, config); + tempConfig._pf = defaultParsingFlags(); + tempConfig._f = config._f[i]; + makeDateFromStringAndFormat(tempConfig); + + if (!isValid(tempConfig)) { + continue; + } + + // if there is any input that was not parsed add a penalty for that format + currentScore += tempConfig._pf.charsLeftOver; + + //or tokens + currentScore += tempConfig._pf.unusedTokens.length * 10; + + tempConfig._pf.score = currentScore; + + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } + + extend(config, bestMoment || tempConfig); + } + + // date from iso format + function makeDateFromString(config) { + var i, l, + string = config._i, + match = isoRegex.exec(string); + + if (match) { + config._pf.iso = true; + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(string)) { + // match[5] should be "T" or undefined + config._f = isoDates[i][0] + (match[6] || " "); + break; + } + } + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (string.match(parseTokenTimezone)) { + config._f += "Z"; + } + makeDateFromStringAndFormat(config); + } + else { + config._d = new Date(string); + } + } + + function makeDateFromInput(config) { + var input = config._i, + matched = aspNetJsonRegex.exec(input); + + if (input === undefined) { + config._d = new Date(); + } else if (matched) { + config._d = new Date(+matched[1]); + } else if (typeof input === 'string') { + makeDateFromString(config); + } else if (isArray(input)) { + config._a = input.slice(0); + dateFromConfig(config); + } else if (isDate(input)) { + config._d = new Date(+input); + } else if (typeof(input) === 'object') { + dateFromObject(config); + } else { + config._d = new Date(input); + } + } + + function makeDate(y, m, d, h, M, s, ms) { + //can't just apply() to create a date: + //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply + var date = new Date(y, m, d, h, M, s, ms); + + //the date constructor doesn't accept years < 1970 + if (y < 1970) { + date.setFullYear(y); + } + return date; + } + + function makeUTCDate(y) { + var date = new Date(Date.UTC.apply(null, arguments)); + if (y < 1970) { + date.setUTCFullYear(y); + } + return date; + } + + function parseWeekday(input, language) { + if (typeof input === 'string') { + if (!isNaN(input)) { + input = parseInt(input, 10); + } + else { + input = language.weekdaysParse(input); + if (typeof input !== 'number') { + return null; + } + } + } + return input; + } + + /************************************ + Relative Time + ************************************/ + + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime(milliseconds, withoutSuffix, lang) { + var seconds = round(Math.abs(milliseconds) / 1000), + minutes = round(seconds / 60), + hours = round(minutes / 60), + days = round(hours / 24), + years = round(days / 365), + args = seconds < 45 && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < 45 && ['mm', minutes] || + hours === 1 && ['h'] || + hours < 22 && ['hh', hours] || + days === 1 && ['d'] || + days <= 25 && ['dd', days] || + days <= 45 && ['M'] || + days < 345 && ['MM', round(days / 30)] || + years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; + args[3] = milliseconds > 0; + args[4] = lang; + return substituteTimeAgo.apply({}, args); + } + + + /************************************ + Week of Year + ************************************/ + + + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(), + adjustedMoment; + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + adjustedMoment = moment(mom).add('d', daysToDayOfWeek); + return { + week: Math.ceil(adjustedMoment.dayOfYear() / 7), + year: adjustedMoment.year() + }; + } + + //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) { + var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear; + + weekday = weekday != null ? weekday : firstDayOfWeek; + daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0); + dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1; + + return { + year: dayOfYear > 0 ? year : year - 1, + dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear + }; + } + + /************************************ + Top Level Functions + ************************************/ + + function makeMoment(config) { + var input = config._i, + format = config._f; + + if (input === null) { + return moment.invalid({nullInput: true}); + } + + if (typeof input === 'string') { + config._i = input = getLangDefinition().preparse(input); + } + + if (moment.isMoment(input)) { + config = cloneMoment(input); + + config._d = new Date(+input._d); + } else if (format) { + if (isArray(format)) { + makeDateFromStringAndArray(config); + } else { + makeDateFromStringAndFormat(config); + } + } else { + makeDateFromInput(config); + } + + return new Moment(config); + } + + moment = function (input, format, lang, strict) { + var c; + + if (typeof(lang) === "boolean") { + strict = lang; + lang = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._i = input; + c._f = format; + c._l = lang; + c._strict = strict; + c._isUTC = false; + c._pf = defaultParsingFlags(); + + return makeMoment(c); + }; + + // creating with utc + moment.utc = function (input, format, lang, strict) { + var c; + + if (typeof(lang) === "boolean") { + strict = lang; + lang = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c = {}; + c._isAMomentObject = true; + c._useUTC = true; + c._isUTC = true; + c._l = lang; + c._i = input; + c._f = format; + c._strict = strict; + c._pf = defaultParsingFlags(); + + return makeMoment(c).utc(); + }; + + // creating with unix timestamp (in seconds) + moment.unix = function (input) { + return moment(input * 1000); + }; + + // duration + moment.duration = function (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + parseIso; + + if (moment.isDuration(input)) { + duration = { + ms: input._milliseconds, + d: input._days, + M: input._months + }; + } else if (typeof input === 'number') { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) { + sign = (match[1] === "-") ? -1 : 1; + duration = { + y: 0, + d: toInt(match[DATE]) * sign, + h: toInt(match[HOUR]) * sign, + m: toInt(match[MINUTE]) * sign, + s: toInt(match[SECOND]) * sign, + ms: toInt(match[MILLISECOND]) * sign + }; + } else if (!!(match = isoDurationRegex.exec(input))) { + sign = (match[1] === "-") ? -1 : 1; + parseIso = function (inp) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; + }; + duration = { + y: parseIso(match[2]), + M: parseIso(match[3]), + d: parseIso(match[4]), + h: parseIso(match[5]), + m: parseIso(match[6]), + s: parseIso(match[7]), + w: parseIso(match[8]) + }; + } + + ret = new Duration(duration); + + if (moment.isDuration(input) && input.hasOwnProperty('_lang')) { + ret._lang = input._lang; + } + + return ret; + }; + + // version number + moment.version = VERSION; + + // default format + moment.defaultFormat = isoFormat; + + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + moment.updateOffset = function () {}; + + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. + moment.lang = function (key, values) { + var r; + if (!key) { + return moment.fn._lang._abbr; + } + if (values) { + loadLang(normalizeLanguage(key), values); + } else if (values === null) { + unloadLang(key); + key = 'en'; + } else if (!languages[key]) { + getLangDefinition(key); + } + r = moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); + return r._abbr; + }; + + // returns language data + moment.langData = function (key) { + if (key && key._lang && key._lang._abbr) { + key = key._lang._abbr; + } + return getLangDefinition(key); + }; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment || + (obj != null && obj.hasOwnProperty('_isAMomentObject')); + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + for (i = lists.length - 1; i >= 0; --i) { + makeList(lists[i]); + } + + moment.normalizeUnits = function (units) { + return normalizeUnits(units); + }; + + moment.invalid = function (flags) { + var m = moment.utc(NaN); + if (flags != null) { + extend(m._pf, flags); + } + else { + m._pf.userInvalidated = true; + } + + return m; + }; + + moment.parseZone = function (input) { + return moment(input).parseZone(); + }; + + /************************************ + Moment Prototype + ************************************/ + + + extend(moment.fn = Moment.prototype, { + + clone : function () { + return moment(this); + }, + + valueOf : function () { + return +this._d + ((this._offset || 0) * 60000); + }, + + unix : function () { + return Math.floor(+this / 1000); + }, + + toString : function () { + return this.clone().lang('en').format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + }, + + toDate : function () { + return this._offset ? new Date(+this) : this._d; + }, + + toISOString : function () { + var m = moment(this).utc(); + if (0 < m.year() && m.year() <= 9999) { + return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } else { + return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + } + }, + + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds() + ]; + }, + + isValid : function () { + return isValid(this); + }, + + isDSTShifted : function () { + + if (this._a) { + return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0; + } + + return false; + }, + + parsingFlags : function () { + return extend({}, this._pf); + }, + + invalidAt: function () { + return this._pf.overflow; + }, + + utc : function () { + return this.zone(0); + }, + + local : function () { + this.zone(0); + this._isUTC = false; + return this; + }, + + format : function (inputString) { + var output = formatMoment(this, inputString || moment.defaultFormat); + return this.lang().postformat(output); + }, + + add : function (input, val) { + var dur; + // switch args to support add('s', 1) and add(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, 1); + return this; + }, + + subtract : function (input, val) { + var dur; + // switch args to support subtract('s', 1) and subtract(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, -1); + return this; + }, + + diff : function (input, units, asFloat) { + var that = makeAs(input, this), + zoneDiff = (this.zone() - that.zone()) * 6e4, + diff, output; + + units = normalizeUnits(units); + + if (units === 'year' || units === 'month') { + // average number of days in the months in the given dates + diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 + // difference in months + output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); + // adjust by taking difference in days, average number of days + // and dst in the given months. + output += ((this - moment(this).startOf('month')) - + (that - moment(that).startOf('month'))) / diff; + // same as above but with zones, to negate all dst + output -= ((this.zone() - moment(this).startOf('month').zone()) - + (that.zone() - moment(that).startOf('month').zone())) * 6e4 / diff; + if (units === 'year') { + output = output / 12; + } + } else { + diff = (this - that); + output = units === 'second' ? diff / 1e3 : // 1000 + units === 'minute' ? diff / 6e4 : // 1000 * 60 + units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 + units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst + units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst + diff; + } + return asFloat ? output : absRound(output); + }, + + from : function (time, withoutSuffix) { + return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + }, + + fromNow : function (withoutSuffix) { + return this.from(moment(), withoutSuffix); + }, + + calendar : function () { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're zone'd or not. + var sod = makeAs(moment(), this).startOf('day'), + diff = this.diff(sod, 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.lang().calendar(format, this)); + }, + + isLeapYear : function () { + return isLeapYear(this.year()); + }, + + isDST : function () { + return (this.zone() < this.clone().month(0).zone() || + this.zone() < this.clone().month(5).zone()); + }, + + day : function (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.lang()); + return this.add({ d : input - day }); + } else { + return day; + } + }, + + month : function (input) { + var utc = this._isUTC ? 'UTC' : '', + dayOfMonth; + + if (input != null) { + if (typeof input === 'string') { + input = this.lang().monthsParse(input); + if (typeof input !== 'number') { + return this; + } + } + + dayOfMonth = this.date(); + this.date(1); + this._d['set' + utc + 'Month'](input); + this.date(Math.min(dayOfMonth, this.daysInMonth())); + + moment.updateOffset(this); + return this; + } else { + return this._d['get' + utc + 'Month'](); + } + }, + + startOf: function (units) { + units = normalizeUnits(units); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'isoWeek': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + + // weeks are a special case + if (units === 'week') { + this.weekday(0); + } else if (units === 'isoWeek') { + this.isoWeekday(1); + } + + return this; + }, + + endOf: function (units) { + units = normalizeUnits(units); + return this.startOf(units).add((units === 'isoWeek' ? 'week' : units), 1).subtract('ms', 1); + }, + + isAfter: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) > +moment(input).startOf(units); + }, + + isBefore: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) < +moment(input).startOf(units); + }, + + isSame: function (input, units) { + units = units || 'ms'; + return +this.clone().startOf(units) === +makeAs(input, this).startOf(units); + }, + + min: function (other) { + other = moment.apply(null, arguments); + return other < this ? this : other; + }, + + max: function (other) { + other = moment.apply(null, arguments); + return other > this ? this : other; + }, + + zone : function (input) { + var offset = this._offset || 0; + if (input != null) { + if (typeof input === "string") { + input = timezoneMinutesFromString(input); + } + if (Math.abs(input) < 16) { + input = input * 60; + } + this._offset = input; + this._isUTC = true; + if (offset !== input) { + addOrSubtractDurationFromMoment(this, moment.duration(offset - input, 'm'), 1, true); + } + } else { + return this._isUTC ? offset : this._d.getTimezoneOffset(); + } + return this; + }, + + zoneAbbr : function () { + return this._isUTC ? "UTC" : ""; + }, + + zoneName : function () { + return this._isUTC ? "Coordinated Universal Time" : ""; + }, + + parseZone : function () { + if (this._tzm) { + this.zone(this._tzm); + } else if (typeof this._i === 'string') { + this.zone(this._i); + } + return this; + }, + + hasAlignedHourOffset : function (input) { + if (!input) { + input = 0; + } + else { + input = moment(input).zone(); + } + + return (this.zone() - input) % 60 === 0; + }, + + daysInMonth : function () { + return daysInMonth(this.year(), this.month()); + }, + + dayOfYear : function (input) { + var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + }, + + quarter : function () { + return Math.ceil((this.month() + 1.0) / 3.0); + }, + + weekYear : function (input) { + var year = weekOfYear(this, this.lang()._week.dow, this.lang()._week.doy).year; + return input == null ? year : this.add("y", (input - year)); + }, + + isoWeekYear : function (input) { + var year = weekOfYear(this, 1, 4).year; + return input == null ? year : this.add("y", (input - year)); + }, + + week : function (input) { + var week = this.lang().week(this); + return input == null ? week : this.add("d", (input - week) * 7); + }, + + isoWeek : function (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add("d", (input - week) * 7); + }, + + weekday : function (input) { + var weekday = (this.day() + 7 - this.lang()._week.dow) % 7; + return input == null ? weekday : this.add("d", input - weekday); + }, + + isoWeekday : function (input) { + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7); + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units](); + }, + + set : function (units, value) { + units = normalizeUnits(units); + if (typeof this[units] === 'function') { + this[units](value); + } + return this; + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (key) { + if (key === undefined) { + return this._lang; + } else { + this._lang = getLangDefinition(key); + return this; + } + } + }); + + // helper for adding shortcuts + function makeGetterAndSetter(name, key) { + moment.fn[name] = moment.fn[name + 's'] = function (input) { + var utc = this._isUTC ? 'UTC' : ''; + if (input != null) { + this._d['set' + utc + key](input); + moment.updateOffset(this); + return this; + } else { + return this._d['get' + utc + key](); + } + }; + } + + // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) + for (i = 0; i < proxyGettersAndSetters.length; i ++) { + makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); + } + + // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') + makeGetterAndSetter('year', 'FullYear'); + + // add plural methods + moment.fn.days = moment.fn.day; + moment.fn.months = moment.fn.month; + moment.fn.weeks = moment.fn.week; + moment.fn.isoWeeks = moment.fn.isoWeek; + + // add aliased format methods + moment.fn.toJSON = moment.fn.toISOString; + + /************************************ + Duration Prototype + ************************************/ + + + extend(moment.duration.fn = Duration.prototype, { + + _bubble : function () { + var milliseconds = this._milliseconds, + days = this._days, + months = this._months, + data = this._data, + seconds, minutes, hours, years; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + + seconds = absRound(milliseconds / 1000); + data.seconds = seconds % 60; + + minutes = absRound(seconds / 60); + data.minutes = minutes % 60; + + hours = absRound(minutes / 60); + data.hours = hours % 24; + + days += absRound(hours / 24); + data.days = days % 30; + + months += absRound(days / 30); + data.months = months % 12; + + years = absRound(months / 12); + data.years = years; + }, + + weeks : function () { + return absRound(this.days() / 7); + }, + + valueOf : function () { + return this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6; + }, + + humanize : function (withSuffix) { + var difference = +this, + output = relativeTime(difference, !withSuffix, this.lang()); + + if (withSuffix) { + output = this.lang().pastFuture(difference, output); + } + + return this.lang().postformat(output); + }, + + add : function (input, val) { + // supports only 2.0-style add(1, 's') or add(moment) + var dur = moment.duration(input, val); + + this._milliseconds += dur._milliseconds; + this._days += dur._days; + this._months += dur._months; + + this._bubble(); + + return this; + }, + + subtract : function (input, val) { + var dur = moment.duration(input, val); + + this._milliseconds -= dur._milliseconds; + this._days -= dur._days; + this._months -= dur._months; + + this._bubble(); + + return this; + }, + + get : function (units) { + units = normalizeUnits(units); + return this[units.toLowerCase() + 's'](); + }, + + as : function (units) { + units = normalizeUnits(units); + return this['as' + units.charAt(0).toUpperCase() + units.slice(1) + 's'](); + }, + + lang : moment.fn.lang, + + toIsoString : function () { + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var years = Math.abs(this.years()), + months = Math.abs(this.months()), + days = Math.abs(this.days()), + hours = Math.abs(this.hours()), + minutes = Math.abs(this.minutes()), + seconds = Math.abs(this.seconds() + this.milliseconds() / 1000); + + if (!this.asSeconds()) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + return (this.asSeconds() < 0 ? '-' : '') + + 'P' + + (years ? years + 'Y' : '') + + (months ? months + 'M' : '') + + (days ? days + 'D' : '') + + ((hours || minutes || seconds) ? 'T' : '') + + (hours ? hours + 'H' : '') + + (minutes ? minutes + 'M' : '') + + (seconds ? seconds + 'S' : ''); + } + }); + + function makeDurationGetter(name) { + moment.duration.fn[name] = function () { + return this._data[name]; + }; + } + + function makeDurationAsGetter(name, factor) { + moment.duration.fn['as' + name] = function () { + return +this / factor; + }; + } + + for (i in unitMillisecondFactors) { + if (unitMillisecondFactors.hasOwnProperty(i)) { + makeDurationAsGetter(i, unitMillisecondFactors[i]); + makeDurationGetter(i.toLowerCase()); + } + } + + makeDurationAsGetter('Weeks', 6048e5); + moment.duration.fn.asMonths = function () { + return (+this - this.years() * 31536e6) / 2592e6 + this.years() * 12; + }; + + + /************************************ + Default Lang + ************************************/ + + + // Set default language, other languages will inherit from English. + moment.lang('en', { + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + /* EMBED_LANGUAGES */ + + /************************************ + Exposing Moment + ************************************/ + + function makeGlobal(deprecate) { + var warned = false, local_moment = moment; + /*global ender:false */ + if (typeof ender !== 'undefined') { + return; + } + // here, `this` means `window` in the browser, or `global` on the server + // add `moment` as a global object via a string identifier, + // for Closure Compiler "advanced" mode + if (deprecate) { + global.moment = function () { + if (!warned && console && console.warn) { + warned = true; + console.warn( + "Accessing Moment through the global scope is " + + "deprecated, and will be removed in an upcoming " + + "release."); + } + return local_moment.apply(null, arguments); + }; + extend(global.moment, local_moment); + } else { + global['moment'] = moment; + } + } + + // CommonJS module is defined + if (hasModule) { + module.exports = moment; + makeGlobal(true); + } else if (typeof define === "function" && define.amd) { + define("moment", function (require, exports, module) { + if (module.config && module.config() && module.config().noGlobal !== true) { + // If user provided noGlobal, he is aware of global + makeGlobal(module.config().noGlobal === undefined); + } + + return moment; + }); + } else { + makeGlobal(); + } +}).call(this); \ No newline at end of file diff --git a/public/webuploader/README.md b/public/webuploader/README.md new file mode 100644 index 0000000..93bcead --- /dev/null +++ b/public/webuploader/README.md @@ -0,0 +1,25 @@ +目录说明 +======================== + +```bash +├── Uploader.swf # SWF文件,当使用Flash运行时需要引入。 +├ +├── webuploader.js # 完全版本。 +├── webuploader.min.js # min版本 +├ +├── webuploader.flashonly.js # 只有Flash实现的版本。 +├── webuploader.flashonly.min.js # min版本 +├ +├── webuploader.html5only.js # 只有Html5实现的版本。 +├── webuploader.html5only.min.js # min版本 +├ +├── webuploader.noimage.js # 去除图片处理的版本,包括HTML5和FLASH. +├── webuploader.noimage.min.js # min版本 +├ +├── webuploader.custom.js # 自定义打包方案,请查看 Gruntfile.js,满足移动端使用。 +└── webuploader.custom.min.js # min版本 +``` + +## 示例 + +请把整个 Git 包下载下来放在 php 服务器下,因为默认提供的文件接受是用 php 编写的,打开 examples 页面便能查看示例效果。 \ No newline at end of file diff --git a/public/webuploader/Uploader.swf b/public/webuploader/Uploader.swf new file mode 100644 index 0000000000000000000000000000000000000000..caef0042179a9048819349fa389764c0f24a2a60 GIT binary patch literal 115554 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�sLSL_)Rz!5P{%LSupTTp12T;% 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 literal 0 HcmV?d00001 diff --git a/public/webuploader/webuploader.css b/public/webuploader/webuploader.css new file mode 100644 index 0000000..12f451f --- /dev/null +++ b/public/webuploader/webuploader.css @@ -0,0 +1,28 @@ +.webuploader-container { + position: relative; +} +.webuploader-element-invisible { + position: absolute !important; + clip: rect(1px 1px 1px 1px); /* IE6, IE7 */ + clip: rect(1px,1px,1px,1px); +} +.webuploader-pick { + position: relative; + display: inline-block; + cursor: pointer; + background: #00b7ee; + padding: 10px 15px; + color: #fff; + text-align: center; + border-radius: 3px; + overflow: hidden; +} +.webuploader-pick-hover { + background: #00a2d4; +} + +.webuploader-pick-disable { + opacity: 0.6; + pointer-events:none; +} + diff --git a/public/webuploader/webuploader.custom.js b/public/webuploader/webuploader.custom.js new file mode 100644 index 0000000..cdb1383 --- /dev/null +++ b/public/webuploader/webuploader.custom.js @@ -0,0 +1,6530 @@ +/*! WebUploader 0.1.6 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + * @require "jquery" + * @require "zepto" + */ + define('dollar-third',[],function() { + var req = window.require; + var $ = window.__dollar || + window.jQuery || + window.Zepto || + req('jquery') || + req('zepto'); + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * 直接来源于jquery的代码。 + * @fileOverview Promise/A+ + * @beta + */ + define('promise-builtin',[ + 'dollar' + ], function( $ ) { + + var api; + + // 简单版Callbacks, 默认memory,可选once. + function Callbacks( once ) { + var list = [], + stack = !once && [], + fire = function( data ) { + memory = data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ); + } + firing = false; + + if ( list ) { + if ( stack ) { + stack.length && fire( stack.shift() ); + } else { + list = []; + } + } + }, + self = { + add: function() { + if ( list ) { + var start = list.length; + (function add ( args ) { + $.each( args, function( _, arg ) { + var type = $.type( arg ); + if ( type === 'function' ) { + list.push( arg ); + } else if ( arg && arg.length && + type !== 'string' ) { + + add( arg ); + } + }); + })( arguments ); + + if ( firing ) { + firingLength = list.length; + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + + disable: function() { + list = stack = memory = undefined; + return this; + }, + + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + + fireWith: function( context, args ) { + if ( list && (!fired || stack) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + + fire: function() { + self.fireWith( this, arguments ); + return this; + } + }, + + fired, firing, firingStart, firingLength, firingIndex, memory; + + return self; + } + + function Deferred( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ 'resolve', 'done', Callbacks( true ), 'resolved' ], + [ 'reject', 'fail', Callbacks( true ), 'rejected' ], + [ 'notify', 'progress', Callbacks() ] + ], + state = 'pending', + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return Deferred(function( newDefer ) { + $.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = $.isFunction( fns[ i ] ) && fns[ i ]; + + // deferred[ done | fail | progress ] for + // forwarding actions to newDefer + deferred[ tuple[ 1 ] ](function() { + var returned; + + returned = fn && fn.apply( this, arguments ); + + if ( returned && + $.isFunction( returned.promise ) ) { + + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + 'With' ]( + this === promise ? + newDefer.promise() : + this, + fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + + return obj != null ? $.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + $.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + 'With' ]( this === deferred ? promise : + this, arguments ); + return this; + }; + deferred[ tuple[ 0 ] + 'With' ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + } + + api = { + /** + * 创建一个[Deferred](http://api.jquery.com/category/deferred-object/)对象。 + * 详细的Deferred用法说明,请参照jQuery的API文档。 + * + * Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。 + * + * @for Base + * @method Deferred + * @grammar Base.Deferred() => Deferred + * @example + * // 在文件开始发送前做些异步操作。 + * // WebUploader会等待此异步操作完成后,开始发送文件。 + * Uploader.register({ + * 'before-send-file': 'doSomthingAsync' + * }, { + * + * doSomthingAsync: function() { + * var deferred = Base.Deferred(); + * + * // 模拟一次异步操作。 + * setTimeout(deferred.resolve, 2000); + * + * return deferred.promise(); + * } + * }); + */ + Deferred: Deferred, + + /** + * 判断传入的参数是否为一个promise对象。 + * @method isPromise + * @grammar Base.isPromise( anything ) => Boolean + * @param {*} anything 检测对象。 + * @return {Boolean} + * @for Base + * @example + * console.log( Base.isPromise() ); // => false + * console.log( Base.isPromise({ key: '123' }) ); // => false + * console.log( Base.isPromise( Base.Deferred().promise() ) ); // => true + * + * // Deferred也是一个Promise + * console.log( Base.isPromise( Base.Deferred() ) ); // => true + */ + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + }, + + /** + * 返回一个promise,此promise在所有传入的promise都完成了后完成。 + * 详细请查看[这里](http://api.jquery.com/jQuery.when/)。 + * + * @method when + * @for Base + * @grammar Base.when( promise1[, promise2[, promise3...]] ) => Promise + */ + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + slice = [].slice, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || (subordinate && + $.isFunction( subordinate.promise )) ? length : 0, + + // the master Deferred. If resolveValues consist of + // only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? + slice.call( arguments ) : value; + + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !(--remaining) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && + $.isFunction( resolveValues[ i ].promise ) ) { + + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, + resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, + progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } + }; + + return api; + }); + define('promise',[ + 'promise-builtin' + ], function( $ ) { + return $; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.6', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClient, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClient.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file', + style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick" + }; + + Base.inherits( RuntimeClient, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button, + style = opts.style; + + if (style) + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + if (style) + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + if (style) + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addBtn + * @for Uploader + * @grammar addBtn( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addBtn({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.on('dialogopen', function() { + me.owner.trigger('dialogOpen', picker.button); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + if ( files.length ) { + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + // 销毁上传相关的属性。 + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定的文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + file.setStatus( Status.QUEUED ); + + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + file.setStatus( Status.PROGRESS ); + }); + + + } else if (file.getStatus() !== Status.PROGRESS) { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return Base.nextTick( me.__tick ); + } + + me.runing = true; + var files = []; + + // 如果有暂停的,则续传 + file || $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + me._trigged = false; + files.push(file); + v.transport && v.transport.send(); + } + }); + + $.each(files, function() { + this.setStatus( Status.PROGRESS ); + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this, + block; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + + + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file === file) { + block = v; + return false; + } + }); + + block.transport && block.transport.abort(); + + if (interrupt) { + me._putback(block); + me._popBlock(block); + } + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + // 正在准备中的文件。 + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + + /** + * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 + * + * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 + * + * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 + * + * 如: + * WebUploader.create({ + * ... + * + * disableWidgets: 'log', + * + * ... + * }) + */ + define('widgets/log',[ + 'base', + 'uploader', + 'widgets/widget' + ], function( Base, Uploader ) { + var $ = Base.$, + logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', + product = (location.hostname || location.host || 'protected').toLowerCase(), + + // 只针对 baidu 内部产品用户做统计功能。 + enable = product && /baidu/i.exec(product), + base; + + if (!enable) { + return; + } + + base = { + dv: 3, + master: 'webuploader', + online: /test/.exec(product) ? 0 : 1, + module: '', + product: product, + type: 0 + }; + + function send(data) { + var obj = $.extend({}, base, data), + url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), + image = new Image(); + + image.src = url; + } + + return Uploader.register({ + name: 'log', + + init: function() { + var owner = this.owner, + count = 0, + size = 0; + + owner + .on('error', function(code) { + send({ + type: 2, + c_error_code: code + }); + }) + .on('uploadError', function(file, reason) { + send({ + type: 2, + c_error_code: 'UPLOAD_ERROR', + c_reason: '' + reason + }); + }) + .on('uploadComplete', function(file) { + count++; + size += file.size; + }). + on('uploadFinished', function() { + send({ + c_count: count, + c_size: size + }); + count = size = 0; + }); + + send({ + c_usage: 1 + }); + } + }); + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'capture', 'camera'); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function(e) { + input.trigger('click'); + e.stopPropagation(); + owner.trigger('dialogopen'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + //debugger; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._metas = val; + return this; + } + + // getter + return this._metas; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + + /** + * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug + * android里面toDataUrl('image/jpege')得到的结果却是png. + * + * 所以这里没辙,只能借助这个工具 + * @fileOverview jpeg encoder + */ + define('runtime/html5/jpegencoder',[], function( require, exports, module ) { + + /* + Copyright (c) 2008, Adobe Systems Incorporated + 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 Adobe Systems Incorporated 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. + */ + /* + JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + + Basic GUI blocking jpeg encoder + */ + + function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + byteout.push(clt[value]); // write char directly instead of converting later + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + // var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + // var duration = new Date().getTime() - time_start; + // console.log('Encoding time: '+ currentQuality + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + // console.log('Quality set to: '+quality +'%'); + } + + function init(){ + // var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + // var duration = new Date().getTime() - time_start; + // console.log('Initialization '+ duration + 'ms'); + } + + init(); + + }; + + JPEGEncoder.encode = function( data, quality ) { + var encoder = new JPEGEncoder( quality ); + + return encoder.encode( data ); + } + + return JPEGEncoder; + }); + /** + * @fileOverview Fix android canvas.toDataUrl bug. + */ + define('runtime/html5/androidpatch',[ + 'runtime/html5/util', + 'runtime/html5/jpegencoder', + 'base' + ], function( Util, encoder, Base ) { + var origin = Util.canvasToDataUrl, + supportJpeg; + + Util.canvasToDataUrl = function( canvas, type, quality ) { + var ctx, w, h, fragement, parts; + + // 非android手机直接跳过。 + if ( !Base.os.android ) { + return origin.apply( null, arguments ); + } + + // 检测是否canvas支持jpeg导出,根据数据格式来判断。 + // JPEG 前两位分别是:255, 216 + if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { + fragement = origin.apply( null, arguments ); + + parts = fragement.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + fragement = atob( parts[ 1 ] ); + } else { + fragement = decodeURIComponent( parts[ 1 ] ); + } + + fragement = fragement.substring( 0, 2 ); + + supportJpeg = fragement.charCodeAt( 0 ) === 255 && + fragement.charCodeAt( 1 ) === 216; + } + + // 只有在android环境下才修复 + if ( type === 'image/jpeg' && !supportJpeg ) { + w = canvas.width; + h = canvas.height; + ctx = canvas.getContext('2d'); + + return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); + } + + return origin.apply( null, arguments ); + }; + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + define('webuploader',[ + 'base', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/log', + 'runtime/html5/blob', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/image', + 'runtime/html5/androidpatch', + 'runtime/html5/transport' + ], function( Base ) { + return Base; + }); + return require('webuploader'); +}); diff --git a/public/webuploader/webuploader.custom.min.js b/public/webuploader/webuploader.custom.min.js new file mode 100644 index 0000000..d01bd47 --- /dev/null +++ b/public/webuploader/webuploader.custom.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.6 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.require,c=a.__dollar||a.jQuery||a.Zepto||b("jquery")||b("zepto");if(!c)throw new Error("jQuery or Zepto not found!");return c}),b("dollar",["dollar-third"],function(a){return a}),b("promise-builtin",["dollar"],function(a){function b(b){var c,d,e,f,g,h,i=[],j=!b&&[],k=function(a){for(h=a,c=!0,g=e||0,e=0,f=i.length,d=!0;i&&f>g;g++)i[g].apply(a[0],a[1]);d=!1,i&&(j?j.length&&k(j.shift()):i=[])},l={add:function(){if(i){var b=i.length;!function c(b){a.each(b,function(b,d){var e=a.type(d);"function"===e?i.push(d):d&&d.length&&"string"!==e&&c(d)})}(arguments),d?f=i.length:h&&(e=b,k(h))}return this},disable:function(){return i=j=h=void 0,this},lock:function(){return j=void 0,h||l.disable(),this},fireWith:function(a,b){return!i||c&&!j||(b=b||[],b=[a,b.slice?b.slice():b],d?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this}};return l}function c(d){var e=[["resolve","done",b(!0),"resolved"],["reject","fail",b(!0),"rejected"],["notify","progress",b()]],f="pending",g={state:function(){return f},always:function(){return h.done(arguments).fail(arguments),this},then:function(){var b=arguments;return c(function(c){a.each(e,function(d,e){var f=e[0],i=a.isFunction(b[d])&&b[d];h[e[1]](function(){var b;b=i&&i.apply(this,arguments),b&&a.isFunction(b.promise)?b.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===g?c.promise():this,i?[b]:arguments)})}),b=null}).promise()},promise:function(b){return null!=b?a.extend(b,g):g}},h={};return g.pipe=g.then,a.each(e,function(a,b){var c=b[2],d=b[3];g[b[1]]=c.add,d&&c.add(function(){f=d},e[1^a][2].disable,e[2][2].lock),h[b[0]]=function(){return h[b[0]+"With"](this===h?g:this,arguments),this},h[b[0]+"With"]=c.fireWith}),g.promise(h),d&&d.call(h,h),h}var d;return d={Deferred:c,isPromise:function(a){return a&&"function"==typeof a.then},when:function(b){var d,e,f,g=0,h=[].slice,i=h.call(arguments),j=i.length,k=1!==j||b&&a.isFunction(b.promise)?j:0,l=1===k?b:c(),m=function(a,b,c){return function(e){b[a]=this,c[a]=arguments.length>1?h.call(arguments):e,c===d?l.notifyWith(b,c):--k||l.resolveWith(b,c)}};if(j>1)for(d=new Array(j),e=new Array(j),f=new Array(j);j>g;g++)i[g]&&a.isFunction(i[g].promise)?i[g].promise().done(m(g,f,i)).fail(l.reject).progress(m(g,e,d)):--k;return k||l.resolveWith(f,i),l.promise()}}}),b("promise",["promise-builtin"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.6",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b,void 0):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file",style:"webuploader-pick"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button,h=e.style;h&&g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":h&&g.addClass("webuploader-pick-hover");break;case"mouseleave":h&&g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?("name"===a&&(g.name=c.name),void 0):(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a,void 0)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.on("dialogopen",function(){e.owner.trigger("dialogOpen",j.button)}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)}),void 0):(b(!0),void 0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):(b.owner.trigger("error","Q_TYPE_DENIED",a),void 0):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),a.length&&(b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20))},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),b||f.request("start-upload"),void 0;for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b?(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT?(b.setStatus(h.QUEUED),f.each(c.pool,function(a,c){c.file===b&&(c.transport&&c.transport.send(),b.setStatus(h.PROGRESS))})):b.getStatus()!==h.PROGRESS&&b.setStatus(h.QUEUED)):f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)}),c.runing)return a.nextTick(c.__tick);c.runing=!0;var d=[];b||f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(c._trigged=!1,d.push(e),b.transport&&b.transport.send())}),f.each(d,function(){this.setStatus(h.PROGRESS)}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")},stopUpload:function(b,c){var d,e=this;if(b===!0&&(c=b,b=null),e.runing!==!1){if(b){if(b=b.id?b:e.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(e.pool,function(a,c){return c.file===b?(d=c,!1):void 0}),d.transport&&d.transport.abort(),c&&(e._putback(d),e._popBlock(d)),a.nextTick(e.__tick)}e.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(e.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),e.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send() +},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/log",["base","uploader","widgets/widget"],function(a,b){function c(a){var b=e.extend({},d,a),c=f.replace(/^(.*)\?/,"$1"+e.param(b)),g=new Image;g.src=c}var d,e=a.$,f=" http://static.tieba.baidu.com/tb/pms/img/st.gif??",g=(location.hostname||location.host||"protected").toLowerCase(),h=g&&/baidu/i.exec(g);if(h)return d={dv:3,master:"webuploader",online:/test/.exec(g)?0:1,module:"",product:g,type:0},b.register({name:"log",init:function(){var a=this.owner,b=0,d=0;a.on("error",function(a){c({type:2,c_error_code:a})}).on("uploadError",function(a,b){c({type:2,c_error_code:"UPLOAD_ERROR",c_reason:""+b})}).on("uploadComplete",function(a){b++,d+=a.size}).on("uploadFinished",function(){c({c_count:b,c_size:d}),b=d=0}),c({c_usage:1})}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("capture","camera"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(a){k.trigger("click"),a.stopPropagation(),h.trigger("dialogopen")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return a.log("Invalid Exif data: Invalid tag type."),void 0;if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return a.log("Invalid Exif data: Invalid data offset."),void 0;if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return a.log("Invalid Exif data: Invalid directory offset."),void 0;if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return a.log("Invalid Exif data: Invalid directory size."),void 0;for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return a.log("Invalid Exif data: Invalid segment size."),void 0;if(0!==b.getUint16(d+8))return a.log("Invalid Exif data: Missing byte alignment offset."),void 0;switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return a.log("Invalid Exif data: Invalid byte alignment marker."),void 0}if(42!==b.getUint16(i+2,g))return a.log("Invalid Exif data: Missing TIFF marker."),void 0;h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/image",["base","runtime/html5/runtime","runtime/html5/util"],function(a,b,c){var d="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D";return b.register("Image",{modified:!1,init:function(){var a=this,b=new Image;b.onload=function(){a._info={type:a.type,width:this.width,height:this.height},a._metas||"image/jpeg"!==a.type?a.owner.trigger("load"):c.parseMeta(a._blob,function(b,c){a._metas=c,a.owner.trigger("load")})},b.onerror=function(){a.owner.trigger("error")},a._img=b},loadFromBlob:function(a){var b=this,d=b._img;b._blob=a,b.type=a.type,d.src=c.createObjectURL(a.getSource()),b.owner.once("load",function(){c.revokeObjectURL(d.src)})},resize:function(a,b){var c=this._canvas||(this._canvas=document.createElement("canvas"));this._resize(this._img,c,a,b),this._blob=null,this.modified=!0,this.owner.trigger("complete","resize")},crop:function(a,b,c,d,e){var f=this._canvas||(this._canvas=document.createElement("canvas")),g=this.options,h=this._img,i=h.naturalWidth,j=h.naturalHeight,k=this.getOrientation();e=e||1,f.width=c,f.height=d,g.preserveHeaders||this._rotate2Orientaion(f,k),this._renderImageToCanvas(f,h,-a,-b,i*e,j*e),this._blob=null,this.modified=!0,this.owner.trigger("complete","crop")},getAsBlob:function(a){var b,d=this._blob,e=this.options;if(a=a||this.type,this.modified||this.type!==a){if(b=this._canvas,"image/jpeg"===a){if(d=c.canvasToDataUrl(b,a,e.quality),e.preserveHeaders&&this._metas&&this._metas.imageHead)return d=c.dataURL2ArrayBuffer(d),d=c.updateImageHead(d,this._metas.imageHead),d=c.arrayBufferToBlob(d,a)}else d=c.canvasToDataUrl(b,a);d=c.dataURL2Blob(d)}return d},getAsDataUrl:function(a){var b=this.options;return a=a||this.type,"image/jpeg"===a?c.canvasToDataUrl(this._canvas,a,b.quality):this._canvas.toDataURL(a)},getOrientation:function(){return this._metas&&this._metas.exif&&this._metas.exif.get("Orientation")||1},info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._metas=a,this):this._metas},destroy:function(){var a=this._canvas;this._img.onload=null,a&&(a.getContext("2d").clearRect(0,0,a.width,a.height),a.width=a.height=0,this._canvas=null),this._img.src=d,this._img=this._blob=null},_resize:function(a,b,c,d){var e,f,g,h,i,j=this.options,k=a.width,l=a.height,m=this.getOrientation();~[5,6,7,8].indexOf(m)&&(c^=d,d^=c,c^=d),e=Math[j.crop?"max":"min"](c/k,d/l),j.allowMagnify||(e=Math.min(1,e)),f=k*e,g=l*e,j.crop?(b.width=c,b.height=d):(b.width=f,b.height=g),h=(b.width-f)/2,i=(b.height-g)/2,j.preserveHeaders||this._rotate2Orientaion(b,m),this._renderImageToCanvas(b,a,h,i,f,g)},_rotate2Orientaion:function(a,b){var c=a.width,d=a.height,e=a.getContext("2d");switch(b){case 5:case 6:case 7:case 8:a.width=d,a.height=c}switch(b){case 2:e.translate(c,0),e.scale(-1,1);break;case 3:e.translate(c,d),e.rotate(Math.PI);break;case 4:e.translate(0,d),e.scale(1,-1);break;case 5:e.rotate(.5*Math.PI),e.scale(1,-1);break;case 6:e.rotate(.5*Math.PI),e.translate(0,-d);break;case 7:e.rotate(.5*Math.PI),e.translate(c,-d),e.scale(-1,1);break;case 8:e.rotate(-.5*Math.PI),e.translate(-c,0)}},_renderImageToCanvas:function(){function b(a,b,c){var d,e,f,g=document.createElement("canvas"),h=g.getContext("2d"),i=0,j=c,k=c;for(g.width=1,g.height=c,h.drawImage(a,0,0),d=h.getImageData(0,0,1,c).data;k>i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(8*z[P[i]]*h[j]*h[k]),C[i]=1/(8*A[P[i]]*h[j]*h[k]),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(255&a>>8),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?0|_+.5:0|_-.5;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=50>a?Math.floor(5e3/a):Math.floor(200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}Math.round;var t,u,v,w,x,y=Math.floor,z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("webuploader",["base","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/log","runtime/html5/blob","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/image","runtime/html5/androidpatch","runtime/html5/transport"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/public/webuploader/webuploader.fis.js b/public/webuploader/webuploader.fis.js new file mode 100644 index 0000000..ad5cc51 --- /dev/null +++ b/public/webuploader/webuploader.fis.js @@ -0,0 +1,8117 @@ +/*! WebUploader 0.1.6 */ + + +var jQuery = require('example:widget/ui/jquery/jquery.js'); + +module.exports = (function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }; + + return makeExport( jQuery ); +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + * @require "jquery" + * @require "zepto" + */ + define('dollar-third',[],function() { + var req = window.require; + var $ = window.__dollar || + window.jQuery || + window.Zepto || + req('jquery') || + req('zepto'); + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.6', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClient, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClient.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file', + style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick" + }; + + Base.inherits( RuntimeClient, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button, + style = opts.style; + + if (style) + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + if (style) + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + if (style) + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addBtn + * @for Uploader + * @grammar addBtn( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addBtn({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.on('dialogopen', function() { + me.owner.trigger('dialogOpen', picker.button); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + if ( files.length ) { + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + // 销毁上传相关的属性。 + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定的文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + file.setStatus( Status.QUEUED ); + + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + file.setStatus( Status.PROGRESS ); + }); + + + } else if (file.getStatus() !== Status.PROGRESS) { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return Base.nextTick( me.__tick ); + } + + me.runing = true; + var files = []; + + // 如果有暂停的,则续传 + file || $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + me._trigged = false; + files.push(file); + v.transport && v.transport.send(); + } + }); + + $.each(files, function() { + this.setStatus( Status.PROGRESS ); + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this, + block; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + + + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file === file) { + block = v; + return false; + } + }); + + block.transport && block.transport.abort(); + + if (interrupt) { + me._putback(block); + me._popBlock(block); + } + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + // 正在准备中的文件。 + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Md5 + */ + define('lib/md5',[ + 'runtime/client', + 'mediator' + ], function( RuntimeClient, Mediator ) { + + function Md5() { + RuntimeClient.call( this, 'Md5' ); + } + + // 让 Md5 具备事件功能。 + Mediator.installTo( Md5.prototype ); + + Md5.prototype.loadFromBlob = function( blob ) { + var me = this; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + me.exec( 'loadFromBlob', blob ); + }); + }; + + Md5.prototype.getResult = function() { + return this.exec('getResult'); + }; + + return Md5; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/md5',[ + 'base', + 'uploader', + 'lib/md5', + 'lib/blob', + 'widgets/widget' + ], function( Base, Uploader, Md5, Blob ) { + + return Uploader.register({ + name: 'md5', + + + /** + * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 + * + * + * @method md5File + * @grammar md5File( file[, start[, end]] ) => promise + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.md5File( file ) + * + * // 及时显示进度 + * .progress(function(percentage) { + * console.log('Percentage:', percentage); + * }) + * + * // 完成 + * .then(function(val) { + * console.log('md5 result:', val); + * }); + * + * }); + */ + md5File: function( file, start, end ) { + var md5 = new Md5(), + deferred = Base.Deferred(), + blob = (file instanceof Blob) ? file : + this.request( 'get-file', file ).source; + + md5.on( 'progress load', function( e ) { + e = e || {}; + deferred.notify( e.total ? e.loaded / e.total : 1 ); + }); + + md5.on( 'complete', function() { + deferred.resolve( md5.getResult() ); + }); + + md5.on( 'error', function( reason ) { + deferred.reject( reason ); + }); + + if ( arguments.length > 1 ) { + start = start || 0; + end = end || 0; + start < 0 && (start = blob.size + start); + end < 0 && (end = blob.size + end); + end = Math.min( end, blob.size ); + blob = blob.slice( start, end ); + } + + md5.loadFromBlob( blob ); + + return deferred.promise(); + } + }); + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'capture', 'camera'); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function(e) { + input.trigger('click'); + e.stopPropagation(); + owner.trigger('dialogopen'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug + * android里面toDataUrl('image/jpege')得到的结果却是png. + * + * 所以这里没辙,只能借助这个工具 + * @fileOverview jpeg encoder + */ + define('runtime/html5/jpegencoder',[], function( require, exports, module ) { + + /* + Copyright (c) 2008, Adobe Systems Incorporated + 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 Adobe Systems Incorporated 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. + */ + /* + JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + + Basic GUI blocking jpeg encoder + */ + + function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + byteout.push(clt[value]); // write char directly instead of converting later + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + // var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + // var duration = new Date().getTime() - time_start; + // console.log('Encoding time: '+ currentQuality + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + // console.log('Quality set to: '+quality +'%'); + } + + function init(){ + // var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + // var duration = new Date().getTime() - time_start; + // console.log('Initialization '+ duration + 'ms'); + } + + init(); + + }; + + JPEGEncoder.encode = function( data, quality ) { + var encoder = new JPEGEncoder( quality ); + + return encoder.encode( data ); + } + + return JPEGEncoder; + }); + /** + * @fileOverview Fix android canvas.toDataUrl bug. + */ + define('runtime/html5/androidpatch',[ + 'runtime/html5/util', + 'runtime/html5/jpegencoder', + 'base' + ], function( Util, encoder, Base ) { + var origin = Util.canvasToDataUrl, + supportJpeg; + + Util.canvasToDataUrl = function( canvas, type, quality ) { + var ctx, w, h, fragement, parts; + + // 非android手机直接跳过。 + if ( !Base.os.android ) { + return origin.apply( null, arguments ); + } + + // 检测是否canvas支持jpeg导出,根据数据格式来判断。 + // JPEG 前两位分别是:255, 216 + if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { + fragement = origin.apply( null, arguments ); + + parts = fragement.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + fragement = atob( parts[ 1 ] ); + } else { + fragement = decodeURIComponent( parts[ 1 ] ); + } + + fragement = fragement.substring( 0, 2 ); + + supportJpeg = fragement.charCodeAt( 0 ) === 255 && + fragement.charCodeAt( 1 ) === 216; + } + + // 只有在android环境下才修复 + if ( type === 'image/jpeg' && !supportJpeg ) { + w = canvas.width; + h = canvas.height; + ctx = canvas.getContext('2d'); + + return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); + } + + return origin.apply( null, arguments ); + }; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + //debugger; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._metas = val; + return this; + } + + // getter + return this._metas; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/html5/md5',[ + 'runtime/html5/runtime' + ], function( FlashRuntime ) { + + /* + * Fastest md5 implementation around (JKM md5) + * Credits: Joseph Myers + * + * @see http://www.myersdaily.org/joseph/javascript/md5-text.html + * @see http://jsperf.com/md5-shootout/7 + */ + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + }, + + cmn = function (q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + }, + + ff = function (a, b, c, d, x, s, t) { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + }, + + gg = function (a, b, c, d, x, s, t) { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + }, + + hh = function (a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); + }, + + ii = function (a, b, c, d, x, s, t) { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + }, + + md5cycle = function (x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + }, + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + md5blk = function (s) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + }, + + md5blk_array = function (a) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + }, + + md51 = function (s) { + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + } + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + return state; + }, + + md51_array = function (a) { + var n = a.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + + // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 + // containing the last element of the parent array if the sub array specified starts + // beyond the length of the parent array - weird. + // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue + a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); + + length = a.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << ((i % 4) << 3); + } + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + + return state; + }, + + hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], + + rhex = function (n) { + var s = '', + j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + } + return s; + }, + + hex = function (x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(''); + }, + + md5 = function (s) { + return hex(md51(s)); + }, + + + + //////////////////////////////////////////////////////////////////////////// + + /** + * SparkMD5 OOP implementation. + * + * Use this class to perform an incremental md5, otherwise use the + * static methods instead. + */ + SparkMD5 = function () { + // call reset to init the instance + this.reset(); + }; + + + // In some cases the fast add32 function cannot be used.. + if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { + add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + } + + + /** + * Appends a string. + * A conversion will be applied if an utf8 string is detected. + * + * @param {String} str The string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.append = function (str) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + // then append as binary + this.appendBinary(str); + + return this; + }; + + /** + * Appends a binary string. + * + * @param {String} contents The binary string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.appendBinary = function (contents) { + this._buff += contents; + this._length += contents.length; + + var length = this._buff.length, + i; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); + } + + this._buff = this._buff.substr(i - 64); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + i, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + /** + * Finish the final calculation based on the tail. + * + * @param {Array} tail The tail (will be modified) + * @param {Number} length The length of the remaining buffer + */ + SparkMD5.prototype._finish = function (tail, length) { + var i = length, + tmp, + lo, + hi; + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(this._state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Do the final computation based on the tail and length + // Beware that the final length may not fit in 32 bits so we take care of that + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + md5cycle(this._state, tail); + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.reset = function () { + this._buff = ""; + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.prototype.destroy = function () { + delete this._state; + delete this._buff; + delete this._length; + }; + + + /** + * Performs the md5 hash on a string. + * A conversion will be applied if utf8 string is detected. + * + * @param {String} str The string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hash = function (str, raw) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + var hash = md51(str); + + return !!raw ? hash : hex(hash); + }; + + /** + * Performs the md5 hash on a binary string. + * + * @param {String} content The binary string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hashBinary = function (content, raw) { + var hash = md51(content); + + return !!raw ? hash : hex(hash); + }; + + /** + * SparkMD5 OOP implementation for array buffers. + * + * Use this class to perform an incremental md5 ONLY for array buffers. + */ + SparkMD5.ArrayBuffer = function () { + // call reset to init the instance + this.reset(); + }; + + //////////////////////////////////////////////////////////////////////////// + + /** + * Appends an array buffer. + * + * @param {ArrayBuffer} arr The array to be appended + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.append = function (arr) { + // TODO: we could avoid the concatenation here but the algorithm would be more complex + // if you find yourself needing extra performance, please make a PR. + var buff = this._concatArrayBuffer(this._buff, arr), + length = buff.length, + i; + + this._length += arr.byteLength; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); + } + + // Avoids IE10 weirdness (documented above) + this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + i, + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.reset = function () { + this._buff = new Uint8Array(0); + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + + /** + * Concats two array buffers, returning a new one. + * + * @param {ArrayBuffer} first The first array buffer + * @param {ArrayBuffer} second The second array buffer + * + * @return {ArrayBuffer} The new array buffer + */ + SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { + var firstLength = first.length, + result = new Uint8Array(firstLength + second.byteLength); + + result.set(first); + result.set(new Uint8Array(second), firstLength); + + return result; + }; + + /** + * Performs the md5 hash on an array buffer. + * + * @param {ArrayBuffer} arr The array buffer + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.hash = function (arr, raw) { + var hash = md51_array(new Uint8Array(arr)); + + return !!raw ? hash : hex(hash); + }; + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( file ) { + var blob = file.getSource(), + chunkSize = 2 * 1024 * 1024, + chunks = Math.ceil( blob.size / chunkSize ), + chunk = 0, + owner = this.owner, + spark = new SparkMD5.ArrayBuffer(), + me = this, + blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, + loadNext, fr; + + fr = new FileReader(); + + loadNext = function() { + var start, end; + + start = chunk * chunkSize; + end = Math.min( start + chunkSize, blob.size ); + + fr.onload = function( e ) { + spark.append( e.target.result ); + owner.trigger( 'progress', { + total: file.size, + loaded: end + }); + }; + + fr.onloadend = function() { + fr.onloadend = fr.onload = null; + + if ( ++chunk < chunks ) { + setTimeout( loadNext, 1 ); + } else { + setTimeout(function(){ + owner.trigger('load'); + me.result = spark.end(); + loadNext = file = blob = spark = null; + owner.trigger('complete'); + }, 50 ); + } + }; + + fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); + }; + + loadNext(); + }, + + getResult: function() { + return this.result; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview 图片压缩 + */ + define('runtime/flash/image',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Image', { + // init: function( options ) { + // var owner = this.owner; + + // this.flashExec( 'Image', 'init', options ); + // owner.on( 'load', function() { + // debugger; + // }); + // }, + + loadFromBlob: function( blob ) { + var owner = this.owner; + + owner.info() && this.flashExec( 'Image', 'info', owner.info() ); + owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); + + this.flashExec( 'Image', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = function( s ) { + try { + if (window.JSON && window.JSON.parse) { + return JSON.parse(s); + } + + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview Md5 flash实现 + */ + define('runtime/flash/md5',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( blob ) { + return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview 完全版本。 + */ + define('preset/all',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + 'widgets/md5', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/androidpatch', + 'runtime/html5/image', + 'runtime/html5/transport', + 'runtime/html5/md5', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/image', + 'runtime/flash/transport', + 'runtime/flash/blob', + 'runtime/flash/md5' + ], function( Base ) { + return Base; + }); + /** + * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 + * + * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 + * + * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 + * + * 如: + * WebUploader.create({ + * ... + * + * disableWidgets: 'log', + * + * ... + * }) + */ + define('widgets/log',[ + 'base', + 'uploader', + 'widgets/widget' + ], function( Base, Uploader ) { + var $ = Base.$, + logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', + product = (location.hostname || location.host || 'protected').toLowerCase(), + + // 只针对 baidu 内部产品用户做统计功能。 + enable = product && /baidu/i.exec(product), + base; + + if (!enable) { + return; + } + + base = { + dv: 3, + master: 'webuploader', + online: /test/.exec(product) ? 0 : 1, + module: '', + product: product, + type: 0 + }; + + function send(data) { + var obj = $.extend({}, base, data), + url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), + image = new Image(); + + image.src = url; + } + + return Uploader.register({ + name: 'log', + + init: function() { + var owner = this.owner, + count = 0, + size = 0; + + owner + .on('error', function(code) { + send({ + type: 2, + c_error_code: code + }); + }) + .on('uploadError', function(file, reason) { + send({ + type: 2, + c_error_code: 'UPLOAD_ERROR', + c_reason: '' + reason + }); + }) + .on('uploadComplete', function(file) { + count++; + size += file.size; + }). + on('uploadFinished', function() { + send({ + c_count: count, + c_size: size + }); + count = size = 0; + }); + + send({ + c_usage: 1 + }); + } + }); + }); + /** + * @fileOverview Uploader上传类 + */ + define('webuploader',[ + 'preset/all', + 'widgets/log' + ], function( preset ) { + return preset; + }); + + var _require = require; + return _require('webuploader'); +}); diff --git a/public/webuploader/webuploader.flashonly.js b/public/webuploader/webuploader.flashonly.js new file mode 100644 index 0000000..16bb156 --- /dev/null +++ b/public/webuploader/webuploader.flashonly.js @@ -0,0 +1,4648 @@ +/*! WebUploader 0.1.6 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + * @require "jquery" + * @require "zepto" + */ + define('dollar-third',[],function() { + var req = window.require; + var $ = window.__dollar || + window.jQuery || + window.Zepto || + req('jquery') || + req('zepto'); + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.6', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClient, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClient.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file', + style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick" + }; + + Base.inherits( RuntimeClient, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button, + style = opts.style; + + if (style) + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + if (style) + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + if (style) + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addBtn + * @for Uploader + * @grammar addBtn( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addBtn({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.on('dialogopen', function() { + me.owner.trigger('dialogOpen', picker.button); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + if ( files.length ) { + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + // 销毁上传相关的属性。 + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定的文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + file.setStatus( Status.QUEUED ); + + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + file.setStatus( Status.PROGRESS ); + }); + + + } else if (file.getStatus() !== Status.PROGRESS) { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return Base.nextTick( me.__tick ); + } + + me.runing = true; + var files = []; + + // 如果有暂停的,则续传 + file || $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + me._trigged = false; + files.push(file); + v.transport && v.transport.send(); + } + }); + + $.each(files, function() { + this.setStatus( Status.PROGRESS ); + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this, + block; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + + + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file === file) { + block = v; + return false; + } + }); + + block.transport && block.transport.abort(); + + if (interrupt) { + me._putback(block); + me._popBlock(block); + } + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + // 正在准备中的文件。 + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview 图片压缩 + */ + define('runtime/flash/image',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Image', { + // init: function( options ) { + // var owner = this.owner; + + // this.flashExec( 'Image', 'init', options ); + // owner.on( 'load', function() { + // debugger; + // }); + // }, + + loadFromBlob: function( blob ) { + var owner = this.owner; + + owner.info() && this.flashExec( 'Image', 'info', owner.info() ); + owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); + + this.flashExec( 'Image', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = function( s ) { + try { + if (window.JSON && window.JSON.parse) { + return JSON.parse(s); + } + + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + + /** + * @fileOverview 只有flash实现的文件版本。 + */ + define('preset/flashonly',[ + 'base', + + // widgets + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/image', + 'runtime/flash/blob', + 'runtime/flash/transport' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/flashonly' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/public/webuploader/webuploader.flashonly.min.js b/public/webuploader/webuploader.flashonly.min.js new file mode 100644 index 0000000..23cefb8 --- /dev/null +++ b/public/webuploader/webuploader.flashonly.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.6 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.require,c=a.__dollar||a.jQuery||a.Zepto||b("jquery")||b("zepto");if(!c)throw new Error("jQuery or Zepto not found!");return c}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.6",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b,void 0):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file",style:"webuploader-pick"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button,h=e.style;h&&g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":h&&g.addClass("webuploader-pick-hover");break;case"mouseleave":h&&g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?("name"===a&&(g.name=c.name),void 0):(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a,void 0)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.on("dialogopen",function(){e.owner.trigger("dialogOpen",j.button)}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)}),void 0):(b(!0),void 0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):(b.owner.trigger("error","Q_TYPE_DENIED",a),void 0):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),a.length&&(b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20))},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),b||f.request("start-upload"),void 0;for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b?(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT?(b.setStatus(h.QUEUED),f.each(c.pool,function(a,c){c.file===b&&(c.transport&&c.transport.send(),b.setStatus(h.PROGRESS))})):b.getStatus()!==h.PROGRESS&&b.setStatus(h.QUEUED)):f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)}),c.runing)return a.nextTick(c.__tick);c.runing=!0;var d=[];b||f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(c._trigged=!1,d.push(e),b.transport&&b.transport.send())}),f.each(d,function(){this.setStatus(h.PROGRESS)}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")},stopUpload:function(b,c){var d,e=this;if(b===!0&&(c=b,b=null),e.runing!==!1){if(b){if(b=b.id?b:e.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(e.pool,function(a,c){return c.file===b?(d=c,!1):void 0}),d.transport&&d.transport.abort(),c&&(e._putback(d),e._popBlock(d)),a.nextTick(e.__tick)}e.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(e.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),e.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c; +return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a=''+''+''+''+"",c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(this.getRuid(),d)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=function(b){try{return a.JSON&&a.JSON.parse?JSON.parse(b):new Function("return "+b).call()}catch(c){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("preset/flashonly",["base","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/flash/filepicker","runtime/flash/image","runtime/flash/blob","runtime/flash/transport"],function(a){return a}),b("webuploader",["preset/flashonly"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/public/webuploader/webuploader.html5nodepend.js b/public/webuploader/webuploader.html5nodepend.js new file mode 100644 index 0000000..f29df3e --- /dev/null +++ b/public/webuploader/webuploader.html5nodepend.js @@ -0,0 +1,6589 @@ +/*! WebUploader 0.1.6 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jq-bridge 主要实现像jQuery一样的功能方法,可以替换成jQuery, + * 这里只实现了此组件所需的部分。 + * + * **此文件的代码还不可用,还是直接用jquery吧** + * @beta + */ + define('dollar-builtin',[],function() { + var doc = window.document, + emptyArray = [], + slice = emptyArray.slice, + class2type = {}, + hasOwn = class2type.hasOwnProperty, + toString = class2type.toString, + rId = /^#(.*)$/; + + function each( obj, iterator ) { + var i; + + //add guard here + if(!obj) { + return; + } + + // like array + if ( typeof obj !== 'function' && typeof obj.length === 'number' ) { + for ( i = 0; i < obj.length; i++ ) { + if ( iterator.call( obj[ i ], i, obj[ i ] ) === false ) { + return obj; + } + } + } else { + for ( i in obj ) { + if ( hasOwn.call( obj, i ) && iterator.call( obj[ i ], i, + obj[ i ] ) === false ) { + return obj; + } + } + } + + return obj; + } + + function extend( target, source, deep ) { + each( source, function( key, val ) { + if ( deep && typeof val === 'object' ) { + if ( typeof target[ key ] !== 'object' ) { + target[ key ] = type( val ) === 'array' ? [] : {}; + } + extend( target[ key ], val, deep ); + } else { + target[ key ] = val; + } + }); + } + + each( ('Boolean Number String Function Array Date RegExp Object' + + ' Error').split(' '), function( i, name ) { + class2type[ '[object ' + name + ']' ] = name.toLowerCase(); + }); + + function setAttribute( node, name, value ) { + value == null ? node.removeAttribute( name ) : + node.setAttribute( name, value ); + } + + /** + * 只支持ID选择。 + */ + function $( elem ) { + var api = {}; + + elem = typeof elem === 'string' && rId.test( elem ) ? + doc.getElementById( RegExp.$1 ) : elem; + + if ( elem ) { + api[ 0 ] = elem; + api.length = 1; + } + + return $.extend( api, { + _wrap: true, + + get: function() { + return elem; + }, + + /** + * 添加className + */ + addClass: function( classname ) { + elem.classList.add( classname ); + return this; + }, + + removeClass: function( classname ) { + elem.classList.remove( classname ); + return this; + }, + + //$(...).each is used in the source + each: function(callback){ + [].every.call(this, function(el, idx){ + return callback.call(el, idx, el) !== false + }) + return this + }, + + html: function( html ) { + if ( html ) { + elem.innerHTML = html; + } + return elem.innerHTML; + }, + + attr: function( key, val ) { + if ( $.isObject( key ) ) { + $.each( key, function( k, v ) { + setAttribute( elem, k, v ); + }); + } else { + setAttribute( elem, key, val ); + } + }, + + empty: function() { + elem.innerHTML = ''; + return this; + }, + + before: function( el ) { + elem.parentNode.insertBefore( el, elem ); + }, + + append: function( el ) { + el = el._wrap ? el.get() : el; + elem.appendChild( el ); + }, + + text: function() { + return elem.textContent; + }, + + // on + on: function( type, fn ) { + if ( elem.addEventListener ) { + elem.addEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.attachEvent( 'on' + type, fn ); + } + + return this; + }, + + // off + off: function( type, fn ) { + if ( elem.removeEventListener ) { + elem.removeEventListener( type, fn, false ); + } else if ( elem.attachEvent ) { + elem.detachEvent( 'on' + type, fn ); + } + return this; + } + + }); + } + + $.each = each; + $.extend = function( /*[deep, ]*/target/*, source...*/ ) { + var args = slice.call( arguments, 1 ), + deep; + + if ( typeof target === 'boolean' ) { + deep = target; + target = args.shift(); + } + + args.forEach(function( arg ) { + arg && extend( target, arg, deep ); + }); + + return target; + }; + + function type( obj ) { + + /*jshint eqnull:true*/ + return obj == null ? String( obj ) : + class2type[ toString.call( obj ) ] || 'object'; + } + $.type = type; + + //$.grep is used in the source + $.grep = function( elems, callback, invert ) { + var callbackInverse, + matches = [], + i = 0, + length = elems.length, + callbackExpect = !invert; + + // Go through the array, only saving the items + // that pass the validator function + for ( ; i < length; i++ ) { + callbackInverse = !callback( elems[ i ], i ); + if ( callbackInverse !== callbackExpect ) { + matches.push( elems[ i ] ); + } + } + + return matches; + } + + $.isWindow = function( obj ) { + return obj && obj.window === obj; + }; + + $.isPlainObject = function( obj ) { + if ( type( obj ) !== 'object' || obj.nodeType || $.isWindow( obj ) ) { + return false; + } + + try { + if ( obj.constructor && !hasOwn.call( obj.constructor.prototype, + 'isPrototypeOf' ) ) { + return false; + } + } catch ( ex ) { + return false; + } + + return true; + }; + + $.isObject = function( anything ) { + return type( anything ) === 'object'; + }; + + $.trim = function( str ) { + return str ? str.trim() : ''; + }; + + $.isFunction = function( obj ) { + return type( obj ) === 'function'; + }; + + emptyArray = null; + + return $; + }); + + define('dollar',[ + 'dollar-builtin' + ], function( $ ) { + return $; + }); + /** + * 直接来源于jquery的代码。 + * @fileOverview Promise/A+ + * @beta + */ + define('promise-builtin',[ + 'dollar' + ], function( $ ) { + + var api; + + // 简单版Callbacks, 默认memory,可选once. + function Callbacks( once ) { + var list = [], + stack = !once && [], + fire = function( data ) { + memory = data; + fired = true; + firingIndex = firingStart || 0; + firingStart = 0; + firingLength = list.length; + firing = true; + + for ( ; list && firingIndex < firingLength; firingIndex++ ) { + list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ); + } + firing = false; + + if ( list ) { + if ( stack ) { + stack.length && fire( stack.shift() ); + } else { + list = []; + } + } + }, + self = { + add: function() { + if ( list ) { + var start = list.length; + (function add ( args ) { + $.each( args, function( _, arg ) { + var type = $.type( arg ); + if ( type === 'function' ) { + list.push( arg ); + } else if ( arg && arg.length && + type !== 'string' ) { + + add( arg ); + } + }); + })( arguments ); + + if ( firing ) { + firingLength = list.length; + } else if ( memory ) { + firingStart = start; + fire( memory ); + } + } + return this; + }, + + disable: function() { + list = stack = memory = undefined; + return this; + }, + + // Lock the list in its current state + lock: function() { + stack = undefined; + if ( !memory ) { + self.disable(); + } + return this; + }, + + fireWith: function( context, args ) { + if ( list && (!fired || stack) ) { + args = args || []; + args = [ context, args.slice ? args.slice() : args ]; + if ( firing ) { + stack.push( args ); + } else { + fire( args ); + } + } + return this; + }, + + fire: function() { + self.fireWith( this, arguments ); + return this; + } + }, + + fired, firing, firingStart, firingLength, firingIndex, memory; + + return self; + } + + function Deferred( func ) { + var tuples = [ + // action, add listener, listener list, final state + [ 'resolve', 'done', Callbacks( true ), 'resolved' ], + [ 'reject', 'fail', Callbacks( true ), 'rejected' ], + [ 'notify', 'progress', Callbacks() ] + ], + state = 'pending', + promise = { + state: function() { + return state; + }, + always: function() { + deferred.done( arguments ).fail( arguments ); + return this; + }, + then: function( /* fnDone, fnFail, fnProgress */ ) { + var fns = arguments; + return Deferred(function( newDefer ) { + $.each( tuples, function( i, tuple ) { + var action = tuple[ 0 ], + fn = $.isFunction( fns[ i ] ) && fns[ i ]; + + // deferred[ done | fail | progress ] for + // forwarding actions to newDefer + deferred[ tuple[ 1 ] ](function() { + var returned; + + returned = fn && fn.apply( this, arguments ); + + if ( returned && + $.isFunction( returned.promise ) ) { + + returned.promise() + .done( newDefer.resolve ) + .fail( newDefer.reject ) + .progress( newDefer.notify ); + } else { + newDefer[ action + 'With' ]( + this === promise ? + newDefer.promise() : + this, + fn ? [ returned ] : arguments ); + } + }); + }); + fns = null; + }).promise(); + }, + + // Get a promise for this deferred + // If obj is provided, the promise aspect is added to the object + promise: function( obj ) { + + return obj != null ? $.extend( obj, promise ) : promise; + } + }, + deferred = {}; + + // Keep pipe for back-compat + promise.pipe = promise.then; + + // Add list-specific methods + $.each( tuples, function( i, tuple ) { + var list = tuple[ 2 ], + stateString = tuple[ 3 ]; + + // promise[ done | fail | progress ] = list.add + promise[ tuple[ 1 ] ] = list.add; + + // Handle state + if ( stateString ) { + list.add(function() { + // state = [ resolved | rejected ] + state = stateString; + + // [ reject_list | resolve_list ].disable; progress_list.lock + }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock ); + } + + // deferred[ resolve | reject | notify ] + deferred[ tuple[ 0 ] ] = function() { + deferred[ tuple[ 0 ] + 'With' ]( this === deferred ? promise : + this, arguments ); + return this; + }; + deferred[ tuple[ 0 ] + 'With' ] = list.fireWith; + }); + + // Make the deferred a promise + promise.promise( deferred ); + + // Call given func if any + if ( func ) { + func.call( deferred, deferred ); + } + + // All done! + return deferred; + } + + api = { + /** + * 创建一个[Deferred](http://api.jquery.com/category/deferred-object/)对象。 + * 详细的Deferred用法说明,请参照jQuery的API文档。 + * + * Deferred对象在钩子回掉函数中经常要用到,用来处理需要等待的异步操作。 + * + * @for Base + * @method Deferred + * @grammar Base.Deferred() => Deferred + * @example + * // 在文件开始发送前做些异步操作。 + * // WebUploader会等待此异步操作完成后,开始发送文件。 + * Uploader.register({ + * 'before-send-file': 'doSomthingAsync' + * }, { + * + * doSomthingAsync: function() { + * var deferred = Base.Deferred(); + * + * // 模拟一次异步操作。 + * setTimeout(deferred.resolve, 2000); + * + * return deferred.promise(); + * } + * }); + */ + Deferred: Deferred, + + /** + * 判断传入的参数是否为一个promise对象。 + * @method isPromise + * @grammar Base.isPromise( anything ) => Boolean + * @param {*} anything 检测对象。 + * @return {Boolean} + * @for Base + * @example + * console.log( Base.isPromise() ); // => false + * console.log( Base.isPromise({ key: '123' }) ); // => false + * console.log( Base.isPromise( Base.Deferred().promise() ) ); // => true + * + * // Deferred也是一个Promise + * console.log( Base.isPromise( Base.Deferred() ) ); // => true + */ + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + }, + + /** + * 返回一个promise,此promise在所有传入的promise都完成了后完成。 + * 详细请查看[这里](http://api.jquery.com/jQuery.when/)。 + * + * @method when + * @for Base + * @grammar Base.when( promise1[, promise2[, promise3...]] ) => Promise + */ + when: function( subordinate /* , ..., subordinateN */ ) { + var i = 0, + slice = [].slice, + resolveValues = slice.call( arguments ), + length = resolveValues.length, + + // the count of uncompleted subordinates + remaining = length !== 1 || (subordinate && + $.isFunction( subordinate.promise )) ? length : 0, + + // the master Deferred. If resolveValues consist of + // only a single Deferred, just use that. + deferred = remaining === 1 ? subordinate : Deferred(), + + // Update function for both resolve and progress values + updateFunc = function( i, contexts, values ) { + return function( value ) { + contexts[ i ] = this; + values[ i ] = arguments.length > 1 ? + slice.call( arguments ) : value; + + if ( values === progressValues ) { + deferred.notifyWith( contexts, values ); + } else if ( !(--remaining) ) { + deferred.resolveWith( contexts, values ); + } + }; + }, + + progressValues, progressContexts, resolveContexts; + + // add listeners to Deferred subordinates; treat others as resolved + if ( length > 1 ) { + progressValues = new Array( length ); + progressContexts = new Array( length ); + resolveContexts = new Array( length ); + for ( ; i < length; i++ ) { + if ( resolveValues[ i ] && + $.isFunction( resolveValues[ i ].promise ) ) { + + resolveValues[ i ].promise() + .done( updateFunc( i, resolveContexts, + resolveValues ) ) + .fail( deferred.reject ) + .progress( updateFunc( i, progressContexts, + progressValues ) ); + } else { + --remaining; + } + } + } + + // if we're not waiting on anything, resolve the master + if ( !remaining ) { + deferred.resolveWith( resolveContexts, resolveValues ); + } + + return deferred.promise(); + } + }; + + return api; + }); + define('promise',[ + 'promise-builtin' + ], function( $ ) { + return $; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.6', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClient, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClient.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file', + style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick" + }; + + Base.inherits( RuntimeClient, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button, + style = opts.style; + + if (style) + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + if (style) + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + if (style) + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addBtn + * @for Uploader + * @grammar addBtn( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addBtn({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.on('dialogopen', function() { + me.owner.trigger('dialogOpen', picker.button); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + if ( files.length ) { + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + // 销毁上传相关的属性。 + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定的文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + file.setStatus( Status.QUEUED ); + + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + file.setStatus( Status.PROGRESS ); + }); + + + } else if (file.getStatus() !== Status.PROGRESS) { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return Base.nextTick( me.__tick ); + } + + me.runing = true; + var files = []; + + // 如果有暂停的,则续传 + file || $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + me._trigged = false; + files.push(file); + v.transport && v.transport.send(); + } + }); + + $.each(files, function() { + this.setStatus( Status.PROGRESS ); + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this, + block; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + + + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file === file) { + block = v; + return false; + } + }); + + block.transport && block.transport.abort(); + + if (interrupt) { + me._putback(block); + me._popBlock(block); + } + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + // 正在准备中的文件。 + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'capture', 'camera'); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function(e) { + input.trigger('click'); + e.stopPropagation(); + owner.trigger('dialogopen'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + //debugger; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._metas = val; + return this; + } + + // getter + return this._metas; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview 只有html5实现的文件版本。 + */ + define('preset/html5only',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/image', + 'runtime/html5/transport' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/html5only' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/public/webuploader/webuploader.html5only.js b/public/webuploader/webuploader.html5only.js new file mode 100644 index 0000000..78843bd --- /dev/null +++ b/public/webuploader/webuploader.html5only.js @@ -0,0 +1,6059 @@ +/*! WebUploader 0.1.6 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + * @require "jquery" + * @require "zepto" + */ + define('dollar-third',[],function() { + var req = window.require; + var $ = window.__dollar || + window.jQuery || + window.Zepto || + req('jquery') || + req('zepto'); + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.6', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClient, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClient.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file', + style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick" + }; + + Base.inherits( RuntimeClient, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button, + style = opts.style; + + if (style) + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + if (style) + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + if (style) + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addBtn + * @for Uploader + * @grammar addBtn( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addBtn({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.on('dialogopen', function() { + me.owner.trigger('dialogOpen', picker.button); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + if ( files.length ) { + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + // 销毁上传相关的属性。 + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定的文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + file.setStatus( Status.QUEUED ); + + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + file.setStatus( Status.PROGRESS ); + }); + + + } else if (file.getStatus() !== Status.PROGRESS) { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return Base.nextTick( me.__tick ); + } + + me.runing = true; + var files = []; + + // 如果有暂停的,则续传 + file || $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + me._trigged = false; + files.push(file); + v.transport && v.transport.send(); + } + }); + + $.each(files, function() { + this.setStatus( Status.PROGRESS ); + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this, + block; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + + + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file === file) { + block = v; + return false; + } + }); + + block.transport && block.transport.abort(); + + if (interrupt) { + me._putback(block); + me._popBlock(block); + } + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + // 正在准备中的文件。 + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'capture', 'camera'); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function(e) { + input.trigger('click'); + e.stopPropagation(); + owner.trigger('dialogopen'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + //debugger; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._metas = val; + return this; + } + + // getter + return this._metas; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview 只有html5实现的文件版本。 + */ + define('preset/html5only',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/image', + 'runtime/html5/transport' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/html5only' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/public/webuploader/webuploader.html5only.min.js b/public/webuploader/webuploader.html5only.min.js new file mode 100644 index 0000000..9565c44 --- /dev/null +++ b/public/webuploader/webuploader.html5only.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.6 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.require,c=a.__dollar||a.jQuery||a.Zepto||b("jquery")||b("zepto");if(!c)throw new Error("jQuery or Zepto not found!");return c}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.6",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b,void 0):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?("name"===a&&(g.name=c.name),void 0):(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a,void 0)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file",style:"webuploader-pick"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button,h=e.style;h&&g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":h&&g.addClass("webuploader-pick-hover");break;case"mouseleave":h&&g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.on("dialogopen",function(){e.owner.trigger("dialogOpen",j.button)}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)}),void 0):(b(!0),void 0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):(b.owner.trigger("error","Q_TYPE_DENIED",a),void 0):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),a.length&&(b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20))},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),b||f.request("start-upload"),void 0;for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b?(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT?(b.setStatus(h.QUEUED),f.each(c.pool,function(a,c){c.file===b&&(c.transport&&c.transport.send(),b.setStatus(h.PROGRESS))})):b.getStatus()!==h.PROGRESS&&b.setStatus(h.QUEUED)):f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)}),c.runing)return a.nextTick(c.__tick);c.runing=!0;var d=[];b||f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(c._trigged=!1,d.push(e),b.transport&&b.transport.send())}),f.each(d,function(){this.setStatus(h.PROGRESS)}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")},stopUpload:function(b,c){var d,e=this;if(b===!0&&(c=b,b=null),e.runing!==!1){if(b){if(b=b.id?b:e.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(e.pool,function(a,c){return c.file===b?(d=c,!1):void 0}),d.transport&&d.transport.abort(),c&&(e._putback(d),e._popBlock(d)),a.nextTick(e.__tick)}e.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(e.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),e.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send() +},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return g.dndOver=!1,g.elem.removeClass(e+"over"),f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("capture","camera"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(a){k.trigger("click"),a.stopPropagation(),h.trigger("dialogopen")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return a.log("Invalid Exif data: Invalid tag type."),void 0;if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return a.log("Invalid Exif data: Invalid data offset."),void 0;if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return a.log("Invalid Exif data: Invalid directory offset."),void 0;if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return a.log("Invalid Exif data: Invalid directory size."),void 0;for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return a.log("Invalid Exif data: Invalid segment size."),void 0;if(0!==b.getUint16(d+8))return a.log("Invalid Exif data: Missing byte alignment offset."),void 0;switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return a.log("Invalid Exif data: Invalid byte alignment marker."),void 0}if(42!==b.getUint16(i+2,g))return a.log("Invalid Exif data: Missing TIFF marker."),void 0;h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/image",["base","runtime/html5/runtime","runtime/html5/util"],function(a,b,c){var d="data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D";return b.register("Image",{modified:!1,init:function(){var a=this,b=new Image;b.onload=function(){a._info={type:a.type,width:this.width,height:this.height},a._metas||"image/jpeg"!==a.type?a.owner.trigger("load"):c.parseMeta(a._blob,function(b,c){a._metas=c,a.owner.trigger("load")})},b.onerror=function(){a.owner.trigger("error")},a._img=b},loadFromBlob:function(a){var b=this,d=b._img;b._blob=a,b.type=a.type,d.src=c.createObjectURL(a.getSource()),b.owner.once("load",function(){c.revokeObjectURL(d.src)})},resize:function(a,b){var c=this._canvas||(this._canvas=document.createElement("canvas"));this._resize(this._img,c,a,b),this._blob=null,this.modified=!0,this.owner.trigger("complete","resize")},crop:function(a,b,c,d,e){var f=this._canvas||(this._canvas=document.createElement("canvas")),g=this.options,h=this._img,i=h.naturalWidth,j=h.naturalHeight,k=this.getOrientation();e=e||1,f.width=c,f.height=d,g.preserveHeaders||this._rotate2Orientaion(f,k),this._renderImageToCanvas(f,h,-a,-b,i*e,j*e),this._blob=null,this.modified=!0,this.owner.trigger("complete","crop")},getAsBlob:function(a){var b,d=this._blob,e=this.options;if(a=a||this.type,this.modified||this.type!==a){if(b=this._canvas,"image/jpeg"===a){if(d=c.canvasToDataUrl(b,a,e.quality),e.preserveHeaders&&this._metas&&this._metas.imageHead)return d=c.dataURL2ArrayBuffer(d),d=c.updateImageHead(d,this._metas.imageHead),d=c.arrayBufferToBlob(d,a)}else d=c.canvasToDataUrl(b,a);d=c.dataURL2Blob(d)}return d},getAsDataUrl:function(a){var b=this.options;return a=a||this.type,"image/jpeg"===a?c.canvasToDataUrl(this._canvas,a,b.quality):this._canvas.toDataURL(a)},getOrientation:function(){return this._metas&&this._metas.exif&&this._metas.exif.get("Orientation")||1},info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._metas=a,this):this._metas},destroy:function(){var a=this._canvas;this._img.onload=null,a&&(a.getContext("2d").clearRect(0,0,a.width,a.height),a.width=a.height=0,this._canvas=null),this._img.src=d,this._img=this._blob=null},_resize:function(a,b,c,d){var e,f,g,h,i,j=this.options,k=a.width,l=a.height,m=this.getOrientation();~[5,6,7,8].indexOf(m)&&(c^=d,d^=c,c^=d),e=Math[j.crop?"max":"min"](c/k,d/l),j.allowMagnify||(e=Math.min(1,e)),f=k*e,g=l*e,j.crop?(b.width=c,b.height=d):(b.width=f,b.height=g),h=(b.width-f)/2,i=(b.height-g)/2,j.preserveHeaders||this._rotate2Orientaion(b,m),this._renderImageToCanvas(b,a,h,i,f,g)},_rotate2Orientaion:function(a,b){var c=a.width,d=a.height,e=a.getContext("2d");switch(b){case 5:case 6:case 7:case 8:a.width=d,a.height=c}switch(b){case 2:e.translate(c,0),e.scale(-1,1);break;case 3:e.translate(c,d),e.rotate(Math.PI);break;case 4:e.translate(0,d),e.scale(1,-1);break;case 5:e.rotate(.5*Math.PI),e.scale(1,-1);break;case 6:e.rotate(.5*Math.PI),e.translate(0,-d);break;case 7:e.rotate(.5*Math.PI),e.translate(c,-d),e.scale(-1,1);break;case 8:e.rotate(-.5*Math.PI),e.translate(-c,0)}},_renderImageToCanvas:function(){function b(a,b,c){var d,e,f,g=document.createElement("canvas"),h=g.getContext("2d"),i=0,j=c,k=c;for(g.width=1,g.height=c,h.drawImage(a,0,0),d=h.getImageData(0,0,1,c).data;k>i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("preset/html5only",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/image","runtime/html5/transport"],function(a){return a}),b("webuploader",["preset/html5only"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/public/webuploader/webuploader.js b/public/webuploader/webuploader.js new file mode 100644 index 0000000..2816e2a --- /dev/null +++ b/public/webuploader/webuploader.js @@ -0,0 +1,8140 @@ +/*! WebUploader 0.1.6 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + * @require "jquery" + * @require "zepto" + */ + define('dollar-third',[],function() { + var req = window.require; + var $ = window.__dollar || + window.jQuery || + window.Zepto || + req('jquery') || + req('zepto'); + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.6', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClient, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClient.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file', + style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick" + }; + + Base.inherits( RuntimeClient, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button, + style = opts.style; + + if (style) + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + if (style) + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + if (style) + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addBtn + * @for Uploader + * @grammar addBtn( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addBtn({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.on('dialogopen', function() { + me.owner.trigger('dialogOpen', picker.button); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + if ( files.length ) { + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + // 销毁上传相关的属性。 + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定的文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + file.setStatus( Status.QUEUED ); + + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + file.setStatus( Status.PROGRESS ); + }); + + + } else if (file.getStatus() !== Status.PROGRESS) { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return Base.nextTick( me.__tick ); + } + + me.runing = true; + var files = []; + + // 如果有暂停的,则续传 + file || $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + me._trigged = false; + files.push(file); + v.transport && v.transport.send(); + } + }); + + $.each(files, function() { + this.setStatus( Status.PROGRESS ); + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this, + block; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + + + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file === file) { + block = v; + return false; + } + }); + + block.transport && block.transport.abort(); + + if (interrupt) { + me._putback(block); + me._popBlock(block); + } + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + // 正在准备中的文件。 + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Md5 + */ + define('lib/md5',[ + 'runtime/client', + 'mediator' + ], function( RuntimeClient, Mediator ) { + + function Md5() { + RuntimeClient.call( this, 'Md5' ); + } + + // 让 Md5 具备事件功能。 + Mediator.installTo( Md5.prototype ); + + Md5.prototype.loadFromBlob = function( blob ) { + var me = this; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + me.exec( 'loadFromBlob', blob ); + }); + }; + + Md5.prototype.getResult = function() { + return this.exec('getResult'); + }; + + return Md5; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/md5',[ + 'base', + 'uploader', + 'lib/md5', + 'lib/blob', + 'widgets/widget' + ], function( Base, Uploader, Md5, Blob ) { + + return Uploader.register({ + name: 'md5', + + + /** + * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 + * + * + * @method md5File + * @grammar md5File( file[, start[, end]] ) => promise + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.md5File( file ) + * + * // 及时显示进度 + * .progress(function(percentage) { + * console.log('Percentage:', percentage); + * }) + * + * // 完成 + * .then(function(val) { + * console.log('md5 result:', val); + * }); + * + * }); + */ + md5File: function( file, start, end ) { + var md5 = new Md5(), + deferred = Base.Deferred(), + blob = (file instanceof Blob) ? file : + this.request( 'get-file', file ).source; + + md5.on( 'progress load', function( e ) { + e = e || {}; + deferred.notify( e.total ? e.loaded / e.total : 1 ); + }); + + md5.on( 'complete', function() { + deferred.resolve( md5.getResult() ); + }); + + md5.on( 'error', function( reason ) { + deferred.reject( reason ); + }); + + if ( arguments.length > 1 ) { + start = start || 0; + end = end || 0; + start < 0 && (start = blob.size + start); + end < 0 && (end = blob.size + end); + end = Math.min( end, blob.size ); + blob = blob.slice( start, end ); + } + + md5.loadFromBlob( blob ); + + return deferred.promise(); + } + }); + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'capture', 'camera'); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function(e) { + input.trigger('click'); + e.stopPropagation(); + owner.trigger('dialogopen'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug + * android里面toDataUrl('image/jpege')得到的结果却是png. + * + * 所以这里没辙,只能借助这个工具 + * @fileOverview jpeg encoder + */ + define('runtime/html5/jpegencoder',[], function( require, exports, module ) { + + /* + Copyright (c) 2008, Adobe Systems Incorporated + 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 Adobe Systems Incorporated 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. + */ + /* + JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + + Basic GUI blocking jpeg encoder + */ + + function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + byteout.push(clt[value]); // write char directly instead of converting later + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + // var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + // var duration = new Date().getTime() - time_start; + // console.log('Encoding time: '+ currentQuality + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + // console.log('Quality set to: '+quality +'%'); + } + + function init(){ + // var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + // var duration = new Date().getTime() - time_start; + // console.log('Initialization '+ duration + 'ms'); + } + + init(); + + }; + + JPEGEncoder.encode = function( data, quality ) { + var encoder = new JPEGEncoder( quality ); + + return encoder.encode( data ); + } + + return JPEGEncoder; + }); + /** + * @fileOverview Fix android canvas.toDataUrl bug. + */ + define('runtime/html5/androidpatch',[ + 'runtime/html5/util', + 'runtime/html5/jpegencoder', + 'base' + ], function( Util, encoder, Base ) { + var origin = Util.canvasToDataUrl, + supportJpeg; + + Util.canvasToDataUrl = function( canvas, type, quality ) { + var ctx, w, h, fragement, parts; + + // 非android手机直接跳过。 + if ( !Base.os.android ) { + return origin.apply( null, arguments ); + } + + // 检测是否canvas支持jpeg导出,根据数据格式来判断。 + // JPEG 前两位分别是:255, 216 + if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { + fragement = origin.apply( null, arguments ); + + parts = fragement.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + fragement = atob( parts[ 1 ] ); + } else { + fragement = decodeURIComponent( parts[ 1 ] ); + } + + fragement = fragement.substring( 0, 2 ); + + supportJpeg = fragement.charCodeAt( 0 ) === 255 && + fragement.charCodeAt( 1 ) === 216; + } + + // 只有在android环境下才修复 + if ( type === 'image/jpeg' && !supportJpeg ) { + w = canvas.width; + h = canvas.height; + ctx = canvas.getContext('2d'); + + return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); + } + + return origin.apply( null, arguments ); + }; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + //debugger; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._metas = val; + return this; + } + + // getter + return this._metas; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/html5/md5',[ + 'runtime/html5/runtime' + ], function( FlashRuntime ) { + + /* + * Fastest md5 implementation around (JKM md5) + * Credits: Joseph Myers + * + * @see http://www.myersdaily.org/joseph/javascript/md5-text.html + * @see http://jsperf.com/md5-shootout/7 + */ + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + }, + + cmn = function (q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + }, + + ff = function (a, b, c, d, x, s, t) { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + }, + + gg = function (a, b, c, d, x, s, t) { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + }, + + hh = function (a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); + }, + + ii = function (a, b, c, d, x, s, t) { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + }, + + md5cycle = function (x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + }, + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + md5blk = function (s) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + }, + + md5blk_array = function (a) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + }, + + md51 = function (s) { + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + } + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + return state; + }, + + md51_array = function (a) { + var n = a.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + + // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 + // containing the last element of the parent array if the sub array specified starts + // beyond the length of the parent array - weird. + // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue + a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); + + length = a.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << ((i % 4) << 3); + } + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + + return state; + }, + + hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], + + rhex = function (n) { + var s = '', + j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + } + return s; + }, + + hex = function (x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(''); + }, + + md5 = function (s) { + return hex(md51(s)); + }, + + + + //////////////////////////////////////////////////////////////////////////// + + /** + * SparkMD5 OOP implementation. + * + * Use this class to perform an incremental md5, otherwise use the + * static methods instead. + */ + SparkMD5 = function () { + // call reset to init the instance + this.reset(); + }; + + + // In some cases the fast add32 function cannot be used.. + if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { + add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + } + + + /** + * Appends a string. + * A conversion will be applied if an utf8 string is detected. + * + * @param {String} str The string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.append = function (str) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + // then append as binary + this.appendBinary(str); + + return this; + }; + + /** + * Appends a binary string. + * + * @param {String} contents The binary string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.appendBinary = function (contents) { + this._buff += contents; + this._length += contents.length; + + var length = this._buff.length, + i; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); + } + + this._buff = this._buff.substr(i - 64); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + i, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + /** + * Finish the final calculation based on the tail. + * + * @param {Array} tail The tail (will be modified) + * @param {Number} length The length of the remaining buffer + */ + SparkMD5.prototype._finish = function (tail, length) { + var i = length, + tmp, + lo, + hi; + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(this._state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Do the final computation based on the tail and length + // Beware that the final length may not fit in 32 bits so we take care of that + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + md5cycle(this._state, tail); + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.reset = function () { + this._buff = ""; + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.prototype.destroy = function () { + delete this._state; + delete this._buff; + delete this._length; + }; + + + /** + * Performs the md5 hash on a string. + * A conversion will be applied if utf8 string is detected. + * + * @param {String} str The string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hash = function (str, raw) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + var hash = md51(str); + + return !!raw ? hash : hex(hash); + }; + + /** + * Performs the md5 hash on a binary string. + * + * @param {String} content The binary string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hashBinary = function (content, raw) { + var hash = md51(content); + + return !!raw ? hash : hex(hash); + }; + + /** + * SparkMD5 OOP implementation for array buffers. + * + * Use this class to perform an incremental md5 ONLY for array buffers. + */ + SparkMD5.ArrayBuffer = function () { + // call reset to init the instance + this.reset(); + }; + + //////////////////////////////////////////////////////////////////////////// + + /** + * Appends an array buffer. + * + * @param {ArrayBuffer} arr The array to be appended + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.append = function (arr) { + // TODO: we could avoid the concatenation here but the algorithm would be more complex + // if you find yourself needing extra performance, please make a PR. + var buff = this._concatArrayBuffer(this._buff, arr), + length = buff.length, + i; + + this._length += arr.byteLength; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); + } + + // Avoids IE10 weirdness (documented above) + this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + i, + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.reset = function () { + this._buff = new Uint8Array(0); + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + + /** + * Concats two array buffers, returning a new one. + * + * @param {ArrayBuffer} first The first array buffer + * @param {ArrayBuffer} second The second array buffer + * + * @return {ArrayBuffer} The new array buffer + */ + SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { + var firstLength = first.length, + result = new Uint8Array(firstLength + second.byteLength); + + result.set(first); + result.set(new Uint8Array(second), firstLength); + + return result; + }; + + /** + * Performs the md5 hash on an array buffer. + * + * @param {ArrayBuffer} arr The array buffer + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.hash = function (arr, raw) { + var hash = md51_array(new Uint8Array(arr)); + + return !!raw ? hash : hex(hash); + }; + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( file ) { + var blob = file.getSource(), + chunkSize = 2 * 1024 * 1024, + chunks = Math.ceil( blob.size / chunkSize ), + chunk = 0, + owner = this.owner, + spark = new SparkMD5.ArrayBuffer(), + me = this, + blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, + loadNext, fr; + + fr = new FileReader(); + + loadNext = function() { + var start, end; + + start = chunk * chunkSize; + end = Math.min( start + chunkSize, blob.size ); + + fr.onload = function( e ) { + spark.append( e.target.result ); + owner.trigger( 'progress', { + total: file.size, + loaded: end + }); + }; + + fr.onloadend = function() { + fr.onloadend = fr.onload = null; + + if ( ++chunk < chunks ) { + setTimeout( loadNext, 1 ); + } else { + setTimeout(function(){ + owner.trigger('load'); + me.result = spark.end(); + loadNext = file = blob = spark = null; + owner.trigger('complete'); + }, 50 ); + } + }; + + fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); + }; + + loadNext(); + }, + + getResult: function() { + return this.result; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview 图片压缩 + */ + define('runtime/flash/image',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Image', { + // init: function( options ) { + // var owner = this.owner; + + // this.flashExec( 'Image', 'init', options ); + // owner.on( 'load', function() { + // debugger; + // }); + // }, + + loadFromBlob: function( blob ) { + var owner = this.owner; + + owner.info() && this.flashExec( 'Image', 'info', owner.info() ); + owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); + + this.flashExec( 'Image', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = function( s ) { + try { + if (window.JSON && window.JSON.parse) { + return JSON.parse(s); + } + + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview Md5 flash实现 + */ + define('runtime/flash/md5',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( blob ) { + return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview 完全版本。 + */ + define('preset/all',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + 'widgets/md5', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/androidpatch', + 'runtime/html5/image', + 'runtime/html5/transport', + 'runtime/html5/md5', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/image', + 'runtime/flash/transport', + 'runtime/flash/blob', + 'runtime/flash/md5' + ], function( Base ) { + return Base; + }); + /** + * @fileOverview 日志组件,主要用来收集错误信息,可以帮助 webuploader 更好的定位问题和发展。 + * + * 如果您不想要启用此功能,请在打包的时候去掉 log 模块。 + * + * 或者可以在初始化的时候通过 options.disableWidgets 属性禁用。 + * + * 如: + * WebUploader.create({ + * ... + * + * disableWidgets: 'log', + * + * ... + * }) + */ + define('widgets/log',[ + 'base', + 'uploader', + 'widgets/widget' + ], function( Base, Uploader ) { + var $ = Base.$, + logUrl = ' http://static.tieba.baidu.com/tb/pms/img/st.gif??', + product = (location.hostname || location.host || 'protected').toLowerCase(), + + // 只针对 baidu 内部产品用户做统计功能。 + enable = product && /baidu/i.exec(product), + base; + + if (!enable) { + return; + } + + base = { + dv: 3, + master: 'webuploader', + online: /test/.exec(product) ? 0 : 1, + module: '', + product: product, + type: 0 + }; + + function send(data) { + var obj = $.extend({}, base, data), + url = logUrl.replace(/^(.*)\?/, '$1' + $.param( obj )), + image = new Image(); + + image.src = url; + } + + return Uploader.register({ + name: 'log', + + init: function() { + var owner = this.owner, + count = 0, + size = 0; + + owner + .on('error', function(code) { + send({ + type: 2, + c_error_code: code + }); + }) + .on('uploadError', function(file, reason) { + send({ + type: 2, + c_error_code: 'UPLOAD_ERROR', + c_reason: '' + reason + }); + }) + .on('uploadComplete', function(file) { + count++; + size += file.size; + }). + on('uploadFinished', function() { + send({ + c_count: count, + c_size: size + }); + count = size = 0; + }); + + send({ + c_usage: 1 + }); + } + }); + }); + /** + * @fileOverview Uploader上传类 + */ + define('webuploader',[ + 'preset/all', + 'widgets/log' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/public/webuploader/webuploader.min.js b/public/webuploader/webuploader.min.js new file mode 100644 index 0000000..c5b2fa5 --- /dev/null +++ b/public/webuploader/webuploader.min.js @@ -0,0 +1,3 @@ +/* WebUploader 0.1.6 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.require,c=a.__dollar||a.jQuery||a.Zepto||b("jquery")||b("zepto");if(!c)throw new Error("jQuery or Zepto not found!");return c}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.6",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b,void 0):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?("name"===a&&(g.name=c.name),void 0):(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a,void 0)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file",style:"webuploader-pick"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button,h=e.style;h&&g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":h&&g.addClass("webuploader-pick-hover");break;case"mouseleave":h&&g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.on("dialogopen",function(){e.owner.trigger("dialogOpen",j.button)}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)}),void 0):(b(!0),void 0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):(b.owner.trigger("error","Q_TYPE_DENIED",a),void 0):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),a.length&&(b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20))},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),b||f.request("start-upload"),void 0;for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b?(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT?(b.setStatus(h.QUEUED),f.each(c.pool,function(a,c){c.file===b&&(c.transport&&c.transport.send(),b.setStatus(h.PROGRESS))})):b.getStatus()!==h.PROGRESS&&b.setStatus(h.QUEUED)):f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)}),c.runing)return a.nextTick(c.__tick);c.runing=!0;var d=[];b||f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(c._trigged=!1,d.push(e),b.transport&&b.transport.send())}),f.each(d,function(){this.setStatus(h.PROGRESS)}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")},stopUpload:function(b,c){var d,e=this;if(b===!0&&(c=b,b=null),e.runing!==!1){if(b){if(b=b.id?b:e.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(e.pool,function(a,c){return c.file===b?(d=c,!1):void 0}),d.transport&&d.transport.abort(),c&&(e._putback(d),e._popBlock(d)),a.nextTick(e.__tick)}e.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(e.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),e.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send() +},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("lib/md5",["runtime/client","mediator"],function(a,b){function c(){a.call(this,"Md5")}return b.installTo(c.prototype),c.prototype.loadFromBlob=function(a){var b=this;b.getRuid()&&b.disconnectRuntime(),b.connectRuntime(a.ruid,function(){b.exec("init"),b.exec("loadFromBlob",a)})},c.prototype.getResult=function(){return this.exec("getResult")},c}),b("widgets/md5",["base","uploader","lib/md5","lib/blob","widgets/widget"],function(a,b,c,d){return b.register({name:"md5",md5File:function(b,e,f){var g=new c,h=a.Deferred(),i=b instanceof d?b:this.request("get-file",b).source;return g.on("progress load",function(a){a=a||{},h.notify(a.total?a.loaded/a.total:1)}),g.on("complete",function(){h.resolve(g.getResult())}),g.on("error",function(a){h.reject(a)}),arguments.length>1&&(e=e||0,f=f||0,0>e&&(e=i.size+e),0>f&&(f=i.size+f),f=Math.min(f,i.size),i=i.slice(e,f)),g.loadFromBlob(i),h.promise()}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return g.dndOver=!1,g.elem.removeClass(e+"over"),f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("capture","camera"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(a){k.trigger("click"),a.stopPropagation(),h.trigger("dialogopen")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return a.log("Invalid Exif data: Invalid tag type."),void 0;if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return a.log("Invalid Exif data: Invalid data offset."),void 0;if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return a.log("Invalid Exif data: Invalid directory offset."),void 0;if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return a.log("Invalid Exif data: Invalid directory size."),void 0;for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return a.log("Invalid Exif data: Invalid segment size."),void 0;if(0!==b.getUint16(d+8))return a.log("Invalid Exif data: Missing byte alignment offset."),void 0;switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return a.log("Invalid Exif data: Invalid byte alignment marker."),void 0}if(42!==b.getUint16(i+2,g))return a.log("Invalid Exif data: Missing TIFF marker."),void 0;h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(8*z[P[i]]*h[j]*h[k]),C[i]=1/(8*A[P[i]]*h[j]*h[k]),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(255&a>>8),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?0|_+.5:0|_-.5;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=50>a?Math.floor(5e3/a):Math.floor(200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}Math.round;var t,u,v,w,x,y=Math.floor,z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/html5/md5",["runtime/html5/runtime"],function(a){var b=function(a,b){return 4294967295&a+b},c=function(a,c,d,e,f,g){return c=b(b(c,a),b(e,g)),b(c<>>32-f,d)},d=function(a,b,d,e,f,g,h){return c(b&d|~b&e,a,b,f,g,h)},e=function(a,b,d,e,f,g,h){return c(b&e|d&~e,a,b,f,g,h)},f=function(a,b,d,e,f,g,h){return c(b^d^e,a,b,f,g,h)},g=function(a,b,d,e,f,g,h){return c(d^(b|~e),a,b,f,g,h)},h=function(a,c){var h=a[0],i=a[1],j=a[2],k=a[3];h=d(h,i,j,k,c[0],7,-680876936),k=d(k,h,i,j,c[1],12,-389564586),j=d(j,k,h,i,c[2],17,606105819),i=d(i,j,k,h,c[3],22,-1044525330),h=d(h,i,j,k,c[4],7,-176418897),k=d(k,h,i,j,c[5],12,1200080426),j=d(j,k,h,i,c[6],17,-1473231341),i=d(i,j,k,h,c[7],22,-45705983),h=d(h,i,j,k,c[8],7,1770035416),k=d(k,h,i,j,c[9],12,-1958414417),j=d(j,k,h,i,c[10],17,-42063),i=d(i,j,k,h,c[11],22,-1990404162),h=d(h,i,j,k,c[12],7,1804603682),k=d(k,h,i,j,c[13],12,-40341101),j=d(j,k,h,i,c[14],17,-1502002290),i=d(i,j,k,h,c[15],22,1236535329),h=e(h,i,j,k,c[1],5,-165796510),k=e(k,h,i,j,c[6],9,-1069501632),j=e(j,k,h,i,c[11],14,643717713),i=e(i,j,k,h,c[0],20,-373897302),h=e(h,i,j,k,c[5],5,-701558691),k=e(k,h,i,j,c[10],9,38016083),j=e(j,k,h,i,c[15],14,-660478335),i=e(i,j,k,h,c[4],20,-405537848),h=e(h,i,j,k,c[9],5,568446438),k=e(k,h,i,j,c[14],9,-1019803690),j=e(j,k,h,i,c[3],14,-187363961),i=e(i,j,k,h,c[8],20,1163531501),h=e(h,i,j,k,c[13],5,-1444681467),k=e(k,h,i,j,c[2],9,-51403784),j=e(j,k,h,i,c[7],14,1735328473),i=e(i,j,k,h,c[12],20,-1926607734),h=f(h,i,j,k,c[5],4,-378558),k=f(k,h,i,j,c[8],11,-2022574463),j=f(j,k,h,i,c[11],16,1839030562),i=f(i,j,k,h,c[14],23,-35309556),h=f(h,i,j,k,c[1],4,-1530992060),k=f(k,h,i,j,c[4],11,1272893353),j=f(j,k,h,i,c[7],16,-155497632),i=f(i,j,k,h,c[10],23,-1094730640),h=f(h,i,j,k,c[13],4,681279174),k=f(k,h,i,j,c[0],11,-358537222),j=f(j,k,h,i,c[3],16,-722521979),i=f(i,j,k,h,c[6],23,76029189),h=f(h,i,j,k,c[9],4,-640364487),k=f(k,h,i,j,c[12],11,-421815835),j=f(j,k,h,i,c[15],16,530742520),i=f(i,j,k,h,c[2],23,-995338651),h=g(h,i,j,k,c[0],6,-198630844),k=g(k,h,i,j,c[7],10,1126891415),j=g(j,k,h,i,c[14],15,-1416354905),i=g(i,j,k,h,c[5],21,-57434055),h=g(h,i,j,k,c[12],6,1700485571),k=g(k,h,i,j,c[3],10,-1894986606),j=g(j,k,h,i,c[10],15,-1051523),i=g(i,j,k,h,c[1],21,-2054922799),h=g(h,i,j,k,c[8],6,1873313359),k=g(k,h,i,j,c[15],10,-30611744),j=g(j,k,h,i,c[6],15,-1560198380),i=g(i,j,k,h,c[13],21,1309151649),h=g(h,i,j,k,c[4],6,-145523070),k=g(k,h,i,j,c[11],10,-1120210379),j=g(j,k,h,i,c[2],15,718787259),i=g(i,j,k,h,c[9],21,-343485551),a[0]=b(h,a[0]),a[1]=b(i,a[1]),a[2]=b(j,a[2]),a[3]=b(k,a[3])},i=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a.charCodeAt(b)+(a.charCodeAt(b+1)<<8)+(a.charCodeAt(b+2)<<16)+(a.charCodeAt(b+3)<<24);return c},j=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a[b]+(a[b+1]<<8)+(a[b+2]<<16)+(a[b+3]<<24);return c},k=function(a){var b,c,d,e,f,g,j=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;j>=b;b+=64)h(k,i(a.substring(b-64,b)));for(a=a.substring(b-64),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a.charCodeAt(b)<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*j,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},l=function(a){var b,c,d,e,f,g,i=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;i>=b;b+=64)h(k,j(a.subarray(b-64,b)));for(a=i>b-64?a.subarray(b-64):new Uint8Array(0),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a[b]<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*i,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},m=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],n=function(a){var b,c="";for(b=0;4>b;b+=1)c+=m[15&a>>8*b+4]+m[15&a>>8*b];return c},o=function(a){var b;for(b=0;b>16)+(b>>16)+(c>>16);return d<<16|65535&c}),q.prototype.append=function(a){return/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a))),this.appendBinary(a),this},q.prototype.appendBinary=function(a){this._buff+=a,this._length+=a.length;var b,c=this._buff.length;for(b=64;c>=b;b+=64)h(this._state,i(this._buff.substring(b-64,b)));return this._buff=this._buff.substr(b-64),this},q.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d.charCodeAt(b)<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.prototype._finish=function(a,b){var c,d,e,f=b;if(a[f>>2]|=128<<(f%4<<3),f>55)for(h(this._state,a),f=0;16>f;f+=1)a[f]=0;c=8*this._length,c=c.toString(16).match(/(.*?)(.{0,8})$/),d=parseInt(c[2],16),e=parseInt(c[1],16)||0,a[14]=d,a[15]=e,h(this._state,a)},q.prototype.reset=function(){return this._buff="",this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.prototype.destroy=function(){delete this._state,delete this._buff,delete this._length},q.hash=function(a,b){/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a)));var c=k(a);return b?c:o(c) +},q.hashBinary=function(a,b){var c=k(a);return b?c:o(c)},q.ArrayBuffer=function(){this.reset()},q.ArrayBuffer.prototype.append=function(a){var b,c=this._concatArrayBuffer(this._buff,a),d=c.length;for(this._length+=a.byteLength,b=64;d>=b;b+=64)h(this._state,j(c.subarray(b-64,b)));return this._buff=d>b-64?c.subarray(b-64):new Uint8Array(0),this},q.ArrayBuffer.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d[b]<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.ArrayBuffer.prototype._finish=q.prototype._finish,q.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.ArrayBuffer.prototype.destroy=q.prototype.destroy,q.ArrayBuffer.prototype._concatArrayBuffer=function(a,b){var c=a.length,d=new Uint8Array(c+b.byteLength);return d.set(a),d.set(new Uint8Array(b),c),d},q.ArrayBuffer.hash=function(a,b){var c=l(new Uint8Array(a));return b?c:o(c)},a.register("Md5",{init:function(){},loadFromBlob:function(a){var b,c,d=a.getSource(),e=2097152,f=Math.ceil(d.size/e),g=0,h=this.owner,i=new q.ArrayBuffer,j=this,k=d.mozSlice||d.webkitSlice||d.slice;c=new FileReader,b=function(){var l,m;l=g*e,m=Math.min(l+e,d.size),c.onload=function(b){i.append(b.target.result),h.trigger("progress",{total:a.size,loaded:m})},c.onloadend=function(){c.onloadend=c.onload=null,++g'+''+''+''+"",c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=function(b){try{return a.JSON&&a.JSON.parse?JSON.parse(b):new Function("return "+b).call()}catch(c){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(this.getRuid(),d)}})}),b("runtime/flash/md5",["runtime/flash/runtime"],function(a){return a.register("Md5",{init:function(){},loadFromBlob:function(a){return this.flashExec("Md5","loadFromBlob",a.uid)}})}),b("preset/all",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","widgets/md5","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/androidpatch","runtime/html5/image","runtime/html5/transport","runtime/html5/md5","runtime/flash/filepicker","runtime/flash/image","runtime/flash/transport","runtime/flash/blob","runtime/flash/md5"],function(a){return a}),b("widgets/log",["base","uploader","widgets/widget"],function(a,b){function c(a){var b=e.extend({},d,a),c=f.replace(/^(.*)\?/,"$1"+e.param(b)),g=new Image;g.src=c}var d,e=a.$,f=" http://static.tieba.baidu.com/tb/pms/img/st.gif??",g=(location.hostname||location.host||"protected").toLowerCase(),h=g&&/baidu/i.exec(g);if(h)return d={dv:3,master:"webuploader",online:/test/.exec(g)?0:1,module:"",product:g,type:0},b.register({name:"log",init:function(){var a=this.owner,b=0,d=0;a.on("error",function(a){c({type:2,c_error_code:a})}).on("uploadError",function(a,b){c({type:2,c_error_code:"UPLOAD_ERROR",c_reason:""+b})}).on("uploadComplete",function(a){b++,d+=a.size}).on("uploadFinished",function(){c({c_count:b,c_size:d}),b=d=0}),c({c_usage:1})}})}),b("webuploader",["preset/all","widgets/log"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/public/webuploader/webuploader.noimage.js b/public/webuploader/webuploader.noimage.js new file mode 100644 index 0000000..b05055b --- /dev/null +++ b/public/webuploader/webuploader.noimage.js @@ -0,0 +1,5057 @@ +/*! WebUploader 0.1.6 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + * @require "jquery" + * @require "zepto" + */ + define('dollar-third',[],function() { + var req = window.require; + var $ = window.__dollar || + window.jQuery || + window.Zepto || + req('jquery') || + req('zepto'); + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.6', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClient, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClient.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file', + style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick" + }; + + Base.inherits( RuntimeClient, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button, + style = opts.style; + + if (style) + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + if (style) + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + if (style) + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addBtn + * @for Uploader + * @grammar addBtn( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addBtn({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.on('dialogopen', function() { + me.owner.trigger('dialogOpen', picker.button); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + if ( files.length ) { + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + // 销毁上传相关的属性。 + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定的文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + file.setStatus( Status.QUEUED ); + + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + file.setStatus( Status.PROGRESS ); + }); + + + } else if (file.getStatus() !== Status.PROGRESS) { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return Base.nextTick( me.__tick ); + } + + me.runing = true; + var files = []; + + // 如果有暂停的,则续传 + file || $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + me._trigged = false; + files.push(file); + v.transport && v.transport.send(); + } + }); + + $.each(files, function() { + this.setStatus( Status.PROGRESS ); + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this, + block; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + + + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file === file) { + block = v; + return false; + } + }); + + block.transport && block.transport.abort(); + + if (interrupt) { + me._putback(block); + me._popBlock(block); + } + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + // 正在准备中的文件。 + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'capture', 'camera'); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function(e) { + input.trigger('click'); + e.stopPropagation(); + owner.trigger('dialogopen'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = function( s ) { + try { + if (window.JSON && window.JSON.parse) { + return JSON.parse(s); + } + + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview 没有图像处理的版本。 + */ + define('preset/withoutimage',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/transport', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/transport', + 'runtime/flash/blob' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/withoutimage' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/public/webuploader/webuploader.noimage.min.js b/public/webuploader/webuploader.noimage.min.js new file mode 100644 index 0000000..f86a5a5 --- /dev/null +++ b/public/webuploader/webuploader.noimage.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.6 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.require,c=a.__dollar||a.jQuery||a.Zepto||b("jquery")||b("zepto");if(!c)throw new Error("jQuery or Zepto not found!");return c}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.6",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b,void 0):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?("name"===a&&(g.name=c.name),void 0):(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a,void 0)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file",style:"webuploader-pick"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button,h=e.style;h&&g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":h&&g.addClass("webuploader-pick-hover");break;case"mouseleave":h&&g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.on("dialogopen",function(){e.owner.trigger("dialogOpen",j.button)}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("file",["base","mediator"],function(a,b){function c(){return f+g++}function d(a){this.name=a.name||"Untitled",this.size=a.size||0,this.type=a.type||"application/octet-stream",this.lastModifiedDate=a.lastModifiedDate||1*new Date,this.id=c(),this.ext=h.exec(this.name)?RegExp.$1:"",this.statusText="",i[this.id]=d.Status.INITED,this.source=a,this.loaded=0,this.on("error",function(a){this.setStatus(d.Status.ERROR,a)})}var e=a.$,f="WU_FILE_",g=0,h=/\.([^.]+)$/,i={};return e.extend(d.prototype,{setStatus:function(a,b){var c=i[this.id];"undefined"!=typeof b&&(this.statusText=b),a!==c&&(i[this.id]=a,this.trigger("statuschange",a,c))},getStatus:function(){return i[this.id]},getSource:function(){return this.source},destroy:function(){this.off(),delete i[this.id]}}),b.installTo(d.prototype),d.Status={INITED:"inited",QUEUED:"queued",PROGRESS:"progress",ERROR:"error",COMPLETE:"complete",CANCELLED:"cancelled",INTERRUPT:"interrupt",INVALID:"invalid"},d}),b("queue",["base","mediator","file"],function(a,b,c){function d(){this.stats={numOfQueue:0,numOfSuccess:0,numOfCancel:0,numOfProgress:0,numOfUploadFailed:0,numOfInvalid:0,numofDeleted:0,numofInterrupt:0},this._queue=[],this._map={}}var e=a.$,f=c.Status;return e.extend(d.prototype,{append:function(a){return this._queue.push(a),this._fileAdded(a),this},prepend:function(a){return this._queue.unshift(a),this._fileAdded(a),this},getFile:function(a){return"string"!=typeof a?a:this._map[a]},fetch:function(a){var b,c,d=this._queue.length;for(a=a||f.QUEUED,b=0;d>b;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):(b.owner.trigger("error","Q_TYPE_DENIED",a),void 0):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),a.length&&(b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20))},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),b||f.request("start-upload"),void 0;for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b?(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT?(b.setStatus(h.QUEUED),f.each(c.pool,function(a,c){c.file===b&&(c.transport&&c.transport.send(),b.setStatus(h.PROGRESS))})):b.getStatus()!==h.PROGRESS&&b.setStatus(h.QUEUED)):f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)}),c.runing)return a.nextTick(c.__tick);c.runing=!0;var d=[];b||f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(c._trigged=!1,d.push(e),b.transport&&b.transport.send())}),f.each(d,function(){this.setStatus(h.PROGRESS)}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")},stopUpload:function(b,c){var d,e=this;if(b===!0&&(c=b,b=null),e.runing!==!1){if(b){if(b=b.id?b:e.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(e.pool,function(a,c){return c.file===b?(d=c,!1):void 0}),d.transport&&d.transport.abort(),c&&(e._putback(d),e._popBlock(d)),a.nextTick(e.__tick)}e.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(e.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),e.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments) +}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return g.dndOver=!1,g.elem.removeClass(e+"over"),f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("capture","camera"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(a){k.trigger("click"),a.stopPropagation(),h.trigger("dialogopen")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a=''+''+''+''+"",c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=function(b){try{return a.JSON&&a.JSON.parse?JSON.parse(b):new Function("return "+b).call()}catch(c){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(this.getRuid(),d)}})}),b("preset/withoutimage",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/transport","runtime/flash/filepicker","runtime/flash/transport","runtime/flash/blob"],function(a){return a}),b("webuploader",["preset/withoutimage"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/public/webuploader/webuploader.nolog.js b/public/webuploader/webuploader.nolog.js new file mode 100644 index 0000000..c7e132a --- /dev/null +++ b/public/webuploader/webuploader.nolog.js @@ -0,0 +1,8046 @@ +/*! WebUploader 0.1.6 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + * @require "jquery" + * @require "zepto" + */ + define('dollar-third',[],function() { + var req = window.require; + var $ = window.__dollar || + window.jQuery || + window.Zepto || + req('jquery') || + req('zepto'); + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.6', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClient, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClient.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file', + style: 'webuploader-pick' //pick element class attribute, default is "webuploader-pick" + }; + + Base.inherits( RuntimeClient, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button, + style = opts.style; + + if (style) + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + if (style) + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + if (style) + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor|dom} 指定选择文件的按钮容器,不指定则不创建按钮。**注意** 这里虽然写的是 id, 但是不是只支持 id, 还支持 class, 或者 dom 节点。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addBtn + * @for Uploader + * @grammar addBtn( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addBtn({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.on('dialogopen', function() { + me.owner.trigger('dialogOpen', picker.button); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview Image + */ + define('lib/image',[ + 'base', + 'runtime/client', + 'lib/blob' + ], function( Base, RuntimeClient, Blob ) { + var $ = Base.$; + + // 构造器。 + function Image( opts ) { + this.options = $.extend({}, Image.options, opts ); + RuntimeClient.call( this, 'Image' ); + + this.on( 'load', function() { + this._info = this.exec('info'); + this._meta = this.exec('meta'); + }); + } + + // 默认选项。 + Image.options = { + + // 默认的图片处理质量 + quality: 90, + + // 是否裁剪 + crop: false, + + // 是否保留头部信息 + preserveHeaders: false, + + // 是否允许放大。 + allowMagnify: false + }; + + // 继承RuntimeClient. + Base.inherits( RuntimeClient, { + constructor: Image, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._meta = val; + return this; + } + + // getter + return this._meta; + }, + + loadFromBlob: function( blob ) { + var me = this, + ruid = blob.getRuid(); + + this.connectRuntime( ruid, function() { + me.exec( 'init', me.options ); + me.exec( 'loadFromBlob', blob ); + }); + }, + + resize: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'resize' ].concat( args ) ); + }, + + crop: function() { + var args = Base.slice( arguments ); + return this.exec.apply( this, [ 'crop' ].concat( args ) ); + }, + + getAsDataUrl: function( type ) { + return this.exec( 'getAsDataUrl', type ); + }, + + getAsBlob: function( type ) { + var blob = this.exec( 'getAsBlob', type ); + + return new Blob( this.getRuid(), blob ); + } + }); + + return Image; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/image',[ + 'base', + 'uploader', + 'lib/image', + 'widgets/widget' + ], function( Base, Uploader, Image ) { + + var $ = Base.$, + throttle; + + // 根据要处理的文件大小来节流,一次不能处理太多,会卡。 + throttle = (function( max ) { + var occupied = 0, + waiting = [], + tick = function() { + var item; + + while ( waiting.length && occupied < max ) { + item = waiting.shift(); + occupied += item[ 0 ]; + item[ 1 ](); + } + }; + + return function( emiter, size, cb ) { + waiting.push([ size, cb ]); + emiter.once( 'destroy', function() { + occupied -= size; + setTimeout( tick, 1 ); + }); + setTimeout( tick, 1 ); + }; + })( 5 * 1024 * 1024 ); + + $.extend( Uploader.options, { + + /** + * @property {Object} [thumb] + * @namespace options + * @for Uploader + * @description 配置生成缩略图的选项。 + * + * 默认为: + * + * ```javascript + * { + * width: 110, + * height: 110, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 70, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: true, + * + * // 是否允许裁剪。 + * crop: true, + * + * // 为空的话则保留原有图片格式。 + * // 否则强制转换成指定的类型。 + * type: 'image/jpeg' + * } + * ``` + */ + thumb: { + width: 110, + height: 110, + quality: 70, + allowMagnify: true, + crop: true, + preserveHeaders: false, + + // 为空的话则保留原有图片格式。 + // 否则强制转换成指定的类型。 + // IE 8下面 base64 大小不能超过 32K 否则预览失败,而非 jpeg 编码的图片很可 + // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg + type: 'image/jpeg' + }, + + /** + * @property {Object} [compress] + * @namespace options + * @for Uploader + * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。 + * + * 默认为: + * + * ```javascript + * { + * width: 1600, + * height: 1600, + * + * // 图片质量,只有type为`image/jpeg`的时候才有效。 + * quality: 90, + * + * // 是否允许放大,如果想要生成小图的时候不失真,此选项应该设置为false. + * allowMagnify: false, + * + * // 是否允许裁剪。 + * crop: false, + * + * // 是否保留头部meta信息。 + * preserveHeaders: true, + * + * // 如果发现压缩后文件大小比原来还大,则使用原来图片 + * // 此属性可能会影响图片自动纠正功能 + * noCompressIfLarger: false, + * + * // 单位字节,如果图片大小小于此值,不会采用压缩。 + * compressSize: 0 + * } + * ``` + */ + compress: { + width: 1600, + height: 1600, + quality: 90, + allowMagnify: false, + crop: false, + preserveHeaders: true + } + }); + + return Uploader.register({ + + name: 'image', + + + /** + * 生成缩略图,此过程为异步,所以需要传入`callback`。 + * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。 + * + * 当 width 或者 height 的值介于 0 - 1 时,被当成百分比使用。 + * + * `callback`中可以接收到两个参数。 + * * 第一个为error,如果生成缩略图有错误,此error将为真。 + * * 第二个为ret, 缩略图的Data URL值。 + * + * **注意** + * Date URL在IE6/7中不支持,所以不用调用此方法了,直接显示一张暂不支持预览图片好了。 + * 也可以借助服务端,将 base64 数据传给服务端,生成一个临时文件供预览。 + * + * @method makeThumb + * @grammar makeThumb( file, callback ) => undefined + * @grammar makeThumb( file, callback, width, height ) => undefined + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.makeThumb( file, function( error, ret ) { + * if ( error ) { + * $li.text('预览错误'); + * } else { + * $li.append(''); + * } + * }); + * + * }); + */ + makeThumb: function( file, cb, width, height ) { + var opts, image; + + file = this.request( 'get-file', file ); + + // 只预览图片格式。 + if ( !file.type.match( /^image/ ) ) { + cb( true ); + return; + } + + opts = $.extend({}, this.options.thumb ); + + // 如果传入的是object. + if ( $.isPlainObject( width ) ) { + opts = $.extend( opts, width ); + width = null; + } + + width = width || opts.width; + height = height || opts.height; + + image = new Image( opts ); + + image.once( 'load', function() { + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + // 当 resize 完后 + image.once( 'complete', function() { + cb( false, image.getAsDataUrl( opts.type ) ); + image.destroy(); + }); + + image.once( 'error', function( reason ) { + cb( reason || true ); + image.destroy(); + }); + + throttle( image, file.source.size, function() { + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + image.loadFromBlob( file.source ); + }); + }, + + beforeSendFile: function( file ) { + var opts = this.options.compress || this.options.resize, + compressSize = opts && opts.compressSize || 0, + noCompressIfLarger = opts && opts.noCompressIfLarger || false, + image, deferred; + + file = this.request( 'get-file', file ); + + // 只压缩 jpeg 图片格式。 + // gif 可能会丢失针 + // bmp png 基本上尺寸都不大,且压缩比比较小。 + if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) || + file.size < compressSize || + file._compressed ) { + return; + } + + opts = $.extend({}, opts ); + deferred = Base.Deferred(); + + image = new Image( opts ); + + deferred.always(function() { + image.destroy(); + image = null; + }); + image.once( 'error', deferred.reject ); + image.once( 'load', function() { + var width = opts.width, + height = opts.height; + + file._info = file._info || image.info(); + file._meta = file._meta || image.meta(); + + // 如果 width 的值介于 0 - 1 + // 说明设置的是百分比。 + if ( width <= 1 && width > 0 ) { + width = file._info.width * width; + } + + // 同样的规则应用于 height + if ( height <= 1 && height > 0 ) { + height = file._info.height * height; + } + + image.resize( width, height ); + }); + + image.once( 'complete', function() { + var blob, size; + + // 移动端 UC / qq 浏览器的无图模式下 + // ctx.getImageData 处理大图的时候会报 Exception + // INDEX_SIZE_ERR: DOM Exception 1 + try { + blob = image.getAsBlob( opts.type ); + + size = file.size; + + // 如果压缩后,比原来还大则不用压缩后的。 + if ( !noCompressIfLarger || blob.size < size ) { + // file.source.destroy && file.source.destroy(); + file.source = blob; + file.size = blob.size; + + file.trigger( 'resize', blob.size, size ); + } + + // 标记,避免重复压缩。 + file._compressed = true; + deferred.resolve(); + } catch ( e ) { + // 出错了直接继续,让其上传原始图片 + deferred.resolve(); + } + }); + + file._info && image.info( file._info ); + file._meta && image.meta( file._meta ); + + image.loadFromBlob( file.source ); + return deferred.promise(); + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0 + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + if ( files.length ) { + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + /** + * @property {Object} [runtimeOrder=html5,flash] + * @namespace options + * @for Uploader + * @description 指定运行时启动顺序。默认会想尝试 html5 是否支持,如果支持则使用 html5, 否则则使用 flash. + * + * 可以将此值设置成 `flash`,来强制使用 flash 运行时。 + */ + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + // 销毁上传相关的属性。 + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定的文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + file.setStatus( Status.QUEUED ); + + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + file.setStatus( Status.PROGRESS ); + }); + + + } else if (file.getStatus() !== Status.PROGRESS) { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return Base.nextTick( me.__tick ); + } + + me.runing = true; + var files = []; + + // 如果有暂停的,则续传 + file || $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + me._trigged = false; + files.push(file); + v.transport && v.transport.send(); + } + }); + + $.each(files, function() { + this.setStatus( Status.PROGRESS ); + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this, + block; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + + + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file === file) { + block = v; + return false; + } + }); + + block.transport && block.transport.abort(); + + if (interrupt) { + me._putback(block); + me._popBlock(block); + } + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + // 正在准备中的文件。 + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me.updateFileProgress( file ); + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + block.percentage = percentage; + me.updateFileProgress( file ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + }, + + updateFileProgress: function(file) { + var totalPercent = 0, + uploaded = 0; + + if (!file.blocks) { + return; + } + + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + this.owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + } + + }); + }); + + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Md5 + */ + define('lib/md5',[ + 'runtime/client', + 'mediator' + ], function( RuntimeClient, Mediator ) { + + function Md5() { + RuntimeClient.call( this, 'Md5' ); + } + + // 让 Md5 具备事件功能。 + Mediator.installTo( Md5.prototype ); + + Md5.prototype.loadFromBlob = function( blob ) { + var me = this; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + me.exec( 'loadFromBlob', blob ); + }); + }; + + Md5.prototype.getResult = function() { + return this.exec('getResult'); + }; + + return Md5; + }); + /** + * @fileOverview 图片操作, 负责预览图片和上传前压缩图片 + */ + define('widgets/md5',[ + 'base', + 'uploader', + 'lib/md5', + 'lib/blob', + 'widgets/widget' + ], function( Base, Uploader, Md5, Blob ) { + + return Uploader.register({ + name: 'md5', + + + /** + * 计算文件 md5 值,返回一个 promise 对象,可以监听 progress 进度。 + * + * + * @method md5File + * @grammar md5File( file[, start[, end]] ) => promise + * @for Uploader + * @example + * + * uploader.on( 'fileQueued', function( file ) { + * var $li = ...; + * + * uploader.md5File( file ) + * + * // 及时显示进度 + * .progress(function(percentage) { + * console.log('Percentage:', percentage); + * }) + * + * // 完成 + * .then(function(val) { + * console.log('md5 result:', val); + * }); + * + * }); + */ + md5File: function( file, start, end ) { + var md5 = new Md5(), + deferred = Base.Deferred(), + blob = (file instanceof Blob) ? file : + this.request( 'get-file', file ).source; + + md5.on( 'progress load', function( e ) { + e = e || {}; + deferred.notify( e.total ? e.loaded / e.total : 1 ); + }); + + md5.on( 'complete', function() { + deferred.resolve( md5.getResult() ); + }); + + md5.on( 'error', function( reason ) { + deferred.reject( reason ); + }); + + if ( arguments.length > 1 ) { + start = start || 0; + end = end || 0; + start < 0 && (start = blob.size + start); + end < 0 && (end = blob.size + end); + end = Math.min( end, blob.size ); + blob = blob.slice( start, end ); + } + + md5.loadFromBlob( blob ); + + return deferred.promise(); + } + }); + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'capture', 'camera'); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function(e) { + input.trigger('click'); + e.stopPropagation(); + owner.trigger('dialogopen'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/util',[ + 'base' + ], function( Base ) { + + var urlAPI = window.createObjectURL && window || + window.URL && URL.revokeObjectURL && URL || + window.webkitURL, + createObjectURL = Base.noop, + revokeObjectURL = createObjectURL; + + if ( urlAPI ) { + + // 更安全的方式调用,比如android里面就能把context改成其他的对象。 + createObjectURL = function() { + return urlAPI.createObjectURL.apply( urlAPI, arguments ); + }; + + revokeObjectURL = function() { + return urlAPI.revokeObjectURL.apply( urlAPI, arguments ); + }; + } + + return { + createObjectURL: createObjectURL, + revokeObjectURL: revokeObjectURL, + + dataURL2Blob: function( dataURI ) { + var byteStr, intArray, ab, i, mimetype, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + ab = new ArrayBuffer( byteStr.length ); + intArray = new Uint8Array( ab ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ]; + + return this.arrayBufferToBlob( ab, mimetype ); + }, + + dataURL2ArrayBuffer: function( dataURI ) { + var byteStr, intArray, i, parts; + + parts = dataURI.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + byteStr = atob( parts[ 1 ] ); + } else { + byteStr = decodeURIComponent( parts[ 1 ] ); + } + + intArray = new Uint8Array( byteStr.length ); + + for ( i = 0; i < byteStr.length; i++ ) { + intArray[ i ] = byteStr.charCodeAt( i ); + } + + return intArray.buffer; + }, + + arrayBufferToBlob: function( buffer, type ) { + var builder = window.BlobBuilder || window.WebKitBlobBuilder, + bb; + + // android不支持直接new Blob, 只能借助blobbuilder. + if ( builder ) { + bb = new builder(); + bb.append( buffer ); + return bb.getBlob( type ); + } + + return new Blob([ buffer ], type ? { type: type } : {} ); + }, + + // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg. + // 你得到的结果是png. + canvasToDataUrl: function( canvas, type, quality ) { + return canvas.toDataURL( type, quality / 100 ); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + parseMeta: function( blob, callback ) { + callback( false, {}); + }, + + // imagemeat会复写这个方法,如果用户选择加载那个文件了的话。 + updateImageHead: function( data ) { + return data; + } + }; + }); + /** + * Terms: + * + * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer + * @fileOverview Image控件 + */ + define('runtime/html5/imagemeta',[ + 'runtime/html5/util' + ], function( Util ) { + + var api; + + api = { + parsers: { + 0xffe1: [] + }, + + maxMetaDataSize: 262144, + + parse: function( blob, cb ) { + var me = this, + fr = new FileReader(); + + fr.onload = function() { + cb( false, me._parse( this.result ) ); + fr = fr.onload = fr.onerror = null; + }; + + fr.onerror = function( e ) { + cb( e.message ); + fr = fr.onload = fr.onerror = null; + }; + + blob = blob.slice( 0, me.maxMetaDataSize ); + fr.readAsArrayBuffer( blob.getSource() ); + }, + + _parse: function( buffer, noParse ) { + if ( buffer.byteLength < 6 ) { + return; + } + + var dataview = new DataView( buffer ), + offset = 2, + maxOffset = dataview.byteLength - 4, + headLength = offset, + ret = {}, + markerBytes, markerLength, parsers, i; + + if ( dataview.getUint16( 0 ) === 0xffd8 ) { + + while ( offset < maxOffset ) { + markerBytes = dataview.getUint16( offset ); + + if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef || + markerBytes === 0xfffe ) { + + markerLength = dataview.getUint16( offset + 2 ) + 2; + + if ( offset + markerLength > dataview.byteLength ) { + break; + } + + parsers = api.parsers[ markerBytes ]; + + if ( !noParse && parsers ) { + for ( i = 0; i < parsers.length; i += 1 ) { + parsers[ i ].call( api, dataview, offset, + markerLength, ret ); + } + } + + offset += markerLength; + headLength = offset; + } else { + break; + } + } + + if ( headLength > 6 ) { + if ( buffer.slice ) { + ret.imageHead = buffer.slice( 2, headLength ); + } else { + // Workaround for IE10, which does not yet + // support ArrayBuffer.slice: + ret.imageHead = new Uint8Array( buffer ) + .subarray( 2, headLength ); + } + } + } + + return ret; + }, + + updateImageHead: function( buffer, head ) { + var data = this._parse( buffer, true ), + buf1, buf2, bodyoffset; + + + bodyoffset = 2; + if ( data.imageHead ) { + bodyoffset = 2 + data.imageHead.byteLength; + } + + if ( buffer.slice ) { + buf2 = buffer.slice( bodyoffset ); + } else { + buf2 = new Uint8Array( buffer ).subarray( bodyoffset ); + } + + buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength ); + + buf1[ 0 ] = 0xFF; + buf1[ 1 ] = 0xD8; + buf1.set( new Uint8Array( head ), 2 ); + buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 ); + + return buf1.buffer; + } + }; + + Util.parseMeta = function() { + return api.parse.apply( api, arguments ); + }; + + Util.updateImageHead = function() { + return api.updateImageHead.apply( api, arguments ); + }; + + return api; + }); + /** + * 代码来自于:https://github.com/blueimp/JavaScript-Load-Image + * 暂时项目中只用了orientation. + * + * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail. + * @fileOverview EXIF解析 + */ + + // Sample + // ==================================== + // Make : Apple + // Model : iPhone 4S + // Orientation : 1 + // XResolution : 72 [72/1] + // YResolution : 72 [72/1] + // ResolutionUnit : 2 + // Software : QuickTime 7.7.1 + // DateTime : 2013:09:01 22:53:55 + // ExifIFDPointer : 190 + // ExposureTime : 0.058823529411764705 [1/17] + // FNumber : 2.4 [12/5] + // ExposureProgram : Normal program + // ISOSpeedRatings : 800 + // ExifVersion : 0220 + // DateTimeOriginal : 2013:09:01 22:52:51 + // DateTimeDigitized : 2013:09:01 22:52:51 + // ComponentsConfiguration : YCbCr + // ShutterSpeedValue : 4.058893515764426 + // ApertureValue : 2.5260688216892597 [4845/1918] + // BrightnessValue : -0.3126686601998395 + // MeteringMode : Pattern + // Flash : Flash did not fire, compulsory flash mode + // FocalLength : 4.28 [107/25] + // SubjectArea : [4 values] + // FlashpixVersion : 0100 + // ColorSpace : 1 + // PixelXDimension : 2448 + // PixelYDimension : 3264 + // SensingMethod : One-chip color area sensor + // ExposureMode : 0 + // WhiteBalance : Auto white balance + // FocalLengthIn35mmFilm : 35 + // SceneCaptureType : Standard + define('runtime/html5/imagemeta/exif',[ + 'base', + 'runtime/html5/imagemeta' + ], function( Base, ImageMeta ) { + + var EXIF = {}; + + EXIF.ExifMap = function() { + return this; + }; + + EXIF.ExifMap.prototype.map = { + 'Orientation': 0x0112 + }; + + EXIF.ExifMap.prototype.get = function( id ) { + return this[ id ] || this[ this.map[ id ] ]; + }; + + EXIF.exifTagTypes = { + // byte, 8-bit unsigned int: + 1: { + getValue: function( dataView, dataOffset ) { + return dataView.getUint8( dataOffset ); + }, + size: 1 + }, + + // ascii, 8-bit byte: + 2: { + getValue: function( dataView, dataOffset ) { + return String.fromCharCode( dataView.getUint8( dataOffset ) ); + }, + size: 1, + ascii: true + }, + + // short, 16 bit int: + 3: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint16( dataOffset, littleEndian ); + }, + size: 2 + }, + + // long, 32 bit int: + 4: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // rational = two long values, + // first is numerator, second is denominator: + 5: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getUint32( dataOffset, littleEndian ) / + dataView.getUint32( dataOffset + 4, littleEndian ); + }, + size: 8 + }, + + // slong, 32 bit signed int: + 9: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ); + }, + size: 4 + }, + + // srational, two slongs, first is numerator, second is denominator: + 10: { + getValue: function( dataView, dataOffset, littleEndian ) { + return dataView.getInt32( dataOffset, littleEndian ) / + dataView.getInt32( dataOffset + 4, littleEndian ); + }, + size: 8 + } + }; + + // undefined, 8-bit byte, value depending on field: + EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ]; + + EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length, + littleEndian ) { + + var tagType = EXIF.exifTagTypes[ type ], + tagSize, dataOffset, values, i, str, c; + + if ( !tagType ) { + Base.log('Invalid Exif data: Invalid tag type.'); + return; + } + + tagSize = tagType.size * length; + + // Determine if the value is contained in the dataOffset bytes, + // or if the value at the dataOffset is a pointer to the actual data: + dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8, + littleEndian ) : (offset + 8); + + if ( dataOffset + tagSize > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid data offset.'); + return; + } + + if ( length === 1 ) { + return tagType.getValue( dataView, dataOffset, littleEndian ); + } + + values = []; + + for ( i = 0; i < length; i += 1 ) { + values[ i ] = tagType.getValue( dataView, + dataOffset + i * tagType.size, littleEndian ); + } + + if ( tagType.ascii ) { + str = ''; + + // Concatenate the chars: + for ( i = 0; i < values.length; i += 1 ) { + c = values[ i ]; + + // Ignore the terminating NULL byte(s): + if ( c === '\u0000' ) { + break; + } + str += c; + } + + return str; + } + return values; + }; + + EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian, + data ) { + + var tag = dataView.getUint16( offset, littleEndian ); + data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset, + dataView.getUint16( offset + 2, littleEndian ), // tag type + dataView.getUint32( offset + 4, littleEndian ), // tag length + littleEndian ); + }; + + EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset, + littleEndian, data ) { + + var tagsNumber, dirEndOffset, i; + + if ( dirOffset + 6 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory offset.'); + return; + } + + tagsNumber = dataView.getUint16( dirOffset, littleEndian ); + dirEndOffset = dirOffset + 2 + 12 * tagsNumber; + + if ( dirEndOffset + 4 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid directory size.'); + return; + } + + for ( i = 0; i < tagsNumber; i += 1 ) { + this.parseExifTag( dataView, tiffOffset, + dirOffset + 2 + 12 * i, // tag offset + littleEndian, data ); + } + + // Return the offset to the next directory: + return dataView.getUint32( dirEndOffset, littleEndian ); + }; + + // EXIF.getExifThumbnail = function(dataView, offset, length) { + // var hexData, + // i, + // b; + // if (!length || offset + length > dataView.byteLength) { + // Base.log('Invalid Exif data: Invalid thumbnail data.'); + // return; + // } + // hexData = []; + // for (i = 0; i < length; i += 1) { + // b = dataView.getUint8(offset + i); + // hexData.push((b < 16 ? '0' : '') + b.toString(16)); + // } + // return 'data:image/jpeg,%' + hexData.join('%'); + // }; + + EXIF.parseExifData = function( dataView, offset, length, data ) { + + var tiffOffset = offset + 10, + littleEndian, dirOffset; + + // Check for the ASCII code for "Exif" (0x45786966): + if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) { + // No Exif data, might be XMP data instead + return; + } + if ( tiffOffset + 8 > dataView.byteLength ) { + Base.log('Invalid Exif data: Invalid segment size.'); + return; + } + + // Check for the two null bytes: + if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) { + Base.log('Invalid Exif data: Missing byte alignment offset.'); + return; + } + + // Check the byte alignment: + switch ( dataView.getUint16( tiffOffset ) ) { + case 0x4949: + littleEndian = true; + break; + + case 0x4D4D: + littleEndian = false; + break; + + default: + Base.log('Invalid Exif data: Invalid byte alignment marker.'); + return; + } + + // Check for the TIFF tag marker (0x002A): + if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) { + Base.log('Invalid Exif data: Missing TIFF marker.'); + return; + } + + // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal: + dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian ); + // Create the exif object to store the tags: + data.exif = new EXIF.ExifMap(); + // Parse the tags of the main image directory and retrieve the + // offset to the next directory, usually the thumbnail directory: + dirOffset = EXIF.parseExifTags( dataView, tiffOffset, + tiffOffset + dirOffset, littleEndian, data ); + + // 尝试读取缩略图 + // if ( dirOffset ) { + // thumbnailData = {exif: {}}; + // dirOffset = EXIF.parseExifTags( + // dataView, + // tiffOffset, + // tiffOffset + dirOffset, + // littleEndian, + // thumbnailData + // ); + + // // Check for JPEG Thumbnail offset: + // if (thumbnailData.exif[0x0201]) { + // data.exif.Thumbnail = EXIF.getExifThumbnail( + // dataView, + // tiffOffset + thumbnailData.exif[0x0201], + // thumbnailData.exif[0x0202] // Thumbnail data length + // ); + // } + // } + }; + + ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData ); + return EXIF; + }); + /** + * 这个方式性能不行,但是可以解决android里面的toDataUrl的bug + * android里面toDataUrl('image/jpege')得到的结果却是png. + * + * 所以这里没辙,只能借助这个工具 + * @fileOverview jpeg encoder + */ + define('runtime/html5/jpegencoder',[], function( require, exports, module ) { + + /* + Copyright (c) 2008, Adobe Systems Incorporated + 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 Adobe Systems Incorporated 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. + */ + /* + JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009 + + Basic GUI blocking jpeg encoder + */ + + function JPEGEncoder(quality) { + var self = this; + var fround = Math.round; + var ffloor = Math.floor; + var YTable = new Array(64); + var UVTable = new Array(64); + var fdtbl_Y = new Array(64); + var fdtbl_UV = new Array(64); + var YDC_HT; + var UVDC_HT; + var YAC_HT; + var UVAC_HT; + + var bitcode = new Array(65535); + var category = new Array(65535); + var outputfDCTQuant = new Array(64); + var DU = new Array(64); + var byteout = []; + var bytenew = 0; + var bytepos = 7; + + var YDU = new Array(64); + var UDU = new Array(64); + var VDU = new Array(64); + var clt = new Array(256); + var RGB_YUV_TABLE = new Array(2048); + var currentQuality; + + var ZigZag = [ + 0, 1, 5, 6,14,15,27,28, + 2, 4, 7,13,16,26,29,42, + 3, 8,12,17,25,30,41,43, + 9,11,18,24,31,40,44,53, + 10,19,23,32,39,45,52,54, + 20,22,33,38,46,51,55,60, + 21,34,37,47,50,56,59,61, + 35,36,48,49,57,58,62,63 + ]; + + var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0]; + var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d]; + var std_ac_luminance_values = [ + 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, + 0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07, + 0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08, + 0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0, + 0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16, + 0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28, + 0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39, + 0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49, + 0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59, + 0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69, + 0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79, + 0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89, + 0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98, + 0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7, + 0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6, + 0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5, + 0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4, + 0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2, + 0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea, + 0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0]; + var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11]; + var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77]; + var std_ac_chrominance_values = [ + 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, + 0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71, + 0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91, + 0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0, + 0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34, + 0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26, + 0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38, + 0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48, + 0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58, + 0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68, + 0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78, + 0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87, + 0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96, + 0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5, + 0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4, + 0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3, + 0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2, + 0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda, + 0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9, + 0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8, + 0xf9,0xfa + ]; + + function initQuantTables(sf){ + var YQT = [ + 16, 11, 10, 16, 24, 40, 51, 61, + 12, 12, 14, 19, 26, 58, 60, 55, + 14, 13, 16, 24, 40, 57, 69, 56, + 14, 17, 22, 29, 51, 87, 80, 62, + 18, 22, 37, 56, 68,109,103, 77, + 24, 35, 55, 64, 81,104,113, 92, + 49, 64, 78, 87,103,121,120,101, + 72, 92, 95, 98,112,100,103, 99 + ]; + + for (var i = 0; i < 64; i++) { + var t = ffloor((YQT[i]*sf+50)/100); + if (t < 1) { + t = 1; + } else if (t > 255) { + t = 255; + } + YTable[ZigZag[i]] = t; + } + var UVQT = [ + 17, 18, 24, 47, 99, 99, 99, 99, + 18, 21, 26, 66, 99, 99, 99, 99, + 24, 26, 56, 99, 99, 99, 99, 99, + 47, 66, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99, + 99, 99, 99, 99, 99, 99, 99, 99 + ]; + for (var j = 0; j < 64; j++) { + var u = ffloor((UVQT[j]*sf+50)/100); + if (u < 1) { + u = 1; + } else if (u > 255) { + u = 255; + } + UVTable[ZigZag[j]] = u; + } + var aasf = [ + 1.0, 1.387039845, 1.306562965, 1.175875602, + 1.0, 0.785694958, 0.541196100, 0.275899379 + ]; + var k = 0; + for (var row = 0; row < 8; row++) + { + for (var col = 0; col < 8; col++) + { + fdtbl_Y[k] = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0)); + k++; + } + } + } + + function computeHuffmanTbl(nrcodes, std_table){ + var codevalue = 0; + var pos_in_table = 0; + var HT = new Array(); + for (var k = 1; k <= 16; k++) { + for (var j = 1; j <= nrcodes[k]; j++) { + HT[std_table[pos_in_table]] = []; + HT[std_table[pos_in_table]][0] = codevalue; + HT[std_table[pos_in_table]][1] = k; + pos_in_table++; + codevalue++; + } + codevalue*=2; + } + return HT; + } + + function initHuffmanTbl() + { + YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values); + UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values); + YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values); + UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values); + } + + function initCategoryNumber() + { + var nrlower = 1; + var nrupper = 2; + for (var cat = 1; cat <= 15; cat++) { + //Positive numbers + for (var nr = nrlower; nr>0] = 38470 * i; + RGB_YUV_TABLE[(i+ 512)>>0] = 7471 * i + 0x8000; + RGB_YUV_TABLE[(i+ 768)>>0] = -11059 * i; + RGB_YUV_TABLE[(i+1024)>>0] = -21709 * i; + RGB_YUV_TABLE[(i+1280)>>0] = 32768 * i + 0x807FFF; + RGB_YUV_TABLE[(i+1536)>>0] = -27439 * i; + RGB_YUV_TABLE[(i+1792)>>0] = - 5329 * i; + } + } + + // IO functions + function writeBits(bs) + { + var value = bs[0]; + var posval = bs[1]-1; + while ( posval >= 0 ) { + if (value & (1 << posval) ) { + bytenew |= (1 << bytepos); + } + posval--; + bytepos--; + if (bytepos < 0) { + if (bytenew == 0xFF) { + writeByte(0xFF); + writeByte(0); + } + else { + writeByte(bytenew); + } + bytepos=7; + bytenew=0; + } + } + } + + function writeByte(value) + { + byteout.push(clt[value]); // write char directly instead of converting later + } + + function writeWord(value) + { + writeByte((value>>8)&0xFF); + writeByte((value )&0xFF); + } + + // DCT & quantization core + function fDCTQuant(data, fdtbl) + { + var d0, d1, d2, d3, d4, d5, d6, d7; + /* Pass 1: process rows. */ + var dataOff=0; + var i; + var I8 = 8; + var I64 = 64; + for (i=0; i 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0); + //outputfDCTQuant[i] = fround(fDCTQuant); + + } + return outputfDCTQuant; + } + + function writeAPP0() + { + writeWord(0xFFE0); // marker + writeWord(16); // length + writeByte(0x4A); // J + writeByte(0x46); // F + writeByte(0x49); // I + writeByte(0x46); // F + writeByte(0); // = "JFIF",'\0' + writeByte(1); // versionhi + writeByte(1); // versionlo + writeByte(0); // xyunits + writeWord(1); // xdensity + writeWord(1); // ydensity + writeByte(0); // thumbnwidth + writeByte(0); // thumbnheight + } + + function writeSOF0(width, height) + { + writeWord(0xFFC0); // marker + writeWord(17); // length, truecolor YUV JPG + writeByte(8); // precision + writeWord(height); + writeWord(width); + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0x11); // HVY + writeByte(0); // QTY + writeByte(2); // IdU + writeByte(0x11); // HVU + writeByte(1); // QTU + writeByte(3); // IdV + writeByte(0x11); // HVV + writeByte(1); // QTV + } + + function writeDQT() + { + writeWord(0xFFDB); // marker + writeWord(132); // length + writeByte(0); + for (var i=0; i<64; i++) { + writeByte(YTable[i]); + } + writeByte(1); + for (var j=0; j<64; j++) { + writeByte(UVTable[j]); + } + } + + function writeDHT() + { + writeWord(0xFFC4); // marker + writeWord(0x01A2); // length + + writeByte(0); // HTYDCinfo + for (var i=0; i<16; i++) { + writeByte(std_dc_luminance_nrcodes[i+1]); + } + for (var j=0; j<=11; j++) { + writeByte(std_dc_luminance_values[j]); + } + + writeByte(0x10); // HTYACinfo + for (var k=0; k<16; k++) { + writeByte(std_ac_luminance_nrcodes[k+1]); + } + for (var l=0; l<=161; l++) { + writeByte(std_ac_luminance_values[l]); + } + + writeByte(1); // HTUDCinfo + for (var m=0; m<16; m++) { + writeByte(std_dc_chrominance_nrcodes[m+1]); + } + for (var n=0; n<=11; n++) { + writeByte(std_dc_chrominance_values[n]); + } + + writeByte(0x11); // HTUACinfo + for (var o=0; o<16; o++) { + writeByte(std_ac_chrominance_nrcodes[o+1]); + } + for (var p=0; p<=161; p++) { + writeByte(std_ac_chrominance_values[p]); + } + } + + function writeSOS() + { + writeWord(0xFFDA); // marker + writeWord(12); // length + writeByte(3); // nrofcomponents + writeByte(1); // IdY + writeByte(0); // HTY + writeByte(2); // IdU + writeByte(0x11); // HTU + writeByte(3); // IdV + writeByte(0x11); // HTV + writeByte(0); // Ss + writeByte(0x3f); // Se + writeByte(0); // Bf + } + + function processDU(CDU, fdtbl, DC, HTDC, HTAC){ + var EOB = HTAC[0x00]; + var M16zeroes = HTAC[0xF0]; + var pos; + var I16 = 16; + var I63 = 63; + var I64 = 64; + var DU_DCT = fDCTQuant(CDU, fdtbl); + //ZigZag reorder + for (var j=0;j0)&&(DU[end0pos]==0); end0pos--) {}; + //end0pos = first element in reverse order !=0 + if ( end0pos == 0) { + writeBits(EOB); + return DC; + } + var i = 1; + var lng; + while ( i <= end0pos ) { + var startpos = i; + for (; (DU[i]==0) && (i<=end0pos); ++i) {} + var nrzeroes = i-startpos; + if ( nrzeroes >= I16 ) { + lng = nrzeroes>>4; + for (var nrmarker=1; nrmarker <= lng; ++nrmarker) + writeBits(M16zeroes); + nrzeroes = nrzeroes&0xF; + } + pos = 32767+DU[i]; + writeBits(HTAC[(nrzeroes<<4)+category[pos]]); + writeBits(bitcode[pos]); + i++; + } + if ( end0pos != I63 ) { + writeBits(EOB); + } + return DC; + } + + function initCharLookupTable(){ + var sfcc = String.fromCharCode; + for(var i=0; i < 256; i++){ ///// ACHTUNG // 255 + clt[i] = sfcc(i); + } + } + + this.encode = function(image,quality) // image data object + { + // var time_start = new Date().getTime(); + + if(quality) setQuality(quality); + + // Initialize bit writer + byteout = new Array(); + bytenew=0; + bytepos=7; + + // Add JPEG headers + writeWord(0xFFD8); // SOI + writeAPP0(); + writeDQT(); + writeSOF0(image.width,image.height); + writeDHT(); + writeSOS(); + + + // Encode 8x8 macroblocks + var DCY=0; + var DCU=0; + var DCV=0; + + bytenew=0; + bytepos=7; + + + this.encode.displayName = "_encode_"; + + var imageData = image.data; + var width = image.width; + var height = image.height; + + var quadWidth = width*4; + var tripleWidth = width*3; + + var x, y = 0; + var r, g, b; + var start,p, col,row,pos; + while(y < height){ + x = 0; + while(x < quadWidth){ + start = quadWidth * y + x; + p = start; + col = -1; + row = 0; + + for(pos=0; pos < 64; pos++){ + row = pos >> 3;// /8 + col = ( pos & 7 ) * 4; // %8 + p = start + ( row * quadWidth ) + col; + + if(y+row >= height){ // padding bottom + p-= (quadWidth*(y+1+row-height)); + } + + if(x+col >= quadWidth){ // padding right + p-= ((x+col) - quadWidth +4) + } + + r = imageData[ p++ ]; + g = imageData[ p++ ]; + b = imageData[ p++ ]; + + + /* // calculate YUV values dynamically + YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80 + UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b)); + VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b)); + */ + + // use lookup table (slightly faster) + YDU[pos] = ((RGB_YUV_TABLE[r] + RGB_YUV_TABLE[(g + 256)>>0] + RGB_YUV_TABLE[(b + 512)>>0]) >> 16)-128; + UDU[pos] = ((RGB_YUV_TABLE[(r + 768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128; + VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128; + + } + + DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT); + DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT); + DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT); + x+=32; + } + y+=8; + } + + + //////////////////////////////////////////////////////////////// + + // Do the bit alignment of the EOI marker + if ( bytepos >= 0 ) { + var fillbits = []; + fillbits[1] = bytepos+1; + fillbits[0] = (1<<(bytepos+1))-1; + writeBits(fillbits); + } + + writeWord(0xFFD9); //EOI + + var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join('')); + + byteout = []; + + // benchmarking + // var duration = new Date().getTime() - time_start; + // console.log('Encoding time: '+ currentQuality + 'ms'); + // + + return jpegDataUri + } + + function setQuality(quality){ + if (quality <= 0) { + quality = 1; + } + if (quality > 100) { + quality = 100; + } + + if(currentQuality == quality) return // don't recalc if unchanged + + var sf = 0; + if (quality < 50) { + sf = Math.floor(5000 / quality); + } else { + sf = Math.floor(200 - quality*2); + } + + initQuantTables(sf); + currentQuality = quality; + // console.log('Quality set to: '+quality +'%'); + } + + function init(){ + // var time_start = new Date().getTime(); + if(!quality) quality = 50; + // Create tables + initCharLookupTable() + initHuffmanTbl(); + initCategoryNumber(); + initRGBYUVTable(); + + setQuality(quality); + // var duration = new Date().getTime() - time_start; + // console.log('Initialization '+ duration + 'ms'); + } + + init(); + + }; + + JPEGEncoder.encode = function( data, quality ) { + var encoder = new JPEGEncoder( quality ); + + return encoder.encode( data ); + } + + return JPEGEncoder; + }); + /** + * @fileOverview Fix android canvas.toDataUrl bug. + */ + define('runtime/html5/androidpatch',[ + 'runtime/html5/util', + 'runtime/html5/jpegencoder', + 'base' + ], function( Util, encoder, Base ) { + var origin = Util.canvasToDataUrl, + supportJpeg; + + Util.canvasToDataUrl = function( canvas, type, quality ) { + var ctx, w, h, fragement, parts; + + // 非android手机直接跳过。 + if ( !Base.os.android ) { + return origin.apply( null, arguments ); + } + + // 检测是否canvas支持jpeg导出,根据数据格式来判断。 + // JPEG 前两位分别是:255, 216 + if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) { + fragement = origin.apply( null, arguments ); + + parts = fragement.split(','); + + if ( ~parts[ 0 ].indexOf('base64') ) { + fragement = atob( parts[ 1 ] ); + } else { + fragement = decodeURIComponent( parts[ 1 ] ); + } + + fragement = fragement.substring( 0, 2 ); + + supportJpeg = fragement.charCodeAt( 0 ) === 255 && + fragement.charCodeAt( 1 ) === 216; + } + + // 只有在android环境下才修复 + if ( type === 'image/jpeg' && !supportJpeg ) { + w = canvas.width; + h = canvas.height; + ctx = canvas.getContext('2d'); + + return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality ); + } + + return origin.apply( null, arguments ); + }; + }); + /** + * @fileOverview Image + */ + define('runtime/html5/image',[ + 'base', + 'runtime/html5/runtime', + 'runtime/html5/util' + ], function( Base, Html5Runtime, Util ) { + + var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D'; + + return Html5Runtime.register( 'Image', { + + // flag: 标记是否被修改过。 + modified: false, + + init: function() { + var me = this, + img = new Image(); + + img.onload = function() { + + me._info = { + type: me.type, + width: this.width, + height: this.height + }; + + //debugger; + + // 读取meta信息。 + if ( !me._metas && 'image/jpeg' === me.type ) { + Util.parseMeta( me._blob, function( error, ret ) { + me._metas = ret; + me.owner.trigger('load'); + }); + } else { + me.owner.trigger('load'); + } + }; + + img.onerror = function() { + me.owner.trigger('error'); + }; + + me._img = img; + }, + + loadFromBlob: function( blob ) { + var me = this, + img = me._img; + + me._blob = blob; + me.type = blob.type; + img.src = Util.createObjectURL( blob.getSource() ); + me.owner.once( 'load', function() { + Util.revokeObjectURL( img.src ); + }); + }, + + resize: function( width, height ) { + var canvas = this._canvas || + (this._canvas = document.createElement('canvas')); + + this._resize( this._img, canvas, width, height ); + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'resize' ); + }, + + crop: function( x, y, w, h, s ) { + var cvs = this._canvas || + (this._canvas = document.createElement('canvas')), + opts = this.options, + img = this._img, + iw = img.naturalWidth, + ih = img.naturalHeight, + orientation = this.getOrientation(); + + s = s || 1; + + // todo 解决 orientation 的问题。 + // values that require 90 degree rotation + // if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // switch ( orientation ) { + // case 6: + // tmp = x; + // x = y; + // y = iw * s - tmp - w; + // console.log(ih * s, tmp, w) + // break; + // } + + // (w ^= h, h ^= w, w ^= h); + // } + + cvs.width = w; + cvs.height = h; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + this._renderImageToCanvas( cvs, img, -x, -y, iw * s, ih * s ); + + this._blob = null; // 没用了,可以删掉了。 + this.modified = true; + this.owner.trigger( 'complete', 'crop' ); + }, + + getAsBlob: function( type ) { + var blob = this._blob, + opts = this.options, + canvas; + + type = type || this.type; + + // blob需要重新生成。 + if ( this.modified || this.type !== type ) { + canvas = this._canvas; + + if ( type === 'image/jpeg' ) { + + blob = Util.canvasToDataUrl( canvas, type, opts.quality ); + + if ( opts.preserveHeaders && this._metas && + this._metas.imageHead ) { + + blob = Util.dataURL2ArrayBuffer( blob ); + blob = Util.updateImageHead( blob, + this._metas.imageHead ); + blob = Util.arrayBufferToBlob( blob, type ); + return blob; + } + } else { + blob = Util.canvasToDataUrl( canvas, type ); + } + + blob = Util.dataURL2Blob( blob ); + } + + return blob; + }, + + getAsDataUrl: function( type ) { + var opts = this.options; + + type = type || this.type; + + if ( type === 'image/jpeg' ) { + return Util.canvasToDataUrl( this._canvas, type, opts.quality ); + } else { + return this._canvas.toDataURL( type ); + } + }, + + getOrientation: function() { + return this._metas && this._metas.exif && + this._metas.exif.get('Orientation') || 1; + }, + + info: function( val ) { + + // setter + if ( val ) { + this._info = val; + return this; + } + + // getter + return this._info; + }, + + meta: function( val ) { + + // setter + if ( val ) { + this._metas = val; + return this; + } + + // getter + return this._metas; + }, + + destroy: function() { + var canvas = this._canvas; + this._img.onload = null; + + if ( canvas ) { + canvas.getContext('2d') + .clearRect( 0, 0, canvas.width, canvas.height ); + canvas.width = canvas.height = 0; + this._canvas = null; + } + + // 释放内存。非常重要,否则释放不了image的内存。 + this._img.src = BLANK; + this._img = this._blob = null; + }, + + _resize: function( img, cvs, width, height ) { + var opts = this.options, + naturalWidth = img.width, + naturalHeight = img.height, + orientation = this.getOrientation(), + scale, w, h, x, y; + + // values that require 90 degree rotation + if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) { + + // 交换width, height的值。 + width ^= height; + height ^= width; + width ^= height; + } + + scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth, + height / naturalHeight ); + + // 不允许放大。 + opts.allowMagnify || (scale = Math.min( 1, scale )); + + w = naturalWidth * scale; + h = naturalHeight * scale; + + if ( opts.crop ) { + cvs.width = width; + cvs.height = height; + } else { + cvs.width = w; + cvs.height = h; + } + + x = (cvs.width - w) / 2; + y = (cvs.height - h) / 2; + + opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation ); + + this._renderImageToCanvas( cvs, img, x, y, w, h ); + }, + + _rotate2Orientaion: function( canvas, orientation ) { + var width = canvas.width, + height = canvas.height, + ctx = canvas.getContext('2d'); + + switch ( orientation ) { + case 5: + case 6: + case 7: + case 8: + canvas.width = height; + canvas.height = width; + break; + } + + switch ( orientation ) { + case 2: // horizontal flip + ctx.translate( width, 0 ); + ctx.scale( -1, 1 ); + break; + + case 3: // 180 rotate left + ctx.translate( width, height ); + ctx.rotate( Math.PI ); + break; + + case 4: // vertical flip + ctx.translate( 0, height ); + ctx.scale( 1, -1 ); + break; + + case 5: // vertical flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.scale( 1, -1 ); + break; + + case 6: // 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( 0, -height ); + break; + + case 7: // horizontal flip + 90 rotate right + ctx.rotate( 0.5 * Math.PI ); + ctx.translate( width, -height ); + ctx.scale( -1, 1 ); + break; + + case 8: // 90 rotate left + ctx.rotate( -0.5 * Math.PI ); + ctx.translate( -width, 0 ); + break; + } + }, + + // https://github.com/stomita/ios-imagefile-megapixel/ + // blob/master/src/megapix-image.js + _renderImageToCanvas: (function() { + + // 如果不是ios, 不需要这么复杂! + if ( !Base.os.ios ) { + return function( canvas ) { + var args = Base.slice( arguments, 1 ), + ctx = canvas.getContext('2d'); + + ctx.drawImage.apply( ctx, args ); + }; + } + + /** + * Detecting vertical squash in loaded image. + * Fixes a bug which squash image vertically while drawing into + * canvas for some images. + */ + function detectVerticalSquash( img, iw, ih ) { + var canvas = document.createElement('canvas'), + ctx = canvas.getContext('2d'), + sy = 0, + ey = ih, + py = ih, + data, alpha, ratio; + + + canvas.width = 1; + canvas.height = ih; + ctx.drawImage( img, 0, 0 ); + data = ctx.getImageData( 0, 0, 1, ih ).data; + + // search image edge pixel position in case + // it is squashed vertically. + while ( py > sy ) { + alpha = data[ (py - 1) * 4 + 3 ]; + + if ( alpha === 0 ) { + ey = py; + } else { + sy = py; + } + + py = (ey + sy) >> 1; + } + + ratio = (py / ih); + return (ratio === 0) ? 1 : ratio; + } + + // fix ie7 bug + // http://stackoverflow.com/questions/11929099/ + // html5-canvas-drawimage-ratio-bug-ios + if ( Base.os.ios >= 7 ) { + return function( canvas, img, x, y, w, h ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + vertSquashRatio = detectVerticalSquash( img, iw, ih ); + + return canvas.getContext('2d').drawImage( img, 0, 0, + iw * vertSquashRatio, ih * vertSquashRatio, + x, y, w, h ); + }; + } + + /** + * Detect subsampling in loaded image. + * In iOS, larger images than 2M pixels may be + * subsampled in rendering. + */ + function detectSubsampling( img ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + canvas, ctx; + + // subsampling may happen overmegapixel image + if ( iw * ih > 1024 * 1024 ) { + canvas = document.createElement('canvas'); + canvas.width = canvas.height = 1; + ctx = canvas.getContext('2d'); + ctx.drawImage( img, -iw + 1, 0 ); + + // subsampled image becomes half smaller in rendering size. + // check alpha channel value to confirm image is covering + // edge pixel or not. if alpha value is 0 + // image is not covering, hence subsampled. + return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0; + } else { + return false; + } + } + + + return function( canvas, img, x, y, width, height ) { + var iw = img.naturalWidth, + ih = img.naturalHeight, + ctx = canvas.getContext('2d'), + subsampled = detectSubsampling( img ), + doSquash = this.type === 'image/jpeg', + d = 1024, + sy = 0, + dy = 0, + tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx; + + if ( subsampled ) { + iw /= 2; + ih /= 2; + } + + ctx.save(); + tmpCanvas = document.createElement('canvas'); + tmpCanvas.width = tmpCanvas.height = d; + + tmpCtx = tmpCanvas.getContext('2d'); + vertSquashRatio = doSquash ? + detectVerticalSquash( img, iw, ih ) : 1; + + dw = Math.ceil( d * width / iw ); + dh = Math.ceil( d * height / ih / vertSquashRatio ); + + while ( sy < ih ) { + sx = 0; + dx = 0; + while ( sx < iw ) { + tmpCtx.clearRect( 0, 0, d, d ); + tmpCtx.drawImage( img, -sx, -sy ); + ctx.drawImage( tmpCanvas, 0, 0, d, d, + x + dx, y + dy, dw, dh ); + sx += d; + dx += dw; + } + sy += d; + dy += dh; + } + ctx.restore(); + tmpCanvas = tmpCtx = null; + }; + })() + }); + }); + + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/html5/md5',[ + 'runtime/html5/runtime' + ], function( FlashRuntime ) { + + /* + * Fastest md5 implementation around (JKM md5) + * Credits: Joseph Myers + * + * @see http://www.myersdaily.org/joseph/javascript/md5-text.html + * @see http://jsperf.com/md5-shootout/7 + */ + + /* this function is much faster, + so if possible we use it. Some IEs + are the only ones I know of that + need the idiotic second function, + generated by an if clause. */ + var add32 = function (a, b) { + return (a + b) & 0xFFFFFFFF; + }, + + cmn = function (q, a, b, x, s, t) { + a = add32(add32(a, q), add32(x, t)); + return add32((a << s) | (a >>> (32 - s)), b); + }, + + ff = function (a, b, c, d, x, s, t) { + return cmn((b & c) | ((~b) & d), a, b, x, s, t); + }, + + gg = function (a, b, c, d, x, s, t) { + return cmn((b & d) | (c & (~d)), a, b, x, s, t); + }, + + hh = function (a, b, c, d, x, s, t) { + return cmn(b ^ c ^ d, a, b, x, s, t); + }, + + ii = function (a, b, c, d, x, s, t) { + return cmn(c ^ (b | (~d)), a, b, x, s, t); + }, + + md5cycle = function (x, k) { + var a = x[0], + b = x[1], + c = x[2], + d = x[3]; + + a = ff(a, b, c, d, k[0], 7, -680876936); + d = ff(d, a, b, c, k[1], 12, -389564586); + c = ff(c, d, a, b, k[2], 17, 606105819); + b = ff(b, c, d, a, k[3], 22, -1044525330); + a = ff(a, b, c, d, k[4], 7, -176418897); + d = ff(d, a, b, c, k[5], 12, 1200080426); + c = ff(c, d, a, b, k[6], 17, -1473231341); + b = ff(b, c, d, a, k[7], 22, -45705983); + a = ff(a, b, c, d, k[8], 7, 1770035416); + d = ff(d, a, b, c, k[9], 12, -1958414417); + c = ff(c, d, a, b, k[10], 17, -42063); + b = ff(b, c, d, a, k[11], 22, -1990404162); + a = ff(a, b, c, d, k[12], 7, 1804603682); + d = ff(d, a, b, c, k[13], 12, -40341101); + c = ff(c, d, a, b, k[14], 17, -1502002290); + b = ff(b, c, d, a, k[15], 22, 1236535329); + + a = gg(a, b, c, d, k[1], 5, -165796510); + d = gg(d, a, b, c, k[6], 9, -1069501632); + c = gg(c, d, a, b, k[11], 14, 643717713); + b = gg(b, c, d, a, k[0], 20, -373897302); + a = gg(a, b, c, d, k[5], 5, -701558691); + d = gg(d, a, b, c, k[10], 9, 38016083); + c = gg(c, d, a, b, k[15], 14, -660478335); + b = gg(b, c, d, a, k[4], 20, -405537848); + a = gg(a, b, c, d, k[9], 5, 568446438); + d = gg(d, a, b, c, k[14], 9, -1019803690); + c = gg(c, d, a, b, k[3], 14, -187363961); + b = gg(b, c, d, a, k[8], 20, 1163531501); + a = gg(a, b, c, d, k[13], 5, -1444681467); + d = gg(d, a, b, c, k[2], 9, -51403784); + c = gg(c, d, a, b, k[7], 14, 1735328473); + b = gg(b, c, d, a, k[12], 20, -1926607734); + + a = hh(a, b, c, d, k[5], 4, -378558); + d = hh(d, a, b, c, k[8], 11, -2022574463); + c = hh(c, d, a, b, k[11], 16, 1839030562); + b = hh(b, c, d, a, k[14], 23, -35309556); + a = hh(a, b, c, d, k[1], 4, -1530992060); + d = hh(d, a, b, c, k[4], 11, 1272893353); + c = hh(c, d, a, b, k[7], 16, -155497632); + b = hh(b, c, d, a, k[10], 23, -1094730640); + a = hh(a, b, c, d, k[13], 4, 681279174); + d = hh(d, a, b, c, k[0], 11, -358537222); + c = hh(c, d, a, b, k[3], 16, -722521979); + b = hh(b, c, d, a, k[6], 23, 76029189); + a = hh(a, b, c, d, k[9], 4, -640364487); + d = hh(d, a, b, c, k[12], 11, -421815835); + c = hh(c, d, a, b, k[15], 16, 530742520); + b = hh(b, c, d, a, k[2], 23, -995338651); + + a = ii(a, b, c, d, k[0], 6, -198630844); + d = ii(d, a, b, c, k[7], 10, 1126891415); + c = ii(c, d, a, b, k[14], 15, -1416354905); + b = ii(b, c, d, a, k[5], 21, -57434055); + a = ii(a, b, c, d, k[12], 6, 1700485571); + d = ii(d, a, b, c, k[3], 10, -1894986606); + c = ii(c, d, a, b, k[10], 15, -1051523); + b = ii(b, c, d, a, k[1], 21, -2054922799); + a = ii(a, b, c, d, k[8], 6, 1873313359); + d = ii(d, a, b, c, k[15], 10, -30611744); + c = ii(c, d, a, b, k[6], 15, -1560198380); + b = ii(b, c, d, a, k[13], 21, 1309151649); + a = ii(a, b, c, d, k[4], 6, -145523070); + d = ii(d, a, b, c, k[11], 10, -1120210379); + c = ii(c, d, a, b, k[2], 15, 718787259); + b = ii(b, c, d, a, k[9], 21, -343485551); + + x[0] = add32(a, x[0]); + x[1] = add32(b, x[1]); + x[2] = add32(c, x[2]); + x[3] = add32(d, x[3]); + }, + + /* there needs to be support for Unicode here, + * unless we pretend that we can redefine the MD-5 + * algorithm for multi-byte characters (perhaps + * by adding every four 16-bit characters and + * shortening the sum to 32 bits). Otherwise + * I suggest performing MD-5 as if every character + * was two bytes--e.g., 0040 0025 = @%--but then + * how will an ordinary MD-5 sum be matched? + * There is no way to standardize text to something + * like UTF-8 before transformation; speed cost is + * utterly prohibitive. The JavaScript standard + * itself needs to look at this: it should start + * providing access to strings as preformed UTF-8 + * 8-bit unsigned value arrays. + */ + md5blk = function (s) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24); + } + return md5blks; + }, + + md5blk_array = function (a) { + var md5blks = [], + i; /* Andy King said do it this way. */ + + for (i = 0; i < 64; i += 4) { + md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24); + } + return md5blks; + }, + + md51 = function (s) { + var n = s.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk(s.substring(i - 64, i))); + } + s = s.substring(i - 64); + length = s.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3); + } + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + return state; + }, + + md51_array = function (a) { + var n = a.length, + state = [1732584193, -271733879, -1732584194, 271733878], + i, + length, + tail, + tmp, + lo, + hi; + + for (i = 64; i <= n; i += 64) { + md5cycle(state, md5blk_array(a.subarray(i - 64, i))); + } + + // Not sure if it is a bug, however IE10 will always produce a sub array of length 1 + // containing the last element of the parent array if the sub array specified starts + // beyond the length of the parent array - weird. + // https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue + a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0); + + length = a.length; + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= a[i] << ((i % 4) << 3); + } + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Beware that the final length might not fit in 32 bits so we take care of that + tmp = n * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + + md5cycle(state, tail); + + return state; + }, + + hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'], + + rhex = function (n) { + var s = '', + j; + for (j = 0; j < 4; j += 1) { + s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F]; + } + return s; + }, + + hex = function (x) { + var i; + for (i = 0; i < x.length; i += 1) { + x[i] = rhex(x[i]); + } + return x.join(''); + }, + + md5 = function (s) { + return hex(md51(s)); + }, + + + + //////////////////////////////////////////////////////////////////////////// + + /** + * SparkMD5 OOP implementation. + * + * Use this class to perform an incremental md5, otherwise use the + * static methods instead. + */ + SparkMD5 = function () { + // call reset to init the instance + this.reset(); + }; + + + // In some cases the fast add32 function cannot be used.. + if (md5('hello') !== '5d41402abc4b2a76b9719d911017c592') { + add32 = function (x, y) { + var lsw = (x & 0xFFFF) + (y & 0xFFFF), + msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xFFFF); + }; + } + + + /** + * Appends a string. + * A conversion will be applied if an utf8 string is detected. + * + * @param {String} str The string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.append = function (str) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + // then append as binary + this.appendBinary(str); + + return this; + }; + + /** + * Appends a binary string. + * + * @param {String} contents The binary string to be appended + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.appendBinary = function (contents) { + this._buff += contents; + this._length += contents.length; + + var length = this._buff.length, + i; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk(this._buff.substring(i - 64, i))); + } + + this._buff = this._buff.substr(i - 64); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + i, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + /** + * Finish the final calculation based on the tail. + * + * @param {Array} tail The tail (will be modified) + * @param {Number} length The length of the remaining buffer + */ + SparkMD5.prototype._finish = function (tail, length) { + var i = length, + tmp, + lo, + hi; + + tail[i >> 2] |= 0x80 << ((i % 4) << 3); + if (i > 55) { + md5cycle(this._state, tail); + for (i = 0; i < 16; i += 1) { + tail[i] = 0; + } + } + + // Do the final computation based on the tail and length + // Beware that the final length may not fit in 32 bits so we take care of that + tmp = this._length * 8; + tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/); + lo = parseInt(tmp[2], 16); + hi = parseInt(tmp[1], 16) || 0; + + tail[14] = lo; + tail[15] = hi; + md5cycle(this._state, tail); + }; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5} The instance itself + */ + SparkMD5.prototype.reset = function () { + this._buff = ""; + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.prototype.destroy = function () { + delete this._state; + delete this._buff; + delete this._length; + }; + + + /** + * Performs the md5 hash on a string. + * A conversion will be applied if utf8 string is detected. + * + * @param {String} str The string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hash = function (str, raw) { + // converts the string to utf8 bytes if necessary + if (/[\u0080-\uFFFF]/.test(str)) { + str = unescape(encodeURIComponent(str)); + } + + var hash = md51(str); + + return !!raw ? hash : hex(hash); + }; + + /** + * Performs the md5 hash on a binary string. + * + * @param {String} content The binary string + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.hashBinary = function (content, raw) { + var hash = md51(content); + + return !!raw ? hash : hex(hash); + }; + + /** + * SparkMD5 OOP implementation for array buffers. + * + * Use this class to perform an incremental md5 ONLY for array buffers. + */ + SparkMD5.ArrayBuffer = function () { + // call reset to init the instance + this.reset(); + }; + + //////////////////////////////////////////////////////////////////////////// + + /** + * Appends an array buffer. + * + * @param {ArrayBuffer} arr The array to be appended + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.append = function (arr) { + // TODO: we could avoid the concatenation here but the algorithm would be more complex + // if you find yourself needing extra performance, please make a PR. + var buff = this._concatArrayBuffer(this._buff, arr), + length = buff.length, + i; + + this._length += arr.byteLength; + + for (i = 64; i <= length; i += 64) { + md5cycle(this._state, md5blk_array(buff.subarray(i - 64, i))); + } + + // Avoids IE10 weirdness (documented above) + this._buff = (i - 64) < length ? buff.subarray(i - 64) : new Uint8Array(0); + + return this; + }; + + /** + * Finishes the incremental computation, reseting the internal state and + * returning the result. + * Use the raw parameter to obtain the raw result instead of the hex one. + * + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.prototype.end = function (raw) { + var buff = this._buff, + length = buff.length, + tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + i, + ret; + + for (i = 0; i < length; i += 1) { + tail[i >> 2] |= buff[i] << ((i % 4) << 3); + } + + this._finish(tail, length); + ret = !!raw ? this._state : hex(this._state); + + this.reset(); + + return ret; + }; + + SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish; + + /** + * Resets the internal state of the computation. + * + * @return {SparkMD5.ArrayBuffer} The instance itself + */ + SparkMD5.ArrayBuffer.prototype.reset = function () { + this._buff = new Uint8Array(0); + this._length = 0; + this._state = [1732584193, -271733879, -1732584194, 271733878]; + + return this; + }; + + /** + * Releases memory used by the incremental buffer and other aditional + * resources. If you plan to use the instance again, use reset instead. + */ + SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy; + + /** + * Concats two array buffers, returning a new one. + * + * @param {ArrayBuffer} first The first array buffer + * @param {ArrayBuffer} second The second array buffer + * + * @return {ArrayBuffer} The new array buffer + */ + SparkMD5.ArrayBuffer.prototype._concatArrayBuffer = function (first, second) { + var firstLength = first.length, + result = new Uint8Array(firstLength + second.byteLength); + + result.set(first); + result.set(new Uint8Array(second), firstLength); + + return result; + }; + + /** + * Performs the md5 hash on an array buffer. + * + * @param {ArrayBuffer} arr The array buffer + * @param {Boolean} raw True to get the raw result, false to get the hex result + * + * @return {String|Array} The result + */ + SparkMD5.ArrayBuffer.hash = function (arr, raw) { + var hash = md51_array(new Uint8Array(arr)); + + return !!raw ? hash : hex(hash); + }; + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( file ) { + var blob = file.getSource(), + chunkSize = 2 * 1024 * 1024, + chunks = Math.ceil( blob.size / chunkSize ), + chunk = 0, + owner = this.owner, + spark = new SparkMD5.ArrayBuffer(), + me = this, + blobSlice = blob.mozSlice || blob.webkitSlice || blob.slice, + loadNext, fr; + + fr = new FileReader(); + + loadNext = function() { + var start, end; + + start = chunk * chunkSize; + end = Math.min( start + chunkSize, blob.size ); + + fr.onload = function( e ) { + spark.append( e.target.result ); + owner.trigger( 'progress', { + total: file.size, + loaded: end + }); + }; + + fr.onloadend = function() { + fr.onloadend = fr.onload = null; + + if ( ++chunk < chunks ) { + setTimeout( loadNext, 1 ); + } else { + setTimeout(function(){ + owner.trigger('load'); + me.result = spark.end(); + loadNext = file = blob = spark = null; + owner.trigger('complete'); + }, 50 ); + } + }; + + fr.readAsArrayBuffer( blobSlice.call( blob, start, end ) ); + }; + + loadNext(); + }, + + getResult: function() { + return this.result; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview 图片压缩 + */ + define('runtime/flash/image',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Image', { + // init: function( options ) { + // var owner = this.owner; + + // this.flashExec( 'Image', 'init', options ); + // owner.on( 'load', function() { + // debugger; + // }); + // }, + + loadFromBlob: function( blob ) { + var owner = this.owner; + + owner.info() && this.flashExec( 'Image', 'info', owner.info() ); + owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() ); + + this.flashExec( 'Image', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = function( s ) { + try { + if (window.JSON && window.JSON.parse) { + return JSON.parse(s); + } + + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + + /** + * @fileOverview Blob Html实现 + */ + define('runtime/flash/blob',[ + 'runtime/flash/runtime', + 'lib/blob' + ], function( FlashRuntime, Blob ) { + + return FlashRuntime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.flashExec( 'Blob', 'slice', start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview Md5 flash实现 + */ + define('runtime/flash/md5',[ + 'runtime/flash/runtime' + ], function( FlashRuntime ) { + + return FlashRuntime.register( 'Md5', { + init: function() { + // do nothing. + }, + + loadFromBlob: function( blob ) { + return this.flashExec( 'Md5', 'loadFromBlob', blob.uid ); + } + }); + }); + /** + * @fileOverview 完全版本。 + */ + define('preset/all',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/image', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + 'widgets/md5', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/imagemeta/exif', + 'runtime/html5/androidpatch', + 'runtime/html5/image', + 'runtime/html5/transport', + 'runtime/html5/md5', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/image', + 'runtime/flash/transport', + 'runtime/flash/blob', + 'runtime/flash/md5' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/all' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/public/webuploader/webuploader.nolog.min.js b/public/webuploader/webuploader.nolog.min.js new file mode 100644 index 0000000..fcb94da --- /dev/null +++ b/public/webuploader/webuploader.nolog.min.js @@ -0,0 +1,3 @@ +/* WebUploader 0.1.6 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.require,c=a.__dollar||a.jQuery||a.Zepto||b("jquery")||b("zepto");if(!c)throw new Error("jQuery or Zepto not found!");return c}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.6",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b,void 0):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?("name"===a&&(g.name=c.name),void 0):(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a,void 0)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file",style:"webuploader-pick"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button,h=e.style;h&&g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":h&&g.addClass("webuploader-pick-hover");break;case"mouseleave":h&&g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.on("dialogopen",function(){e.owner.trigger("dialogOpen",j.button)}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("lib/image",["base","runtime/client","lib/blob"],function(a,b,c){function d(a){this.options=e.extend({},d.options,a),b.call(this,"Image"),this.on("load",function(){this._info=this.exec("info"),this._meta=this.exec("meta")})}var e=a.$;return d.options={quality:90,crop:!1,preserveHeaders:!1,allowMagnify:!1},a.inherits(b,{constructor:d,info:function(a){return a?(this._info=a,this):this._info},meta:function(a){return a?(this._meta=a,this):this._meta},loadFromBlob:function(a){var b=this,c=a.getRuid();this.connectRuntime(c,function(){b.exec("init",b.options),b.exec("loadFromBlob",a)})},resize:function(){var b=a.slice(arguments);return this.exec.apply(this,["resize"].concat(b))},crop:function(){var b=a.slice(arguments);return this.exec.apply(this,["crop"].concat(b))},getAsDataUrl:function(a){return this.exec("getAsDataUrl",a)},getAsBlob:function(a){var b=this.exec("getAsBlob",a);return new c(this.getRuid(),b)}}),d}),b("widgets/image",["base","uploader","lib/image","widgets/widget"],function(a,b,c){var d,e=a.$;return d=function(a){var b=0,c=[],d=function(){for(var d;c.length&&a>b;)d=c.shift(),b+=d[0],d[1]()};return function(a,e,f){c.push([e,f]),a.once("destroy",function(){b-=e,setTimeout(d,1)}),setTimeout(d,1)}}(5242880),e.extend(b.options,{thumb:{width:110,height:110,quality:70,allowMagnify:!0,crop:!0,preserveHeaders:!1,type:"image/jpeg"},compress:{width:1600,height:1600,quality:90,allowMagnify:!1,crop:!1,preserveHeaders:!0}}),b.register({name:"image",makeThumb:function(a,b,f,g){var h,i;return a=this.request("get-file",a),a.type.match(/^image/)?(h=e.extend({},this.options.thumb),e.isPlainObject(f)&&(h=e.extend(h,f),f=null),f=f||h.width,g=g||h.height,i=new c(h),i.once("load",function(){a._info=a._info||i.info(),a._meta=a._meta||i.meta(),1>=f&&f>0&&(f=a._info.width*f),1>=g&&g>0&&(g=a._info.height*g),i.resize(f,g)}),i.once("complete",function(){b(!1,i.getAsDataUrl(h.type)),i.destroy()}),i.once("error",function(a){b(a||!0),i.destroy()}),d(i,a.source.size,function(){a._info&&i.info(a._info),a._meta&&i.meta(a._meta),i.loadFromBlob(a.source)}),void 0):(b(!0),void 0)},beforeSendFile:function(b){var d,f,g=this.options.compress||this.options.resize,h=g&&g.compressSize||0,i=g&&g.noCompressIfLarger||!1;return b=this.request("get-file",b),!g||!~"image/jpeg,image/jpg".indexOf(b.type)||b.size=a&&a>0&&(a=b._info.width*a),1>=c&&c>0&&(c=b._info.height*c),d.resize(a,c)}),d.once("complete",function(){var a,c;try{a=d.getAsBlob(g.type),c=b.size,(!i||a.sizeb;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):(b.owner.trigger("error","Q_TYPE_DENIED",a),void 0):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),a.length&&(b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20))},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),b||f.request("start-upload"),void 0;for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b?(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT?(b.setStatus(h.QUEUED),f.each(c.pool,function(a,c){c.file===b&&(c.transport&&c.transport.send(),b.setStatus(h.PROGRESS))})):b.getStatus()!==h.PROGRESS&&b.setStatus(h.QUEUED)):f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)}),c.runing)return a.nextTick(c.__tick);c.runing=!0;var d=[];b||f.each(c.pool,function(a,b){var e=b.file;e.getStatus()===h.INTERRUPT&&(c._trigged=!1,d.push(e),b.transport&&b.transport.send())}),f.each(d,function(){this.setStatus(h.PROGRESS)}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload")},stopUpload:function(b,c){var d,e=this;if(b===!0&&(c=b,b=null),e.runing!==!1){if(b){if(b=b.id?b:e.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(e.pool,function(a,c){return c.file===b?(d=c,!1):void 0}),d.transport&&d.transport.abort(),c&&(e._putback(d),e._popBlock(d)),a.nextTick(e.__tick)}e.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(e.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),e.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):(d.pool.length1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send() +},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})},updateFileProgress:function(a){var b=0,c=0;a.blocks&&(f.each(a.blocks,function(a,b){c+=(b.percentage||0)*(b.end-b.start)}),b=c/a.size,this.owner.trigger("uploadProgress",a,b||0))}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("lib/md5",["runtime/client","mediator"],function(a,b){function c(){a.call(this,"Md5")}return b.installTo(c.prototype),c.prototype.loadFromBlob=function(a){var b=this;b.getRuid()&&b.disconnectRuntime(),b.connectRuntime(a.ruid,function(){b.exec("init"),b.exec("loadFromBlob",a)})},c.prototype.getResult=function(){return this.exec("getResult")},c}),b("widgets/md5",["base","uploader","lib/md5","lib/blob","widgets/widget"],function(a,b,c,d){return b.register({name:"md5",md5File:function(b,e,f){var g=new c,h=a.Deferred(),i=b instanceof d?b:this.request("get-file",b).source;return g.on("progress load",function(a){a=a||{},h.notify(a.total?a.loaded/a.total:1)}),g.on("complete",function(){h.resolve(g.getResult())}),g.on("error",function(a){h.reject(a)}),arguments.length>1&&(e=e||0,f=f||0,0>e&&(e=i.size+e),0>f&&(f=i.size+f),f=Math.min(f,i.size),i=i.slice(e,f)),g.loadFromBlob(i),h.promise()}})}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice;return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return g.dndOver=!1,g.elem.removeClass(e+"over"),f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("capture","camera"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(a){k.trigger("click"),a.stopPropagation(),h.trigger("dialogopen")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/util",["base"],function(b){var c=a.createObjectURL&&a||a.URL&&URL.revokeObjectURL&&URL||a.webkitURL,d=b.noop,e=d;return c&&(d=function(){return c.createObjectURL.apply(c,arguments)},e=function(){return c.revokeObjectURL.apply(c,arguments)}),{createObjectURL:d,revokeObjectURL:e,dataURL2Blob:function(a){var b,c,d,e,f,g;for(g=a.split(","),b=~g[0].indexOf("base64")?atob(g[1]):decodeURIComponent(g[1]),d=new ArrayBuffer(b.length),c=new Uint8Array(d),e=0;ei&&(d=h.getUint16(i),d>=65504&&65519>=d||65534===d)&&(e=h.getUint16(i+2)+2,!(i+e>h.byteLength));){if(f=b.parsers[d],!c&&f)for(g=0;g6&&(l.imageHead=a.slice?a.slice(2,k):new Uint8Array(a).subarray(2,k))}return l}},updateImageHead:function(a,b){var c,d,e,f=this._parse(a,!0);return e=2,f.imageHead&&(e=2+f.imageHead.byteLength),d=a.slice?a.slice(e):new Uint8Array(a).subarray(e),c=new Uint8Array(b.byteLength+2+d.byteLength),c[0]=255,c[1]=216,c.set(new Uint8Array(b),2),c.set(new Uint8Array(d),b.byteLength+2),c.buffer}},a.parseMeta=function(){return b.parse.apply(b,arguments)},a.updateImageHead=function(){return b.updateImageHead.apply(b,arguments)},b}),b("runtime/html5/imagemeta/exif",["base","runtime/html5/imagemeta"],function(a,b){var c={};return c.ExifMap=function(){return this},c.ExifMap.prototype.map={Orientation:274},c.ExifMap.prototype.get=function(a){return this[a]||this[this.map[a]]},c.exifTagTypes={1:{getValue:function(a,b){return a.getUint8(b)},size:1},2:{getValue:function(a,b){return String.fromCharCode(a.getUint8(b))},size:1,ascii:!0},3:{getValue:function(a,b,c){return a.getUint16(b,c)},size:2},4:{getValue:function(a,b,c){return a.getUint32(b,c)},size:4},5:{getValue:function(a,b,c){return a.getUint32(b,c)/a.getUint32(b+4,c)},size:8},9:{getValue:function(a,b,c){return a.getInt32(b,c)},size:4},10:{getValue:function(a,b,c){return a.getInt32(b,c)/a.getInt32(b+4,c)},size:8}},c.exifTagTypes[7]=c.exifTagTypes[1],c.getExifValue=function(b,d,e,f,g,h){var i,j,k,l,m,n,o=c.exifTagTypes[f];if(!o)return a.log("Invalid Exif data: Invalid tag type."),void 0;if(i=o.size*g,j=i>4?d+b.getUint32(e+8,h):e+8,j+i>b.byteLength)return a.log("Invalid Exif data: Invalid data offset."),void 0;if(1===g)return o.getValue(b,j,h);for(k=[],l=0;g>l;l+=1)k[l]=o.getValue(b,j+l*o.size,h);if(o.ascii){for(m="",l=0;lb.byteLength)return a.log("Invalid Exif data: Invalid directory offset."),void 0;if(g=b.getUint16(d,e),h=d+2+12*g,h+4>b.byteLength)return a.log("Invalid Exif data: Invalid directory size."),void 0;for(i=0;g>i;i+=1)this.parseExifTag(b,c,d+2+12*i,e,f);return b.getUint32(h,e)},c.parseExifData=function(b,d,e,f){var g,h,i=d+10;if(1165519206===b.getUint32(d+4)){if(i+8>b.byteLength)return a.log("Invalid Exif data: Invalid segment size."),void 0;if(0!==b.getUint16(d+8))return a.log("Invalid Exif data: Missing byte alignment offset."),void 0;switch(b.getUint16(i)){case 18761:g=!0;break;case 19789:g=!1;break;default:return a.log("Invalid Exif data: Invalid byte alignment marker."),void 0}if(42!==b.getUint16(i+2,g))return a.log("Invalid Exif data: Missing TIFF marker."),void 0;h=b.getUint32(i+4,g),f.exif=new c.ExifMap,h=c.parseExifTags(b,i,i+h,g,f)}},b.parsers[65505].push(c.parseExifData),c}),b("runtime/html5/jpegencoder",[],function(){function a(a){function b(a){for(var b=[16,11,10,16,24,40,51,61,12,12,14,19,26,58,60,55,14,13,16,24,40,57,69,56,14,17,22,29,51,87,80,62,18,22,37,56,68,109,103,77,24,35,55,64,81,104,113,92,49,64,78,87,103,121,120,101,72,92,95,98,112,100,103,99],c=0;64>c;c++){var d=y((b[c]*a+50)/100);1>d?d=1:d>255&&(d=255),z[P[c]]=d}for(var e=[17,18,24,47,99,99,99,99,18,21,26,66,99,99,99,99,24,26,56,99,99,99,99,99,47,66,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99,99],f=0;64>f;f++){var g=y((e[f]*a+50)/100);1>g?g=1:g>255&&(g=255),A[P[f]]=g}for(var h=[1,1.387039845,1.306562965,1.175875602,1,.785694958,.5411961,.275899379],i=0,j=0;8>j;j++)for(var k=0;8>k;k++)B[i]=1/(8*z[P[i]]*h[j]*h[k]),C[i]=1/(8*A[P[i]]*h[j]*h[k]),i++}function c(a,b){for(var c=0,d=0,e=new Array,f=1;16>=f;f++){for(var g=1;g<=a[f];g++)e[b[d]]=[],e[b[d]][0]=c,e[b[d]][1]=f,d++,c++;c*=2}return e}function d(){t=c(Q,R),u=c(U,V),v=c(S,T),w=c(W,X)}function e(){for(var a=1,b=2,c=1;15>=c;c++){for(var d=a;b>d;d++)E[32767+d]=c,D[32767+d]=[],D[32767+d][1]=c,D[32767+d][0]=d;for(var e=-(b-1);-a>=e;e++)E[32767+e]=c,D[32767+e]=[],D[32767+e][1]=c,D[32767+e][0]=b-1+e;a<<=1,b<<=1}}function f(){for(var a=0;256>a;a++)O[a]=19595*a,O[a+256>>0]=38470*a,O[a+512>>0]=7471*a+32768,O[a+768>>0]=-11059*a,O[a+1024>>0]=-21709*a,O[a+1280>>0]=32768*a+8421375,O[a+1536>>0]=-27439*a,O[a+1792>>0]=-5329*a}function g(a){for(var b=a[0],c=a[1]-1;c>=0;)b&1<J&&(255==I?(h(255),h(0)):h(I),J=7,I=0)}function h(a){H.push(N[a])}function i(a){h(255&a>>8),h(255&a)}function j(a,b){var c,d,e,f,g,h,i,j,k,l=0,m=8,n=64;for(k=0;m>k;++k){c=a[l],d=a[l+1],e=a[l+2],f=a[l+3],g=a[l+4],h=a[l+5],i=a[l+6],j=a[l+7];var o=c+j,p=c-j,q=d+i,r=d-i,s=e+h,t=e-h,u=f+g,v=f-g,w=o+u,x=o-u,y=q+s,z=q-s;a[l]=w+y,a[l+4]=w-y;var A=.707106781*(z+x);a[l+2]=x+A,a[l+6]=x-A,w=v+t,y=t+r,z=r+p;var B=.382683433*(w-z),C=.5411961*w+B,D=1.306562965*z+B,E=.707106781*y,G=p+E,H=p-E;a[l+5]=H+C,a[l+3]=H-C,a[l+1]=G+D,a[l+7]=G-D,l+=8}for(l=0,k=0;m>k;++k){c=a[l],d=a[l+8],e=a[l+16],f=a[l+24],g=a[l+32],h=a[l+40],i=a[l+48],j=a[l+56];var I=c+j,J=c-j,K=d+i,L=d-i,M=e+h,N=e-h,O=f+g,P=f-g,Q=I+O,R=I-O,S=K+M,T=K-M;a[l]=Q+S,a[l+32]=Q-S;var U=.707106781*(T+R);a[l+16]=R+U,a[l+48]=R-U,Q=P+N,S=N+L,T=L+J;var V=.382683433*(Q-T),W=.5411961*Q+V,X=1.306562965*T+V,Y=.707106781*S,Z=J+Y,$=J-Y;a[l+40]=$+W,a[l+24]=$-W,a[l+8]=Z+X,a[l+56]=Z-X,l++}var _;for(k=0;n>k;++k)_=a[k]*b[k],F[k]=_>0?0|_+.5:0|_-.5;return F}function k(){i(65504),i(16),h(74),h(70),h(73),h(70),h(0),h(1),h(1),h(0),i(1),i(1),h(0),h(0)}function l(a,b){i(65472),i(17),h(8),i(b),i(a),h(3),h(1),h(17),h(0),h(2),h(17),h(1),h(3),h(17),h(1)}function m(){i(65499),i(132),h(0);for(var a=0;64>a;a++)h(z[a]);h(1);for(var b=0;64>b;b++)h(A[b])}function n(){i(65476),i(418),h(0);for(var a=0;16>a;a++)h(Q[a+1]);for(var b=0;11>=b;b++)h(R[b]);h(16);for(var c=0;16>c;c++)h(S[c+1]);for(var d=0;161>=d;d++)h(T[d]);h(1);for(var e=0;16>e;e++)h(U[e+1]);for(var f=0;11>=f;f++)h(V[f]);h(17);for(var g=0;16>g;g++)h(W[g+1]);for(var j=0;161>=j;j++)h(X[j])}function o(){i(65498),i(12),h(3),h(1),h(0),h(2),h(17),h(3),h(17),h(0),h(63),h(0)}function p(a,b,c,d,e){for(var f,h=e[0],i=e[240],k=16,l=63,m=64,n=j(a,b),o=0;m>o;++o)G[P[o]]=n[o];var p=G[0]-c;c=G[0],0==p?g(d[0]):(f=32767+p,g(d[E[f]]),g(D[f]));for(var q=63;q>0&&0==G[q];q--);if(0==q)return g(h),c;for(var r,s=1;q>=s;){for(var t=s;0==G[s]&&q>=s;++s);var u=s-t;if(u>=k){r=u>>4;for(var v=1;r>=v;++v)g(i);u=15&u}f=32767+G[s],g(e[(u<<4)+E[f]]),g(D[f]),s++}return q!=l&&g(h),c}function q(){for(var a=String.fromCharCode,b=0;256>b;b++)N[b]=a(b)}function r(a){if(0>=a&&(a=1),a>100&&(a=100),x!=a){var c=0;c=50>a?Math.floor(5e3/a):Math.floor(200-2*a),b(c),x=a}}function s(){a||(a=50),q(),d(),e(),f(),r(a)}Math.round;var t,u,v,w,x,y=Math.floor,z=new Array(64),A=new Array(64),B=new Array(64),C=new Array(64),D=new Array(65535),E=new Array(65535),F=new Array(64),G=new Array(64),H=[],I=0,J=7,K=new Array(64),L=new Array(64),M=new Array(64),N=new Array(256),O=new Array(2048),P=[0,1,5,6,14,15,27,28,2,4,7,13,16,26,29,42,3,8,12,17,25,30,41,43,9,11,18,24,31,40,44,53,10,19,23,32,39,45,52,54,20,22,33,38,46,51,55,60,21,34,37,47,50,56,59,61,35,36,48,49,57,58,62,63],Q=[0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0],R=[0,1,2,3,4,5,6,7,8,9,10,11],S=[0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,125],T=[1,2,3,0,4,17,5,18,33,49,65,6,19,81,97,7,34,113,20,50,129,145,161,8,35,66,177,193,21,82,209,240,36,51,98,114,130,9,10,22,23,24,25,26,37,38,39,40,41,42,52,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,225,226,227,228,229,230,231,232,233,234,241,242,243,244,245,246,247,248,249,250],U=[0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0],V=[0,1,2,3,4,5,6,7,8,9,10,11],W=[0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,119],X=[0,1,2,3,17,4,5,33,49,6,18,65,81,7,97,113,19,34,50,129,8,20,66,145,161,177,193,9,35,51,82,240,21,98,114,209,10,22,36,52,225,37,241,23,24,25,26,38,39,40,41,42,53,54,55,56,57,58,67,68,69,70,71,72,73,74,83,84,85,86,87,88,89,90,99,100,101,102,103,104,105,106,115,116,117,118,119,120,121,122,130,131,132,133,134,135,136,137,138,146,147,148,149,150,151,152,153,154,162,163,164,165,166,167,168,169,170,178,179,180,181,182,183,184,185,186,194,195,196,197,198,199,200,201,202,210,211,212,213,214,215,216,217,218,226,227,228,229,230,231,232,233,234,242,243,244,245,246,247,248,249,250];this.encode=function(a,b){b&&r(b),H=new Array,I=0,J=7,i(65496),k(),m(),l(a.width,a.height),n(),o();var c=0,d=0,e=0;I=0,J=7,this.encode.displayName="_encode_";for(var f,h,j,q,s,x,y,z,A,D=a.data,E=a.width,F=a.height,G=4*E,N=0;F>N;){for(f=0;G>f;){for(s=G*N+f,x=s,y=-1,z=0,A=0;64>A;A++)z=A>>3,y=4*(7&A),x=s+z*G+y,N+z>=F&&(x-=G*(N+1+z-F)),f+y>=G&&(x-=f+y-G+4),h=D[x++],j=D[x++],q=D[x++],K[A]=(O[h]+O[j+256>>0]+O[q+512>>0]>>16)-128,L[A]=(O[h+768>>0]+O[j+1024>>0]+O[q+1280>>0]>>16)-128,M[A]=(O[h+1280>>0]+O[j+1536>>0]+O[q+1792>>0]>>16)-128;c=p(K,B,c,t,v),d=p(L,C,d,u,w),e=p(M,C,e,u,w),f+=32}N+=8}if(J>=0){var P=[];P[1]=J+1,P[0]=(1<i;)e=d[4*(k-1)+3],0===e?j=k:i=k,k=j+i>>1;return f=k/c,0===f?1:f}function c(a){var b,c,d=a.naturalWidth,e=a.naturalHeight;return d*e>1048576?(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,-d+1,0),0===c.getImageData(0,0,1,1).data[3]):!1}return a.os.ios?a.os.ios>=7?function(a,c,d,e,f,g){var h=c.naturalWidth,i=c.naturalHeight,j=b(c,h,i);return a.getContext("2d").drawImage(c,0,0,h*j,i*j,d,e,f,g)}:function(a,d,e,f,g,h){var i,j,k,l,m,n,o,p=d.naturalWidth,q=d.naturalHeight,r=a.getContext("2d"),s=c(d),t="image/jpeg"===this.type,u=1024,v=0,w=0;for(s&&(p/=2,q/=2),r.save(),i=document.createElement("canvas"),i.width=i.height=u,j=i.getContext("2d"),k=t?b(d,p,q):1,l=Math.ceil(u*g/p),m=Math.ceil(u*h/q/k);q>v;){for(n=0,o=0;p>n;)j.clearRect(0,0,u,u),j.drawImage(d,-n,-v),r.drawImage(i,0,0,u,u,e+o,f+w,l,m),n+=u,o+=l;v+=u,w+=m}r.restore(),i=j=null}:function(b){var c=a.slice(arguments,1),d=b.getContext("2d");d.drawImage.apply(d,c)}}()})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/html5/md5",["runtime/html5/runtime"],function(a){var b=function(a,b){return 4294967295&a+b},c=function(a,c,d,e,f,g){return c=b(b(c,a),b(e,g)),b(c<>>32-f,d)},d=function(a,b,d,e,f,g,h){return c(b&d|~b&e,a,b,f,g,h)},e=function(a,b,d,e,f,g,h){return c(b&e|d&~e,a,b,f,g,h)},f=function(a,b,d,e,f,g,h){return c(b^d^e,a,b,f,g,h)},g=function(a,b,d,e,f,g,h){return c(d^(b|~e),a,b,f,g,h)},h=function(a,c){var h=a[0],i=a[1],j=a[2],k=a[3];h=d(h,i,j,k,c[0],7,-680876936),k=d(k,h,i,j,c[1],12,-389564586),j=d(j,k,h,i,c[2],17,606105819),i=d(i,j,k,h,c[3],22,-1044525330),h=d(h,i,j,k,c[4],7,-176418897),k=d(k,h,i,j,c[5],12,1200080426),j=d(j,k,h,i,c[6],17,-1473231341),i=d(i,j,k,h,c[7],22,-45705983),h=d(h,i,j,k,c[8],7,1770035416),k=d(k,h,i,j,c[9],12,-1958414417),j=d(j,k,h,i,c[10],17,-42063),i=d(i,j,k,h,c[11],22,-1990404162),h=d(h,i,j,k,c[12],7,1804603682),k=d(k,h,i,j,c[13],12,-40341101),j=d(j,k,h,i,c[14],17,-1502002290),i=d(i,j,k,h,c[15],22,1236535329),h=e(h,i,j,k,c[1],5,-165796510),k=e(k,h,i,j,c[6],9,-1069501632),j=e(j,k,h,i,c[11],14,643717713),i=e(i,j,k,h,c[0],20,-373897302),h=e(h,i,j,k,c[5],5,-701558691),k=e(k,h,i,j,c[10],9,38016083),j=e(j,k,h,i,c[15],14,-660478335),i=e(i,j,k,h,c[4],20,-405537848),h=e(h,i,j,k,c[9],5,568446438),k=e(k,h,i,j,c[14],9,-1019803690),j=e(j,k,h,i,c[3],14,-187363961),i=e(i,j,k,h,c[8],20,1163531501),h=e(h,i,j,k,c[13],5,-1444681467),k=e(k,h,i,j,c[2],9,-51403784),j=e(j,k,h,i,c[7],14,1735328473),i=e(i,j,k,h,c[12],20,-1926607734),h=f(h,i,j,k,c[5],4,-378558),k=f(k,h,i,j,c[8],11,-2022574463),j=f(j,k,h,i,c[11],16,1839030562),i=f(i,j,k,h,c[14],23,-35309556),h=f(h,i,j,k,c[1],4,-1530992060),k=f(k,h,i,j,c[4],11,1272893353),j=f(j,k,h,i,c[7],16,-155497632),i=f(i,j,k,h,c[10],23,-1094730640),h=f(h,i,j,k,c[13],4,681279174),k=f(k,h,i,j,c[0],11,-358537222),j=f(j,k,h,i,c[3],16,-722521979),i=f(i,j,k,h,c[6],23,76029189),h=f(h,i,j,k,c[9],4,-640364487),k=f(k,h,i,j,c[12],11,-421815835),j=f(j,k,h,i,c[15],16,530742520),i=f(i,j,k,h,c[2],23,-995338651),h=g(h,i,j,k,c[0],6,-198630844),k=g(k,h,i,j,c[7],10,1126891415),j=g(j,k,h,i,c[14],15,-1416354905),i=g(i,j,k,h,c[5],21,-57434055),h=g(h,i,j,k,c[12],6,1700485571),k=g(k,h,i,j,c[3],10,-1894986606),j=g(j,k,h,i,c[10],15,-1051523),i=g(i,j,k,h,c[1],21,-2054922799),h=g(h,i,j,k,c[8],6,1873313359),k=g(k,h,i,j,c[15],10,-30611744),j=g(j,k,h,i,c[6],15,-1560198380),i=g(i,j,k,h,c[13],21,1309151649),h=g(h,i,j,k,c[4],6,-145523070),k=g(k,h,i,j,c[11],10,-1120210379),j=g(j,k,h,i,c[2],15,718787259),i=g(i,j,k,h,c[9],21,-343485551),a[0]=b(h,a[0]),a[1]=b(i,a[1]),a[2]=b(j,a[2]),a[3]=b(k,a[3])},i=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a.charCodeAt(b)+(a.charCodeAt(b+1)<<8)+(a.charCodeAt(b+2)<<16)+(a.charCodeAt(b+3)<<24);return c},j=function(a){var b,c=[];for(b=0;64>b;b+=4)c[b>>2]=a[b]+(a[b+1]<<8)+(a[b+2]<<16)+(a[b+3]<<24);return c},k=function(a){var b,c,d,e,f,g,j=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;j>=b;b+=64)h(k,i(a.substring(b-64,b)));for(a=a.substring(b-64),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a.charCodeAt(b)<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*j,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},l=function(a){var b,c,d,e,f,g,i=a.length,k=[1732584193,-271733879,-1732584194,271733878];for(b=64;i>=b;b+=64)h(k,j(a.subarray(b-64,b)));for(a=i>b-64?a.subarray(b-64):new Uint8Array(0),c=a.length,d=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],b=0;c>b;b+=1)d[b>>2]|=a[b]<<(b%4<<3);if(d[b>>2]|=128<<(b%4<<3),b>55)for(h(k,d),b=0;16>b;b+=1)d[b]=0;return e=8*i,e=e.toString(16).match(/(.*?)(.{0,8})$/),f=parseInt(e[2],16),g=parseInt(e[1],16)||0,d[14]=f,d[15]=g,h(k,d),k},m=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],n=function(a){var b,c="";for(b=0;4>b;b+=1)c+=m[15&a>>8*b+4]+m[15&a>>8*b];return c},o=function(a){var b;for(b=0;b>16)+(b>>16)+(c>>16);return d<<16|65535&c}),q.prototype.append=function(a){return/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a))),this.appendBinary(a),this},q.prototype.appendBinary=function(a){this._buff+=a,this._length+=a.length;var b,c=this._buff.length;for(b=64;c>=b;b+=64)h(this._state,i(this._buff.substring(b-64,b)));return this._buff=this._buff.substr(b-64),this},q.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d.charCodeAt(b)<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.prototype._finish=function(a,b){var c,d,e,f=b;if(a[f>>2]|=128<<(f%4<<3),f>55)for(h(this._state,a),f=0;16>f;f+=1)a[f]=0;c=8*this._length,c=c.toString(16).match(/(.*?)(.{0,8})$/),d=parseInt(c[2],16),e=parseInt(c[1],16)||0,a[14]=d,a[15]=e,h(this._state,a)},q.prototype.reset=function(){return this._buff="",this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.prototype.destroy=function(){delete this._state,delete this._buff,delete this._length},q.hash=function(a,b){/[\u0080-\uFFFF]/.test(a)&&(a=unescape(encodeURIComponent(a)));var c=k(a);return b?c:o(c) +},q.hashBinary=function(a,b){var c=k(a);return b?c:o(c)},q.ArrayBuffer=function(){this.reset()},q.ArrayBuffer.prototype.append=function(a){var b,c=this._concatArrayBuffer(this._buff,a),d=c.length;for(this._length+=a.byteLength,b=64;d>=b;b+=64)h(this._state,j(c.subarray(b-64,b)));return this._buff=d>b-64?c.subarray(b-64):new Uint8Array(0),this},q.ArrayBuffer.prototype.end=function(a){var b,c,d=this._buff,e=d.length,f=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];for(b=0;e>b;b+=1)f[b>>2]|=d[b]<<(b%4<<3);return this._finish(f,e),c=a?this._state:o(this._state),this.reset(),c},q.ArrayBuffer.prototype._finish=q.prototype._finish,q.ArrayBuffer.prototype.reset=function(){return this._buff=new Uint8Array(0),this._length=0,this._state=[1732584193,-271733879,-1732584194,271733878],this},q.ArrayBuffer.prototype.destroy=q.prototype.destroy,q.ArrayBuffer.prototype._concatArrayBuffer=function(a,b){var c=a.length,d=new Uint8Array(c+b.byteLength);return d.set(a),d.set(new Uint8Array(b),c),d},q.ArrayBuffer.hash=function(a,b){var c=l(new Uint8Array(a));return b?c:o(c)},a.register("Md5",{init:function(){},loadFromBlob:function(a){var b,c,d=a.getSource(),e=2097152,f=Math.ceil(d.size/e),g=0,h=this.owner,i=new q.ArrayBuffer,j=this,k=d.mozSlice||d.webkitSlice||d.slice;c=new FileReader,b=function(){var l,m;l=g*e,m=Math.min(l+e,d.size),c.onload=function(b){i.append(b.target.result),h.trigger("progress",{total:a.size,loaded:m})},c.onloadend=function(){c.onloadend=c.onload=null,++g'+''+''+''+"",c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/image",["runtime/flash/runtime"],function(a){return a.register("Image",{loadFromBlob:function(a){var b=this.owner;b.info()&&this.flashExec("Image","info",b.info()),b.meta()&&this.flashExec("Image","meta",b.meta()),this.flashExec("Image","loadFromBlob",a.uid)}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=function(b){try{return a.JSON&&a.JSON.parse?JSON.parse(b):new Function("return "+b).call()}catch(c){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("runtime/flash/blob",["runtime/flash/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.flashExec("Blob","slice",a,c);return new b(this.getRuid(),d)}})}),b("runtime/flash/md5",["runtime/flash/runtime"],function(a){return a.register("Md5",{init:function(){},loadFromBlob:function(a){return this.flashExec("Md5","loadFromBlob",a.uid)}})}),b("preset/all",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/image","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","widgets/md5","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/imagemeta/exif","runtime/html5/androidpatch","runtime/html5/image","runtime/html5/transport","runtime/html5/md5","runtime/flash/filepicker","runtime/flash/image","runtime/flash/transport","runtime/flash/blob","runtime/flash/md5"],function(a){return a}),b("webuploader",["preset/all"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/public/webuploader/webuploader.withoutimage.js b/public/webuploader/webuploader.withoutimage.js new file mode 100644 index 0000000..9e6e3a3 --- /dev/null +++ b/public/webuploader/webuploader.withoutimage.js @@ -0,0 +1,4993 @@ +/*! WebUploader 0.1.5 */ + + +/** + * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。 + * + * AMD API 内部的简单不完全实现,请忽略。只有当WebUploader被合并成一个文件的时候才会引入。 + */ +(function( root, factory ) { + var modules = {}, + + // 内部require, 简单不完全实现。 + // https://github.com/amdjs/amdjs-api/wiki/require + _require = function( deps, callback ) { + var args, len, i; + + // 如果deps不是数组,则直接返回指定module + if ( typeof deps === 'string' ) { + return getModule( deps ); + } else { + args = []; + for( len = deps.length, i = 0; i < len; i++ ) { + args.push( getModule( deps[ i ] ) ); + } + + return callback.apply( null, args ); + } + }, + + // 内部define,暂时不支持不指定id. + _define = function( id, deps, factory ) { + if ( arguments.length === 2 ) { + factory = deps; + deps = null; + } + + _require( deps || [], function() { + setModule( id, factory, arguments ); + }); + }, + + // 设置module, 兼容CommonJs写法。 + setModule = function( id, factory, args ) { + var module = { + exports: factory + }, + returned; + + if ( typeof factory === 'function' ) { + args.length || (args = [ _require, module.exports, module ]); + returned = factory.apply( null, args ); + returned !== undefined && (module.exports = returned); + } + + modules[ id ] = module.exports; + }, + + // 根据id获取module + getModule = function( id ) { + var module = modules[ id ] || root[ id ]; + + if ( !module ) { + throw new Error( '`' + id + '` is undefined' ); + } + + return module; + }, + + // 将所有modules,将路径ids装换成对象。 + exportsTo = function( obj ) { + var key, host, parts, part, last, ucFirst; + + // make the first character upper case. + ucFirst = function( str ) { + return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 )); + }; + + for ( key in modules ) { + host = obj; + + if ( !modules.hasOwnProperty( key ) ) { + continue; + } + + parts = key.split('/'); + last = ucFirst( parts.pop() ); + + while( (part = ucFirst( parts.shift() )) ) { + host[ part ] = host[ part ] || {}; + host = host[ part ]; + } + + host[ last ] = modules[ key ]; + } + + return obj; + }, + + makeExport = function( dollar ) { + root.__dollar = dollar; + + // exports every module. + return exportsTo( factory( root, _define, _require ) ); + }, + + origin; + + if ( typeof module === 'object' && typeof module.exports === 'object' ) { + + // For CommonJS and CommonJS-like environments where a proper window is present, + module.exports = makeExport(); + } else if ( typeof define === 'function' && define.amd ) { + + // Allow using this built library as an AMD module + // in another project. That other project will only + // see this AMD call, not the internal modules in + // the closure below. + define([ 'jquery' ], makeExport ); + } else { + + // Browser globals case. Just assign the + // result to a property on the global. + origin = root.WebUploader; + root.WebUploader = makeExport(); + root.WebUploader.noConflict = function() { + root.WebUploader = origin; + }; + } +})( window, function( window, define, require ) { + + + /** + * @fileOverview jQuery or Zepto + */ + define('dollar-third',[],function() { + var $ = window.__dollar || window.jQuery || window.Zepto; + + if ( !$ ) { + throw new Error('jQuery or Zepto not found!'); + } + + return $; + }); + /** + * @fileOverview Dom 操作相关 + */ + define('dollar',[ + 'dollar-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 使用jQuery的Promise + */ + define('promise-third',[ + 'dollar' + ], function( $ ) { + return { + Deferred: $.Deferred, + when: $.when, + + isPromise: function( anything ) { + return anything && typeof anything.then === 'function'; + } + }; + }); + /** + * @fileOverview Promise/A+ + */ + define('promise',[ + 'promise-third' + ], function( _ ) { + return _; + }); + /** + * @fileOverview 基础类方法。 + */ + + /** + * Web Uploader内部类的详细说明,以下提及的功能类,都可以在`WebUploader`这个变量中访问到。 + * + * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id. + * 默认module id为该文件的路径,而此路径将会转化成名字空间存放在WebUploader中。如: + * + * * module `base`:WebUploader.Base + * * module `file`: WebUploader.File + * * module `lib/dnd`: WebUploader.Lib.Dnd + * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd + * + * + * 以下文档中对类的使用可能省略掉了`WebUploader`前缀。 + * @module WebUploader + * @title WebUploader API文档 + */ + define('base',[ + 'dollar', + 'promise' + ], function( $, promise ) { + + var noop = function() {}, + call = Function.call; + + // http://jsperf.com/uncurrythis + // 反科里化 + function uncurryThis( fn ) { + return function() { + return call.apply( fn, arguments ); + }; + } + + function bindFn( fn, context ) { + return function() { + return fn.apply( context, arguments ); + }; + } + + function createObject( proto ) { + var f; + + if ( Object.create ) { + return Object.create( proto ); + } else { + f = function() {}; + f.prototype = proto; + return new f(); + } + } + + + /** + * 基础类,提供一些简单常用的方法。 + * @class Base + */ + return { + + /** + * @property {String} version 当前版本号。 + */ + version: '0.1.5', + + /** + * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。 + */ + $: $, + + Deferred: promise.Deferred, + + isPromise: promise.isPromise, + + when: promise.when, + + /** + * @description 简单的浏览器检查结果。 + * + * * `webkit` webkit版本号,如果浏览器为非webkit内核,此属性为`undefined`。 + * * `chrome` chrome浏览器版本号,如果浏览器为chrome,此属性为`undefined`。 + * * `ie` ie浏览器版本号,如果浏览器为非ie,此属性为`undefined`。**暂不支持ie10+** + * * `firefox` firefox浏览器版本号,如果浏览器为非firefox,此属性为`undefined`。 + * * `safari` safari浏览器版本号,如果浏览器为非safari,此属性为`undefined`。 + * * `opera` opera浏览器版本号,如果浏览器为非opera,此属性为`undefined`。 + * + * @property {Object} [browser] + */ + browser: (function( ua ) { + var ret = {}, + webkit = ua.match( /WebKit\/([\d.]+)/ ), + chrome = ua.match( /Chrome\/([\d.]+)/ ) || + ua.match( /CriOS\/([\d.]+)/ ), + + ie = ua.match( /MSIE\s([\d\.]+)/ ) || + ua.match( /(?:trident)(?:.*rv:([\w.]+))?/i ), + firefox = ua.match( /Firefox\/([\d.]+)/ ), + safari = ua.match( /Safari\/([\d.]+)/ ), + opera = ua.match( /OPR\/([\d.]+)/ ); + + webkit && (ret.webkit = parseFloat( webkit[ 1 ] )); + chrome && (ret.chrome = parseFloat( chrome[ 1 ] )); + ie && (ret.ie = parseFloat( ie[ 1 ] )); + firefox && (ret.firefox = parseFloat( firefox[ 1 ] )); + safari && (ret.safari = parseFloat( safari[ 1 ] )); + opera && (ret.opera = parseFloat( opera[ 1 ] )); + + return ret; + })( navigator.userAgent ), + + /** + * @description 操作系统检查结果。 + * + * * `android` 如果在android浏览器环境下,此值为对应的android版本号,否则为`undefined`。 + * * `ios` 如果在ios浏览器环境下,此值为对应的ios版本号,否则为`undefined`。 + * @property {Object} [os] + */ + os: (function( ua ) { + var ret = {}, + + // osx = !!ua.match( /\(Macintosh\; Intel / ), + android = ua.match( /(?:Android);?[\s\/]+([\d.]+)?/ ), + ios = ua.match( /(?:iPad|iPod|iPhone).*OS\s([\d_]+)/ ); + + // osx && (ret.osx = true); + android && (ret.android = parseFloat( android[ 1 ] )); + ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) )); + + return ret; + })( navigator.userAgent ), + + /** + * 实现类与类之间的继承。 + * @method inherits + * @grammar Base.inherits( super ) => child + * @grammar Base.inherits( super, protos ) => child + * @grammar Base.inherits( super, protos, statics ) => child + * @param {Class} super 父类 + * @param {Object | Function} [protos] 子类或者对象。如果对象中包含constructor,子类将是用此属性值。 + * @param {Function} [protos.constructor] 子类构造器,不指定的话将创建个临时的直接执行父类构造器的方法。 + * @param {Object} [statics] 静态属性或方法。 + * @return {Class} 返回子类。 + * @example + * function Person() { + * console.log( 'Super' ); + * } + * Person.prototype.hello = function() { + * console.log( 'hello' ); + * }; + * + * var Manager = Base.inherits( Person, { + * world: function() { + * console.log( 'World' ); + * } + * }); + * + * // 因为没有指定构造器,父类的构造器将会执行。 + * var instance = new Manager(); // => Super + * + * // 继承子父类的方法 + * instance.hello(); // => hello + * instance.world(); // => World + * + * // 子类的__super__属性指向父类 + * console.log( Manager.__super__ === Person ); // => true + */ + inherits: function( Super, protos, staticProtos ) { + var child; + + if ( typeof protos === 'function' ) { + child = protos; + protos = null; + } else if ( protos && protos.hasOwnProperty('constructor') ) { + child = protos.constructor; + } else { + child = function() { + return Super.apply( this, arguments ); + }; + } + + // 复制静态方法 + $.extend( true, child, Super, staticProtos || {} ); + + /* jshint camelcase: false */ + + // 让子类的__super__属性指向父类。 + child.__super__ = Super.prototype; + + // 构建原型,添加原型方法或属性。 + // 暂时用Object.create实现。 + child.prototype = createObject( Super.prototype ); + protos && $.extend( true, child.prototype, protos ); + + return child; + }, + + /** + * 一个不做任何事情的方法。可以用来赋值给默认的callback. + * @method noop + */ + noop: noop, + + /** + * 返回一个新的方法,此方法将已指定的`context`来执行。 + * @grammar Base.bindFn( fn, context ) => Function + * @method bindFn + * @example + * var doSomething = function() { + * console.log( this.name ); + * }, + * obj = { + * name: 'Object Name' + * }, + * aliasFn = Base.bind( doSomething, obj ); + * + * aliasFn(); // => Object Name + * + */ + bindFn: bindFn, + + /** + * 引用Console.log如果存在的话,否则引用一个[空函数noop](#WebUploader:Base.noop)。 + * @grammar Base.log( args... ) => undefined + * @method log + */ + log: (function() { + if ( window.console ) { + return bindFn( console.log, console ); + } + return noop; + })(), + + nextTick: (function() { + + return function( cb ) { + setTimeout( cb, 1 ); + }; + + // @bug 当浏览器不在当前窗口时就停了。 + // var next = window.requestAnimationFrame || + // window.webkitRequestAnimationFrame || + // window.mozRequestAnimationFrame || + // function( cb ) { + // window.setTimeout( cb, 1000 / 60 ); + // }; + + // // fix: Uncaught TypeError: Illegal invocation + // return bindFn( next, window ); + })(), + + /** + * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。 + * 将用来将非数组对象转化成数组对象。 + * @grammar Base.slice( target, start[, end] ) => Array + * @method slice + * @example + * function doSomthing() { + * var args = Base.slice( arguments, 1 ); + * console.log( args ); + * } + * + * doSomthing( 'ignored', 'arg2', 'arg3' ); // => Array ["arg2", "arg3"] + */ + slice: uncurryThis( [].slice ), + + /** + * 生成唯一的ID + * @method guid + * @grammar Base.guid() => String + * @grammar Base.guid( prefx ) => String + */ + guid: (function() { + var counter = 0; + + return function( prefix ) { + var guid = (+new Date()).toString( 32 ), + i = 0; + + for ( ; i < 5; i++ ) { + guid += Math.floor( Math.random() * 65535 ).toString( 32 ); + } + + return (prefix || 'wu_') + guid + (counter++).toString( 32 ); + }; + })(), + + /** + * 格式化文件大小, 输出成带单位的字符串 + * @method formatSize + * @grammar Base.formatSize( size ) => String + * @grammar Base.formatSize( size, pointLength ) => String + * @grammar Base.formatSize( size, pointLength, units ) => String + * @param {Number} size 文件大小 + * @param {Number} [pointLength=2] 精确到的小数点数。 + * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节,到千字节,一直往上指定。如果单位数组里面只指定了到了K(千字节),同时文件大小大于M, 此方法的输出将还是显示成多少K. + * @example + * console.log( Base.formatSize( 100 ) ); // => 100B + * console.log( Base.formatSize( 1024 ) ); // => 1.00K + * console.log( Base.formatSize( 1024, 0 ) ); // => 1K + * console.log( Base.formatSize( 1024 * 1024 ) ); // => 1.00M + * console.log( Base.formatSize( 1024 * 1024 * 1024 ) ); // => 1.00G + * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) ); // => 1024MB + */ + formatSize: function( size, pointLength, units ) { + var unit; + + units = units || [ 'B', 'K', 'M', 'G', 'TB' ]; + + while ( (unit = units.shift()) && size > 1024 ) { + size = size / 1024; + } + + return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) + + unit; + } + }; + }); + /** + * 事件处理类,可以独立使用,也可以扩展给对象使用。 + * @fileOverview Mediator + */ + define('mediator',[ + 'base' + ], function( Base ) { + var $ = Base.$, + slice = [].slice, + separator = /\s+/, + protos; + + // 根据条件过滤出事件handlers. + function findHandlers( arr, name, callback, context ) { + return $.grep( arr, function( handler ) { + return handler && + (!name || handler.e === name) && + (!callback || handler.cb === callback || + handler.cb._cb === callback) && + (!context || handler.ctx === context); + }); + } + + function eachEvent( events, callback, iterator ) { + // 不支持对象,只支持多个event用空格隔开 + $.each( (events || '').split( separator ), function( _, key ) { + iterator( key, callback ); + }); + } + + function triggerHanders( events, args ) { + var stoped = false, + i = -1, + len = events.length, + handler; + + while ( ++i < len ) { + handler = events[ i ]; + + if ( handler.cb.apply( handler.ctx2, args ) === false ) { + stoped = true; + break; + } + } + + return !stoped; + } + + protos = { + + /** + * 绑定事件。 + * + * `callback`方法在执行时,arguments将会来源于trigger的时候携带的参数。如 + * ```javascript + * var obj = {}; + * + * // 使得obj有事件行为 + * Mediator.installTo( obj ); + * + * obj.on( 'testa', function( arg1, arg2 ) { + * console.log( arg1, arg2 ); // => 'arg1', 'arg2' + * }); + * + * obj.trigger( 'testa', 'arg1', 'arg2' ); + * ``` + * + * 如果`callback`中,某一个方法`return false`了,则后续的其他`callback`都不会被执行到。 + * 切会影响到`trigger`方法的返回值,为`false`。 + * + * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处, + * 就是第一个参数为`type`,记录当前是什么事件在触发。此类`callback`的优先级比脚低,会再正常`callback`执行完后触发。 + * ```javascript + * obj.on( 'all', function( type, arg1, arg2 ) { + * console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2' + * }); + * ``` + * + * @method on + * @grammar on( name, callback[, context] ) => self + * @param {String} name 事件名,支持多个事件用空格隔开 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + * @class Mediator + */ + on: function( name, callback, context ) { + var me = this, + set; + + if ( !callback ) { + return this; + } + + set = this._events || (this._events = []); + + eachEvent( name, callback, function( name, callback ) { + var handler = { e: name }; + + handler.cb = callback; + handler.ctx = context; + handler.ctx2 = context || me; + handler.id = set.length; + + set.push( handler ); + }); + + return this; + }, + + /** + * 绑定事件,且当handler执行完后,自动解除绑定。 + * @method once + * @grammar once( name, callback[, context] ) => self + * @param {String} name 事件名 + * @param {Function} callback 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + once: function( name, callback, context ) { + var me = this; + + if ( !callback ) { + return me; + } + + eachEvent( name, callback, function( name, callback ) { + var once = function() { + me.off( name, once ); + return callback.apply( context || me, arguments ); + }; + + once._cb = callback; + me.on( name, once, context ); + }); + + return me; + }, + + /** + * 解除事件绑定 + * @method off + * @grammar off( [name[, callback[, context] ] ] ) => self + * @param {String} [name] 事件名 + * @param {Function} [callback] 事件处理器 + * @param {Object} [context] 事件处理器的上下文。 + * @return {self} 返回自身,方便链式 + * @chainable + */ + off: function( name, cb, ctx ) { + var events = this._events; + + if ( !events ) { + return this; + } + + if ( !name && !cb && !ctx ) { + this._events = []; + return this; + } + + eachEvent( name, cb, function( name, cb ) { + $.each( findHandlers( events, name, cb, ctx ), function() { + delete events[ this.id ]; + }); + }); + + return this; + }, + + /** + * 触发事件 + * @method trigger + * @grammar trigger( name[, args...] ) => self + * @param {String} type 事件名 + * @param {*} [...] 任意参数 + * @return {Boolean} 如果handler中return false了,则返回false, 否则返回true + */ + trigger: function( type ) { + var args, events, allEvents; + + if ( !this._events || !type ) { + return this; + } + + args = slice.call( arguments, 1 ); + events = findHandlers( this._events, type ); + allEvents = findHandlers( this._events, 'all' ); + + return triggerHanders( events, args ) && + triggerHanders( allEvents, arguments ); + } + }; + + /** + * 中介者,它本身是个单例,但可以通过[installTo](#WebUploader:Mediator:installTo)方法,使任何对象具备事件行为。 + * 主要目的是负责模块与模块之间的合作,降低耦合度。 + * + * @class Mediator + */ + return $.extend({ + + /** + * 可以通过这个接口,使任何对象具备事件功能。 + * @method installTo + * @param {Object} obj 需要具备事件行为的对象。 + * @return {Object} 返回obj. + */ + installTo: function( obj ) { + return $.extend( obj, protos ); + } + + }, protos ); + }); + /** + * @fileOverview Uploader上传类 + */ + define('uploader',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$; + + /** + * 上传入口类。 + * @class Uploader + * @constructor + * @grammar new Uploader( opts ) => Uploader + * @example + * var uploader = WebUploader.Uploader({ + * swf: 'path_of_swf/Uploader.swf', + * + * // 开起分片上传。 + * chunked: true + * }); + */ + function Uploader( opts ) { + this.options = $.extend( true, {}, Uploader.options, opts ); + this._init( this.options ); + } + + // default Options + // widgets中有相应扩展 + Uploader.options = {}; + Mediator.installTo( Uploader.prototype ); + + // 批量添加纯命令式方法。 + $.each({ + upload: 'start-upload', + stop: 'stop-upload', + getFile: 'get-file', + getFiles: 'get-files', + addFile: 'add-file', + addFiles: 'add-file', + sort: 'sort-files', + removeFile: 'remove-file', + cancelFile: 'cancel-file', + skipFile: 'skip-file', + retry: 'retry', + isInProgress: 'is-in-progress', + makeThumb: 'make-thumb', + md5File: 'md5-file', + getDimension: 'get-dimension', + addButton: 'add-btn', + predictRuntimeType: 'predict-runtime-type', + refresh: 'refresh', + disable: 'disable', + enable: 'enable', + reset: 'reset' + }, function( fn, command ) { + Uploader.prototype[ fn ] = function() { + return this.request( command, arguments ); + }; + }); + + $.extend( Uploader.prototype, { + state: 'pending', + + _init: function( opts ) { + var me = this; + + me.request( 'init', opts, function() { + me.state = 'ready'; + me.trigger('ready'); + }); + }, + + /** + * 获取或者设置Uploader配置项。 + * @method option + * @grammar option( key ) => * + * @grammar option( key, val ) => self + * @example + * + * // 初始状态图片上传前不会压缩 + * var uploader = new WebUploader.Uploader({ + * compress: null; + * }); + * + * // 修改后图片上传前,尝试将图片压缩到1600 * 1600 + * uploader.option( 'compress', { + * width: 1600, + * height: 1600 + * }); + */ + option: function( key, val ) { + var opts = this.options; + + // setter + if ( arguments.length > 1 ) { + + if ( $.isPlainObject( val ) && + $.isPlainObject( opts[ key ] ) ) { + $.extend( opts[ key ], val ); + } else { + opts[ key ] = val; + } + + } else { // getter + return key ? opts[ key ] : opts; + } + }, + + /** + * 获取文件统计信息。返回一个包含一下信息的对象。 + * * `successNum` 上传成功的文件数 + * * `progressNum` 上传中的文件数 + * * `cancelNum` 被删除的文件数 + * * `invalidNum` 无效的文件数 + * * `uploadFailNum` 上传失败的文件数 + * * `queueNum` 还在队列中的文件数 + * * `interruptNum` 被暂停的文件数 + * @method getStats + * @grammar getStats() => Object + */ + getStats: function() { + // return this._mgr.getStats.apply( this._mgr, arguments ); + var stats = this.request('get-stats'); + + return stats ? { + successNum: stats.numOfSuccess, + progressNum: stats.numOfProgress, + + // who care? + // queueFailNum: 0, + cancelNum: stats.numOfCancel, + invalidNum: stats.numOfInvalid, + uploadFailNum: stats.numOfUploadFailed, + queueNum: stats.numOfQueue, + interruptNum: stats.numofInterrupt + } : {}; + }, + + // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器 + trigger: function( type/*, args...*/ ) { + var args = [].slice.call( arguments, 1 ), + opts = this.options, + name = 'on' + type.substring( 0, 1 ).toUpperCase() + + type.substring( 1 ); + + if ( + // 调用通过on方法注册的handler. + Mediator.trigger.apply( this, arguments ) === false || + + // 调用opts.onEvent + $.isFunction( opts[ name ] ) && + opts[ name ].apply( this, args ) === false || + + // 调用this.onEvent + $.isFunction( this[ name ] ) && + this[ name ].apply( this, args ) === false || + + // 广播所有uploader的事件。 + Mediator.trigger.apply( Mediator, + [ this, type ].concat( args ) ) === false ) { + + return false; + } + + return true; + }, + + /** + * 销毁 webuploader 实例 + * @method destroy + * @grammar destroy() => undefined + */ + destroy: function() { + this.request( 'destroy', arguments ); + this.off(); + }, + + // widgets/widget.js将补充此方法的详细文档。 + request: Base.noop + }); + + /** + * 创建Uploader实例,等同于new Uploader( opts ); + * @method create + * @class Base + * @static + * @grammar Base.create( opts ) => Uploader + */ + Base.create = Uploader.create = function( opts ) { + return new Uploader( opts ); + }; + + // 暴露Uploader,可以通过它来扩展业务逻辑。 + Base.Uploader = Uploader; + + return Uploader; + }); + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/runtime',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + factories = {}, + + // 获取对象的第一个key + getFirstKey = function( obj ) { + for ( var key in obj ) { + if ( obj.hasOwnProperty( key ) ) { + return key; + } + } + return null; + }; + + // 接口类。 + function Runtime( options ) { + this.options = $.extend({ + container: document.body + }, options ); + this.uid = Base.guid('rt_'); + } + + $.extend( Runtime.prototype, { + + getContainer: function() { + var opts = this.options, + parent, container; + + if ( this._container ) { + return this._container; + } + + parent = $( opts.container || document.body ); + container = $( document.createElement('div') ); + + container.attr( 'id', 'rt_' + this.uid ); + container.css({ + position: 'absolute', + top: '0px', + left: '0px', + width: '1px', + height: '1px', + overflow: 'hidden' + }); + + parent.append( container ); + parent.addClass('webuploader-container'); + this._container = container; + this._parent = parent; + return container; + }, + + init: Base.noop, + exec: Base.noop, + + destroy: function() { + this._container && this._container.remove(); + this._parent && this._parent.removeClass('webuploader-container'); + this.off(); + } + }); + + Runtime.orders = 'html5,flash'; + + + /** + * 添加Runtime实现。 + * @param {String} type 类型 + * @param {Runtime} factory 具体Runtime实现。 + */ + Runtime.addRuntime = function( type, factory ) { + factories[ type ] = factory; + }; + + Runtime.hasRuntime = function( type ) { + return !!(type ? factories[ type ] : getFirstKey( factories )); + }; + + Runtime.create = function( opts, orders ) { + var type, runtime; + + orders = orders || Runtime.orders; + $.each( orders.split( /\s*,\s*/g ), function() { + if ( factories[ this ] ) { + type = this; + return false; + } + }); + + type = type || getFirstKey( factories ); + + if ( !type ) { + throw new Error('Runtime Error'); + } + + runtime = new factories[ type ]( opts ); + return runtime; + }; + + Mediator.installTo( Runtime.prototype ); + return Runtime; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/client',[ + 'base', + 'mediator', + 'runtime/runtime' + ], function( Base, Mediator, Runtime ) { + + var cache; + + cache = (function() { + var obj = {}; + + return { + add: function( runtime ) { + obj[ runtime.uid ] = runtime; + }, + + get: function( ruid, standalone ) { + var i; + + if ( ruid ) { + return obj[ ruid ]; + } + + for ( i in obj ) { + // 有些类型不能重用,比如filepicker. + if ( standalone && obj[ i ].__standalone ) { + continue; + } + + return obj[ i ]; + } + + return null; + }, + + remove: function( runtime ) { + delete obj[ runtime.uid ]; + } + }; + })(); + + function RuntimeClient( component, standalone ) { + var deferred = Base.Deferred(), + runtime; + + this.uid = Base.guid('client_'); + + // 允许runtime没有初始化之前,注册一些方法在初始化后执行。 + this.runtimeReady = function( cb ) { + return deferred.done( cb ); + }; + + this.connectRuntime = function( opts, cb ) { + + // already connected. + if ( runtime ) { + throw new Error('already connected!'); + } + + deferred.done( cb ); + + if ( typeof opts === 'string' && cache.get( opts ) ) { + runtime = cache.get( opts ); + } + + // 像filePicker只能独立存在,不能公用。 + runtime = runtime || cache.get( null, standalone ); + + // 需要创建 + if ( !runtime ) { + runtime = Runtime.create( opts, opts.runtimeOrder ); + runtime.__promise = deferred.promise(); + runtime.once( 'ready', deferred.resolve ); + runtime.init(); + cache.add( runtime ); + runtime.__client = 1; + } else { + // 来自cache + Base.$.extend( runtime.options, opts ); + runtime.__promise.then( deferred.resolve ); + runtime.__client++; + } + + standalone && (runtime.__standalone = standalone); + return runtime; + }; + + this.getRuntime = function() { + return runtime; + }; + + this.disconnectRuntime = function() { + if ( !runtime ) { + return; + } + + runtime.__client--; + + if ( runtime.__client <= 0 ) { + cache.remove( runtime ); + delete runtime.__promise; + runtime.destroy(); + } + + runtime = null; + }; + + this.exec = function() { + if ( !runtime ) { + return; + } + + var args = Base.slice( arguments ); + component && args.unshift( component ); + + return runtime.exec.apply( this, args ); + }; + + this.getRuid = function() { + return runtime && runtime.uid; + }; + + this.destroy = (function( destroy ) { + return function() { + destroy && destroy.apply( this, arguments ); + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }; + })( this.destroy ); + } + + Mediator.installTo( RuntimeClient.prototype ); + return RuntimeClient; + }); + /** + * @fileOverview 错误信息 + */ + define('lib/dnd',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function DragAndDrop( opts ) { + opts = this.options = $.extend({}, DragAndDrop.options, opts ); + + opts.container = $( opts.container ); + + if ( !opts.container.length ) { + return; + } + + RuntimeClent.call( this, 'DragAndDrop' ); + } + + DragAndDrop.options = { + accept: null, + disableGlobalDnd: false + }; + + Base.inherits( RuntimeClent, { + constructor: DragAndDrop, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( DragAndDrop.prototype ); + + return DragAndDrop; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/widget',[ + 'base', + 'uploader' + ], function( Base, Uploader ) { + + var $ = Base.$, + _init = Uploader.prototype._init, + _destroy = Uploader.prototype.destroy, + IGNORE = {}, + widgetClass = []; + + function isArrayLike( obj ) { + if ( !obj ) { + return false; + } + + var length = obj.length, + type = $.type( obj ); + + if ( obj.nodeType === 1 && length ) { + return true; + } + + return type === 'array' || type !== 'function' && type !== 'string' && + (length === 0 || typeof length === 'number' && length > 0 && + (length - 1) in obj); + } + + function Widget( uploader ) { + this.owner = uploader; + this.options = uploader.options; + } + + $.extend( Widget.prototype, { + + init: Base.noop, + + // 类Backbone的事件监听声明,监听uploader实例上的事件 + // widget直接无法监听事件,事件只能通过uploader来传递 + invoke: function( apiName, args ) { + + /* + { + 'make-thumb': 'makeThumb' + } + */ + var map = this.responseMap; + + // 如果无API响应声明则忽略 + if ( !map || !(apiName in map) || !(map[ apiName ] in this) || + !$.isFunction( this[ map[ apiName ] ] ) ) { + + return IGNORE; + } + + return this[ map[ apiName ] ].apply( this, args ); + + }, + + /** + * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。 + * @method request + * @grammar request( command, args ) => * | Promise + * @grammar request( command, args, callback ) => Promise + * @for Uploader + */ + request: function() { + return this.owner.request.apply( this.owner, arguments ); + } + }); + + // 扩展Uploader. + $.extend( Uploader.prototype, { + + /** + * @property {String | Array} [disableWidgets=undefined] + * @namespace options + * @for Uploader + * @description 默认所有 Uploader.register 了的 widget 都会被加载,如果禁用某一部分,请通过此 option 指定黑名单。 + */ + + // 覆写_init用来初始化widgets + _init: function() { + var me = this, + widgets = me._widgets = [], + deactives = me.options.disableWidgets || ''; + + $.each( widgetClass, function( _, klass ) { + (!deactives || !~deactives.indexOf( klass._name )) && + widgets.push( new klass( me ) ); + }); + + return _init.apply( me, arguments ); + }, + + request: function( apiName, args, callback ) { + var i = 0, + widgets = this._widgets, + len = widgets && widgets.length, + rlts = [], + dfds = [], + widget, rlt, promise, key; + + args = isArrayLike( args ) ? args : [ args ]; + + for ( ; i < len; i++ ) { + widget = widgets[ i ]; + rlt = widget.invoke( apiName, args ); + + if ( rlt !== IGNORE ) { + + // Deferred对象 + if ( Base.isPromise( rlt ) ) { + dfds.push( rlt ); + } else { + rlts.push( rlt ); + } + } + } + + // 如果有callback,则用异步方式。 + if ( callback || dfds.length ) { + promise = Base.when.apply( Base, dfds ); + key = promise.pipe ? 'pipe' : 'then'; + + // 很重要不能删除。删除了会死循环。 + // 保证执行顺序。让callback总是在下一个 tick 中执行。 + return promise[ key ](function() { + var deferred = Base.Deferred(), + args = arguments; + + if ( args.length === 1 ) { + args = args[ 0 ]; + } + + setTimeout(function() { + deferred.resolve( args ); + }, 1 ); + + return deferred.promise(); + })[ callback ? key : 'done' ]( callback || Base.noop ); + } else { + return rlts[ 0 ]; + } + }, + + destroy: function() { + _destroy.apply( this, arguments ); + this._widgets = null; + } + }); + + /** + * 添加组件 + * @grammar Uploader.register(proto); + * @grammar Uploader.register(map, proto); + * @param {object} responseMap API 名称与函数实现的映射 + * @param {object} proto 组件原型,构造函数通过 constructor 属性定义 + * @method Uploader.register + * @for Uploader + * @example + * Uploader.register({ + * 'make-thumb': 'makeThumb' + * }, { + * init: function( options ) {}, + * makeThumb: function() {} + * }); + * + * Uploader.register({ + * 'make-thumb': function() { + * + * } + * }); + */ + Uploader.register = Widget.register = function( responseMap, widgetProto ) { + var map = { init: 'init', destroy: 'destroy', name: 'anonymous' }, + klass; + + if ( arguments.length === 1 ) { + widgetProto = responseMap; + + // 自动生成 map 表。 + $.each(widgetProto, function(key) { + if ( key[0] === '_' || key === 'name' ) { + key === 'name' && (map.name = widgetProto.name); + return; + } + + map[key.replace(/[A-Z]/g, '-$&').toLowerCase()] = key; + }); + + } else { + map = $.extend( map, responseMap ); + } + + widgetProto.responseMap = map; + klass = Base.inherits( Widget, widgetProto ); + klass._name = map.name; + widgetClass.push( klass ); + + return klass; + }; + + /** + * 删除插件,只有在注册时指定了名字的才能被删除。 + * @grammar Uploader.unRegister(name); + * @param {string} name 组件名字 + * @method Uploader.unRegister + * @for Uploader + * @example + * + * Uploader.register({ + * name: 'custom', + * + * 'make-thumb': function() { + * + * } + * }); + * + * Uploader.unRegister('custom'); + */ + Uploader.unRegister = Widget.unRegister = function( name ) { + if ( !name || name === 'anonymous' ) { + return; + } + + // 删除指定的插件。 + for ( var i = widgetClass.length; i--; ) { + if ( widgetClass[i]._name === name ) { + widgetClass.splice(i, 1) + } + } + }; + + return Widget; + }); + /** + * @fileOverview DragAndDrop Widget。 + */ + define('widgets/filednd',[ + 'base', + 'uploader', + 'lib/dnd', + 'widgets/widget' + ], function( Base, Uploader, Dnd ) { + var $ = Base.$; + + Uploader.options.dnd = ''; + + /** + * @property {Selector} [dnd=undefined] 指定Drag And Drop拖拽的容器,如果不指定,则不启动。 + * @namespace options + * @for Uploader + */ + + /** + * @property {Selector} [disableGlobalDnd=false] 是否禁掉整个页面的拖拽功能,如果不禁用,图片拖进来的时候会默认被浏览器打开。 + * @namespace options + * @for Uploader + */ + + /** + * @event dndAccept + * @param {DataTransferItemList} items DataTransferItem + * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API,且只能通过 mime-type 验证。 + * @for Uploader + */ + return Uploader.register({ + name: 'dnd', + + init: function( opts ) { + + if ( !opts.dnd || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + disableGlobalDnd: opts.disableGlobalDnd, + container: opts.dnd, + accept: opts.accept + }), + dnd; + + this.dnd = dnd = new Dnd( options ); + + dnd.once( 'ready', deferred.resolve ); + dnd.on( 'drop', function( files ) { + me.request( 'add-file', [ files ]); + }); + + // 检测文件是否全部允许添加。 + dnd.on( 'accept', function( items ) { + return me.owner.trigger( 'dndAccept', items ); + }); + + dnd.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.dnd && this.dnd.destroy(); + } + }); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepaste',[ + 'base', + 'mediator', + 'runtime/client' + ], function( Base, Mediator, RuntimeClent ) { + + var $ = Base.$; + + function FilePaste( opts ) { + opts = this.options = $.extend({}, opts ); + opts.container = $( opts.container || document.body ); + RuntimeClent.call( this, 'FilePaste' ); + } + + Base.inherits( RuntimeClent, { + constructor: FilePaste, + + init: function() { + var me = this; + + me.connectRuntime( me.options, function() { + me.exec('init'); + me.trigger('ready'); + }); + } + }); + + Mediator.installTo( FilePaste.prototype ); + + return FilePaste; + }); + /** + * @fileOverview 组件基类。 + */ + define('widgets/filepaste',[ + 'base', + 'uploader', + 'lib/filepaste', + 'widgets/widget' + ], function( Base, Uploader, FilePaste ) { + var $ = Base.$; + + /** + * @property {Selector} [paste=undefined] 指定监听paste事件的容器,如果不指定,不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`. + * @namespace options + * @for Uploader + */ + return Uploader.register({ + name: 'paste', + + init: function( opts ) { + + if ( !opts.paste || + this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + var me = this, + deferred = Base.Deferred(), + options = $.extend({}, { + container: opts.paste, + accept: opts.accept + }), + paste; + + this.paste = paste = new FilePaste( options ); + + paste.once( 'ready', deferred.resolve ); + paste.on( 'paste', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + paste.init(); + + return deferred.promise(); + }, + + destroy: function() { + this.paste && this.paste.destroy(); + } + }); + }); + /** + * @fileOverview Blob + */ + define('lib/blob',[ + 'base', + 'runtime/client' + ], function( Base, RuntimeClient ) { + + function Blob( ruid, source ) { + var me = this; + + me.source = source; + me.ruid = ruid; + this.size = source.size || 0; + + // 如果没有指定 mimetype, 但是知道文件后缀。 + if ( !source.type && this.ext && + ~'jpg,jpeg,png,gif,bmp'.indexOf( this.ext ) ) { + this.type = 'image/' + (this.ext === 'jpg' ? 'jpeg' : this.ext); + } else { + this.type = source.type || 'application/octet-stream'; + } + + RuntimeClient.call( me, 'Blob' ); + this.uid = source.uid || this.uid; + + if ( ruid ) { + me.connectRuntime( ruid ); + } + } + + Base.inherits( RuntimeClient, { + constructor: Blob, + + slice: function( start, end ) { + return this.exec( 'slice', start, end ); + }, + + getSource: function() { + return this.source; + } + }); + + return Blob; + }); + /** + * 为了统一化Flash的File和HTML5的File而存在。 + * 以至于要调用Flash里面的File,也可以像调用HTML5版本的File一下。 + * @fileOverview File + */ + define('lib/file',[ + 'base', + 'lib/blob' + ], function( Base, Blob ) { + + var uid = 1, + rExt = /\.([^.]+)$/; + + function File( ruid, file ) { + var ext; + + this.name = file.name || ('untitled' + uid++); + ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : ''; + + // todo 支持其他类型文件的转换。 + // 如果有 mimetype, 但是文件名里面没有找出后缀规律 + if ( !ext && file.type ) { + ext = /\/(jpg|jpeg|png|gif|bmp)$/i.exec( file.type ) ? + RegExp.$1.toLowerCase() : ''; + this.name += '.' + ext; + } + + this.ext = ext; + this.lastModifiedDate = file.lastModifiedDate || + (new Date()).toLocaleString(); + + Blob.apply( this, arguments ); + } + + return Base.inherits( Blob, File ); + }); + + /** + * @fileOverview 错误信息 + */ + define('lib/filepicker',[ + 'base', + 'runtime/client', + 'lib/file' + ], function( Base, RuntimeClent, File ) { + + var $ = Base.$; + + function FilePicker( opts ) { + opts = this.options = $.extend({}, FilePicker.options, opts ); + + opts.container = $( opts.id ); + + if ( !opts.container.length ) { + throw new Error('按钮指定错误'); + } + + opts.innerHTML = opts.innerHTML || opts.label || + opts.container.html() || ''; + + opts.button = $( opts.button || document.createElement('div') ); + opts.button.html( opts.innerHTML ); + opts.container.html( opts.button ); + + RuntimeClent.call( this, 'FilePicker', true ); + } + + FilePicker.options = { + button: null, + container: null, + label: null, + innerHTML: null, + multiple: true, + accept: null, + name: 'file' + }; + + Base.inherits( RuntimeClent, { + constructor: FilePicker, + + init: function() { + var me = this, + opts = me.options, + button = opts.button; + + button.addClass('webuploader-pick'); + + me.on( 'all', function( type ) { + var files; + + switch ( type ) { + case 'mouseenter': + button.addClass('webuploader-pick-hover'); + break; + + case 'mouseleave': + button.removeClass('webuploader-pick-hover'); + break; + + case 'change': + files = me.exec('getFiles'); + me.trigger( 'select', $.map( files, function( file ) { + file = new File( me.getRuid(), file ); + + // 记录来源。 + file._refer = opts.container; + return file; + }), opts.container ); + break; + } + }); + + me.connectRuntime( opts, function() { + me.refresh(); + me.exec( 'init', opts ); + me.trigger('ready'); + }); + + this._resizeHandler = Base.bindFn( this.refresh, this ); + $( window ).on( 'resize', this._resizeHandler ); + }, + + refresh: function() { + var shimContainer = this.getRuntime().getContainer(), + button = this.options.button, + width = button.outerWidth ? + button.outerWidth() : button.width(), + + height = button.outerHeight ? + button.outerHeight() : button.height(), + + pos = button.offset(); + + width && height && shimContainer.css({ + bottom: 'auto', + right: 'auto', + width: width + 'px', + height: height + 'px' + }).offset( pos ); + }, + + enable: function() { + var btn = this.options.button; + + btn.removeClass('webuploader-pick-disable'); + this.refresh(); + }, + + disable: function() { + var btn = this.options.button; + + this.getRuntime().getContainer().css({ + top: '-99999px' + }); + + btn.addClass('webuploader-pick-disable'); + }, + + destroy: function() { + var btn = this.options.button; + $( window ).off( 'resize', this._resizeHandler ); + btn.removeClass('webuploader-pick-disable webuploader-pick-hover ' + + 'webuploader-pick'); + } + }); + + return FilePicker; + }); + + /** + * @fileOverview 文件选择相关 + */ + define('widgets/filepicker',[ + 'base', + 'uploader', + 'lib/filepicker', + 'widgets/widget' + ], function( Base, Uploader, FilePicker ) { + var $ = Base.$; + + $.extend( Uploader.options, { + + /** + * @property {Selector | Object} [pick=undefined] + * @namespace options + * @for Uploader + * @description 指定选择文件的按钮容器,不指定则不创建按钮。 + * + * * `id` {Seletor} 指定选择文件的按钮容器,不指定则不创建按钮。 + * * `label` {String} 请采用 `innerHTML` 代替 + * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。 + * * `multiple` {Boolean} 是否开起同时选择多个文件能力。 + */ + pick: null, + + /** + * @property {Arroy} [accept=null] + * @namespace options + * @for Uploader + * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表,所以这里需要分开指定。 + * + * * `title` {String} 文字描述 + * * `extensions` {String} 允许的文件后缀,不带点,多个用逗号分割。 + * * `mimeTypes` {String} 多个用逗号分割。 + * + * 如: + * + * ``` + * { + * title: 'Images', + * extensions: 'gif,jpg,jpeg,bmp,png', + * mimeTypes: 'image/*' + * } + * ``` + */ + accept: null/*{ + title: 'Images', + extensions: 'gif,jpg,jpeg,bmp,png', + mimeTypes: 'image/*' + }*/ + }); + + return Uploader.register({ + name: 'picker', + + init: function( opts ) { + this.pickers = []; + return opts.pick && this.addBtn( opts.pick ); + }, + + refresh: function() { + $.each( this.pickers, function() { + this.refresh(); + }); + }, + + /** + * @method addButton + * @for Uploader + * @grammar addButton( pick ) => Promise + * @description + * 添加文件选择按钮,如果一个按钮不够,需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。 + * @example + * uploader.addButton({ + * id: '#btnContainer', + * innerHTML: '选择文件' + * }); + */ + addBtn: function( pick ) { + var me = this, + opts = me.options, + accept = opts.accept, + promises = []; + + if ( !pick ) { + return; + } + + $.isPlainObject( pick ) || (pick = { + id: pick + }); + + $( pick.id ).each(function() { + var options, picker, deferred; + + deferred = Base.Deferred(); + + options = $.extend({}, pick, { + accept: $.isPlainObject( accept ) ? [ accept ] : accept, + swf: opts.swf, + runtimeOrder: opts.runtimeOrder, + id: this + }); + + picker = new FilePicker( options ); + + picker.once( 'ready', deferred.resolve ); + picker.on( 'select', function( files ) { + me.owner.request( 'add-file', [ files ]); + }); + picker.init(); + + me.pickers.push( picker ); + + promises.push( deferred.promise() ); + }); + + return Base.when.apply( Base, promises ); + }, + + disable: function() { + $.each( this.pickers, function() { + this.disable(); + }); + }, + + enable: function() { + $.each( this.pickers, function() { + this.enable(); + }); + }, + + destroy: function() { + $.each( this.pickers, function() { + this.destroy(); + }); + this.pickers = null; + } + }); + }); + /** + * @fileOverview 文件属性封装 + */ + define('file',[ + 'base', + 'mediator' + ], function( Base, Mediator ) { + + var $ = Base.$, + idPrefix = 'WU_FILE_', + idSuffix = 0, + rExt = /\.([^.]+)$/, + statusMap = {}; + + function gid() { + return idPrefix + idSuffix++; + } + + /** + * 文件类 + * @class File + * @constructor 构造函数 + * @grammar new File( source ) => File + * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。 + */ + function WUFile( source ) { + + /** + * 文件名,包括扩展名(后缀) + * @property name + * @type {string} + */ + this.name = source.name || 'Untitled'; + + /** + * 文件体积(字节) + * @property size + * @type {uint} + * @default 0 + */ + this.size = source.size || 0; + + /** + * 文件MIMETYPE类型,与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny) + * @property type + * @type {string} + * @default 'application/octet-stream' + */ + this.type = source.type || 'application/octet-stream'; + + /** + * 文件最后修改日期 + * @property lastModifiedDate + * @type {int} + * @default 当前时间戳 + */ + this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1); + + /** + * 文件ID,每个对象具有唯一ID,与文件名无关 + * @property id + * @type {string} + */ + this.id = gid(); + + /** + * 文件扩展名,通过文件名获取,例如test.png的扩展名为png + * @property ext + * @type {string} + */ + this.ext = rExt.exec( this.name ) ? RegExp.$1 : ''; + + + /** + * 状态文字说明。在不同的status语境下有不同的用途。 + * @property statusText + * @type {string} + */ + this.statusText = ''; + + // 存储文件状态,防止通过属性直接修改 + statusMap[ this.id ] = WUFile.Status.INITED; + + this.source = source; + this.loaded = 0; + + this.on( 'error', function( msg ) { + this.setStatus( WUFile.Status.ERROR, msg ); + }); + } + + $.extend( WUFile.prototype, { + + /** + * 设置状态,状态变化时会触发`change`事件。 + * @method setStatus + * @grammar setStatus( status[, statusText] ); + * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status) + * @param {String} [statusText=''] 状态说明,常在error时使用,用http, abort,server等来标记是由于什么原因导致文件错误。 + */ + setStatus: function( status, text ) { + + var prevStatus = statusMap[ this.id ]; + + typeof text !== 'undefined' && (this.statusText = text); + + if ( status !== prevStatus ) { + statusMap[ this.id ] = status; + /** + * 文件状态变化 + * @event statuschange + */ + this.trigger( 'statuschange', status, prevStatus ); + } + + }, + + /** + * 获取文件状态 + * @return {File.Status} + * @example + 文件状态具体包括以下几种类型: + { + // 初始化 + INITED: 0, + // 已入队列 + QUEUED: 1, + // 正在上传 + PROGRESS: 2, + // 上传出错 + ERROR: 3, + // 上传成功 + COMPLETE: 4, + // 上传取消 + CANCELLED: 5 + } + */ + getStatus: function() { + return statusMap[ this.id ]; + }, + + /** + * 获取文件原始信息。 + * @return {*} + */ + getSource: function() { + return this.source; + }, + + destroy: function() { + this.off(); + delete statusMap[ this.id ]; + } + }); + + Mediator.installTo( WUFile.prototype ); + + /** + * 文件状态值,具体包括以下几种类型: + * * `inited` 初始状态 + * * `queued` 已经进入队列, 等待上传 + * * `progress` 上传中 + * * `complete` 上传完成。 + * * `error` 上传出错,可重试 + * * `interrupt` 上传中断,可续传。 + * * `invalid` 文件不合格,不能重试上传。会自动从队列中移除。 + * * `cancelled` 文件被移除。 + * @property {Object} Status + * @namespace File + * @class File + * @static + */ + WUFile.Status = { + INITED: 'inited', // 初始状态 + QUEUED: 'queued', // 已经进入队列, 等待上传 + PROGRESS: 'progress', // 上传中 + ERROR: 'error', // 上传出错,可重试 + COMPLETE: 'complete', // 上传完成。 + CANCELLED: 'cancelled', // 上传取消。 + INTERRUPT: 'interrupt', // 上传中断,可续传。 + INVALID: 'invalid' // 文件不合格,不能重试上传。 + }; + + return WUFile; + }); + + /** + * @fileOverview 文件队列 + */ + define('queue',[ + 'base', + 'mediator', + 'file' + ], function( Base, Mediator, WUFile ) { + + var $ = Base.$, + STATUS = WUFile.Status; + + /** + * 文件队列, 用来存储各个状态中的文件。 + * @class Queue + * @extends Mediator + */ + function Queue() { + + /** + * 统计文件数。 + * * `numOfQueue` 队列中的文件数。 + * * `numOfSuccess` 上传成功的文件数 + * * `numOfCancel` 被取消的文件数 + * * `numOfProgress` 正在上传中的文件数 + * * `numOfUploadFailed` 上传错误的文件数。 + * * `numOfInvalid` 无效的文件数。 + * * `numofDeleted` 被移除的文件数。 + * @property {Object} stats + */ + this.stats = { + numOfQueue: 0, + numOfSuccess: 0, + numOfCancel: 0, + numOfProgress: 0, + numOfUploadFailed: 0, + numOfInvalid: 0, + numofDeleted: 0, + numofInterrupt: 0, + }; + + // 上传队列,仅包括等待上传的文件 + this._queue = []; + + // 存储所有文件 + this._map = {}; + } + + $.extend( Queue.prototype, { + + /** + * 将新文件加入对队列尾部 + * + * @method append + * @param {File} file 文件对象 + */ + append: function( file ) { + this._queue.push( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 将新文件加入对队列头部 + * + * @method prepend + * @param {File} file 文件对象 + */ + prepend: function( file ) { + this._queue.unshift( file ); + this._fileAdded( file ); + return this; + }, + + /** + * 获取文件对象 + * + * @method getFile + * @param {String} fileId 文件ID + * @return {File} + */ + getFile: function( fileId ) { + if ( typeof fileId !== 'string' ) { + return fileId; + } + return this._map[ fileId ]; + }, + + /** + * 从队列中取出一个指定状态的文件。 + * @grammar fetch( status ) => File + * @method fetch + * @param {String} status [文件状态值](#WebUploader:File:File.Status) + * @return {File} [File](#WebUploader:File) + */ + fetch: function( status ) { + var len = this._queue.length, + i, file; + + status = status || STATUS.QUEUED; + + for ( i = 0; i < len; i++ ) { + file = this._queue[ i ]; + + if ( status === file.getStatus() ) { + return file; + } + } + + return null; + }, + + /** + * 对队列进行排序,能够控制文件上传顺序。 + * @grammar sort( fn ) => undefined + * @method sort + * @param {Function} fn 排序方法 + */ + sort: function( fn ) { + if ( typeof fn === 'function' ) { + this._queue.sort( fn ); + } + }, + + /** + * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。 + * @grammar getFiles( [status1[, status2 ...]] ) => Array + * @method getFiles + * @param {String} [status] [文件状态值](#WebUploader:File:File.Status) + */ + getFiles: function() { + var sts = [].slice.call( arguments, 0 ), + ret = [], + i = 0, + len = this._queue.length, + file; + + for ( ; i < len; i++ ) { + file = this._queue[ i ]; + + if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) { + continue; + } + + ret.push( file ); + } + + return ret; + }, + + /** + * 在队列中删除文件。 + * @grammar removeFile( file ) => Array + * @method removeFile + * @param {File} 文件对象。 + */ + removeFile: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( existing ) { + delete this._map[ file.id ]; + file.destroy(); + this.stats.numofDeleted++; + } + }, + + _fileAdded: function( file ) { + var me = this, + existing = this._map[ file.id ]; + + if ( !existing ) { + this._map[ file.id ] = file; + + file.on( 'statuschange', function( cur, pre ) { + me._onFileStatusChange( cur, pre ); + }); + } + }, + + _onFileStatusChange: function( curStatus, preStatus ) { + var stats = this.stats; + + switch ( preStatus ) { + case STATUS.PROGRESS: + stats.numOfProgress--; + break; + + case STATUS.QUEUED: + stats.numOfQueue --; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed--; + break; + + case STATUS.INVALID: + stats.numOfInvalid--; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt--; + break; + } + + switch ( curStatus ) { + case STATUS.QUEUED: + stats.numOfQueue++; + break; + + case STATUS.PROGRESS: + stats.numOfProgress++; + break; + + case STATUS.ERROR: + stats.numOfUploadFailed++; + break; + + case STATUS.COMPLETE: + stats.numOfSuccess++; + break; + + case STATUS.CANCELLED: + stats.numOfCancel++; + break; + + + case STATUS.INVALID: + stats.numOfInvalid++; + break; + + case STATUS.INTERRUPT: + stats.numofInterrupt++; + break; + } + } + + }); + + Mediator.installTo( Queue.prototype ); + + return Queue; + }); + /** + * @fileOverview 队列 + */ + define('widgets/queue',[ + 'base', + 'uploader', + 'queue', + 'file', + 'lib/file', + 'runtime/client', + 'widgets/widget' + ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) { + + var $ = Base.$, + rExt = /\.\w+$/, + Status = WUFile.Status; + + return Uploader.register({ + name: 'queue', + + init: function( opts ) { + var me = this, + deferred, len, i, item, arr, accept, runtime; + + if ( $.isPlainObject( opts.accept ) ) { + opts.accept = [ opts.accept ]; + } + + // accept中的中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].extensions; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = '\\.' + arr.join(',') + .replace( /,/g, '$|\\.' ) + .replace( /\*/g, '.*' ) + '$'; + } + + me.accept = new RegExp( accept, 'i' ); + } + + me.queue = new Queue(); + me.stats = me.queue.stats; + + // 如果当前不是html5运行时,那就算了。 + // 不执行后续操作 + if ( this.request('predict-runtime-type') !== 'html5' ) { + return; + } + + // 创建一个 html5 运行时的 placeholder + // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。 + deferred = Base.Deferred(); + this.placeholder = runtime = new RuntimeClient('Placeholder'); + runtime.connectRuntime({ + runtimeOrder: 'html5' + }, function() { + me._ruid = runtime.getRuid(); + deferred.resolve(); + }); + return deferred.promise(); + }, + + + // 为了支持外部直接添加一个原生File对象。 + _wrapFile: function( file ) { + if ( !(file instanceof WUFile) ) { + + if ( !(file instanceof File) ) { + if ( !this._ruid ) { + throw new Error('Can\'t add external files.'); + } + file = new File( this._ruid, file ); + } + + file = new WUFile( file ); + } + + return file; + }, + + // 判断文件是否可以被加入队列 + acceptFile: function( file ) { + var invalid = !file || !file.size || this.accept && + + // 如果名字中有后缀,才做后缀白名单处理。 + rExt.exec( file.name ) && !this.accept.test( file.name ); + + return !invalid; + }, + + + /** + * @event beforeFileQueued + * @param {File} file File对象 + * @description 当文件被加入队列之前触发,此事件的handler返回值为`false`,则此文件不会被添加进入队列。 + * @for Uploader + */ + + /** + * @event fileQueued + * @param {File} file File对象 + * @description 当文件被加入队列以后触发。 + * @for Uploader + */ + + _addFile: function( file ) { + var me = this; + + file = me._wrapFile( file ); + + // 不过类型判断允许不允许,先派送 `beforeFileQueued` + if ( !me.owner.trigger( 'beforeFileQueued', file ) ) { + return; + } + + // 类型不匹配,则派送错误事件,并返回。 + if ( !me.acceptFile( file ) ) { + me.owner.trigger( 'error', 'Q_TYPE_DENIED', file ); + return; + } + + me.queue.append( file ); + me.owner.trigger( 'fileQueued', file ); + return file; + }, + + getFile: function( fileId ) { + return this.queue.getFile( fileId ); + }, + + /** + * @event filesQueued + * @param {File} files 数组,内容为原始File(lib/File)对象。 + * @description 当一批文件添加进队列以后触发。 + * @for Uploader + */ + + /** + * @property {Boolean} [auto=false] + * @namespace options + * @for Uploader + * @description 设置为 true 后,不需要手动调用上传,有文件选择即开始上传。 + * + */ + + /** + * @method addFiles + * @grammar addFiles( file ) => undefined + * @grammar addFiles( [file1, file2 ...] ) => undefined + * @param {Array of File or File} [files] Files 对象 数组 + * @description 添加文件到队列 + * @for Uploader + */ + addFile: function( files ) { + var me = this; + + if ( !files.length ) { + files = [ files ]; + } + + files = $.map( files, function( file ) { + return me._addFile( file ); + }); + + me.owner.trigger( 'filesQueued', files ); + + if ( me.options.auto ) { + setTimeout(function() { + me.request('start-upload'); + }, 20 ); + } + }, + + getStats: function() { + return this.stats; + }, + + /** + * @event fileDequeued + * @param {File} file File对象 + * @description 当文件被移除队列后触发。 + * @for Uploader + */ + + /** + * @method removeFile + * @grammar removeFile( file ) => undefined + * @grammar removeFile( id ) => undefined + * @grammar removeFile( file, true ) => undefined + * @grammar removeFile( id, true ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 移除某一文件, 默认只会标记文件状态为已取消,如果第二个参数为 `true` 则会从 queue 中移除。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.removeFile( file ); + * }) + */ + removeFile: function( file, remove ) { + var me = this; + + file = file.id ? file : me.queue.getFile( file ); + + this.request( 'cancel-file', file ); + + if ( remove ) { + this.queue.removeFile( file ); + } + }, + + /** + * @method getFiles + * @grammar getFiles() => Array + * @grammar getFiles( status1, status2, status... ) => Array + * @description 返回指定状态的文件集合,不传参数将返回所有状态的文件。 + * @for Uploader + * @example + * console.log( uploader.getFiles() ); // => all files + * console.log( uploader.getFiles('error') ) // => all error files. + */ + getFiles: function() { + return this.queue.getFiles.apply( this.queue, arguments ); + }, + + fetchFile: function() { + return this.queue.fetch.apply( this.queue, arguments ); + }, + + /** + * @method retry + * @grammar retry() => undefined + * @grammar retry( file ) => undefined + * @description 重试上传,重试指定文件,或者从出错的文件开始重新上传。 + * @for Uploader + * @example + * function retry() { + * uploader.retry(); + * } + */ + retry: function( file, noForceStart ) { + var me = this, + files, i, len; + + if ( file ) { + file = file.id ? file : me.queue.getFile( file ); + file.setStatus( Status.QUEUED ); + noForceStart || me.request('start-upload'); + return; + } + + files = me.queue.getFiles( Status.ERROR ); + i = 0; + len = files.length; + + for ( ; i < len; i++ ) { + file = files[ i ]; + file.setStatus( Status.QUEUED ); + } + + me.request('start-upload'); + }, + + /** + * @method sort + * @grammar sort( fn ) => undefined + * @description 排序队列中的文件,在上传之前调整可以控制上传顺序。 + * @for Uploader + */ + sortFiles: function() { + return this.queue.sort.apply( this.queue, arguments ); + }, + + /** + * @event reset + * @description 当 uploader 被重置的时候触发。 + * @for Uploader + */ + + /** + * @method reset + * @grammar reset() => undefined + * @description 重置uploader。目前只重置了队列。 + * @for Uploader + * @example + * uploader.reset(); + */ + reset: function() { + this.owner.trigger('reset'); + this.queue = new Queue(); + this.stats = this.queue.stats; + }, + + destroy: function() { + this.reset(); + this.placeholder && this.placeholder.destroy(); + } + }); + + }); + /** + * @fileOverview 添加获取Runtime相关信息的方法。 + */ + define('widgets/runtime',[ + 'uploader', + 'runtime/runtime', + 'widgets/widget' + ], function( Uploader, Runtime ) { + + Uploader.support = function() { + return Runtime.hasRuntime.apply( Runtime, arguments ); + }; + + return Uploader.register({ + name: 'runtime', + + init: function() { + if ( !this.predictRuntimeType() ) { + throw Error('Runtime Error'); + } + }, + + /** + * 预测Uploader将采用哪个`Runtime` + * @grammar predictRuntimeType() => String + * @method predictRuntimeType + * @for Uploader + */ + predictRuntimeType: function() { + var orders = this.options.runtimeOrder || Runtime.orders, + type = this.type, + i, len; + + if ( !type ) { + orders = orders.split( /\s*,\s*/g ); + + for ( i = 0, len = orders.length; i < len; i++ ) { + if ( Runtime.hasRuntime( orders[ i ] ) ) { + this.type = type = orders[ i ]; + break; + } + } + } + + return type; + } + }); + }); + /** + * @fileOverview Transport + */ + define('lib/transport',[ + 'base', + 'runtime/client', + 'mediator' + ], function( Base, RuntimeClient, Mediator ) { + + var $ = Base.$; + + function Transport( opts ) { + var me = this; + + opts = me.options = $.extend( true, {}, Transport.options, opts || {} ); + RuntimeClient.call( this, 'Transport' ); + + this._blob = null; + this._formData = opts.formData || {}; + this._headers = opts.headers || {}; + + this.on( 'progress', this._timeout ); + this.on( 'load error', function() { + me.trigger( 'progress', 1 ); + clearTimeout( me._timer ); + }); + } + + Transport.options = { + server: '', + method: 'POST', + + // 跨域时,是否允许携带cookie, 只有html5 runtime才有效 + withCredentials: false, + fileVal: 'file', + timeout: 2 * 60 * 1000, // 2分钟 + formData: {}, + headers: {}, + sendAsBinary: false + }; + + $.extend( Transport.prototype, { + + // 添加Blob, 只能添加一次,最后一次有效。 + appendBlob: function( key, blob, filename ) { + var me = this, + opts = me.options; + + if ( me.getRuid() ) { + me.disconnectRuntime(); + } + + // 连接到blob归属的同一个runtime. + me.connectRuntime( blob.ruid, function() { + me.exec('init'); + }); + + me._blob = blob; + opts.fileVal = key || opts.fileVal; + opts.filename = filename || opts.filename; + }, + + // 添加其他字段 + append: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._formData, key ); + } else { + this._formData[ key ] = value; + } + }, + + setRequestHeader: function( key, value ) { + if ( typeof key === 'object' ) { + $.extend( this._headers, key ); + } else { + this._headers[ key ] = value; + } + }, + + send: function( method ) { + this.exec( 'send', method ); + this._timeout(); + }, + + abort: function() { + clearTimeout( this._timer ); + return this.exec('abort'); + }, + + destroy: function() { + this.trigger('destroy'); + this.off(); + this.exec('destroy'); + this.disconnectRuntime(); + }, + + getResponse: function() { + return this.exec('getResponse'); + }, + + getResponseAsJson: function() { + return this.exec('getResponseAsJson'); + }, + + getStatus: function() { + return this.exec('getStatus'); + }, + + _timeout: function() { + var me = this, + duration = me.options.timeout; + + if ( !duration ) { + return; + } + + clearTimeout( me._timer ); + me._timer = setTimeout(function() { + me.abort(); + me.trigger( 'error', 'timeout' ); + }, duration ); + } + + }); + + // 让Transport具备事件功能。 + Mediator.installTo( Transport.prototype ); + + return Transport; + }); + /** + * @fileOverview 负责文件上传相关。 + */ + define('widgets/upload',[ + 'base', + 'uploader', + 'file', + 'lib/transport', + 'widgets/widget' + ], function( Base, Uploader, WUFile, Transport ) { + + var $ = Base.$, + isPromise = Base.isPromise, + Status = WUFile.Status; + + // 添加默认配置项 + $.extend( Uploader.options, { + + + /** + * @property {Boolean} [prepareNextFile=false] + * @namespace options + * @for Uploader + * @description 是否允许在文件传输时提前把下一个文件准备好。 + * 对于一个文件的准备工作比较耗时,比如图片压缩,md5序列化。 + * 如果能提前在当前文件传输期处理,可以节省总体耗时。 + */ + prepareNextFile: false, + + /** + * @property {Boolean} [chunked=false] + * @namespace options + * @for Uploader + * @description 是否要分片处理大文件上传。 + */ + chunked: false, + + /** + * @property {Boolean} [chunkSize=5242880] + * @namespace options + * @for Uploader + * @description 如果要分片,分多大一片? 默认大小为5M. + */ + chunkSize: 5 * 1024 * 1024, + + /** + * @property {Boolean} [chunkRetry=2] + * @namespace options + * @for Uploader + * @description 如果某个分片由于网络问题出错,允许自动重传多少次? + */ + chunkRetry: 2, + + /** + * @property {Boolean} [threads=3] + * @namespace options + * @for Uploader + * @description 上传并发数。允许同时最大上传进程数。 + */ + threads: 3, + + + /** + * @property {Object} [formData={}] + * @namespace options + * @for Uploader + * @description 文件上传请求的参数表,每次发送都会发送此对象中的参数。 + */ + formData: {} + + /** + * @property {Object} [fileVal='file'] + * @namespace options + * @for Uploader + * @description 设置文件上传域的name。 + */ + + /** + * @property {Object} [method='POST'] + * @namespace options + * @for Uploader + * @description 文件上传方式,`POST`或者`GET`。 + */ + + /** + * @property {Object} [sendAsBinary=false] + * @namespace options + * @for Uploader + * @description 是否已二进制的流的方式发送文件,这样整个上传内容`php://input`都为文件内容, + * 其他参数在$_GET数组中。 + */ + }); + + // 负责将文件切片。 + function CuteFile( file, chunkSize ) { + var pending = [], + blob = file.source, + total = blob.size, + chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1, + start = 0, + index = 0, + len, api; + + api = { + file: file, + + has: function() { + return !!pending.length; + }, + + shift: function() { + return pending.shift(); + }, + + unshift: function( block ) { + pending.unshift( block ); + } + }; + + while ( index < chunks ) { + len = Math.min( chunkSize, total - start ); + + pending.push({ + file: file, + start: start, + end: chunkSize ? (start + len) : total, + total: total, + chunks: chunks, + chunk: index++, + cuted: api + }); + start += len; + } + + file.blocks = pending.concat(); + file.remaning = pending.length; + + return api; + } + + Uploader.register({ + name: 'upload', + + init: function() { + var owner = this.owner, + me = this; + + this.runing = false; + this.progress = false; + + owner + .on( 'startUpload', function() { + me.progress = true; + }) + .on( 'uploadFinished', function() { + me.progress = false; + }); + + // 记录当前正在传的数据,跟threads相关 + this.pool = []; + + // 缓存分好片的文件。 + this.stack = []; + + // 缓存即将上传的文件。 + this.pending = []; + + // 跟踪还有多少分片在上传中但是没有完成上传。 + this.remaning = 0; + this.__tick = Base.bindFn( this._tick, this ); + + owner.on( 'uploadComplete', function( file ) { + + // 把其他块取消了。 + file.blocks && $.each( file.blocks, function( _, v ) { + v.transport && (v.transport.abort(), v.transport.destroy()); + delete v.transport; + }); + + delete file.blocks; + delete file.remaning; + }); + }, + + reset: function() { + this.request( 'stop-upload', true ); + this.runing = false; + this.pool = []; + this.stack = []; + this.pending = []; + this.remaning = 0; + this._trigged = false; + this._promise = null; + }, + + /** + * @event startUpload + * @description 当开始上传流程时触发。 + * @for Uploader + */ + + /** + * 开始上传。此方法可以从初始状态调用开始上传流程,也可以从暂停状态调用,继续上传流程。 + * + * 可以指定开始某一个文件。 + * @grammar upload() => undefined + * @grammar upload( file | fileId) => undefined + * @method upload + * @for Uploader + */ + startUpload: function(file) { + var me = this; + + // 移出invalid的文件 + $.each( me.request( 'get-files', Status.INVALID ), function() { + me.request( 'remove-file', this ); + }); + + // 如果指定了开始某个文件,则只开始指定文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if (file.getStatus() === Status.INTERRUPT) { + $.each( me.pool, function( _, v ) { + + // 之前暂停过。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.send(); + }); + + file.setStatus( Status.QUEUED ); + } else if (file.getStatus() === Status.PROGRESS) { + return; + } else { + file.setStatus( Status.QUEUED ); + } + } else { + $.each( me.request( 'get-files', [ Status.INITED ] ), function() { + this.setStatus( Status.QUEUED ); + }); + } + + if ( me.runing ) { + return; + } + + me.runing = true; + + // 如果有暂停的,则续传 + $.each( me.pool, function( _, v ) { + var file = v.file; + + if ( file.getStatus() === Status.INTERRUPT ) { + file.setStatus( Status.PROGRESS ); + me._trigged = false; + v.transport && v.transport.send(); + } + }); + + file || $.each( me.request( 'get-files', + Status.INTERRUPT ), function() { + this.setStatus( Status.PROGRESS ); + }); + + me._trigged = false; + Base.nextTick( me.__tick ); + me.owner.trigger('startUpload'); + }, + + /** + * @event stopUpload + * @description 当开始上传流程暂停时触发。 + * @for Uploader + */ + + /** + * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。 + * + * 如果第一个参数是文件,则只暂停指定文件。 + * @grammar stop() => undefined + * @grammar stop( true ) => undefined + * @grammar stop( file ) => undefined + * @method stop + * @for Uploader + */ + stopUpload: function( file, interrupt ) { + var me = this; + + if (file === true) { + interrupt = file; + file = null; + } + + if ( me.runing === false ) { + return; + } + + // 如果只是暂停某个文件。 + if ( file ) { + file = file.id ? file : me.request( 'get-file', file ); + + if ( file.getStatus() !== Status.PROGRESS && + file.getStatus() !== Status.QUEUED ) { + return; + } + + file.setStatus( Status.INTERRUPT ); + $.each( me.pool, function( _, v ) { + + // 只 abort 指定的文件。 + if (v.file !== file) { + return; + } + + v.transport && v.transport.abort(); + me._putback(v); + me._popBlock(v); + }); + + return Base.nextTick( me.__tick ); + } + + me.runing = false; + + if (this._promise && this._promise.file) { + this._promise.file.setStatus( Status.INTERRUPT ); + } + + interrupt && $.each( me.pool, function( _, v ) { + v.transport && v.transport.abort(); + v.file.setStatus( Status.INTERRUPT ); + }); + + me.owner.trigger('stopUpload'); + }, + + /** + * @method cancelFile + * @grammar cancelFile( file ) => undefined + * @grammar cancelFile( id ) => undefined + * @param {File|id} file File对象或这File对象的id + * @description 标记文件状态为已取消, 同时将中断文件传输。 + * @for Uploader + * @example + * + * $li.on('click', '.remove-this', function() { + * uploader.cancelFile( file ); + * }) + */ + cancelFile: function( file ) { + file = file.id ? file : this.request( 'get-file', file ); + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + file.setStatus( Status.CANCELLED ); + this.owner.trigger( 'fileDequeued', file ); + }, + + /** + * 判断`Uplaode`r是否正在上传中。 + * @grammar isInProgress() => Boolean + * @method isInProgress + * @for Uploader + */ + isInProgress: function() { + return !!this.progress; + }, + + _getStats: function() { + return this.request('get-stats'); + }, + + /** + * 掉过一个文件上传,直接标记指定文件为已上传状态。 + * @grammar skipFile( file ) => undefined + * @method skipFile + * @for Uploader + */ + skipFile: function( file, status ) { + file = file.id ? file : this.request( 'get-file', file ); + + file.setStatus( status || Status.COMPLETE ); + file.skipped = true; + + // 如果正在上传。 + file.blocks && $.each( file.blocks, function( _, v ) { + var _tr = v.transport; + + if ( _tr ) { + _tr.abort(); + _tr.destroy(); + delete v.transport; + } + }); + + this.owner.trigger( 'uploadSkip', file ); + }, + + /** + * @event uploadFinished + * @description 当所有文件上传结束时触发。 + * @for Uploader + */ + _tick: function() { + var me = this, + opts = me.options, + fn, val; + + // 上一个promise还没有结束,则等待完成后再执行。 + if ( me._promise ) { + return me._promise.always( me.__tick ); + } + + // 还有位置,且还有文件要处理的话。 + if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) { + me._trigged = false; + + fn = function( val ) { + me._promise = null; + + // 有可能是reject过来的,所以要检测val的类型。 + val && val.file && me._startSend( val ); + Base.nextTick( me.__tick ); + }; + + me._promise = isPromise( val ) ? val.always( fn ) : fn( val ); + + // 没有要上传的了,且没有正在传输的了。 + } else if ( !me.remaning && !me._getStats().numOfQueue && + !me._getStats().numofInterrupt ) { + me.runing = false; + + me._trigged || Base.nextTick(function() { + me.owner.trigger('uploadFinished'); + }); + me._trigged = true; + } + }, + + _putback: function(block) { + var idx; + + block.cuted.unshift(block); + idx = this.stack.indexOf(block.cuted); + + if (!~idx) { + this.stack.unshift(block.cuted); + } + }, + + _getStack: function() { + var i = 0, + act; + + while ( (act = this.stack[ i++ ]) ) { + if ( act.has() && act.file.getStatus() === Status.PROGRESS ) { + return act; + } else if (!act.has() || + act.file.getStatus() !== Status.PROGRESS && + act.file.getStatus() !== Status.INTERRUPT ) { + + // 把已经处理完了的,或者,状态为非 progress(上传中)、 + // interupt(暂停中) 的移除。 + this.stack.splice( --i, 1 ); + } + } + + return null; + }, + + _nextBlock: function() { + var me = this, + opts = me.options, + act, next, done, preparing; + + // 如果当前文件还有没有需要传输的,则直接返回剩下的。 + if ( (act = this._getStack()) ) { + + // 是否提前准备下一个文件 + if ( opts.prepareNextFile && !me.pending.length ) { + me._prepareNextFile(); + } + + return act.shift(); + + // 否则,如果正在运行,则准备下一个文件,并等待完成后返回下个分片。 + } else if ( me.runing ) { + + // 如果缓存中有,则直接在缓存中取,没有则去queue中取。 + if ( !me.pending.length && me._getStats().numOfQueue ) { + me._prepareNextFile(); + } + + next = me.pending.shift(); + done = function( file ) { + if ( !file ) { + return null; + } + + act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 ); + me.stack.push(act); + return act.shift(); + }; + + // 文件可能还在prepare中,也有可能已经完全准备好了。 + if ( isPromise( next) ) { + preparing = next.file; + next = next[ next.pipe ? 'pipe' : 'then' ]( done ); + next.file = preparing; + return next; + } + + return done( next ); + } + }, + + + /** + * @event uploadStart + * @param {File} file File对象 + * @description 某个文件开始上传前触发,一个文件只会触发一次。 + * @for Uploader + */ + _prepareNextFile: function() { + var me = this, + file = me.request('fetch-file'), + pending = me.pending, + promise; + + if ( file ) { + promise = me.request( 'before-send-file', file, function() { + + // 有可能文件被skip掉了。文件被skip掉后,状态坑定不是Queued. + if ( file.getStatus() === Status.PROGRESS || + file.getStatus() === Status.INTERRUPT ) { + return file; + } + + return me._finishFile( file ); + }); + + me.owner.trigger( 'uploadStart', file ); + file.setStatus( Status.PROGRESS ); + + promise.file = file; + + // 如果还在pending中,则替换成文件本身。 + promise.done(function() { + var idx = $.inArray( promise, pending ); + + ~idx && pending.splice( idx, 1, file ); + }); + + // befeore-send-file的钩子就有错误发生。 + promise.fail(function( reason ) { + file.setStatus( Status.ERROR, reason ); + me.owner.trigger( 'uploadError', file, reason ); + me.owner.trigger( 'uploadComplete', file ); + }); + + pending.push( promise ); + } + }, + + // 让出位置了,可以让其他分片开始上传 + _popBlock: function( block ) { + var idx = $.inArray( block, this.pool ); + + this.pool.splice( idx, 1 ); + block.file.remaning--; + this.remaning--; + }, + + // 开始上传,可以被掉过。如果promise被reject了,则表示跳过此分片。 + _startSend: function( block ) { + var me = this, + file = block.file, + promise; + + // 有可能在 before-send-file 的 promise 期间改变了文件状态。 + // 如:暂停,取消 + // 我们不能中断 promise, 但是可以在 promise 完后,不做上传操作。 + if ( file.getStatus() !== Status.PROGRESS ) { + + // 如果是中断,则还需要放回去。 + if (file.getStatus() === Status.INTERRUPT) { + me._putback(block); + } + + return; + } + + me.pool.push( block ); + me.remaning++; + + // 如果没有分片,则直接使用原始的。 + // 不会丢失content-type信息。 + block.blob = block.chunks === 1 ? file.source : + file.source.slice( block.start, block.end ); + + // hook, 每个分片发送之前可能要做些异步的事情。 + promise = me.request( 'before-send', block, function() { + + // 有可能文件已经上传出错了,所以不需要再传输了。 + if ( file.getStatus() === Status.PROGRESS ) { + me._doSend( block ); + } else { + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + + // 如果为fail了,则跳过此分片。 + promise.fail(function() { + if ( file.remaning === 1 ) { + me._finishFile( file ).always(function() { + block.percentage = 1; + me._popBlock( block ); + me.owner.trigger( 'uploadComplete', file ); + Base.nextTick( me.__tick ); + }); + } else { + block.percentage = 1; + me._popBlock( block ); + Base.nextTick( me.__tick ); + } + }); + }, + + + /** + * @event uploadBeforeSend + * @param {Object} object + * @param {Object} data 默认的上传参数,可以扩展此对象来控制上传参数。 + * @param {Object} headers 可以扩展此对象来控制上传头部。 + * @description 当某个文件的分块在发送前触发,主要用来询问是否要添加附带参数,大文件在开起分片上传的前提下此事件可能会触发多次。 + * @for Uploader + */ + + /** + * @event uploadAccept + * @param {Object} object + * @param {Object} ret 服务端的返回数据,json格式,如果服务端不是json格式,从ret._raw中取数据,自行解析。 + * @description 当某个文件上传到服务端响应后,会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。 + * @for Uploader + */ + + /** + * @event uploadProgress + * @param {File} file File对象 + * @param {Number} percentage 上传进度 + * @description 上传过程中触发,携带上传进度。 + * @for Uploader + */ + + + /** + * @event uploadError + * @param {File} file File对象 + * @param {String} reason 出错的code + * @description 当文件上传出错时触发。 + * @for Uploader + */ + + /** + * @event uploadSuccess + * @param {File} file File对象 + * @param {Object} response 服务端返回的数据 + * @description 当文件上传成功时触发。 + * @for Uploader + */ + + /** + * @event uploadComplete + * @param {File} [file] File对象 + * @description 不管成功或者失败,文件上传完成时触发。 + * @for Uploader + */ + + // 做上传操作。 + _doSend: function( block ) { + var me = this, + owner = me.owner, + opts = me.options, + file = block.file, + tr = new Transport( opts ), + data = $.extend({}, opts.formData ), + headers = $.extend({}, opts.headers ), + requestAccept, ret; + + block.transport = tr; + + tr.on( 'destroy', function() { + delete block.transport; + me._popBlock( block ); + Base.nextTick( me.__tick ); + }); + + // 广播上传进度。以文件为单位。 + tr.on( 'progress', function( percentage ) { + var totalPercent = 0, + uploaded = 0; + + // 可能没有abort掉,progress还是执行进来了。 + // if ( !file.blocks ) { + // return; + // } + + totalPercent = block.percentage = percentage; + + if ( block.chunks > 1 ) { // 计算文件的整体速度。 + $.each( file.blocks, function( _, v ) { + uploaded += (v.percentage || 0) * (v.end - v.start); + }); + + totalPercent = uploaded / file.size; + } + + owner.trigger( 'uploadProgress', file, totalPercent || 0 ); + }); + + // 用来询问,是否返回的结果是有错误的。 + requestAccept = function( reject ) { + var fn; + + ret = tr.getResponseAsJson() || {}; + ret._raw = tr.getResponse(); + fn = function( value ) { + reject = value; + }; + + // 服务端响应了,不代表成功了,询问是否响应正确。 + if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) { + reject = reject || 'server'; + } + + return reject; + }; + + // 尝试重试,然后广播文件上传出错。 + tr.on( 'error', function( type, flag ) { + block.retried = block.retried || 0; + + // 自动重试 + if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) && + block.retried < opts.chunkRetry ) { + + block.retried++; + tr.send(); + + } else { + + // http status 500 ~ 600 + if ( !flag && type === 'server' ) { + type = requestAccept( type ); + } + + file.setStatus( Status.ERROR, type ); + owner.trigger( 'uploadError', file, type ); + owner.trigger( 'uploadComplete', file ); + } + }); + + // 上传成功 + tr.on( 'load', function() { + var reason; + + // 如果非预期,转向上传出错。 + if ( (reason = requestAccept()) ) { + tr.trigger( 'error', reason, true ); + return; + } + + // 全部上传完成。 + if ( file.remaning === 1 ) { + me._finishFile( file, ret ); + } else { + tr.destroy(); + } + }); + + // 配置默认的上传字段。 + data = $.extend( data, { + id: file.id, + name: file.name, + type: file.type, + lastModifiedDate: file.lastModifiedDate, + size: file.size + }); + + block.chunks > 1 && $.extend( data, { + chunks: block.chunks, + chunk: block.chunk + }); + + // 在发送之间可以添加字段什么的。。。 + // 如果默认的字段不够使用,可以通过监听此事件来扩展 + owner.trigger( 'uploadBeforeSend', block, data, headers ); + + // 开始发送。 + tr.appendBlob( opts.fileVal, block.blob, file.name ); + tr.append( data ); + tr.setRequestHeader( headers ); + tr.send(); + }, + + // 完成上传。 + _finishFile: function( file, ret, hds ) { + var owner = this.owner; + + return owner + .request( 'after-send-file', arguments, function() { + file.setStatus( Status.COMPLETE ); + owner.trigger( 'uploadSuccess', file, ret, hds ); + }) + .fail(function( reason ) { + + // 如果外部已经标记为invalid什么的,不再改状态。 + if ( file.getStatus() === Status.PROGRESS ) { + file.setStatus( Status.ERROR, reason ); + } + + owner.trigger( 'uploadError', file, reason ); + }) + .always(function() { + owner.trigger( 'uploadComplete', file ); + }); + } + + }); + }); + /** + * @fileOverview 各种验证,包括文件总大小是否超出、单文件是否超出和文件是否重复。 + */ + + define('widgets/validator',[ + 'base', + 'uploader', + 'file', + 'widgets/widget' + ], function( Base, Uploader, WUFile ) { + + var $ = Base.$, + validators = {}, + api; + + /** + * @event error + * @param {String} type 错误类型。 + * @description 当validate不通过时,会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误,目前有以下错误会在特定的情况下派送错来。 + * + * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。 + * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。 + * * `Q_TYPE_DENIED` 当文件类型不满足时触发。。 + * @for Uploader + */ + + // 暴露给外面的api + api = { + + // 添加验证器 + addValidator: function( type, cb ) { + validators[ type ] = cb; + }, + + // 移除验证器 + removeValidator: function( type ) { + delete validators[ type ]; + } + }; + + // 在Uploader初始化的时候启动Validators的初始化 + Uploader.register({ + name: 'validator', + + init: function() { + var me = this; + Base.nextTick(function() { + $.each( validators, function() { + this.call( me.owner ); + }); + }); + } + }); + + /** + * @property {int} [fileNumLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总数量, 超出则不允许加入队列。 + */ + api.addValidator( 'fileNumLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileNumLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( count >= max && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return count >= max ? false : true; + }); + + uploader.on( 'fileQueued', function() { + count++; + }); + + uploader.on( 'fileDequeued', function() { + count--; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + + /** + * @property {int} [fileSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSizeLimit', function() { + var uploader = this, + opts = uploader.options, + count = 0, + max = parseInt( opts.fileSizeLimit, 10 ), + flag = true; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var invalid = count + file.size > max; + + if ( invalid && flag ) { + flag = false; + this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file ); + setTimeout(function() { + flag = true; + }, 1 ); + } + + return invalid ? false : true; + }); + + uploader.on( 'fileQueued', function( file ) { + count += file.size; + }); + + uploader.on( 'fileDequeued', function( file ) { + count -= file.size; + }); + + uploader.on( 'reset', function() { + count = 0; + }); + }); + + /** + * @property {int} [fileSingleSizeLimit=undefined] + * @namespace options + * @for Uploader + * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。 + */ + api.addValidator( 'fileSingleSizeLimit', function() { + var uploader = this, + opts = uploader.options, + max = opts.fileSingleSizeLimit; + + if ( !max ) { + return; + } + + uploader.on( 'beforeFileQueued', function( file ) { + + if ( file.size > max ) { + file.setStatus( WUFile.Status.INVALID, 'exceed_size' ); + this.trigger( 'error', 'F_EXCEED_SIZE', max, file ); + return false; + } + + }); + + }); + + /** + * @property {Boolean} [duplicate=undefined] + * @namespace options + * @for Uploader + * @description 去重, 根据文件名字、文件大小和最后修改时间来生成hash Key. + */ + api.addValidator( 'duplicate', function() { + var uploader = this, + opts = uploader.options, + mapping = {}; + + if ( opts.duplicate ) { + return; + } + + function hashString( str ) { + var hash = 0, + i = 0, + len = str.length, + _char; + + for ( ; i < len; i++ ) { + _char = str.charCodeAt( i ); + hash = _char + (hash << 6) + (hash << 16) - hash; + } + + return hash; + } + + uploader.on( 'beforeFileQueued', function( file ) { + var hash = file.__hash || (file.__hash = hashString( file.name + + file.size + file.lastModifiedDate )); + + // 已经重复了 + if ( mapping[ hash ] ) { + this.trigger( 'error', 'F_DUPLICATE', file ); + return false; + } + }); + + uploader.on( 'fileQueued', function( file ) { + var hash = file.__hash; + + hash && (mapping[ hash ] = true); + }); + + uploader.on( 'fileDequeued', function( file ) { + var hash = file.__hash; + + hash && (delete mapping[ hash ]); + }); + + uploader.on( 'reset', function() { + mapping = {}; + }); + }); + + return api; + }); + + /** + * @fileOverview Runtime管理器,负责Runtime的选择, 连接 + */ + define('runtime/compbase',[],function() { + + function CompBase( owner, runtime ) { + + this.owner = owner; + this.options = owner.options; + + this.getRuntime = function() { + return runtime; + }; + + this.getRuid = function() { + return runtime.uid; + }; + + this.trigger = function() { + return owner.trigger.apply( owner, arguments ); + }; + } + + return CompBase; + }); + /** + * @fileOverview Html5Runtime + */ + define('runtime/html5/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var type = 'html5', + components = {}; + + function Html5Runtime() { + var pool = {}, + me = this, + destroy = this.destroy; + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + if ( components[ comp ] ) { + instance = pool[ uid ] = pool[ uid ] || + new components[ comp ]( client, me ); + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + }; + + me.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + } + + Base.inherits( Runtime, { + constructor: Html5Runtime, + + // 不需要连接其他程序,直接执行callback + init: function() { + var me = this; + setTimeout(function() { + me.trigger('ready'); + }, 1 ); + } + + }); + + // 注册Components + Html5Runtime.register = function( name, component ) { + var klass = components[ name ] = Base.inherits( CompBase, component ); + return klass; + }; + + // 注册html5运行时。 + // 只有在支持的前提下注册。 + if ( window.Blob && window.FileReader && window.DataView ) { + Runtime.addRuntime( type, Html5Runtime ); + } + + return Html5Runtime; + }); + /** + * @fileOverview Blob Html实现 + */ + define('runtime/html5/blob',[ + 'runtime/html5/runtime', + 'lib/blob' + ], function( Html5Runtime, Blob ) { + + return Html5Runtime.register( 'Blob', { + slice: function( start, end ) { + var blob = this.owner.source, + slice = blob.slice || blob.webkitSlice || blob.mozSlice; + + blob = slice.call( blob, start, end ); + + return new Blob( this.getRuid(), blob ); + } + }); + }); + /** + * @fileOverview FilePaste + */ + define('runtime/html5/dnd',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + var $ = Base.$, + prefix = 'webuploader-dnd-'; + + return Html5Runtime.register( 'DragAndDrop', { + init: function() { + var elem = this.elem = this.options.container; + + this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this ); + this.dragOverHandler = Base.bindFn( this._dragOverHandler, this ); + this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this ); + this.dropHandler = Base.bindFn( this._dropHandler, this ); + this.dndOver = false; + + elem.on( 'dragenter', this.dragEnterHandler ); + elem.on( 'dragover', this.dragOverHandler ); + elem.on( 'dragleave', this.dragLeaveHandler ); + elem.on( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).on( 'dragover', this.dragOverHandler ); + $( document ).on( 'drop', this.dropHandler ); + } + }, + + _dragEnterHandler: function( e ) { + var me = this, + denied = me._denied || false, + items; + + e = e.originalEvent || e; + + if ( !me.dndOver ) { + me.dndOver = true; + + // 注意只有 chrome 支持。 + items = e.dataTransfer.items; + + if ( items && items.length ) { + me._denied = denied = !me.trigger( 'accept', items ); + } + + me.elem.addClass( prefix + 'over' ); + me.elem[ denied ? 'addClass' : + 'removeClass' ]( prefix + 'denied' ); + } + + e.dataTransfer.dropEffect = denied ? 'none' : 'copy'; + + return false; + }, + + _dragOverHandler: function( e ) { + // 只处理框内的。 + var parentElem = this.elem.parent().get( 0 ); + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + clearTimeout( this._leaveTimer ); + this._dragEnterHandler.call( this, e ); + + return false; + }, + + _dragLeaveHandler: function() { + var me = this, + handler; + + handler = function() { + me.dndOver = false; + me.elem.removeClass( prefix + 'over ' + prefix + 'denied' ); + }; + + clearTimeout( me._leaveTimer ); + me._leaveTimer = setTimeout( handler, 100 ); + return false; + }, + + _dropHandler: function( e ) { + var me = this, + ruid = me.getRuid(), + parentElem = me.elem.parent().get( 0 ), + dataTransfer, data; + + // 只处理框内的。 + if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) { + return false; + } + + e = e.originalEvent || e; + dataTransfer = e.dataTransfer; + + // 如果是页面内拖拽,还不能处理,不阻止事件。 + // 此处 ie11 下会报参数错误, + try { + data = dataTransfer.getData('text/html'); + } catch( err ) { + } + + if ( data ) { + return; + } + + me._getTansferFiles( dataTransfer, function( results ) { + me.trigger( 'drop', $.map( results, function( file ) { + return new File( ruid, file ); + }) ); + }); + + me.dndOver = false; + me.elem.removeClass( prefix + 'over' ); + return false; + }, + + // 如果传入 callback 则去查看文件夹,否则只管当前文件夹。 + _getTansferFiles: function( dataTransfer, callback ) { + var results = [], + promises = [], + items, files, file, item, i, len, canAccessFolder; + + items = dataTransfer.items; + files = dataTransfer.files; + + canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry); + + for ( i = 0, len = files.length; i < len; i++ ) { + file = files[ i ]; + item = items && items[ i ]; + + if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) { + + promises.push( this._traverseDirectoryTree( + item.webkitGetAsEntry(), results ) ); + } else { + results.push( file ); + } + } + + Base.when.apply( Base, promises ).done(function() { + + if ( !results.length ) { + return; + } + + callback( results ); + }); + }, + + _traverseDirectoryTree: function( entry, results ) { + var deferred = Base.Deferred(), + me = this; + + if ( entry.isFile ) { + entry.file(function( file ) { + results.push( file ); + deferred.resolve(); + }); + } else if ( entry.isDirectory ) { + entry.createReader().readEntries(function( entries ) { + var len = entries.length, + promises = [], + arr = [], // 为了保证顺序。 + i; + + for ( i = 0; i < len; i++ ) { + promises.push( me._traverseDirectoryTree( + entries[ i ], arr ) ); + } + + Base.when.apply( Base, promises ).then(function() { + results.push.apply( results, arr ); + deferred.resolve(); + }, deferred.reject ); + }); + } + + return deferred.promise(); + }, + + destroy: function() { + var elem = this.elem; + + // 还没 init 就调用 destroy + if (!elem) { + return; + } + + elem.off( 'dragenter', this.dragEnterHandler ); + elem.off( 'dragover', this.dragOverHandler ); + elem.off( 'dragleave', this.dragLeaveHandler ); + elem.off( 'drop', this.dropHandler ); + + if ( this.options.disableGlobalDnd ) { + $( document ).off( 'dragover', this.dragOverHandler ); + $( document ).off( 'drop', this.dropHandler ); + } + } + }); + }); + + /** + * @fileOverview FilePaste + */ + define('runtime/html5/filepaste',[ + 'base', + 'runtime/html5/runtime', + 'lib/file' + ], function( Base, Html5Runtime, File ) { + + return Html5Runtime.register( 'FilePaste', { + init: function() { + var opts = this.options, + elem = this.elem = opts.container, + accept = '.*', + arr, i, len, item; + + // accetp的mimeTypes中生成匹配正则。 + if ( opts.accept ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + item = opts.accept[ i ].mimeTypes; + item && arr.push( item ); + } + + if ( arr.length ) { + accept = arr.join(','); + accept = accept.replace( /,/g, '|' ).replace( /\*/g, '.*' ); + } + } + this.accept = accept = new RegExp( accept, 'i' ); + this.hander = Base.bindFn( this._pasteHander, this ); + elem.on( 'paste', this.hander ); + }, + + _pasteHander: function( e ) { + var allowed = [], + ruid = this.getRuid(), + items, item, blob, i, len; + + e = e.originalEvent || e; + items = e.clipboardData.items; + + for ( i = 0, len = items.length; i < len; i++ ) { + item = items[ i ]; + + if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) { + continue; + } + + allowed.push( new File( ruid, blob ) ); + } + + if ( allowed.length ) { + // 不阻止非文件粘贴(文字粘贴)的事件冒泡 + e.preventDefault(); + e.stopPropagation(); + this.trigger( 'paste', allowed ); + } + }, + + destroy: function() { + this.elem.off( 'paste', this.hander ); + } + }); + }); + + /** + * @fileOverview FilePicker + */ + define('runtime/html5/filepicker',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var $ = Base.$; + + return Html5Runtime.register( 'FilePicker', { + init: function() { + var container = this.getRuntime().getContainer(), + me = this, + owner = me.owner, + opts = me.options, + label = this.label = $( document.createElement('label') ), + input = this.input = $( document.createElement('input') ), + arr, i, len, mouseHandler; + + input.attr( 'type', 'file' ); + input.attr( 'name', opts.name ); + input.addClass('webuploader-element-invisible'); + + label.on( 'click', function() { + input.trigger('click'); + }); + + label.css({ + opacity: 0, + width: '100%', + height: '100%', + display: 'block', + cursor: 'pointer', + background: '#ffffff' + }); + + if ( opts.multiple ) { + input.attr( 'multiple', 'multiple' ); + } + + // @todo Firefox不支持单独指定后缀 + if ( opts.accept && opts.accept.length > 0 ) { + arr = []; + + for ( i = 0, len = opts.accept.length; i < len; i++ ) { + arr.push( opts.accept[ i ].mimeTypes ); + } + + input.attr( 'accept', arr.join(',') ); + } + + container.append( input ); + container.append( label ); + + mouseHandler = function( e ) { + owner.trigger( e.type ); + }; + + input.on( 'change', function( e ) { + var fn = arguments.callee, + clone; + + me.files = e.target.files; + + // reset input + clone = this.cloneNode( true ); + clone.value = null; + this.parentNode.replaceChild( clone, this ); + + input.off(); + input = $( clone ).on( 'change', fn ) + .on( 'mouseenter mouseleave', mouseHandler ); + + owner.trigger('change'); + }); + + label.on( 'mouseenter mouseleave', mouseHandler ); + + }, + + + getFiles: function() { + return this.files; + }, + + destroy: function() { + this.input.off(); + this.label.off(); + } + }); + }); + /** + * @fileOverview Transport + * @todo 支持chunked传输,优势: + * 可以将大文件分成小块,挨个传输,可以提高大文件成功率,当失败的时候,也只需要重传那小部分, + * 而不需要重头再传一次。另外断点续传也需要用chunked方式。 + */ + define('runtime/html5/transport',[ + 'base', + 'runtime/html5/runtime' + ], function( Base, Html5Runtime ) { + + var noop = Base.noop, + $ = Base.$; + + return Html5Runtime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + formData, binary, fr; + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.getSource(); + } else { + formData = new FormData(); + $.each( owner._formData, function( k, v ) { + formData.append( k, v ); + }); + + formData.append( opts.fileVal, blob.getSource(), + opts.filename || owner._formData.name || '' ); + } + + if ( opts.withCredentials && 'withCredentials' in xhr ) { + xhr.open( opts.method, server, true ); + xhr.withCredentials = true; + } else { + xhr.open( opts.method, server ); + } + + this._setRequestHeader( xhr, opts.headers ); + + if ( binary ) { + // 强制设置成 content-type 为文件流。 + xhr.overrideMimeType && + xhr.overrideMimeType('application/octet-stream'); + + // android直接发送blob会导致服务端接收到的是空文件。 + // bug详情。 + // https://code.google.com/p/android/issues/detail?id=39882 + // 所以先用fileReader读取出来再通过arraybuffer的方式发送。 + if ( Base.os.android ) { + fr = new FileReader(); + + fr.onload = function() { + xhr.send( this.result ); + fr = fr.onload = null; + }; + + fr.readAsArrayBuffer( binary ); + } else { + xhr.send( binary ); + } + } else { + xhr.send( formData ); + } + }, + + getResponse: function() { + return this._response; + }, + + getResponseAsJson: function() { + return this._parseJson( this._response ); + }, + + getStatus: function() { + return this._status; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + xhr.abort(); + + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new XMLHttpRequest(), + opts = this.options; + + if ( opts.withCredentials && !('withCredentials' in xhr) && + typeof XDomainRequest !== 'undefined' ) { + xhr = new XDomainRequest(); + } + + xhr.upload.onprogress = function( e ) { + var percentage = 0; + + if ( e.lengthComputable ) { + percentage = e.loaded / e.total; + } + + return me.trigger( 'progress', percentage ); + }; + + xhr.onreadystatechange = function() { + + if ( xhr.readyState !== 4 ) { + return; + } + + xhr.upload.onprogress = noop; + xhr.onreadystatechange = noop; + me._xhr = null; + me._status = xhr.status; + + if ( xhr.status >= 200 && xhr.status < 300 ) { + me._response = xhr.responseText; + return me.trigger('load'); + } else if ( xhr.status >= 500 && xhr.status < 600 ) { + me._response = xhr.responseText; + return me.trigger( 'error', 'server' ); + } + + + return me.trigger( 'error', me._status ? 'http' : 'abort' ); + }; + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.setRequestHeader( key, val ); + }); + }, + + _parseJson: function( str ) { + var json; + + try { + json = JSON.parse( str ); + } catch ( ex ) { + json = {}; + } + + return json; + } + }); + }); + /** + * @fileOverview FlashRuntime + */ + define('runtime/flash/runtime',[ + 'base', + 'runtime/runtime', + 'runtime/compbase' + ], function( Base, Runtime, CompBase ) { + + var $ = Base.$, + type = 'flash', + components = {}; + + + function getFlashVersion() { + var version; + + try { + version = navigator.plugins[ 'Shockwave Flash' ]; + version = version.description; + } catch ( ex ) { + try { + version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash') + .GetVariable('$version'); + } catch ( ex2 ) { + version = '0.0'; + } + } + version = version.match( /\d+/g ); + return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 ); + } + + function FlashRuntime() { + var pool = {}, + clients = {}, + destroy = this.destroy, + me = this, + jsreciver = Base.guid('webuploader_'); + + Runtime.apply( me, arguments ); + me.type = type; + + + // 这个方法的调用者,实际上是RuntimeClient + me.exec = function( comp, fn/*, args...*/ ) { + var client = this, + uid = client.uid, + args = Base.slice( arguments, 2 ), + instance; + + clients[ uid ] = client; + + if ( components[ comp ] ) { + if ( !pool[ uid ] ) { + pool[ uid ] = new components[ comp ]( client, me ); + } + + instance = pool[ uid ]; + + if ( instance[ fn ] ) { + return instance[ fn ].apply( instance, args ); + } + } + + return me.flashExec.apply( client, arguments ); + }; + + function handler( evt, obj ) { + var type = evt.type || evt, + parts, uid; + + parts = type.split('::'); + uid = parts[ 0 ]; + type = parts[ 1 ]; + + // console.log.apply( console, arguments ); + + if ( type === 'Ready' && uid === me.uid ) { + me.trigger('ready'); + } else if ( clients[ uid ] ) { + clients[ uid ].trigger( type.toLowerCase(), evt, obj ); + } + + // Base.log( evt, obj ); + } + + // flash的接受器。 + window[ jsreciver ] = function() { + var args = arguments; + + // 为了能捕获得到。 + setTimeout(function() { + handler.apply( null, args ); + }, 1 ); + }; + + this.jsreciver = jsreciver; + + this.destroy = function() { + // @todo 删除池子中的所有实例 + return destroy && destroy.apply( this, arguments ); + }; + + this.flashExec = function( comp, fn ) { + var flash = me.getFlash(), + args = Base.slice( arguments, 2 ); + + return flash.exec( this.uid, comp, fn, args ); + }; + + // @todo + } + + Base.inherits( Runtime, { + constructor: FlashRuntime, + + init: function() { + var container = this.getContainer(), + opts = this.options, + html; + + // if not the minimal height, shims are not initialized + // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc) + container.css({ + position: 'absolute', + top: '-8px', + left: '-8px', + width: '9px', + height: '9px', + overflow: 'hidden' + }); + + // insert flash object + html = '' + + '' + + '' + + '' + + ''; + + container.html( html ); + }, + + getFlash: function() { + if ( this._flash ) { + return this._flash; + } + + this._flash = $( '#' + this.uid ).get( 0 ); + return this._flash; + } + + }); + + FlashRuntime.register = function( name, component ) { + component = components[ name ] = Base.inherits( CompBase, $.extend({ + + // @todo fix this later + flashExec: function() { + var owner = this.owner, + runtime = this.getRuntime(); + + return runtime.flashExec.apply( owner, arguments ); + } + }, component ) ); + + return component; + }; + + if ( getFlashVersion() >= 11.4 ) { + Runtime.addRuntime( type, FlashRuntime ); + } + + return FlashRuntime; + }); + /** + * @fileOverview FilePicker + */ + define('runtime/flash/filepicker',[ + 'base', + 'runtime/flash/runtime' + ], function( Base, FlashRuntime ) { + var $ = Base.$; + + return FlashRuntime.register( 'FilePicker', { + init: function( opts ) { + var copy = $.extend({}, opts ), + len, i; + + // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug. + len = copy.accept && copy.accept.length; + for ( i = 0; i < len; i++ ) { + if ( !copy.accept[ i ].title ) { + copy.accept[ i ].title = 'Files'; + } + } + + delete copy.button; + delete copy.id; + delete copy.container; + + this.flashExec( 'FilePicker', 'init', copy ); + }, + + destroy: function() { + this.flashExec( 'FilePicker', 'destroy' ); + } + }); + }); + /** + * @fileOverview Transport flash实现 + */ + define('runtime/flash/transport',[ + 'base', + 'runtime/flash/runtime', + 'runtime/client' + ], function( Base, FlashRuntime, RuntimeClient ) { + var $ = Base.$; + + return FlashRuntime.register( 'Transport', { + init: function() { + this._status = 0; + this._response = null; + this._responseJson = null; + }, + + send: function() { + var owner = this.owner, + opts = this.options, + xhr = this._initAjax(), + blob = owner._blob, + server = opts.server, + binary; + + xhr.connectRuntime( blob.ruid ); + + if ( opts.sendAsBinary ) { + server += (/\?/.test( server ) ? '&' : '?') + + $.param( owner._formData ); + + binary = blob.uid; + } else { + $.each( owner._formData, function( k, v ) { + xhr.exec( 'append', k, v ); + }); + + xhr.exec( 'appendBlob', opts.fileVal, blob.uid, + opts.filename || owner._formData.name || '' ); + } + + this._setRequestHeader( xhr, opts.headers ); + xhr.exec( 'send', { + method: opts.method, + url: server, + forceURLStream: opts.forceURLStream, + mimeType: 'application/octet-stream' + }, binary ); + }, + + getStatus: function() { + return this._status; + }, + + getResponse: function() { + return this._response || ''; + }, + + getResponseAsJson: function() { + return this._responseJson; + }, + + abort: function() { + var xhr = this._xhr; + + if ( xhr ) { + xhr.exec('abort'); + xhr.destroy(); + this._xhr = xhr = null; + } + }, + + destroy: function() { + this.abort(); + }, + + _initAjax: function() { + var me = this, + xhr = new RuntimeClient('XMLHttpRequest'); + + xhr.on( 'uploadprogress progress', function( e ) { + var percent = e.loaded / e.total; + percent = Math.min( 1, Math.max( 0, percent ) ); + return me.trigger( 'progress', percent ); + }); + + xhr.on( 'load', function() { + var status = xhr.exec('getStatus'), + readBody = false, + err = '', + p; + + xhr.off(); + me._xhr = null; + + if ( status >= 200 && status < 300 ) { + readBody = true; + } else if ( status >= 500 && status < 600 ) { + readBody = true; + err = 'server'; + } else { + err = 'http'; + } + + if ( readBody ) { + me._response = xhr.exec('getResponse'); + me._response = decodeURIComponent( me._response ); + + // flash 处理可能存在 bug, 没辙只能靠 js 了 + // try { + // me._responseJson = xhr.exec('getResponseAsJson'); + // } catch ( error ) { + + p = window.JSON && window.JSON.parse || function( s ) { + try { + return new Function('return ' + s).call(); + } catch ( err ) { + return {}; + } + }; + me._responseJson = me._response ? p(me._response) : {}; + + // } + } + + xhr.destroy(); + xhr = null; + + return err ? me.trigger( 'error', err ) : me.trigger('load'); + }); + + xhr.on( 'error', function() { + xhr.off(); + me._xhr = null; + me.trigger( 'error', 'http' ); + }); + + me._xhr = xhr; + return xhr; + }, + + _setRequestHeader: function( xhr, headers ) { + $.each( headers, function( key, val ) { + xhr.exec( 'setRequestHeader', key, val ); + }); + } + }); + }); + /** + * @fileOverview 没有图像处理的版本。 + */ + define('preset/withoutimage',[ + 'base', + + // widgets + 'widgets/filednd', + 'widgets/filepaste', + 'widgets/filepicker', + 'widgets/queue', + 'widgets/runtime', + 'widgets/upload', + 'widgets/validator', + + // runtimes + // html5 + 'runtime/html5/blob', + 'runtime/html5/dnd', + 'runtime/html5/filepaste', + 'runtime/html5/filepicker', + 'runtime/html5/transport', + + // flash + 'runtime/flash/filepicker', + 'runtime/flash/transport' + ], function( Base ) { + return Base; + }); + define('webuploader',[ + 'preset/withoutimage' + ], function( preset ) { + return preset; + }); + return require('webuploader'); +}); diff --git a/public/webuploader/webuploader.withoutimage.min.js b/public/webuploader/webuploader.withoutimage.min.js new file mode 100644 index 0000000..84ba198 --- /dev/null +++ b/public/webuploader/webuploader.withoutimage.min.js @@ -0,0 +1,2 @@ +/* WebUploader 0.1.5 */!function(a,b){var c,d={},e=function(a,b){var c,d,e;if("string"==typeof a)return h(a);for(c=[],d=a.length,e=0;d>e;e++)c.push(h(a[e]));return b.apply(null,c)},f=function(a,b,c){2===arguments.length&&(c=b,b=null),e(b||[],function(){g(a,c,arguments)})},g=function(a,b,c){var f,g={exports:b};"function"==typeof b&&(c.length||(c=[e,g.exports,g]),f=b.apply(null,c),void 0!==f&&(g.exports=f)),d[a]=g.exports},h=function(b){var c=d[b]||a[b];if(!c)throw new Error("`"+b+"` is undefined");return c},i=function(a){var b,c,e,f,g,h;h=function(a){return a&&a.charAt(0).toUpperCase()+a.substr(1)};for(b in d)if(c=a,d.hasOwnProperty(b)){for(e=b.split("/"),g=h(e.pop());f=h(e.shift());)c[f]=c[f]||{},c=c[f];c[g]=d[b]}return a},j=function(c){return a.__dollar=c,i(b(a,f,e))};"object"==typeof module&&"object"==typeof module.exports?module.exports=j():"function"==typeof define&&define.amd?define(["jquery"],j):(c=a.WebUploader,a.WebUploader=j(),a.WebUploader.noConflict=function(){a.WebUploader=c})}(window,function(a,b,c){return b("dollar-third",[],function(){var b=a.__dollar||a.jQuery||a.Zepto;if(!b)throw new Error("jQuery or Zepto not found!");return b}),b("dollar",["dollar-third"],function(a){return a}),b("promise-third",["dollar"],function(a){return{Deferred:a.Deferred,when:a.when,isPromise:function(a){return a&&"function"==typeof a.then}}}),b("promise",["promise-third"],function(a){return a}),b("base",["dollar","promise"],function(b,c){function d(a){return function(){return h.apply(a,arguments)}}function e(a,b){return function(){return a.apply(b,arguments)}}function f(a){var b;return Object.create?Object.create(a):(b=function(){},b.prototype=a,new b)}var g=function(){},h=Function.call;return{version:"0.1.5",$:b,Deferred:c.Deferred,isPromise:c.isPromise,when:c.when,browser:function(a){var b={},c=a.match(/WebKit\/([\d.]+)/),d=a.match(/Chrome\/([\d.]+)/)||a.match(/CriOS\/([\d.]+)/),e=a.match(/MSIE\s([\d\.]+)/)||a.match(/(?:trident)(?:.*rv:([\w.]+))?/i),f=a.match(/Firefox\/([\d.]+)/),g=a.match(/Safari\/([\d.]+)/),h=a.match(/OPR\/([\d.]+)/);return c&&(b.webkit=parseFloat(c[1])),d&&(b.chrome=parseFloat(d[1])),e&&(b.ie=parseFloat(e[1])),f&&(b.firefox=parseFloat(f[1])),g&&(b.safari=parseFloat(g[1])),h&&(b.opera=parseFloat(h[1])),b}(navigator.userAgent),os:function(a){var b={},c=a.match(/(?:Android);?[\s\/]+([\d.]+)?/),d=a.match(/(?:iPad|iPod|iPhone).*OS\s([\d_]+)/);return c&&(b.android=parseFloat(c[1])),d&&(b.ios=parseFloat(d[1].replace(/_/g,"."))),b}(navigator.userAgent),inherits:function(a,c,d){var e;return"function"==typeof c?(e=c,c=null):e=c&&c.hasOwnProperty("constructor")?c.constructor:function(){return a.apply(this,arguments)},b.extend(!0,e,a,d||{}),e.__super__=a.prototype,e.prototype=f(a.prototype),c&&b.extend(!0,e.prototype,c),e},noop:g,bindFn:e,log:function(){return a.console?e(console.log,console):g}(),nextTick:function(){return function(a){setTimeout(a,1)}}(),slice:d([].slice),guid:function(){var a=0;return function(b){for(var c=(+new Date).toString(32),d=0;5>d;d++)c+=Math.floor(65535*Math.random()).toString(32);return(b||"wu_")+c+(a++).toString(32)}}(),formatSize:function(a,b,c){var d;for(c=c||["B","K","M","G","TB"];(d=c.shift())&&a>1024;)a/=1024;return("B"===d?a:a.toFixed(b||2))+d}}}),b("mediator",["base"],function(a){function b(a,b,c,d){return f.grep(a,function(a){return!(!a||b&&a.e!==b||c&&a.cb!==c&&a.cb._cb!==c||d&&a.ctx!==d)})}function c(a,b,c){f.each((a||"").split(h),function(a,d){c(d,b)})}function d(a,b){for(var c,d=!1,e=-1,f=a.length;++e1?void(d.isPlainObject(b)&&d.isPlainObject(c[a])?d.extend(c[a],b):c[a]=b):a?c[a]:c},getStats:function(){var a=this.request("get-stats");return a?{successNum:a.numOfSuccess,progressNum:a.numOfProgress,cancelNum:a.numOfCancel,invalidNum:a.numOfInvalid,uploadFailNum:a.numOfUploadFailed,queueNum:a.numOfQueue,interruptNum:a.numofInterrupt}:{}},trigger:function(a){var c=[].slice.call(arguments,1),e=this.options,f="on"+a.substring(0,1).toUpperCase()+a.substring(1);return b.trigger.apply(this,arguments)===!1||d.isFunction(e[f])&&e[f].apply(this,c)===!1||d.isFunction(this[f])&&this[f].apply(this,c)===!1||b.trigger.apply(b,[this,a].concat(c))===!1?!1:!0},destroy:function(){this.request("destroy",arguments),this.off()},request:a.noop}),a.create=c.create=function(a){return new c(a)},a.Uploader=c,c}),b("runtime/runtime",["base","mediator"],function(a,b){function c(b){this.options=d.extend({container:document.body},b),this.uid=a.guid("rt_")}var d=a.$,e={},f=function(a){for(var b in a)if(a.hasOwnProperty(b))return b;return null};return d.extend(c.prototype,{getContainer:function(){var a,b,c=this.options;return this._container?this._container:(a=d(c.container||document.body),b=d(document.createElement("div")),b.attr("id","rt_"+this.uid),b.css({position:"absolute",top:"0px",left:"0px",width:"1px",height:"1px",overflow:"hidden"}),a.append(b),a.addClass("webuploader-container"),this._container=b,this._parent=a,b)},init:a.noop,exec:a.noop,destroy:function(){this._container&&this._container.remove(),this._parent&&this._parent.removeClass("webuploader-container"),this.off()}}),c.orders="html5,flash",c.addRuntime=function(a,b){e[a]=b},c.hasRuntime=function(a){return!!(a?e[a]:f(e))},c.create=function(a,b){var g,h;if(b=b||c.orders,d.each(b.split(/\s*,\s*/g),function(){return e[this]?(g=this,!1):void 0}),g=g||f(e),!g)throw new Error("Runtime Error");return h=new e[g](a)},b.installTo(c.prototype),c}),b("runtime/client",["base","mediator","runtime/runtime"],function(a,b,c){function d(b,d){var f,g=a.Deferred();this.uid=a.guid("client_"),this.runtimeReady=function(a){return g.done(a)},this.connectRuntime=function(b,h){if(f)throw new Error("already connected!");return g.done(h),"string"==typeof b&&e.get(b)&&(f=e.get(b)),f=f||e.get(null,d),f?(a.$.extend(f.options,b),f.__promise.then(g.resolve),f.__client++):(f=c.create(b,b.runtimeOrder),f.__promise=g.promise(),f.once("ready",g.resolve),f.init(),e.add(f),f.__client=1),d&&(f.__standalone=d),f},this.getRuntime=function(){return f},this.disconnectRuntime=function(){f&&(f.__client--,f.__client<=0&&(e.remove(f),delete f.__promise,f.destroy()),f=null)},this.exec=function(){if(f){var c=a.slice(arguments);return b&&c.unshift(b),f.exec.apply(this,c)}},this.getRuid=function(){return f&&f.uid},this.destroy=function(a){return function(){a&&a.apply(this,arguments),this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()}}(this.destroy)}var e;return e=function(){var a={};return{add:function(b){a[b.uid]=b},get:function(b,c){var d;if(b)return a[b];for(d in a)if(!c||!a[d].__standalone)return a[d];return null},remove:function(b){delete a[b.uid]}}}(),b.installTo(d.prototype),d}),b("lib/dnd",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},d.options,a),a.container=e(a.container),a.container.length&&c.call(this,"DragAndDrop")}var e=a.$;return d.options={accept:null,disableGlobalDnd:!1},a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/widget",["base","uploader"],function(a,b){function c(a){if(!a)return!1;var b=a.length,c=e.type(a);return 1===a.nodeType&&b?!0:"array"===c||"function"!==c&&"string"!==c&&(0===b||"number"==typeof b&&b>0&&b-1 in a)}function d(a){this.owner=a,this.options=a.options}var e=a.$,f=b.prototype._init,g=b.prototype.destroy,h={},i=[];return e.extend(d.prototype,{init:a.noop,invoke:function(a,b){var c=this.responseMap;return c&&a in c&&c[a]in this&&e.isFunction(this[c[a]])?this[c[a]].apply(this,b):h},request:function(){return this.owner.request.apply(this.owner,arguments)}}),e.extend(b.prototype,{_init:function(){var a=this,b=a._widgets=[],c=a.options.disableWidgets||"";return e.each(i,function(d,e){(!c||!~c.indexOf(e._name))&&b.push(new e(a))}),f.apply(a,arguments)},request:function(b,d,e){var f,g,i,j,k=0,l=this._widgets,m=l&&l.length,n=[],o=[];for(d=c(d)?d:[d];m>k;k++)f=l[k],g=f.invoke(b,d),g!==h&&(a.isPromise(g)?o.push(g):n.push(g));return e||o.length?(i=a.when.apply(a,o),j=i.pipe?"pipe":"then",i[j](function(){var b=a.Deferred(),c=arguments;return 1===c.length&&(c=c[0]),setTimeout(function(){b.resolve(c)},1),b.promise()})[e?j:"done"](e||a.noop)):n[0]},destroy:function(){g.apply(this,arguments),this._widgets=null}}),b.register=d.register=function(b,c){var f,g={init:"init",destroy:"destroy",name:"anonymous"};return 1===arguments.length?(c=b,e.each(c,function(a){return"_"===a[0]||"name"===a?void("name"===a&&(g.name=c.name)):void(g[a.replace(/[A-Z]/g,"-$&").toLowerCase()]=a)})):g=e.extend(g,b),c.responseMap=g,f=a.inherits(d,c),f._name=g.name,i.push(f),f},b.unRegister=d.unRegister=function(a){if(a&&"anonymous"!==a)for(var b=i.length;b--;)i[b]._name===a&&i.splice(b,1)},d}),b("widgets/filednd",["base","uploader","lib/dnd","widgets/widget"],function(a,b,c){var d=a.$;return b.options.dnd="",b.register({name:"dnd",init:function(b){if(b.dnd&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{disableGlobalDnd:b.disableGlobalDnd,container:b.dnd,accept:b.accept});return this.dnd=e=new c(h),e.once("ready",g.resolve),e.on("drop",function(a){f.request("add-file",[a])}),e.on("accept",function(a){return f.owner.trigger("dndAccept",a)}),e.init(),g.promise()}},destroy:function(){this.dnd&&this.dnd.destroy()}})}),b("lib/filepaste",["base","mediator","runtime/client"],function(a,b,c){function d(a){a=this.options=e.extend({},a),a.container=e(a.container||document.body),c.call(this,"FilePaste")}var e=a.$;return a.inherits(c,{constructor:d,init:function(){var a=this;a.connectRuntime(a.options,function(){a.exec("init"),a.trigger("ready")})}}),b.installTo(d.prototype),d}),b("widgets/filepaste",["base","uploader","lib/filepaste","widgets/widget"],function(a,b,c){var d=a.$;return b.register({name:"paste",init:function(b){if(b.paste&&"html5"===this.request("predict-runtime-type")){var e,f=this,g=a.Deferred(),h=d.extend({},{container:b.paste,accept:b.accept});return this.paste=e=new c(h),e.once("ready",g.resolve),e.on("paste",function(a){f.owner.request("add-file",[a])}),e.init(),g.promise()}},destroy:function(){this.paste&&this.paste.destroy()}})}),b("lib/blob",["base","runtime/client"],function(a,b){function c(a,c){var d=this;d.source=c,d.ruid=a,this.size=c.size||0,this.type=!c.type&&this.ext&&~"jpg,jpeg,png,gif,bmp".indexOf(this.ext)?"image/"+("jpg"===this.ext?"jpeg":this.ext):c.type||"application/octet-stream",b.call(d,"Blob"),this.uid=c.uid||this.uid,a&&d.connectRuntime(a)}return a.inherits(b,{constructor:c,slice:function(a,b){return this.exec("slice",a,b)},getSource:function(){return this.source}}),c}),b("lib/file",["base","lib/blob"],function(a,b){function c(a,c){var f;this.name=c.name||"untitled"+d++,f=e.exec(c.name)?RegExp.$1.toLowerCase():"",!f&&c.type&&(f=/\/(jpg|jpeg|png|gif|bmp)$/i.exec(c.type)?RegExp.$1.toLowerCase():"",this.name+="."+f),this.ext=f,this.lastModifiedDate=c.lastModifiedDate||(new Date).toLocaleString(),b.apply(this,arguments)}var d=1,e=/\.([^.]+)$/;return a.inherits(b,c)}),b("lib/filepicker",["base","runtime/client","lib/file"],function(b,c,d){function e(a){if(a=this.options=f.extend({},e.options,a),a.container=f(a.id),!a.container.length)throw new Error("按钮指定错误");a.innerHTML=a.innerHTML||a.label||a.container.html()||"",a.button=f(a.button||document.createElement("div")),a.button.html(a.innerHTML),a.container.html(a.button),c.call(this,"FilePicker",!0)}var f=b.$;return e.options={button:null,container:null,label:null,innerHTML:null,multiple:!0,accept:null,name:"file"},b.inherits(c,{constructor:e,init:function(){var c=this,e=c.options,g=e.button;g.addClass("webuploader-pick"),c.on("all",function(a){var b;switch(a){case"mouseenter":g.addClass("webuploader-pick-hover");break;case"mouseleave":g.removeClass("webuploader-pick-hover");break;case"change":b=c.exec("getFiles"),c.trigger("select",f.map(b,function(a){return a=new d(c.getRuid(),a),a._refer=e.container,a}),e.container)}}),c.connectRuntime(e,function(){c.refresh(),c.exec("init",e),c.trigger("ready")}),this._resizeHandler=b.bindFn(this.refresh,this),f(a).on("resize",this._resizeHandler)},refresh:function(){var a=this.getRuntime().getContainer(),b=this.options.button,c=b.outerWidth?b.outerWidth():b.width(),d=b.outerHeight?b.outerHeight():b.height(),e=b.offset();c&&d&&a.css({bottom:"auto",right:"auto",width:c+"px",height:d+"px"}).offset(e)},enable:function(){var a=this.options.button;a.removeClass("webuploader-pick-disable"),this.refresh()},disable:function(){var a=this.options.button;this.getRuntime().getContainer().css({top:"-99999px"}),a.addClass("webuploader-pick-disable")},destroy:function(){var b=this.options.button;f(a).off("resize",this._resizeHandler),b.removeClass("webuploader-pick-disable webuploader-pick-hover webuploader-pick")}}),e}),b("widgets/filepicker",["base","uploader","lib/filepicker","widgets/widget"],function(a,b,c){var d=a.$;return d.extend(b.options,{pick:null,accept:null}),b.register({name:"picker",init:function(a){return this.pickers=[],a.pick&&this.addBtn(a.pick)},refresh:function(){d.each(this.pickers,function(){this.refresh()})},addBtn:function(b){var e=this,f=e.options,g=f.accept,h=[];if(b)return d.isPlainObject(b)||(b={id:b}),d(b.id).each(function(){var i,j,k;k=a.Deferred(),i=d.extend({},b,{accept:d.isPlainObject(g)?[g]:g,swf:f.swf,runtimeOrder:f.runtimeOrder,id:this}),j=new c(i),j.once("ready",k.resolve),j.on("select",function(a){e.owner.request("add-file",[a])}),j.init(),e.pickers.push(j),h.push(k.promise())}),a.when.apply(a,h)},disable:function(){d.each(this.pickers,function(){this.disable()})},enable:function(){d.each(this.pickers,function(){this.enable()})},destroy:function(){d.each(this.pickers,function(){this.destroy()}),this.pickers=null}})}),b("file",["base","mediator"],function(a,b){function c(){return f+g++}function d(a){this.name=a.name||"Untitled",this.size=a.size||0,this.type=a.type||"application/octet-stream",this.lastModifiedDate=a.lastModifiedDate||1*new Date,this.id=c(),this.ext=h.exec(this.name)?RegExp.$1:"",this.statusText="",i[this.id]=d.Status.INITED,this.source=a,this.loaded=0,this.on("error",function(a){this.setStatus(d.Status.ERROR,a)})}var e=a.$,f="WU_FILE_",g=0,h=/\.([^.]+)$/,i={};return e.extend(d.prototype,{setStatus:function(a,b){var c=i[this.id];"undefined"!=typeof b&&(this.statusText=b),a!==c&&(i[this.id]=a,this.trigger("statuschange",a,c))},getStatus:function(){return i[this.id]},getSource:function(){return this.source},destroy:function(){this.off(),delete i[this.id]}}),b.installTo(d.prototype),d.Status={INITED:"inited",QUEUED:"queued",PROGRESS:"progress",ERROR:"error",COMPLETE:"complete",CANCELLED:"cancelled",INTERRUPT:"interrupt",INVALID:"invalid"},d}),b("queue",["base","mediator","file"],function(a,b,c){function d(){this.stats={numOfQueue:0,numOfSuccess:0,numOfCancel:0,numOfProgress:0,numOfUploadFailed:0,numOfInvalid:0,numofDeleted:0,numofInterrupt:0},this._queue=[],this._map={}}var e=a.$,f=c.Status;return e.extend(d.prototype,{append:function(a){return this._queue.push(a),this._fileAdded(a),this},prepend:function(a){return this._queue.unshift(a),this._fileAdded(a),this},getFile:function(a){return"string"!=typeof a?a:this._map[a]},fetch:function(a){var b,c,d=this._queue.length;for(a=a||f.QUEUED,b=0;d>b;b++)if(c=this._queue[b],a===c.getStatus())return c;return null},sort:function(a){"function"==typeof a&&this._queue.sort(a)},getFiles:function(){for(var a,b=[].slice.call(arguments,0),c=[],d=0,f=this._queue.length;f>d;d++)a=this._queue[d],(!b.length||~e.inArray(a.getStatus(),b))&&c.push(a);return c},removeFile:function(a){var b=this._map[a.id];b&&(delete this._map[a.id],a.destroy(),this.stats.numofDeleted++)},_fileAdded:function(a){var b=this,c=this._map[a.id];c||(this._map[a.id]=a,a.on("statuschange",function(a,c){b._onFileStatusChange(a,c)}))},_onFileStatusChange:function(a,b){var c=this.stats;switch(b){case f.PROGRESS:c.numOfProgress--;break;case f.QUEUED:c.numOfQueue--;break;case f.ERROR:c.numOfUploadFailed--;break;case f.INVALID:c.numOfInvalid--;break;case f.INTERRUPT:c.numofInterrupt--}switch(a){case f.QUEUED:c.numOfQueue++;break;case f.PROGRESS:c.numOfProgress++;break;case f.ERROR:c.numOfUploadFailed++;break;case f.COMPLETE:c.numOfSuccess++;break;case f.CANCELLED:c.numOfCancel++;break;case f.INVALID:c.numOfInvalid++;break;case f.INTERRUPT:c.numofInterrupt++}}}),b.installTo(d.prototype),d}),b("widgets/queue",["base","uploader","queue","file","lib/file","runtime/client","widgets/widget"],function(a,b,c,d,e,f){var g=a.$,h=/\.\w+$/,i=d.Status;return b.register({name:"queue",init:function(b){var d,e,h,i,j,k,l,m=this;if(g.isPlainObject(b.accept)&&(b.accept=[b.accept]),b.accept){for(j=[],h=0,e=b.accept.length;e>h;h++)i=b.accept[h].extensions,i&&j.push(i);j.length&&(k="\\."+j.join(",").replace(/,/g,"$|\\.").replace(/\*/g,".*")+"$"),m.accept=new RegExp(k,"i")}return m.queue=new c,m.stats=m.queue.stats,"html5"===this.request("predict-runtime-type")?(d=a.Deferred(),this.placeholder=l=new f("Placeholder"),l.connectRuntime({runtimeOrder:"html5"},function(){m._ruid=l.getRuid(),d.resolve()}),d.promise()):void 0},_wrapFile:function(a){if(!(a instanceof d)){if(!(a instanceof e)){if(!this._ruid)throw new Error("Can't add external files.");a=new e(this._ruid,a)}a=new d(a)}return a},acceptFile:function(a){var b=!a||!a.size||this.accept&&h.exec(a.name)&&!this.accept.test(a.name);return!b},_addFile:function(a){var b=this;return a=b._wrapFile(a),b.owner.trigger("beforeFileQueued",a)?b.acceptFile(a)?(b.queue.append(a),b.owner.trigger("fileQueued",a),a):void b.owner.trigger("error","Q_TYPE_DENIED",a):void 0},getFile:function(a){return this.queue.getFile(a)},addFile:function(a){var b=this;a.length||(a=[a]),a=g.map(a,function(a){return b._addFile(a)}),b.owner.trigger("filesQueued",a),b.options.auto&&setTimeout(function(){b.request("start-upload")},20)},getStats:function(){return this.stats},removeFile:function(a,b){var c=this;a=a.id?a:c.queue.getFile(a),this.request("cancel-file",a),b&&this.queue.removeFile(a)},getFiles:function(){return this.queue.getFiles.apply(this.queue,arguments)},fetchFile:function(){return this.queue.fetch.apply(this.queue,arguments)},retry:function(a,b){var c,d,e,f=this;if(a)return a=a.id?a:f.queue.getFile(a),a.setStatus(i.QUEUED),void(b||f.request("start-upload"));for(c=f.queue.getFiles(i.ERROR),d=0,e=c.length;e>d;d++)a=c[d],a.setStatus(i.QUEUED);f.request("start-upload")},sortFiles:function(){return this.queue.sort.apply(this.queue,arguments)},reset:function(){this.owner.trigger("reset"),this.queue=new c,this.stats=this.queue.stats},destroy:function(){this.reset(),this.placeholder&&this.placeholder.destroy()}})}),b("widgets/runtime",["uploader","runtime/runtime","widgets/widget"],function(a,b){return a.support=function(){return b.hasRuntime.apply(b,arguments)},a.register({name:"runtime",init:function(){if(!this.predictRuntimeType())throw Error("Runtime Error")},predictRuntimeType:function(){var a,c,d=this.options.runtimeOrder||b.orders,e=this.type;if(!e)for(d=d.split(/\s*,\s*/g),a=0,c=d.length;c>a;a++)if(b.hasRuntime(d[a])){this.type=e=d[a];break}return e}})}),b("lib/transport",["base","runtime/client","mediator"],function(a,b,c){function d(a){var c=this;a=c.options=e.extend(!0,{},d.options,a||{}),b.call(this,"Transport"),this._blob=null,this._formData=a.formData||{},this._headers=a.headers||{},this.on("progress",this._timeout),this.on("load error",function(){c.trigger("progress",1),clearTimeout(c._timer)})}var e=a.$;return d.options={server:"",method:"POST",withCredentials:!1,fileVal:"file",timeout:12e4,formData:{},headers:{},sendAsBinary:!1},e.extend(d.prototype,{appendBlob:function(a,b,c){var d=this,e=d.options;d.getRuid()&&d.disconnectRuntime(),d.connectRuntime(b.ruid,function(){d.exec("init")}),d._blob=b,e.fileVal=a||e.fileVal,e.filename=c||e.filename},append:function(a,b){"object"==typeof a?e.extend(this._formData,a):this._formData[a]=b},setRequestHeader:function(a,b){"object"==typeof a?e.extend(this._headers,a):this._headers[a]=b},send:function(a){this.exec("send",a),this._timeout()},abort:function(){return clearTimeout(this._timer),this.exec("abort")},destroy:function(){this.trigger("destroy"),this.off(),this.exec("destroy"),this.disconnectRuntime()},getResponse:function(){return this.exec("getResponse")},getResponseAsJson:function(){return this.exec("getResponseAsJson")},getStatus:function(){return this.exec("getStatus")},_timeout:function(){var a=this,b=a.options.timeout;b&&(clearTimeout(a._timer),a._timer=setTimeout(function(){a.abort(),a.trigger("error","timeout")},b))}}),c.installTo(d.prototype),d}),b("widgets/upload",["base","uploader","file","lib/transport","widgets/widget"],function(a,b,c,d){function e(a,b){var c,d,e=[],f=a.source,g=f.size,h=b?Math.ceil(g/b):1,i=0,j=0;for(d={file:a,has:function(){return!!e.length},shift:function(){return e.shift()},unshift:function(a){e.unshift(a)}};h>j;)c=Math.min(b,g-i),e.push({file:a,start:i,end:b?i+c:g,total:g,chunks:h,chunk:j++,cuted:d}),i+=c;return a.blocks=e.concat(),a.remaning=e.length,d}var f=a.$,g=a.isPromise,h=c.Status;f.extend(b.options,{prepareNextFile:!1,chunked:!1,chunkSize:5242880,chunkRetry:2,threads:3,formData:{}}),b.register({name:"upload",init:function(){var b=this.owner,c=this;this.runing=!1,this.progress=!1,b.on("startUpload",function(){c.progress=!0}).on("uploadFinished",function(){c.progress=!1}),this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this.__tick=a.bindFn(this._tick,this),b.on("uploadComplete",function(a){a.blocks&&f.each(a.blocks,function(a,b){b.transport&&(b.transport.abort(),b.transport.destroy()),delete b.transport}),delete a.blocks,delete a.remaning})},reset:function(){this.request("stop-upload",!0),this.runing=!1,this.pool=[],this.stack=[],this.pending=[],this.remaning=0,this._trigged=!1,this._promise=null},startUpload:function(b){var c=this;if(f.each(c.request("get-files",h.INVALID),function(){c.request("remove-file",this)}),b)if(b=b.id?b:c.request("get-file",b),b.getStatus()===h.INTERRUPT)f.each(c.pool,function(a,c){c.file===b&&c.transport&&c.transport.send()}),b.setStatus(h.QUEUED);else{if(b.getStatus()===h.PROGRESS)return;b.setStatus(h.QUEUED)}else f.each(c.request("get-files",[h.INITED]),function(){this.setStatus(h.QUEUED)});c.runing||(c.runing=!0,f.each(c.pool,function(a,b){var d=b.file;d.getStatus()===h.INTERRUPT&&(d.setStatus(h.PROGRESS),c._trigged=!1,b.transport&&b.transport.send())}),b||f.each(c.request("get-files",h.INTERRUPT),function(){this.setStatus(h.PROGRESS)}),c._trigged=!1,a.nextTick(c.__tick),c.owner.trigger("startUpload"))},stopUpload:function(b,c){var d=this;if(b===!0&&(c=b,b=null),d.runing!==!1){if(b){if(b=b.id?b:d.request("get-file",b),b.getStatus()!==h.PROGRESS&&b.getStatus()!==h.QUEUED)return;return b.setStatus(h.INTERRUPT),f.each(d.pool,function(a,c){c.file===b&&(c.transport&&c.transport.abort(),d._putback(c),d._popBlock(c))}),a.nextTick(d.__tick)}d.runing=!1,this._promise&&this._promise.file&&this._promise.file.setStatus(h.INTERRUPT),c&&f.each(d.pool,function(a,b){b.transport&&b.transport.abort(),b.file.setStatus(h.INTERRUPT)}),d.owner.trigger("stopUpload")}},cancelFile:function(a){a=a.id?a:this.request("get-file",a),a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),a.setStatus(h.CANCELLED),this.owner.trigger("fileDequeued",a)},isInProgress:function(){return!!this.progress},_getStats:function(){return this.request("get-stats")},skipFile:function(a,b){a=a.id?a:this.request("get-file",a),a.setStatus(b||h.COMPLETE),a.skipped=!0,a.blocks&&f.each(a.blocks,function(a,b){var c=b.transport;c&&(c.abort(),c.destroy(),delete b.transport)}),this.owner.trigger("uploadSkip",a)},_tick:function(){var b,c,d=this,e=d.options;return d._promise?d._promise.always(d.__tick):void(d.pool.length1&&(f.each(k.blocks,function(a,b){d+=(b.percentage||0)*(b.end-b.start)}),c=d/k.size),i.trigger("uploadProgress",k,c||0)}),c=function(a){var c;return e=l.getResponseAsJson()||{},e._raw=l.getResponse(),c=function(b){a=b},i.trigger("uploadAccept",b,e,c)||(a=a||"server"),a},l.on("error",function(a,d){b.retried=b.retried||0,b.chunks>1&&~"http,abort".indexOf(a)&&b.retried1&&f.extend(m,{chunks:b.chunks,chunk:b.chunk}),i.trigger("uploadBeforeSend",b,m,n),l.appendBlob(j.fileVal,b.blob,k.name),l.append(m),l.setRequestHeader(n),l.send()},_finishFile:function(a,b,c){var d=this.owner;return d.request("after-send-file",arguments,function(){a.setStatus(h.COMPLETE),d.trigger("uploadSuccess",a,b,c)}).fail(function(b){a.getStatus()===h.PROGRESS&&a.setStatus(h.ERROR,b),d.trigger("uploadError",a,b)}).always(function(){d.trigger("uploadComplete",a)})}})}),b("widgets/validator",["base","uploader","file","widgets/widget"],function(a,b,c){var d,e=a.$,f={};return d={addValidator:function(a,b){f[a]=b},removeValidator:function(a){delete f[a]}},b.register({name:"validator",init:function(){var b=this;a.nextTick(function(){e.each(f,function(){this.call(b.owner)})})}}),d.addValidator("fileNumLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileNumLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){return c>=d&&e&&(e=!1,this.trigger("error","Q_EXCEED_NUM_LIMIT",d,a),setTimeout(function(){e=!0},1)),c>=d?!1:!0}),a.on("fileQueued",function(){c++}),a.on("fileDequeued",function(){c--}),a.on("reset",function(){c=0}))}),d.addValidator("fileSizeLimit",function(){var a=this,b=a.options,c=0,d=parseInt(b.fileSizeLimit,10),e=!0;d&&(a.on("beforeFileQueued",function(a){var b=c+a.size>d;return b&&e&&(e=!1,this.trigger("error","Q_EXCEED_SIZE_LIMIT",d,a),setTimeout(function(){e=!0},1)),b?!1:!0}),a.on("fileQueued",function(a){c+=a.size}),a.on("fileDequeued",function(a){c-=a.size}),a.on("reset",function(){c=0}))}),d.addValidator("fileSingleSizeLimit",function(){var a=this,b=a.options,d=b.fileSingleSizeLimit;d&&a.on("beforeFileQueued",function(a){return a.size>d?(a.setStatus(c.Status.INVALID,"exceed_size"),this.trigger("error","F_EXCEED_SIZE",d,a),!1):void 0})}),d.addValidator("duplicate",function(){function a(a){for(var b,c=0,d=0,e=a.length;e>d;d++)b=a.charCodeAt(d),c=b+(c<<6)+(c<<16)-c;return c}var b=this,c=b.options,d={};c.duplicate||(b.on("beforeFileQueued",function(b){var c=b.__hash||(b.__hash=a(b.name+b.size+b.lastModifiedDate));return d[c]?(this.trigger("error","F_DUPLICATE",b),!1):void 0}),b.on("fileQueued",function(a){var b=a.__hash;b&&(d[b]=!0)}),b.on("fileDequeued",function(a){var b=a.__hash;b&&delete d[b]}),b.on("reset",function(){d={}}))}),d}),b("runtime/compbase",[],function(){function a(a,b){this.owner=a,this.options=a.options,this.getRuntime=function(){return b},this.getRuid=function(){return b.uid},this.trigger=function(){return a.trigger.apply(a,arguments)}}return a}),b("runtime/html5/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a={},d=this,e=this.destroy;c.apply(d,arguments),d.type=f,d.exec=function(c,e){var f,h=this,i=h.uid,j=b.slice(arguments,2);return g[c]&&(f=a[i]=a[i]||new g[c](h,d),f[e])?f[e].apply(f,j):void 0},d.destroy=function(){return e&&e.apply(this,arguments)}}var f="html5",g={};return b.inherits(c,{constructor:e,init:function(){var a=this;setTimeout(function(){a.trigger("ready")},1)}}),e.register=function(a,c){var e=g[a]=b.inherits(d,c);return e},a.Blob&&a.FileReader&&a.DataView&&c.addRuntime(f,e),e}),b("runtime/html5/blob",["runtime/html5/runtime","lib/blob"],function(a,b){return a.register("Blob",{slice:function(a,c){var d=this.owner.source,e=d.slice||d.webkitSlice||d.mozSlice; +return d=e.call(d,a,c),new b(this.getRuid(),d)}})}),b("runtime/html5/dnd",["base","runtime/html5/runtime","lib/file"],function(a,b,c){var d=a.$,e="webuploader-dnd-";return b.register("DragAndDrop",{init:function(){var b=this.elem=this.options.container;this.dragEnterHandler=a.bindFn(this._dragEnterHandler,this),this.dragOverHandler=a.bindFn(this._dragOverHandler,this),this.dragLeaveHandler=a.bindFn(this._dragLeaveHandler,this),this.dropHandler=a.bindFn(this._dropHandler,this),this.dndOver=!1,b.on("dragenter",this.dragEnterHandler),b.on("dragover",this.dragOverHandler),b.on("dragleave",this.dragLeaveHandler),b.on("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).on("dragover",this.dragOverHandler),d(document).on("drop",this.dropHandler))},_dragEnterHandler:function(a){var b,c=this,d=c._denied||!1;return a=a.originalEvent||a,c.dndOver||(c.dndOver=!0,b=a.dataTransfer.items,b&&b.length&&(c._denied=d=!c.trigger("accept",b)),c.elem.addClass(e+"over"),c.elem[d?"addClass":"removeClass"](e+"denied")),a.dataTransfer.dropEffect=d?"none":"copy",!1},_dragOverHandler:function(a){var b=this.elem.parent().get(0);return b&&!d.contains(b,a.currentTarget)?!1:(clearTimeout(this._leaveTimer),this._dragEnterHandler.call(this,a),!1)},_dragLeaveHandler:function(){var a,b=this;return a=function(){b.dndOver=!1,b.elem.removeClass(e+"over "+e+"denied")},clearTimeout(b._leaveTimer),b._leaveTimer=setTimeout(a,100),!1},_dropHandler:function(a){var b,f,g=this,h=g.getRuid(),i=g.elem.parent().get(0);if(i&&!d.contains(i,a.currentTarget))return!1;a=a.originalEvent||a,b=a.dataTransfer;try{f=b.getData("text/html")}catch(j){}return f?void 0:(g._getTansferFiles(b,function(a){g.trigger("drop",d.map(a,function(a){return new c(h,a)}))}),g.dndOver=!1,g.elem.removeClass(e+"over"),!1)},_getTansferFiles:function(b,c){var d,e,f,g,h,i,j,k=[],l=[];for(d=b.items,e=b.files,j=!(!d||!d[0].webkitGetAsEntry),h=0,i=e.length;i>h;h++)f=e[h],g=d&&d[h],j&&g.webkitGetAsEntry().isDirectory?l.push(this._traverseDirectoryTree(g.webkitGetAsEntry(),k)):k.push(f);a.when.apply(a,l).done(function(){k.length&&c(k)})},_traverseDirectoryTree:function(b,c){var d=a.Deferred(),e=this;return b.isFile?b.file(function(a){c.push(a),d.resolve()}):b.isDirectory&&b.createReader().readEntries(function(b){var f,g=b.length,h=[],i=[];for(f=0;g>f;f++)h.push(e._traverseDirectoryTree(b[f],i));a.when.apply(a,h).then(function(){c.push.apply(c,i),d.resolve()},d.reject)}),d.promise()},destroy:function(){var a=this.elem;a&&(a.off("dragenter",this.dragEnterHandler),a.off("dragover",this.dragOverHandler),a.off("dragleave",this.dragLeaveHandler),a.off("drop",this.dropHandler),this.options.disableGlobalDnd&&(d(document).off("dragover",this.dragOverHandler),d(document).off("drop",this.dropHandler)))}})}),b("runtime/html5/filepaste",["base","runtime/html5/runtime","lib/file"],function(a,b,c){return b.register("FilePaste",{init:function(){var b,c,d,e,f=this.options,g=this.elem=f.container,h=".*";if(f.accept){for(b=[],c=0,d=f.accept.length;d>c;c++)e=f.accept[c].mimeTypes,e&&b.push(e);b.length&&(h=b.join(","),h=h.replace(/,/g,"|").replace(/\*/g,".*"))}this.accept=h=new RegExp(h,"i"),this.hander=a.bindFn(this._pasteHander,this),g.on("paste",this.hander)},_pasteHander:function(a){var b,d,e,f,g,h=[],i=this.getRuid();for(a=a.originalEvent||a,b=a.clipboardData.items,f=0,g=b.length;g>f;f++)d=b[f],"file"===d.kind&&(e=d.getAsFile())&&h.push(new c(i,e));h.length&&(a.preventDefault(),a.stopPropagation(),this.trigger("paste",h))},destroy:function(){this.elem.off("paste",this.hander)}})}),b("runtime/html5/filepicker",["base","runtime/html5/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(){var a,b,d,e,f=this.getRuntime().getContainer(),g=this,h=g.owner,i=g.options,j=this.label=c(document.createElement("label")),k=this.input=c(document.createElement("input"));if(k.attr("type","file"),k.attr("name",i.name),k.addClass("webuploader-element-invisible"),j.on("click",function(){k.trigger("click")}),j.css({opacity:0,width:"100%",height:"100%",display:"block",cursor:"pointer",background:"#ffffff"}),i.multiple&&k.attr("multiple","multiple"),i.accept&&i.accept.length>0){for(a=[],b=0,d=i.accept.length;d>b;b++)a.push(i.accept[b].mimeTypes);k.attr("accept",a.join(","))}f.append(k),f.append(j),e=function(a){h.trigger(a.type)},k.on("change",function(a){var b,d=arguments.callee;g.files=a.target.files,b=this.cloneNode(!0),b.value=null,this.parentNode.replaceChild(b,this),k.off(),k=c(b).on("change",d).on("mouseenter mouseleave",e),h.trigger("change")}),j.on("mouseenter mouseleave",e)},getFiles:function(){return this.files},destroy:function(){this.input.off(),this.label.off()}})}),b("runtime/html5/transport",["base","runtime/html5/runtime"],function(a,b){var c=a.noop,d=a.$;return b.register("Transport",{init:function(){this._status=0,this._response=null},send:function(){var b,c,e,f=this.owner,g=this.options,h=this._initAjax(),i=f._blob,j=g.server;g.sendAsBinary?(j+=(/\?/.test(j)?"&":"?")+d.param(f._formData),c=i.getSource()):(b=new FormData,d.each(f._formData,function(a,c){b.append(a,c)}),b.append(g.fileVal,i.getSource(),g.filename||f._formData.name||"")),g.withCredentials&&"withCredentials"in h?(h.open(g.method,j,!0),h.withCredentials=!0):h.open(g.method,j),this._setRequestHeader(h,g.headers),c?(h.overrideMimeType&&h.overrideMimeType("application/octet-stream"),a.os.android?(e=new FileReader,e.onload=function(){h.send(this.result),e=e.onload=null},e.readAsArrayBuffer(c)):h.send(c)):h.send(b)},getResponse:function(){return this._response},getResponseAsJson:function(){return this._parseJson(this._response)},getStatus:function(){return this._status},abort:function(){var a=this._xhr;a&&(a.upload.onprogress=c,a.onreadystatechange=c,a.abort(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var a=this,b=new XMLHttpRequest,d=this.options;return!d.withCredentials||"withCredentials"in b||"undefined"==typeof XDomainRequest||(b=new XDomainRequest),b.upload.onprogress=function(b){var c=0;return b.lengthComputable&&(c=b.loaded/b.total),a.trigger("progress",c)},b.onreadystatechange=function(){return 4===b.readyState?(b.upload.onprogress=c,b.onreadystatechange=c,a._xhr=null,a._status=b.status,b.status>=200&&b.status<300?(a._response=b.responseText,a.trigger("load")):b.status>=500&&b.status<600?(a._response=b.responseText,a.trigger("error","server")):a.trigger("error",a._status?"http":"abort")):void 0},a._xhr=b,b},_setRequestHeader:function(a,b){d.each(b,function(b,c){a.setRequestHeader(b,c)})},_parseJson:function(a){var b;try{b=JSON.parse(a)}catch(c){b={}}return b}})}),b("runtime/flash/runtime",["base","runtime/runtime","runtime/compbase"],function(b,c,d){function e(){var a;try{a=navigator.plugins["Shockwave Flash"],a=a.description}catch(b){try{a=new ActiveXObject("ShockwaveFlash.ShockwaveFlash").GetVariable("$version")}catch(c){a="0.0"}}return a=a.match(/\d+/g),parseFloat(a[0]+"."+a[1],10)}function f(){function d(a,b){var c,d,e=a.type||a;c=e.split("::"),d=c[0],e=c[1],"Ready"===e&&d===j.uid?j.trigger("ready"):f[d]&&f[d].trigger(e.toLowerCase(),a,b)}var e={},f={},g=this.destroy,j=this,k=b.guid("webuploader_");c.apply(j,arguments),j.type=h,j.exec=function(a,c){var d,g=this,h=g.uid,k=b.slice(arguments,2);return f[h]=g,i[a]&&(e[h]||(e[h]=new i[a](g,j)),d=e[h],d[c])?d[c].apply(d,k):j.flashExec.apply(g,arguments)},a[k]=function(){var a=arguments;setTimeout(function(){d.apply(null,a)},1)},this.jsreciver=k,this.destroy=function(){return g&&g.apply(this,arguments)},this.flashExec=function(a,c){var d=j.getFlash(),e=b.slice(arguments,2);return d.exec(this.uid,a,c,e)}}var g=b.$,h="flash",i={};return b.inherits(c,{constructor:f,init:function(){var a,c=this.getContainer(),d=this.options;c.css({position:"absolute",top:"-8px",left:"-8px",width:"9px",height:"9px",overflow:"hidden"}),a='',c.html(a)},getFlash:function(){return this._flash?this._flash:(this._flash=g("#"+this.uid).get(0),this._flash)}}),f.register=function(a,c){return c=i[a]=b.inherits(d,g.extend({flashExec:function(){var a=this.owner,b=this.getRuntime();return b.flashExec.apply(a,arguments)}},c))},e()>=11.4&&c.addRuntime(h,f),f}),b("runtime/flash/filepicker",["base","runtime/flash/runtime"],function(a,b){var c=a.$;return b.register("FilePicker",{init:function(a){var b,d,e=c.extend({},a);for(b=e.accept&&e.accept.length,d=0;b>d;d++)e.accept[d].title||(e.accept[d].title="Files");delete e.button,delete e.id,delete e.container,this.flashExec("FilePicker","init",e)},destroy:function(){this.flashExec("FilePicker","destroy")}})}),b("runtime/flash/transport",["base","runtime/flash/runtime","runtime/client"],function(b,c,d){var e=b.$;return c.register("Transport",{init:function(){this._status=0,this._response=null,this._responseJson=null},send:function(){var a,b=this.owner,c=this.options,d=this._initAjax(),f=b._blob,g=c.server;d.connectRuntime(f.ruid),c.sendAsBinary?(g+=(/\?/.test(g)?"&":"?")+e.param(b._formData),a=f.uid):(e.each(b._formData,function(a,b){d.exec("append",a,b)}),d.exec("appendBlob",c.fileVal,f.uid,c.filename||b._formData.name||"")),this._setRequestHeader(d,c.headers),d.exec("send",{method:c.method,url:g,forceURLStream:c.forceURLStream,mimeType:"application/octet-stream"},a)},getStatus:function(){return this._status},getResponse:function(){return this._response||""},getResponseAsJson:function(){return this._responseJson},abort:function(){var a=this._xhr;a&&(a.exec("abort"),a.destroy(),this._xhr=a=null)},destroy:function(){this.abort()},_initAjax:function(){var b=this,c=new d("XMLHttpRequest");return c.on("uploadprogress progress",function(a){var c=a.loaded/a.total;return c=Math.min(1,Math.max(0,c)),b.trigger("progress",c)}),c.on("load",function(){var d,e=c.exec("getStatus"),f=!1,g="";return c.off(),b._xhr=null,e>=200&&300>e?f=!0:e>=500&&600>e?(f=!0,g="server"):g="http",f&&(b._response=c.exec("getResponse"),b._response=decodeURIComponent(b._response),d=a.JSON&&a.JSON.parse||function(a){try{return new Function("return "+a).call()}catch(b){return{}}},b._responseJson=b._response?d(b._response):{}),c.destroy(),c=null,g?b.trigger("error",g):b.trigger("load")}),c.on("error",function(){c.off(),b._xhr=null,b.trigger("error","http")}),b._xhr=c,c},_setRequestHeader:function(a,b){e.each(b,function(b,c){a.exec("setRequestHeader",b,c)})}})}),b("preset/withoutimage",["base","widgets/filednd","widgets/filepaste","widgets/filepicker","widgets/queue","widgets/runtime","widgets/upload","widgets/validator","runtime/html5/blob","runtime/html5/dnd","runtime/html5/filepaste","runtime/html5/filepicker","runtime/html5/transport","runtime/flash/filepicker","runtime/flash/transport"],function(a){return a}),b("webuploader",["preset/withoutimage"],function(a){return a}),c("webuploader")}); \ No newline at end of file diff --git a/public/yfcmf/dispatch_jump.html b/public/yfcmf/dispatch_jump.html new file mode 100644 index 0000000..d7923c7 --- /dev/null +++ b/public/yfcmf/dispatch_jump.html @@ -0,0 +1,33 @@ + + + + + {:lang('error')} + + + + + +

+
+
+

{:lang('reason')}

+

!

+

SZCAEE:{:lang('automatically')} {:lang('jump')} {:lang('wait second')}

+
+
+
+ + + \ No newline at end of file diff --git a/public/yfcmf/error.html b/public/yfcmf/error.html new file mode 100644 index 0000000..2ac3ba4 --- /dev/null +++ b/public/yfcmf/error.html @@ -0,0 +1,34 @@ + + + + + 访问错误--页面跳转中... + + + + + + + +
+
{:config('sys_name')}
+
+
+
+
SORRY,你要访问的页面弄丢了
+
你可以通过以下方式重新访问......
+
+
+ + + + diff --git a/public/yfcmf/yfcmf.css b/public/yfcmf/yfcmf.css new file mode 100644 index 0000000..bc7ae80 --- /dev/null +++ b/public/yfcmf/yfcmf.css @@ -0,0 +1,22 @@ +.font12{font-size:14px}.highmsg{color:red}.rigbg{color:#bb3f2b;font-weight:700}.none{display:none}.look{padding:20px}.look ul li{list-style:none;line-height:30px}.charsLeft,.charsLeft1,.charsLeft3,.charsLeft2,.charsLeftone,.charsLeftone2{color:red}.rule{border:#ccc solid 1px;height:30px;width:90pt}.ruleadd{height:30px;margin-bottom:3px}.sl-left10{margin-left:5px}.thopen{min-width:130px}.wh50{width:70px}.wh40,.wh50{height:30px;border:#ccc solid 1px}.wh40{width:40px}.wh30{width:30px;height:30px;border:#ccc solid 1px;text-align:center}.rule-top{line-height:25px;color:#666}.sl-indextop7{margin-top:4px}.sl-indextop10{margin-top:10px}.sl-font14{font-size:14px}.sl-line-height25{line-height:25px}.sl-date{height:30px;width:200px}.top10{margin-top:10px}.top20{margin-top:20px}.top30{margin-top:30px}.top40{margin-top:40px}.file{position:relative;display:inline-block;background:#d0eeff;border:1px solid #99d3f5;border-radius:4px;padding:4px 9pt;overflow:hidden;color:#1e88c7;text-decoration:none;text-indent:0;line-height:25px}.file input{position:absolute;font-size:75pt;right:10;top:0;opacity:0}.file:hover{background:#aadffd;border-color:#78c3f3;color:#004974;text-decoration:none}.list_order{width:50px;border:#fc6 solid 1px}.bgccc{color:#999}.bgc{color:#666}.input_last{line-height:35px;margin-left:10px}.maintop{width:100%;padding-bottom:10px}.admin_sea{height:30px}.lbl{line-height:30px}.pagination{margin:0;float:right}.pagination ul{display:inline-block;list-style:none;*display:inline;*zoom:1;margin-left:0;margin-bottom:0;border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,.05)}.pagination ul>li{display:inline}.pagination #lastspan,.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 9pt;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>.active>a,.pagination ul>.active>span,.pagination ul>li>a:focus,.pagination ul>li>a:hover{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>a,.pagination ul>.disabled>a:focus,.pagination ul>.disabled>a:hover,.pagination ul>.disabled>span{color:#999;background-color:transparent;cursor:default}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination #lastspan,.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large #lastspan,.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-large #lastspan,.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-mini ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>a,.pagination-small ul>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-mini ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>a,.pagination-small ul>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:0 6px;font-size:10.5px}.first_checkbox li{float:none;border-bottom:1px dotted #ccc;overflow:auto;font-size:1pc}.first_checkbox li:last-child{border-bottom:0}.first_checkbox li input{position:relative;top:8px;margin-left:3px;margin-right:5px}.first_checkbox li span{margin-right:25px}.first_checkbox li .two_checkbox{margin-left:30px;margin-bottom:10px}#w{width:750px;margin:0 auto;padding-top:30px}#content,#w{display:block}#content{width:100%;background:#fff;padding:25px 20px;padding-bottom:35px;box-shadow:rgba(0,0,0,.1) 0 1px 2px 0}.flatbtn-blu{box-sizing:border-box;display:inline-block;outline:0;border:0;color:#edf4f9;text-decoration:none;background-color:#4f94cf;border-color:rgba(0,0,0,.1) rgba(0,0,0,.1) rgba(0,0,0,.25);font-size:1.3em;font-weight:700;padding:9pt 26px;line-height:normal;text-align:center;vertical-align:middle;cursor:pointer;text-transform:uppercase;text-shadow:0 1px 0 rgba(0,0,0,.3);border-radius:3px;box-shadow:0 1px 1px rgba(0,0,0,.2)}.flatbtn-blu:hover{color:#fff;background-color:#519dde}.flatbtn-blu:active{box-shadow:inset 0 1px 5px rgba(0,0,0,.1)}#lean_overlay{position:fixed;z-index:100;top:0;left:0;height:100%;width:100%;background:#000;display:none}#loginmodal{width:300px;padding:15px 20px;background:#f3f6fa;border-radius:6px;box-shadow:0 1px 5px rgba(0,0,0,.5)}#loginform label{display:block;font-size:1.1em;font-weight:700;color:#7c8291;margin-bottom:3px}.txtfield{display:block;width:100%;padding:6px 5px;margin-bottom:15px;font-family:Helvetica Neue,Helvetica,Verdana,sans-serif;color:#7988a3;font-size:1.4em;text-shadow:1px 1px 0 hsla(0,0%,100%,.8);background-color:#fff;background-image:-webkit-gradient(linear,left top,left bottom,from(#edf3f9),to(#fff));background-image:-webkit-linear-gradient(top,#edf3f9,#fff);background-image:linear-gradient(top,#edf3f9,#fff);border:1px solid;border-color:#abbce8 #c3cae0 #b9c8ef;border-radius:4px;box-shadow:inset 0 1px 2px rgba(0,0,0,.25),0 1px hsla(0,0%,100%,.4);-webkit-transition:all .25s linear;transition:all .25s linear}.txtfield:focus{outline:0;color:#525864;border-color:#84c0ee;box-shadow:inset 0 1px 2px rgba(0,0,0,.15),0 0 7px #96c7ec}.excel_file{width:9pc;height:41px;cursor:pointer;font-size:30px;outline:medium 0;position:absolute}.main{margin:0 auto;padding:15px;width:750px;font-family:microsoft yahei}.cf:after,.cf:before{display:table;content:"";line-height:0}.cf:after{clear:both}.cf{*zoom:1}.upload-area{position:relative;float:left;margin-right:30px;width:200px;height:200px;background-color:#f5f5f5;border:2px solid #e1e1e1}.upload-area .file-tips{position:absolute;top:90px;left:0;padding:0 15px;width:170px;line-height:1.4;font-size:9pt;color:#a8a8a3;text-align:center}.userup-icon{display:inline-block;margin-right:3px;width:1pc;height:1pc;vertical-align:-2px;background:url(__ROOT__/public/img/userup_icon.png) no-repeat}.uploadify-button{line-height:90pt!important;text-align:center}.preview-area{float:left}.tcrop{clear:right;font-size:14px;font-weight:700}.update-pic .crop{background:url(__ROOT__/public/img/headicon.png) no-repeat scroll center center #eee;float:left;margin-bottom:20px;margin-top:10px;overflow:hidden}.crop100{height:75pt;width:75pt}.crop60{height:60px;margin-left:20px;width:60px}.update-pic .save-pic{clear:left;margin-right:20px}.update-pic .uppic-btn{background-color:#348fd4;color:#fff;display:block;float:left;font-size:1pc;height:30px;line-height:30px;text-align:center;vertical-align:middle;width:89px}.preview{position:absolute;top:0;left:0;z-index:11;width:200px;height:200px;overflow:hidden;background:#fff;display:none}#news_titleshort{margin-left:15px}@media screen and (max-width:767px){.all-btn{margin-left:9pt}.header-nav{margin-top:45px}.btn-xs,.margintop5{margin-top:5px}.btm-input{margin-top:0}.btn-sespan{padding-top:5px}.infobox{width:100%}#news_titleshort{margin-left:0;margin-top:10px}.collapse_btn{display:block;visibility:inherit}.collapse_btn ul li{float:left;margin:5px 10px}}a.maintain{border:1px solid #FFF;}.news_order{width:30px;text-align:center;margin:0 3px;}.r-20{margin-right:-20px;}a.newsorder:hover{text-decoration:none;}.modal{overflow:scroll}@media (min-width:768px){.modal-dialog{width:720px;margin-top:90px;}.ace-file-input{width: 50%}}@media (max-width:767px){.ace-file-input{width: 83.333%}}input[type=checkbox].ace.ace-switch.ace-switch-4+.lbl::before {text-indent: -32px;}.action-buttons{display:inline-block;vertical-align:top;} + .filebtn{ + position: relative; + display: inline-block; + background: #d0eeff; + border: 1px solid #99d3f5; + border-radius: 4px; + padding: 4px 9pt; + overflow: hidden; + color: #1e88c7; + text-decoration: none; + text-indent: 0; + line-height: 25px; +} +.filebtn input[type='file']{ + position: absolute; + z-index:999; + font-size: 75pt; + right: 10; + top: 0; + opacity: 0; +} \ No newline at end of file diff --git a/public/yfcmf/yfcmf.js b/public/yfcmf/yfcmf.js new file mode 100644 index 0000000..1fc0a64 --- /dev/null +++ b/public/yfcmf/yfcmf.js @@ -0,0 +1,1244 @@ +/************************************************************* 所有带确认的ajax提交btn ********************************************************/ +/* get执行并返回结果,执行后不带跳转 */ +$(function () { + $('body').on('click','.rst-btn',function () { + var $url = this.href; + $.get($url, function (data) { + if (data.code == 1) { + layer.alert(data.msg, {icon: 6}); + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* get执行并返回结果,执行后带跳转 */ +$(function () { + $('body').on('click','.rst-url-btn',function () { + var $url = this.href; + $.get($url, function (data) { + if (data.code==1) { + layer.alert(data.msg, {icon: 6}, function (index) { + layer.close(index); + window.location.href = data.url; + }); + } else { + layer.alert(data.msg, {icon: 5}, function (index) { + layer.close(index); + }); + } + }, "json"); + return false; + }); +}); +/* 直接跳转 */ +$(function () { + $('body').on('click','.confirm-btn',function () { + var $url = this.href, + $info = $(this).data('info'); + layer.confirm($info, {icon: 3}, function (index) { + layer.close(index); + window.location.href = $url; + }); + return false; + }); +}); +/* post执行并返回结果,执行后不带跳转 */ +$(function () { + $('body').on('click','.confirm-rst-btn',function () { + var $url = this.href, + $info = $(this).data('info'); + layer.confirm($info, {icon: 3}, function (index) { + layer.close(index); + $.post($url, {}, function (data) { + layer.alert(data.msg, {icon: 6}); + }, "json"); + }); + return false; + }); +}); +/* get执行并返回结果,执行后带跳转 */ +$(function () { + $('body').on('click','.confirm-rst-url-btn',function () { + var $url = this.href, + $info = $(this).data('info'); + layer.confirm($info, {icon: 3}, function (index) { + layer.close(index); + $.get($url, function (data) { + if (data.code==1) { + layer.alert(data.msg, {icon: 6}, function (index) { + layer.close(index); + window.location.href = data.url; + }); + } else { + layer.alert(data.msg, {icon: 5}, function (index) { + layer.close(index); + }); + } + }, "json"); + }); + return false; + }); +}); +$(function () { + $('body').on('click','.confirm-url-btn',function () { + var $url = this.href, + $info = $(this).data('info'); + layer.confirm($info, {icon: 3}, function (index) { + layer.close(index); + window.location.href = $url; + }); + return false; + }); +}); +/*************************************************************************** 所有状态类的ajax提交btn ********************************************************/ +/* 审核状态操作 */ +$(function () { + $('body').on('click','.state-btn',function () { + var $url = this.href, + val = $(this).data('id'), + $btn=$(this); + $.post($url, {x: val}, function (data) { + if (data.code==1) { + if (data.msg == '未审') { + var a = ''; + $btn.children('div').html(a).attr('title','未审'); + return false; + } else { + var b = ''; + $btn.children('div').html(b).attr('title','已审'); + return false; + } + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 文化四板审核通过状态操作 */ +$(function () { + $('body').on('click','.state-btn2',function () { + var $url = this.href, + val = $(this).data('id'), + $btn=$(this); + $.post($url, {x: val}, function (data) { + if (data.code==1) { + if (data.msg == '审核未通过') { + var a = ''; + $btn.children('div').html(a).attr('title','未通过'); + return false; + } else { + var b = ''; + $btn.children('div').html(b).attr('title','已通过'); + return false; + } + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 文化四板缴费状态操作 */ +$(function () { + $('body').on('click','.state-btn3',function () { + var $url = $(this).data('href'), + val = $(this).data('id'), + $btn=$(this); + $btn.attr('disabled',true); + loading("提交中"); + $.post($url, {x: val}, function (data) { + if (data.code==1) { + if (data.msg == '当前未缴费') { + var a = ''; + $btn.children('div').html(a).attr('title','未缴费'); + window.location.reload(); + return false; + } else { + var b = ''; + $btn.children('div').html(b).attr('title','已缴费'); + window.location.reload(); + return false; + } + + layer.closeAll(); + } else { + layer.closeAll(); + if(data.url){ + layer.alert(data.msg, {icon: 5}, function (index) { + window.location.href = data.url; + layer.close(index); + }); + }else{ + layer.alert(data.msg, {icon: 5}); + } + } + }, "json"); + return false; + }); +}); +$(function () { + $('body').on('click','#btnorder',function () { + var $url=$(this).attr("href"); + if(!$url){ + $url=$(this).parents('form').attr('action'); + } + $.post($url, $("input.list_order").serialize(), function (data) { + if (data.code==1) { + layer.alert(data.msg, {icon: 6}, function (index) { + window.location.href = data.url; + layer.close(index); + }); + }else{ + layer.alert(data.msg, {icon: 5}, function (index) { + layer.close(index); + }); + } + }, "json"); + return false; + }); +}); +/* 启用状态操作 */ +$(function () { + $('body').on('click','.open-btn',function () { + var $url = this.href, + val = $(this).data('id'), + $btn=$(this); + $.post($url, {x: val}, function (data) { + if (data.code==1) { + var res={}; + var msg='开启'; + var cla='btn-yellow'; + console.log("data",data); + if(typeof data.data!='undefined'&&data.data!==''){ + res=data.data; + if(!res)cla='btn-danger'; + msg=data.msg; + } + if (data.msg == '状态禁止') { + msg='禁用'; + cla='btn-danger'; + } + var a = ''; + $btn.children('div').html(a).attr('title','已'+msg); + return false; + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 销售状态操作 */ +$(function () { + $('body').on('click','.sale-btn',function () { + var $url = this.href, + val = $(this).data('id'), + $btn=$(this); + $.post($url, {x: val}, function (data) { + if (data.code==1) { + if (data.msg == '当前为已销售状态') { + var a = ''; + $btn.children('div').html(a).attr('title','已销售状态'); + return false; + } else { + var b = ''; + $btn.children('div').html(b).attr('title','未销售状态'); + return false; + } + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 显示状态操作 */ +$(function () { + $('body').on('click','.display-btn',function () { + var $url = this.href, + val = $(this).data('id'), + $btn=$(this); + $.post($url, {x: val}, function (data) { + if (data.code==1) { + if (data.msg == '状态禁止') { + var a = ''; + $btn.children('div').html(a).attr('title','已隐藏'); + return false; + } else { + var b = ''; + $btn.children('div').html(b).attr('title','已显示'); + return false; + } + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 检测状态操作 */ +$(function () { + $('body').on('click','.notcheck-btn',function () { + var $url = this.href, + val = $(this).data('id'), + $btn=$(this); + $.post($url, {x: val}, function (data) { + if (data.code==1) { + if (data.msg == '检测') { + var a = ''; + $btn.children('div').html(a).attr('title','检测'); + return false; + } else { + var b = ''; + $btn.children('div').html(b).attr('title','不检测'); + return false; + } + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 激活状态操作 */ +$(function () { + $('body').on('click','.active-btn',function () { + var $url = this.href, + val = $(this).data('id'), + $btn=$(this); + $.post($url, {x: val}, function (data) { + if (data.code==1) { + if (data.msg == '未激活') { + var a = ''; + $btn.children('div').html(a).attr('title','未激活'); + return false; + } else { + var b = ''; + $btn.children('div').html(b).attr('title','已激活'); + return false; + } + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/*************************************************************************** 所有ajaxForm提交 ********************************************************/ +/* 通用表单不带检查操作,失败不跳转 */ +$(function () { + $('.ajaxForm').ajaxForm({ + success: complete2, // 这是提交后的方法 + beforeSubmit: showRequest, //提交前的回调函数 + dataType: 'json' + }); +}); +/* 通用表单不带检查操作,失败跳转 */ +$(function () { + $('.ajaxForm2').ajaxForm({ + beforeSubmit: showRequest, //提交前的回调函数 + success: complete, // 这是提交后的方法 + dataType: 'json' + }); +}); +/* 通用含验证码表单不带检查操作,失败不跳转 */ +$(function () { + $('.ajaxForm3').ajaxForm({ + beforeSubmit:function(){ + loading("loading..."); + }, + success: complete3, // 这是提交后的方法 + dataType: 'json' + }); +}); +/* 会员增加编辑表单,带检查 */ +$(function () { + $('.memberform').ajaxForm({ + beforeSubmit: checkmemberForm, // 此方法主要是提交前执行的方法,根据需要设置 + success: complete, // 这是提交后的方法 + dataType: 'json' + }); +}); +/* admin增加编辑表单,带检查 */ +$(function () { + $('.adminform').ajaxForm({ + beforeSubmit: checkadminForm, // 此方法主要是提交前执行的方法,根据需要设置 + success: complete, // 这是提交后的方法 + dataType: 'json' + }); +}); +/* 多选删除操作 */ +$(function () { + $('#alldel').ajaxForm({ + beforeSubmit: checkselectForm, // 此方法主要是提交前执行的方法,根据需要设置,一般是判断为空获取其他规则 + success: complete2, // 这是提交后的方法 + dataType: 'json' + }); +}); +function loading(msg){ + if(typeof msg=='undefined'||msg=='')msg=''; + layer.msg(msg, {icon: 16,time:600000,shade:0.6}); +} + +function closeloading(){ + layer.closeAll(); +} + +function showRequest(){ + loading("保存中"); + return true; +} +//失败跳转 +function complete(data) { + closeloading(); + if (data.code == 1) { + layer.alert(data.msg, {icon: 6}, function (index) { + layer.close(index); + window.location.href = data.url; + }); + } else { + layer.alert(data.msg, {icon: 5}, function (index) { + layer.close(index); + window.location.href = data.url; + }); + return false; + } +} +//失败不跳转 +function complete2(data) { + if (data.code == 1) { + layer.alert(data.msg, {icon: 6}, function (index) { + layer.close(index); + window.location.href = data.url; + }); + } else { + layer.alert(data.msg, {icon: 5}, function (index) { + layer.close(index); + }); + } +} +//失败不跳转,验证码刷新 +function complete3(data) { + closeloading(); + if (data.code == 1) { + window.location.href = data.url; + } else { + $("#verify").val(''); + $("#verify_img").click(); + layer.alert(data.msg, {icon: 5}); + } +} +//admin表单检查 +function checkadminForm() { + var admin_username = $.trim($('input[name="admin_username"]').val()); //获取INPUT值 + var myReg = /^[\u4e00-\u9fa5]+$/;//验证中文 + if (admin_username.indexOf(" ") >= 0) { + layer.alert('登录用户名包含了空格,请重新输入', {icon: 5}, function (index) { + layer.close(index); + $('#admin_username').focus(); + }); + return false; + } + if (myReg.test(admin_username)) { + layer.alert('用户名必须是字母,数字,符号', {icon: 5}, function (index) { + layer.close(index); + $('#admin_username').focus(); + }); + return false; + } + if (!$("#admin_tel").val().match(/^(((13[0-9]{1})|(15[0-9]{1})|(16[0-9]{1})|(17[0-9]{1})|(19[0-9]{1})|(18[0-9]{1}))+\d{8})$/)) { + layer.alert('电话号码格式不正确', {icon: 5}, function (index) { + layer.close(index); + $('#admin_tel').focus(); + }); + return false; + } +} +//member表单检查 +function checkmemberForm() { + if (!$("#member_list_tel").val().match(/^(((13[0-9]{1})|(15[0-9]{1})|(16[0-9]{1})|(17[0-9]{1})|(19[0-9]{1})|(18[0-9]{1}))+\d{8})$/)) { + layer.alert('电话号码格式不正确', {icon: 5}, function (index) { + layer.close(index); + $('#member_list_tel').focus(); + }); + return false; + } +} +//多选表单检查 +function checkselectForm() { + var chk_value = []; + $('input[id="navid"]:checked').each(function () { + chk_value.push($(this).val()); + }); + + if (!chk_value.length) { + layer.alert('至少选择一个删除项', {icon: 5}); + return false; + } +} +/*************************************************************************** 所有css操作 ********************************************************/ +/* 菜单样式 */ +$(function () { + //插入header-nav + $('#sidebar2').insertBefore('.page-content'); + $('.navbar-toggle[data-target="#sidebar2"]').insertAfter('#menu-toggler'); + //固定 + $(document).on('settings.ace.two_menu', function (e, event_name, event_val) { + if (event_name == 'sidebar_fixed') { + if ($('#sidebar').hasClass('sidebar-fixed')) { + $('#sidebar2').addClass('sidebar-fixed'); + $('#navbar').addClass('h-navbar'); + } + else { + $('#sidebar2').removeClass('sidebar-fixed'); + $('#navbar').removeClass('h-navbar'); + } + } + }).triggerHandler('settings.ace.two_menu', ['sidebar_fixed', $('#sidebar').hasClass('sidebar-fixed')]); +}); +/* 多选判断 */ +function unselectall() { + if (document.myform.chkAll.checked) { + document.myform.chkAll.checked = document.myform.chkAll.checked & 0; + } +} +function CheckAll(form) { + for (var i = 0; i < form.elements.length; i++) { + var e = form.elements[i]; + if (e.Name != 'chkAll' && e.disabled == false) { + e.checked = form.chkAll.checked; + } + } +} +/* 日期选择器 */ +var dateInput = $("input.js-date") +if (dateInput.length) { + dateInput.datePicker(); +} +/* 权限配置 */ +$(function () { + //动态选择框,上下级选中状态变化 + $('input.checkbox-parent').on('change', function () { + var dataid = $(this).attr("dataid"); + $('input[dataid^=' + dataid + '-]').prop('checked', $(this).is(':checked')); + }); + $('input.checkbox-child').on('change', function () { + var dataid = $(this).attr("dataid"); + dataid = dataid.substring(0, dataid.lastIndexOf("-")); + var parent = $('input[dataid=' + dataid + ']'); + if ($(this).is(':checked')) { + parent.prop('checked', true); + //循环到顶级 + while (dataid.lastIndexOf("-") != 2) { + dataid = dataid.substring(0, dataid.lastIndexOf("-")); + parent = $('input[dataid=' + dataid + ']'); + parent.prop('checked', true); + } + } else { + //父级 + if ($('input[dataid^=' + dataid + '-]:checked').length == 0) { + parent.prop('checked', false); + //循环到顶级 + while (dataid.lastIndexOf("-") != 2) { + dataid = dataid.substring(0, dataid.lastIndexOf("-")); + parent = $('input[dataid=' + dataid + ']'); + if ($('input[dataid^=' + dataid + '-]:checked').length == 0) { + parent.prop('checked', false); + } + } + } + } + }); +}); +//模态框状态 +$(document).ready(function () { + $("#myModaledit").hide(); + $("#gb").click(function () { + $("#myModaledit").hide(200); + }); + $("#gbb").click(function () { + $("#myModaledit").hide(200); + }); + $("#gbbb").click(function () { + $("#myModaledit").hide(200); + }); +}); +$(document).ready(function () { + $("#myModal").hide(); + $("#gb").click(function () { + $("#myModal").hide(200); + }); + $("#gbb").click(function () { + $("#myModal").hide(200); + }); + $("#gbbb").click(function () { + $("#myModal").hide(200); + }); +}); +/*************************************************************************** 所有ajax获取编辑数据 ********************************************************/ +/* 会员组修改操作 */ +$(function () { + $('body').on('click','.memberedit-btn',function () { + var $url = this.href, + val = $(this).data('id'); + $.post($url, {member_group_id: val}, function (data) { + if (data.code == 1) { + $("#myModaledit").show(300); + $("#editmember_group_id").val(data.member_group_id); + $("#editmember_group_name").val(data.member_group_name); + $("#editmember_group_open").val(data.member_group_open); + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 友链类型 */ +function openWindow(a, b, c) { + $("#myModal").show(300); + $("#plug_linktype_id").val(a); + $("#newplug_linktype_name").val(b); + $("#newplug_linktype_order").val(c); +} +/* 模型添加到menu */ +function addmenu(a) { + $("#myModal").show(300); + $("#model_id").val(a); +} +/* we菜单添加 */ +function add_we_menu(a) { + $('#myModal').modal('show'); + $("#we_menu_leftid").val(a); +} +/* 路由规则编辑 */ +$(function () { + $('body').on('click','.routeedit-btn',function () { + var $url = this.href, + val = $(this).data('id'); + $.post($url, {id: val}, function (data) { + if (data.code == 1) { + $("#myModaledit").show(300); + $("#editroute_id").val(data.id); + $("#editroute_full_url").val(data.full_url); + $("#editroute_url").val(data.url); + if (data.status == 1) { + $("#editroute_status").prop("checked",true); + } else { + $("#editroute_status").prop("checked", false); + } + $("#editroute_listorder").val(data.listorder); + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 友链编辑 */ +$(function () { + $('body').on('click','.linkedit-btn',function () { + var $url = this.href, + val = $(this).data('id'); + $.post($url, {plug_link_id: val}, function (data) { + if (data.code == 1) { + $("#myModaledit").show(300); + $("#editplug_link_id").val(data.plug_link_id); + $("#editplug_link_l").val(data.plug_link_l); + $("#editplug_link_name").val(data.plug_link_name); + $("#editplug_link_url").val(data.plug_link_url); + $("#editplug_link_target").val(data.plug_link_target); + $("#editplug_link_qq").val(data.plug_link_qq); + $("#editplug_link_order").val(data.plug_link_order); + $("#editplug_link_typeid").val(data.plug_link_typeid); + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 广告位编辑 */ +$(function () { + $('body').on('click','.adtypeedit-btn',function () { + var $url = this.href, + val = $(this).data('id'); + $.post($url, {plug_adtype_id: val}, function (data) { + if (data.code == 1) { + $("#myModaledit").show(300); + $("#adtype_id").val(data.plug_adtype_id); + $("#adtype_name").val(data.plug_adtype_name); + $("#adtype_order").val(data.plug_adtype_order); + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 回复留言 */ +$(function () { + $('body').on('click','.sugreply-btn',function () { + var $url = this.href, + val = $(this).data('id'); + $.post($url, {plug_sug_id: val}, function (data) { + if (data.code == 1) { + $("#myModal").show(300); + $("#plug_sug_toemail").val(data.plug_sug_email); + $("#plug_sug_toname").val(data.plug_sug_name); + $("#plug_sug_id").val(data.plug_sug_id); + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 来源编辑 */ +$(function () { + $('body').on('click','.sourceedit-btn',function () { + var $url = this.href, + val = $(this).data('id'); + $.post($url, {source_id: val}, function (data) { + if (data.code == 1) { + $("#myModaledit").show(300); + $("#editsource_id").val(data.source_id); + $("#editsource_name").val(data.source_name); + $("#editsource_order").val(data.source_order); + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +//来源 +function souadd(val) { + $('#news_source').val(val); +} +/* 微信菜单编辑 */ +$(function () { + $('body').on('click','.menuedit-btn',function () { + var $url = this.href, + val = $(this).data('id'); + $.post($url, {we_menu_id: val}, function (data) { + if (data.code == 1) { + $("#myModaledit").show(300); + $("#editwe_menu_id").val(data.we_menu_id); + $("#editwe_menu_name").val(data.we_menu_name); + $("#editwe_menu_leftid").val(data.we_menu_leftid); + $("#editwe_menu_type").val(data.we_menu_type); + $("#editwe_menu_typeval").val(data.we_menu_typeval); + $("#editwe_menu_order").val(data.we_menu_order); + if(data.we_menu_open){ + $("#editwe_menu_open").prop("checked",true); + }else{ + $("#editwe_menu_open").prop("checked",false); + } + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/* 微信关键词回复编辑 */ +$(function () { + $('body').on('click','.replyedit-btn',function () { + var $url = this.href, + val = $(this).data('id'); + $.post($url, {we_reply_id: val}, function (data) { + if (data.code == 1) { + $("#myModaledit").show(300); + $("#editwe_reply_id").val(data.we_reply_id); + $("#editwe_reply_key").val(data.we_reply_key); + $("#editwe_reply_type").val(data.we_reply_type); + var Modal=$("#editwe_reply_type").parents('.modal'); + if(data.we_reply_type=='news'){ + Modal.find("#input-text").hide(); + Modal.find("#input-news").show(); + $("#editnews_title").val(data.we_reply_content.title); + $("#editnews_description").val(data.we_reply_content.description); + $("#editnews_url").val(data.we_reply_content.url); + $("#editnews_image").val(data.we_reply_content.image); + }else{ + Modal.find("#input-news").hide(); + Modal.find("#input-text").show(); + $("#editwe_reply_content").val(data.we_reply_content); + } + if(data.we_reply_open){ + $("#editwe_reply_open").prop("checked",true); + }else{ + $("#editwe_reply_open").prop("checked",false); + } + } else { + layer.alert(data.msg, {icon: 5}); + } + }, "json"); + return false; + }); +}); +/*************************************************************************** 单图/多图操作********************************************************/ +/* 单图上传 */ +$("#file0").change(function () { + var objUrl = getObjectURL(this.files[0]); + console.log("objUrl = " + objUrl); + if (objUrl) { + $("#img0").attr("src", objUrl); + } +}); +$("#file1").change(function () { + var objUrl = getObjectURL(this.files[0]); + console.log("objUrl = " + objUrl); + if (objUrl) { + $("#img1").attr("src", objUrl); + } +}); +$("#file2").change(function () { + var objUrl = getObjectURL(this.files[0]); + console.log("objUrl = " + objUrl); + if (objUrl) { + $("#img2").attr("src", objUrl); + } +}); +// +$("input[id^=file_]").change(function () { + var field=$(this).data('field'),objUrl = getObjectURL2(this.files[0],field); + console.log("objUrl = " + objUrl); + if (objUrl) { + $("#img_"+field).attr("src", objUrl); + } +}); +function getObjectURL(file) { + var url = null; + if (window.createObjectURL != undefined) { // basic + $("#oldcheckpic").val("nopic"); + url = window.createObjectURL(file); + } else if (window.URL != undefined) { // mozilla(firefox) + $("#oldcheckpic").val("nopic"); + url = window.URL.createObjectURL(file); + } else if (window.webkitURL != undefined) { // webkit or chrome + $("#oldcheckpic").val("nopic"); + url = window.webkitURL.createObjectURL(file); + } + return url; +} +function getObjectURL2(file,field) { + var url = null; + if (window.createObjectURL != undefined) { // basic + $("#oldcheckpic_"+field).val("nopic"); + url = window.createObjectURL(file); + } else if (window.URL != undefined) { // mozilla(firefox) + $("#oldcheckpic_"+field).val("nopic"); + url = window.URL.createObjectURL(file); + } else if (window.webkitURL != undefined) { // webkit or chrome + $("#oldcheckpic_"+field).val("nopic"); + url = window.webkitURL.createObjectURL(file); + } + return url; +} +function backpic2(picurl,field) { + $("#img_"+field).attr("src", picurl);//还原修改前的图片 + $("#file_"+field).val("");//清空文本框的值 + $("#oldcheckpic_"+field).val(picurl);//清空文本框的值 +} +function backpic(picurl) { + $("#img0").attr("src", picurl);//还原修改前的图片 + $("input[name='file0']").val("");//清空文本框的值 + $("input[name='oldcheckpic']").val(picurl);//清空文本框的值 +} +/* 新闻多图删除 */ +function delall(id, url) { + $('#id' + id).hide(); + var str = $('#pic_oldlist').val();//最原始的完整路径 + var surl = url + ','; + var pic_newold = str.replace(surl, ""); + $('#pic_oldlist').val(pic_newold); +} +/*************************************************************************** 数据备份还原********************************************************/ +/* 数据库备份、优化、修复 */ +(function ($) { + $("a[id^=optimize_]").click(function () { + $.get(this.href, function (data) { + if (data.code==1) { + layer.alert(data.msg, {icon: 6}); + } else { + layer.alert(data.msg, {icon: 5}); + } + }); + return false; + }); + $("a[id^=repair_]").click(function () { + $.get(this.href, function (data) { + if (data.code==1) { + layer.alert(data.msg, {icon: 6}); + } else { + layer.alert(data.msg, {icon: 5}); + } + }); + return false; + }); + + var $form = $("#export-form"), $export = $("#export"), tables + $optimize = $("#optimize"), $repair = $("#repair"); + + $optimize.add($repair).click(function () { + var that=this; + $.post(this.href, $form.serialize(), function (data) { + if (data.code==1) { + layer.alert(data.msg, {icon: 6}, function (index) { + layer.close(index); + }); + } else { + layer.alert(data.msg, {icon: 5}, function (index) { + layer.close(index); + }); + } + setTimeout(function () { + $('#top-alert').find('button').click(); + $(that).removeClass('disabled').prop('disabled', false); + }, 1500); + }, "json"); + return false; + }); + + $export.click(function () { + $export.children().addClass("disabled"); + $export.children().text("正在发送备份请求..."); + var that=this; + $.post( + $form.attr("action"), + $form.serialize(), + function (data) { + if (data.code==1) { + tables = data.tables; + $export.children().text(data.msg + "开始备份,请不要关闭本页面!"); + backup(data.tab); + window.onbeforeunload = function () { + return "正在备份数据库,请不要关闭!" + } + } else { + layer.alert(data.msg, {icon: 5}); + $export.children().removeClass("disabled"); + $export.children().text("立即备份"); + setTimeout(function () { + $('#top-alert').find('button').click(); + $(that).removeClass('disabled').prop('disabled', false); + }, 1500); + } + }, + "json" + ); + return false; + }); + + function backup(tab, status) { + status && showmsg(tab.id, "开始备份...(0%)"); + var that=this; + $.get($form.attr("action"), tab, function (data) { + if (data.code==1) { + showmsg(tab.id, data.msg); + if (!$.isPlainObject(data.tab)) { + $export.children().removeClass("disabled"); + $export.children().text("备份完成,点击重新备份"); + window.onbeforeunload = null; + } + if(data.tab !=undefined){ + backup(data.tab, tab.id != data.tab.id); + } + } else { + updateAlert(data.msg, 'alert-error'); + $export.children().removeClass("disabled"); + $export.children().text("立即备份"); + setTimeout(function () { + $('#top-alert').find('button').click(); + $(that).removeClass('disabled').prop('disabled', false); + }, 1500); + } + }, "json"); + } + + function showmsg(id, msg) { + $tr=$form.find("input[value=" + tables[id] + "]").closest("tr"); + $tr.find(".green").html(""); + $tr.find(".info").html(""); + $tr.find(".backup").html(msg); + } +})(jQuery); +/*************************************************************************** 其它********************************************************/ +/* textarea字数提示 */ +$(function () { + $('textarea.limited').maxlength({ + 'feedback': '.charsLeft', + }); + $('textarea.limited1').maxlength({ + 'feedback': '.charsLeft1', + }); + $('textarea.limited2').maxlength({ + 'feedback': '.charsLeft2', + }); + $('textarea.limited3').maxlength({ + 'feedback': '.charsLeft3', + }); + $('textarea.limited4').maxlength({ + 'feedback': '.charsLeft4', + }); + $('textarea.limited5').maxlength({ + 'feedback': '.charsLeft5', + }); +}); +$(function () { + $("[data-toggle='tooltip']").tooltip(); +}); +/*************************************************************************** 生成安全文件********************************************************/ +(function ($) { + $('body').on('click','#security_generate',function () { + $(this).children().addClass("disabled"); + $(this).find("span").text("正在生成安全文件..."); + $.get(this.href, function (data) { + if (data.code==1) { + layer.alert(data.msg, {icon: 6}, function (index) { + layer.close(index); + window.location.href = data.url; + }); + } else { + layer.alert(data.msg, {icon: 5}, function (index) { + layer.close(index); + }); + } + $(this).children().removeClass("disabled"); + $(this).find("span").text("重新生成安全文件"); + }); + return false; +}); +})(jQuery); +/*************************************************************************** 选择列表框change事件********************************************************/ +(function ($) { + $('body').on('change','.ajax_change',function () { + var $form = $(this).parents("form"); + $.ajax({ + url:$form.attr('action'), + type:"POST", + data:$form.serialize(), + success: function(data,status){ + $("#ajax-data").html(data); + } + }); + }); + $('body').on('change','.ajax_change2',function () { + var $form = $(this).parents("form"); + $.ajax({ + url:$form.attr('action'), + type:"POST", + data:$form.serialize(), + success: function(data,status){ + $("#ajax-data").html(data); + changeProvice(); + } + }); + + }); + $('body').on('click','.range_inputs .applyBtn',function () { + var reservation=$('#reservation'); + var $form = reservation.parents("form"); + reservation.val($('input[name="daterangepicker_start"]').val()+' - '+$('input[name="daterangepicker_end"]').val()); + $.ajax({ + url:$form.attr('action'), + type:"POST", + data:$form.serialize(), + success: function(data,status){ + $("#ajax-data").html(data); + } + }); + }); + })(jQuery); +(function ($) { + $('body').on('change','.submit_change',function () { + var $form = $(this).parents("form"); + $form.submit(); + }); + })(jQuery); +function ajax_page(page) { + $.ajax({ + type:"POST", + data:$('#list-filter').serialize()+'&page='+page, + success: function(data,status){ + $("#ajax-data").html(data); + } + }); +} +/*搜索form*/ +$(function () { + $('body').on('click','.ajax-search-form',function () { + $.ajax({ + type:"POST", + data:$(this).parents("form").serialize(), + success: function(data,status){ + $("#ajax-data").html(data); + } + }); + return false; + }); +}); +/*搜索type*/ +$(function () { + $('body').on('click','.ajax-search-type',function () { + $(this).parents("form")[0].reset(); + $.ajax({ + type:"POST", + data:{type:$(this).data('type')}, + success: function(data,status){ + $("#ajax-data").html(data); + } + }); + return false; + }); +}); +/*搜索form显示全部*/ +$(function () { + $('body').on('click','.ajax-display-all',function () { + $(this).parents("form")[0].reset(); + $.ajax({ + type:"POST", + data:{}, + success: function(data,status){ + $("#ajax-data").html(data); + } + }); + return false; + }); +}); +/*清空*/ +$(function () { + $('body').on('click','.ajax-drop',function () { + $(this).parents("form")[0].reset(); + var url=$(this).parent('a').attr('href'); + $.ajax({ + type:"POST", + url:url, + data:{}, + success: function(data,status){ + layer.alert(data.msg, {icon: 6}, function (index) { + layer.close(index); + window.location.href = data.url; + }); + } + }); + return false; + }); +}); +/*详情*/ +$(function () { + $('body').on('click','.show-details-btn',function (e) { + e.preventDefault(); + $(this).closest('tr').next().toggleClass('open'); + $(this).find(ace.vars['.icon']).toggleClass('fa-angle-double-down').toggleClass('fa-angle-double-up'); + }); +}); +$(function () { + /*权限管理*/ + $('body').on('click','.rule-list',function () { + var $a=$(this),$tr=$a.parents('tr'); + var $pid=$tr.attr('id'); + if($a.find('span').hasClass('fa-minus')){ + $("tr[id^='"+$pid+"-']").attr('style','display:none'); + $a.find('span').removeClass('fa-minus').addClass('fa-plus'); + }else{ + if($("tr[id^='"+$pid+"-']").length>0){ + $("tr[id^='"+$pid+"-']").attr('style',''); + $a.find('span').removeClass('fa-plus').addClass('fa-minus'); + }else{ + var $url = this.href,$id=$a.data('id'),$level=$a.data('level'); + $.post($url,{pid:$id,level:$level,id:$pid}, function (data) { + if (data) { + $a.find('span').removeClass('fa-plus').addClass('fa-minus'); + $tr.after(data); + }else{ + $a.find('span').removeClass('fa-plus').addClass('fa-minus'); + } + }, "json"); + } + } + return false; + }).on('change','.ajax_change_news_columnid',function () { + var obj=$(this).siblings('.action'); + var old_id=obj.find('.cancel-change-columnid').data('columnid'),new_id=$(this).val(); + if(old_id != new_id){ + obj.find('.change-columnid').data('columnid',new_id); + obj.removeClass('none'); + }else{ + obj.addClass('none'); + } + }).on('click','a.change-columnid',function () { + var $url=this.href,$news_columnid=$(this).data('columnid'),$n_id=$(this).data('id'); + var obj=$(this); + $.post($url,{news_columnid:$news_columnid,n_id:$n_id}, function (data) { + if (data.code==1) { + obj.parent().addClass('none'); + obj.siblings('.cancel-change-columnid').data('columnid',$news_columnid); + layer.msg(data.msg,{icon: 6}); + }else{ + layer.msg(data.msg,{icon: 5}); + } + }, "json"); + return false; + }).on('click','a.cancel-change-columnid',function () { + var old_id=$(this).data('columnid'),obj=$(this).parent(); + obj.addClass('none').siblings('.ajax_change_news_columnid').val(old_id); + return false; + }); + //极验验证 + $('#geetest_on').click(function(){ + $("#geetest").toggle(200); + }); +}); +//checkform +function submitForm(btnid,obj){ + + var $form=$('#checkform'); + if(obj){ + $form=$(obj).parents("form[name='checkform']"); + $(obj).parents("form[name='checkform']").find("[name='submitType']").val(btnid); + }else{ + $('#submitType').val(btnid); + } + $.ajax({ + url:$form.attr('action'), + type:"POST", + data:$form.serialize(), + success: function(data,status){ + if (data.code == 1) { + layer.alert(data.msg, {icon: 6}, function (index) { + layer.close(index); + window.location.href = data.url; + }); + } else { + layer.alert(data.msg, {icon: 5}, function (index) { + layer.close(index); + }); + } + } + }); + return false; +} +$(function () { + if(!ace.vars['old_ie']) $('#roadshowdt').datetimepicker({ + //format: 'MM/DD/YYYY h:mm:ss A',//use this option to display seconds + icons: { + time: 'fa fa-clock-o', + date: 'fa fa-calendar', + up: 'fa fa-chevron-up', + down: 'fa fa-chevron-down', + previous: 'fa fa-chevron-left', + next: 'fa fa-chevron-right', + today: 'fa fa-arrows ', + clear: 'fa fa-trash', + close: 'fa fa-times' + } + }).next().on(ace.click_event, function(){ + $(this).prev().focus(); + }); +}); \ No newline at end of file