├── LICENSE ├── README.md ├── README_CN.md ├── bin ├── examples │ ├── avatar │ │ ├── css │ │ │ ├── bootstrap.min.css │ │ │ ├── cropper.min.css │ │ │ ├── main.css │ │ │ └── qunit.css │ │ ├── img │ │ │ ├── loading.gif │ │ │ └── picture.jpg │ │ ├── index.html │ │ ├── js │ │ │ ├── bootstrap.min.js │ │ │ ├── cropper.min.js │ │ │ ├── jquery.min.js │ │ │ └── main.js │ │ └── server.py │ ├── simple │ │ ├── css │ │ │ ├── dropzone.css │ │ │ └── style.css │ │ ├── index.html │ │ ├── js │ │ │ └── dropzone.js │ │ ├── main.css │ │ ├── main.js │ │ └── server.py │ └── upload.html └── stack ├── gruntfile.js ├── package.json ├── src ├── .baseDir.ts ├── _all.d.ts ├── app.ts ├── route.ts ├── storage.ts └── storage │ ├── disk.ts │ ├── ftp.ts │ ├── hdfs.ts │ ├── maria.ts │ ├── memcached.ts │ ├── mongo.ts │ ├── pg.ts │ └── redis.ts ├── tslint.json └── typings.json /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 k9982874 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Stack - a distributed file storage service 2 | =========================== 3 | This is a distributed file storage service, the back-end through a variety of different drivers can be achieved with distributed file storage, front-end with HAProxy achieve high availability. 4 | 5 | Installing 6 | ---------- 7 | ```bash 8 | git clone http:///stack 9 | cd stack 10 | npm install 11 | node_modules/.bin/typings install 12 | ``` 13 | 14 | Usage 15 | ----- 16 | ```bash 17 | npm run grunt serve 18 | ``` 19 | 20 | API 21 | --- 22 | "upload" 23 | Accepts a `POST` request of type `multipart/form-data` and saves the data in the `file` field of the request to `local`. 24 | 25 | "file" 26 | Accept the `GET` request to get the saved file from `local`. 27 | 28 | 29 | Supported storage drivers 30 | ------------------------- 31 | "disk" 32 | Local disk drive, through this drive with `moosefs`, `GlusterFS`, `ceph` and other distributed file system can support distributed storage. 33 | 34 | "ftp" 35 | ftp driver. 36 | 37 | "hdfs" 38 | [hdfs driver.](https://hadoop.apache.org/) 39 | 40 | "maria" 41 | [maria driver.](https://mariadb.org/) 42 | 43 | "memcached" 44 | [memcached driver.](https://memcached.org/) 45 | 46 | "mongo" 47 | [mongodb driver.](https://www.mongodb.com/) 48 | 49 | "pg" 50 | [postgresql driver.](https://www.postgresql.org/) 51 | 52 | "redis" 53 | [redis driver.](http://redis.io/) 54 | -------------------------------------------------------------------------------- /README_CN.md: -------------------------------------------------------------------------------- 1 | Stack - 分布式文件存储服务 2 | =========================== 3 | 这是一个分布式文件存储服务,后端通过配合多种不同驱动可以实现分布式文件存储,前端配合`HAProxy`实现高可用性。 4 | 5 | 安装 6 | ---- 7 | ```bash 8 | git clone http:///stack 9 | cd stack 10 | npm install 11 | node_modules/.bin/typings install 12 | ``` 13 | 14 | 如何使用 15 | -------- 16 | ```bash 17 | npm run grunt serve 18 | ``` 19 | 20 | API 21 | --- 22 | "upload" 23 | 接受`multipart/form-data`类型的`POST`请求,保存请求中`file`字段的数据到“本地”。 24 | 25 | "file" 26 | 接受`GET`请求,从“本地”获取保存的文件。 27 | 28 | 29 | 后端支持引擎 30 | ------------ 31 | "disk" 32 | 本地磁盘驱动,通过此驱动配合`moosefs`、`GlusterFS`、`ceph`等分布式文件系统可支持分布式存储。 33 | 34 | "ftp" 35 | ftp服务器驱动。 36 | 37 | "hdfs" 38 | [hdfs driver。](https://hadoop.apache.org/) 39 | 40 | "maria" 41 | [maria driver。](https://mariadb.org/) 42 | 43 | "memcached" 44 | [memcached driver。](https://memcached.org/) 45 | 46 | "mongo" 47 | [mongodb driver。](https://www.mongodb.com/) 48 | 49 | "pg" 50 | [postgresql driver。](https://www.postgresql.org/) 51 | 52 | "redis" 53 | [redis driver。](http://redis.io/) 54 | -------------------------------------------------------------------------------- /bin/examples/avatar/css/cropper.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Cropper v2.3.4 3 | * https://github.com/fengyuanchen/cropper 4 | * 5 | * Copyright (c) 2014-2016 Fengyuan Chen and contributors 6 | * Released under the MIT license 7 | * 8 | * Date: 2016-09-03T05:50:45.412Z 9 | */.cropper-container{font-size:0;line-height:0;position:relative;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;direction:ltr!important}.cropper-container img{display:block;width:100%;min-width:0!important;max-width:none!important;height:100%;min-height:0!important;max-height:none!important;image-orientation:0deg!important}.cropper-canvas,.cropper-crop-box,.cropper-drag-box,.cropper-modal,.cropper-wrap-box{position:absolute;top:0;right:0;bottom:0;left:0}.cropper-wrap-box{overflow:hidden}.cropper-drag-box{opacity:0;background-color:#fff;filter:alpha(opacity=0)}.cropper-dashed,.cropper-modal{opacity:.5;filter:alpha(opacity=50)}.cropper-modal{background-color:#000}.cropper-view-box{display:block;overflow:hidden;width:100%;height:100%;outline:#39f solid 1px;outline-color:rgba(51,153,255,.75)}.cropper-dashed{position:absolute;display:block;border:0 dashed #eee}.cropper-dashed.dashed-h{top:33.33333%;left:0;width:100%;height:33.33333%;border-top-width:1px;border-bottom-width:1px}.cropper-dashed.dashed-v{top:0;left:33.33333%;width:33.33333%;height:100%;border-right-width:1px;border-left-width:1px}.cropper-center{position:absolute;top:50%;left:50%;display:block;width:0;height:0;opacity:.75;filter:alpha(opacity=75)}.cropper-center:after,.cropper-center:before{position:absolute;display:block;content:' ';background-color:#eee}.cropper-center:before{top:0;left:-3px;width:7px;height:1px}.cropper-center:after{top:-3px;left:0;width:1px;height:7px}.cropper-face,.cropper-line,.cropper-point{position:absolute;display:block;width:100%;height:100%;opacity:.1;filter:alpha(opacity=10)}.cropper-face{top:0;left:0;background-color:#fff}.cropper-line,.cropper-point{background-color:#39f}.cropper-line.line-e{top:0;right:-3px;width:5px;cursor:e-resize}.cropper-line.line-n{top:-3px;left:0;height:5px;cursor:n-resize}.cropper-line.line-w{top:0;left:-3px;width:5px;cursor:w-resize}.cropper-line.line-s{bottom:-3px;left:0;height:5px;cursor:s-resize}.cropper-point{width:5px;height:5px;opacity:.75;filter:alpha(opacity=75)}.cropper-point.point-e{top:50%;right:-3px;margin-top:-3px;cursor:e-resize}.cropper-point.point-n{top:-3px;left:50%;margin-left:-3px;cursor:n-resize}.cropper-point.point-w{top:50%;left:-3px;margin-top:-3px;cursor:w-resize}.cropper-point.point-s{bottom:-3px;left:50%;margin-left:-3px;cursor:s-resize}.cropper-point.point-ne{top:-3px;right:-3px;cursor:ne-resize}.cropper-point.point-nw{top:-3px;left:-3px;cursor:nw-resize}.cropper-point.point-sw{bottom:-3px;left:-3px;cursor:sw-resize}.cropper-point.point-se{right:-3px;bottom:-3px;width:20px;height:20px;cursor:se-resize;opacity:1;filter:alpha(opacity=100)}.cropper-point.point-se:before{position:absolute;right:-50%;bottom:-50%;display:block;width:200%;height:200%;content:' ';opacity:0;background-color:#39f;filter:alpha(opacity=0)}@media (min-width:768px){.cropper-point.point-se{width:15px;height:15px}}@media (min-width:992px){.cropper-point.point-se{width:10px;height:10px}}@media (min-width:1200px){.cropper-point.point-se{width:5px;height:5px;opacity:.75;filter:alpha(opacity=75)}}.cropper-invisible{opacity:0;filter:alpha(opacity=0)}.cropper-bg{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC)}.cropper-hide{position:absolute;display:block;width:0;height:0}.cropper-hidden{display:none!important}.cropper-move{cursor:move}.cropper-crop{cursor:crosshair}.cropper-disabled .cropper-drag-box,.cropper-disabled .cropper-face,.cropper-disabled .cropper-line,.cropper-disabled .cropper-point{cursor:not-allowed} -------------------------------------------------------------------------------- /bin/examples/avatar/css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fcfcfc; 3 | } 4 | 5 | .avatar-view { 6 | display: block; 7 | margin: 15% auto 5%; 8 | height: 220px; 9 | width: 220px; 10 | border: 3px solid #fff; 11 | border-radius: 5px; 12 | box-shadow: 0 0 5px rgba(0,0,0,.15); 13 | cursor: pointer; 14 | overflow: hidden; 15 | } 16 | 17 | .avatar-view img { 18 | width: 100%; 19 | } 20 | 21 | .avatar-body { 22 | padding-right: 15px; 23 | padding-left: 15px; 24 | } 25 | 26 | .avatar-upload { 27 | overflow: hidden; 28 | } 29 | 30 | .avatar-upload label { 31 | display: block; 32 | float: left; 33 | clear: left; 34 | width: 100px; 35 | } 36 | 37 | .avatar-upload input { 38 | display: block; 39 | margin-left: 110px; 40 | } 41 | 42 | .avatar-alert { 43 | margin-top: 10px; 44 | margin-bottom: 10px; 45 | } 46 | 47 | .avatar-wrapper { 48 | height: 364px; 49 | width: 100%; 50 | margin-top: 15px; 51 | box-shadow: inset 0 0 5px rgba(0,0,0,.25); 52 | background-color: #fcfcfc; 53 | overflow: hidden; 54 | } 55 | 56 | .avatar-wrapper img { 57 | display: block; 58 | height: auto; 59 | max-width: 100%; 60 | } 61 | 62 | .avatar-preview { 63 | float: left; 64 | margin-top: 15px; 65 | margin-right: 15px; 66 | border: 1px solid #eee; 67 | border-radius: 4px; 68 | background-color: #fff; 69 | overflow: hidden; 70 | } 71 | 72 | .avatar-preview:hover { 73 | border-color: #ccf; 74 | box-shadow: 0 0 5px rgba(0,0,0,.15); 75 | } 76 | 77 | .avatar-preview img { 78 | width: 100%; 79 | } 80 | 81 | .preview-lg { 82 | height: 184px; 83 | width: 184px; 84 | margin-top: 15px; 85 | } 86 | 87 | .preview-md { 88 | height: 100px; 89 | width: 100px; 90 | } 91 | 92 | .preview-sm { 93 | height: 50px; 94 | width: 50px; 95 | } 96 | 97 | @media (min-width: 992px) { 98 | .avatar-preview { 99 | float: none; 100 | } 101 | } 102 | 103 | .avatar-btns { 104 | margin-top: 30px; 105 | margin-bottom: 15px; 106 | } 107 | 108 | .avatar-btns .btn-group { 109 | margin-right: 5px; 110 | } 111 | 112 | .loading { 113 | display: none; 114 | position: absolute; 115 | top: 0; 116 | right: 0; 117 | bottom: 0; 118 | left: 0; 119 | background: #fff url("../img/loading.gif") no-repeat center center; 120 | opacity: .75; 121 | filter: alpha(opacity=75); 122 | z-index: 20140628; 123 | } 124 | -------------------------------------------------------------------------------- /bin/examples/avatar/css/qunit.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * QUnit 2.0.1 3 | * https://qunitjs.com/ 4 | * 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under the MIT license 7 | * https://jquery.org/license 8 | * 9 | * Date: 2016-07-23T19:39Z 10 | */ 11 | 12 | /** Font Family and Sizes */ 13 | 14 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult { 15 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 16 | } 17 | 18 | #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 19 | #qunit-tests { font-size: smaller; } 20 | 21 | 22 | /** Resets */ 23 | 24 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 25 | margin: 0; 26 | padding: 0; 27 | } 28 | 29 | 30 | /** Header (excluding toolbar) */ 31 | 32 | #qunit-header { 33 | padding: 0.5em 0 0.5em 1em; 34 | 35 | color: #8699A4; 36 | background-color: #0D3349; 37 | 38 | font-size: 1.5em; 39 | line-height: 1em; 40 | font-weight: 400; 41 | 42 | border-radius: 5px 5px 0 0; 43 | } 44 | 45 | #qunit-header a { 46 | text-decoration: none; 47 | color: #C2CCD1; 48 | } 49 | 50 | #qunit-header a:hover, 51 | #qunit-header a:focus { 52 | color: #FFF; 53 | } 54 | 55 | #qunit-banner { 56 | height: 5px; 57 | } 58 | 59 | #qunit-filteredTest { 60 | padding: 0.5em 1em 0.5em 1em; 61 | color: #366097; 62 | background-color: #F4FF77; 63 | } 64 | 65 | #qunit-userAgent { 66 | padding: 0.5em 1em 0.5em 1em; 67 | color: #FFF; 68 | background-color: #2B81AF; 69 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 70 | } 71 | 72 | 73 | /** Toolbar */ 74 | 75 | #qunit-testrunner-toolbar { 76 | padding: 0.5em 1em 0.5em 1em; 77 | color: #5E740B; 78 | background-color: #EEE; 79 | } 80 | 81 | #qunit-testrunner-toolbar .clearfix { 82 | height: 0; 83 | clear: both; 84 | } 85 | 86 | #qunit-testrunner-toolbar label { 87 | display: inline-block; 88 | } 89 | 90 | #qunit-testrunner-toolbar input[type=checkbox], 91 | #qunit-testrunner-toolbar input[type=radio] { 92 | margin: 3px; 93 | vertical-align: -2px; 94 | } 95 | 96 | #qunit-testrunner-toolbar input[type=text] { 97 | box-sizing: border-box; 98 | height: 1.6em; 99 | } 100 | 101 | .qunit-url-config, 102 | .qunit-filter, 103 | #qunit-modulefilter { 104 | display: inline-block; 105 | line-height: 2.1em; 106 | } 107 | 108 | .qunit-filter, 109 | #qunit-modulefilter { 110 | float: right; 111 | position: relative; 112 | margin-left: 1em; 113 | } 114 | 115 | .qunit-url-config label { 116 | margin-right: 0.5em; 117 | } 118 | 119 | #qunit-modulefilter-search { 120 | box-sizing: border-box; 121 | width: 400px; 122 | } 123 | 124 | #qunit-modulefilter-search-container:after { 125 | position: absolute; 126 | right: 0.3em; 127 | content: "\25bc"; 128 | color: black; 129 | } 130 | 131 | #qunit-modulefilter-dropdown { 132 | /* align with #qunit-modulefilter-search */ 133 | box-sizing: border-box; 134 | width: 400px; 135 | position: absolute; 136 | right: 0; 137 | top: 50%; 138 | margin-top: 0.8em; 139 | 140 | border: 1px solid #D3D3D3; 141 | border-top: none; 142 | border-radius: 0 0 .25em .25em; 143 | color: #000; 144 | background-color: #F5F5F5; 145 | z-index: 99; 146 | } 147 | 148 | #qunit-modulefilter-dropdown a { 149 | color: inherit; 150 | text-decoration: none; 151 | } 152 | 153 | #qunit-modulefilter-dropdown .clickable.checked { 154 | font-weight: bold; 155 | color: #000; 156 | background-color: #D2E0E6; 157 | } 158 | 159 | #qunit-modulefilter-dropdown .clickable:hover { 160 | color: #FFF; 161 | background-color: #0D3349; 162 | } 163 | 164 | #qunit-modulefilter-actions { 165 | display: block; 166 | overflow: auto; 167 | 168 | /* align with #qunit-modulefilter-dropdown-list */ 169 | font: smaller/1.5em sans-serif; 170 | } 171 | 172 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > * { 173 | box-sizing: border-box; 174 | max-height: 2.8em; 175 | display: block; 176 | padding: 0.4em; 177 | } 178 | 179 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > button { 180 | float: right; 181 | font: inherit; 182 | } 183 | 184 | #qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child { 185 | /* insert padding to align with checkbox margins */ 186 | padding-left: 3px; 187 | } 188 | 189 | #qunit-modulefilter-dropdown-list { 190 | max-height: 200px; 191 | overflow-y: auto; 192 | margin: 0; 193 | border-top: 2px groove threedhighlight; 194 | padding: 0.4em 0 0; 195 | font: smaller/1.5em sans-serif; 196 | } 197 | 198 | #qunit-modulefilter-dropdown-list li { 199 | white-space: nowrap; 200 | overflow: hidden; 201 | text-overflow: ellipsis; 202 | } 203 | 204 | #qunit-modulefilter-dropdown-list .clickable { 205 | display: block; 206 | padding-left: 0.15em; 207 | } 208 | 209 | 210 | /** Tests: Pass/Fail */ 211 | 212 | #qunit-tests { 213 | list-style-position: inside; 214 | } 215 | 216 | #qunit-tests li { 217 | padding: 0.4em 1em 0.4em 1em; 218 | border-bottom: 1px solid #FFF; 219 | list-style-position: inside; 220 | } 221 | 222 | #qunit-tests > li { 223 | display: none; 224 | } 225 | 226 | #qunit-tests li.running, 227 | #qunit-tests li.pass, 228 | #qunit-tests li.fail, 229 | #qunit-tests li.skipped { 230 | display: list-item; 231 | } 232 | 233 | #qunit-tests.hidepass { 234 | position: relative; 235 | } 236 | 237 | #qunit-tests.hidepass li.running, 238 | #qunit-tests.hidepass li.pass { 239 | visibility: hidden; 240 | position: absolute; 241 | width: 0; 242 | height: 0; 243 | padding: 0; 244 | border: 0; 245 | margin: 0; 246 | } 247 | 248 | #qunit-tests li strong { 249 | cursor: pointer; 250 | } 251 | 252 | #qunit-tests li.skipped strong { 253 | cursor: default; 254 | } 255 | 256 | #qunit-tests li a { 257 | padding: 0.5em; 258 | color: #C2CCD1; 259 | text-decoration: none; 260 | } 261 | 262 | #qunit-tests li p a { 263 | padding: 0.25em; 264 | color: #6B6464; 265 | } 266 | #qunit-tests li a:hover, 267 | #qunit-tests li a:focus { 268 | color: #000; 269 | } 270 | 271 | #qunit-tests li .runtime { 272 | float: right; 273 | font-size: smaller; 274 | } 275 | 276 | .qunit-assert-list { 277 | margin-top: 0.5em; 278 | padding: 0.5em; 279 | 280 | background-color: #FFF; 281 | 282 | border-radius: 5px; 283 | } 284 | 285 | .qunit-source { 286 | margin: 0.6em 0 0.3em; 287 | } 288 | 289 | .qunit-collapsed { 290 | display: none; 291 | } 292 | 293 | #qunit-tests table { 294 | border-collapse: collapse; 295 | margin-top: 0.2em; 296 | } 297 | 298 | #qunit-tests th { 299 | text-align: right; 300 | vertical-align: top; 301 | padding: 0 0.5em 0 0; 302 | } 303 | 304 | #qunit-tests td { 305 | vertical-align: top; 306 | } 307 | 308 | #qunit-tests pre { 309 | margin: 0; 310 | white-space: pre-wrap; 311 | word-wrap: break-word; 312 | } 313 | 314 | #qunit-tests del { 315 | color: #374E0C; 316 | background-color: #E0F2BE; 317 | text-decoration: none; 318 | } 319 | 320 | #qunit-tests ins { 321 | color: #500; 322 | background-color: #FFCACA; 323 | text-decoration: none; 324 | } 325 | 326 | /*** Test Counts */ 327 | 328 | #qunit-tests b.counts { color: #000; } 329 | #qunit-tests b.passed { color: #5E740B; } 330 | #qunit-tests b.failed { color: #710909; } 331 | 332 | #qunit-tests li li { 333 | padding: 5px; 334 | background-color: #FFF; 335 | border-bottom: none; 336 | list-style-position: inside; 337 | } 338 | 339 | /*** Passing Styles */ 340 | 341 | #qunit-tests li li.pass { 342 | color: #3C510C; 343 | background-color: #FFF; 344 | border-left: 10px solid #C6E746; 345 | } 346 | 347 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 348 | #qunit-tests .pass .test-name { color: #366097; } 349 | 350 | #qunit-tests .pass .test-actual, 351 | #qunit-tests .pass .test-expected { color: #999; } 352 | 353 | #qunit-banner.qunit-pass { background-color: #C6E746; } 354 | 355 | /*** Failing Styles */ 356 | 357 | #qunit-tests li li.fail { 358 | color: #710909; 359 | background-color: #FFF; 360 | border-left: 10px solid #EE5757; 361 | white-space: pre; 362 | } 363 | 364 | #qunit-tests > li:last-child { 365 | border-radius: 0 0 5px 5px; 366 | } 367 | 368 | #qunit-tests .fail { color: #000; background-color: #EE5757; } 369 | #qunit-tests .fail .test-name, 370 | #qunit-tests .fail .module-name { color: #000; } 371 | 372 | #qunit-tests .fail .test-actual { color: #EE5757; } 373 | #qunit-tests .fail .test-expected { color: #008000; } 374 | 375 | #qunit-banner.qunit-fail { background-color: #EE5757; } 376 | 377 | /*** Skipped tests */ 378 | 379 | #qunit-tests .skipped { 380 | background-color: #EBECE9; 381 | } 382 | 383 | #qunit-tests .qunit-skipped-label { 384 | background-color: #F4FF77; 385 | display: inline-block; 386 | font-style: normal; 387 | color: #366097; 388 | line-height: 1.8em; 389 | padding: 0 0.5em; 390 | margin: -0.4em 0.4em -0.4em 0; 391 | } 392 | 393 | /** Result */ 394 | 395 | #qunit-testresult { 396 | padding: 0.5em 1em 0.5em 1em; 397 | 398 | color: #2B81AF; 399 | background-color: #D2E0E6; 400 | 401 | border-bottom: 1px solid #FFF; 402 | } 403 | #qunit-testresult .module-name { 404 | font-weight: 700; 405 | } 406 | 407 | /** Fixture */ 408 | 409 | #qunit-fixture { 410 | position: absolute; 411 | top: -10000px; 412 | left: -10000px; 413 | width: 1000px; 414 | height: 1000px; 415 | } 416 | -------------------------------------------------------------------------------- /bin/examples/avatar/img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k9982874/stack/3c1ff3b9dfd96556b3a3e09487cb0befc5e926a7/bin/examples/avatar/img/loading.gif -------------------------------------------------------------------------------- /bin/examples/avatar/img/picture.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k9982874/stack/3c1ff3b9dfd96556b3a3e09487cb0befc5e926a7/bin/examples/avatar/img/picture.jpg -------------------------------------------------------------------------------- /bin/examples/avatar/index.html: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k9982874/stack/3c1ff3b9dfd96556b3a3e09487cb0befc5e926a7/bin/examples/avatar/index.html -------------------------------------------------------------------------------- /bin/examples/avatar/js/bootstrap.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.7 (http://getbootstrap.com) 3 | * Copyright 2011-2016 Twitter, Inc. 4 | * Licensed under the MIT license 5 | */ 6 | if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&jdocument.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:''}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b=e[a]&&(void 0===e[a+1]||b .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery); -------------------------------------------------------------------------------- /bin/examples/avatar/js/cropper.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * Cropper v2.3.4 3 | * https://github.com/fengyuanchen/cropper 4 | * 5 | * Copyright (c) 2014-2016 Fengyuan Chen and contributors 6 | * Released under the MIT license 7 | * 8 | * Date: 2016-09-03T05:50:45.412Z 9 | */ 10 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){"use strict";function i(t){return"number"==typeof t&&!isNaN(t)}function e(t){return"undefined"==typeof t}function s(t,e){var s=[];return i(e)&&s.push(e),s.slice.apply(t,s)}function a(t,i){var e=s(arguments,2);return function(){return t.apply(i,e.concat(s(arguments)))}}function o(t){var i=t.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);return i&&(i[1]!==C.protocol||i[2]!==C.hostname||i[3]!==C.port)}function h(t){var i="timestamp="+(new Date).getTime();return t+(t.indexOf("?")===-1?"?":"&")+i}function n(t){return t?' crossOrigin="'+t+'"':""}function r(t,i){var e;return t.naturalWidth&&!mt?i(t.naturalWidth,t.naturalHeight):(e=document.createElement("img"),e.onload=function(){i(this.width,this.height)},void(e.src=t.src))}function p(t){var e=[],s=t.rotate,a=t.scaleX,o=t.scaleY;return i(s)&&0!==s&&e.push("rotate("+s+"deg)"),i(a)&&1!==a&&e.push("scaleX("+a+")"),i(o)&&1!==o&&e.push("scaleY("+o+")"),e.length?e.join(" "):"none"}function l(t,i){var e,s,a=Ct(t.degree)%180,o=(a>90?180-a:a)*Math.PI/180,h=bt(o),n=Bt(o),r=t.width,p=t.height,l=t.aspectRatio;return i?(e=r/(n+h/l),s=e/l):(e=r*n+p*h,s=r*h+p*n),{width:e,height:s}}function c(e,s){var a,o,h,n=t("")[0],r=n.getContext("2d"),p=0,c=0,d=s.naturalWidth,g=s.naturalHeight,u=s.rotate,f=s.scaleX,m=s.scaleY,v=i(f)&&i(m)&&(1!==f||1!==m),w=i(u)&&0!==u,x=w||v,C=d*Ct(f||1),b=g*Ct(m||1);return v&&(a=C/2,o=b/2),w&&(h=l({width:C,height:b,degree:u}),C=h.width,b=h.height,a=C/2,o=b/2),n.width=C,n.height=b,x&&(p=-d/2,c=-g/2,r.save(),r.translate(a,o)),w&&r.rotate(u*Math.PI/180),v&&r.scale(f,m),r.drawImage(e,$t(p),$t(c),$t(d),$t(g)),x&&r.restore(),n}function d(i){var e=i.length,s=0,a=0;return e&&(t.each(i,function(t,i){s+=i.pageX,a+=i.pageY}),s/=e,a/=e),{pageX:s,pageY:a}}function g(t,i,e){var s,a="";for(s=i,e+=i;s=8&&(r=s+a)))),r)for(d=c.getUint16(r,o),l=0;l")[0].getContext),mt=b&&/(Macintosh|iPhone|iPod|iPad).*AppleWebKit/i.test(b.userAgent),vt=Number,wt=Math.min,xt=Math.max,Ct=Math.abs,bt=Math.sin,Bt=Math.cos,yt=Math.sqrt,Dt=Math.round,$t=Math.floor,Lt=String.fromCharCode;v.prototype={constructor:v,init:function(){var t,i=this.$element;if(i.is("img")){if(this.isImg=!0,this.originalUrl=t=i.attr("src"),!t)return;t=i.prop("src")}else i.is("canvas")&&ft&&(t=i[0].toDataURL());this.load(t)},trigger:function(i,e){var s=t.Event(i,e);return this.$element.trigger(s),s},load:function(i){var e,s,a=this.options,n=this.$element;if(i&&(n.one(A,a.build),!this.trigger(A).isDefaultPrevented())){if(this.url=i,this.image={},!a.checkOrientation||!B)return this.clone();if(e=t.proxy(this.read,this),V.test(i))return J.test(i)?e(f(i)):this.clone();s=new XMLHttpRequest,s.onerror=s.onabort=t.proxy(function(){this.clone()},this),s.onload=function(){e(this.response)},a.checkCrossOrigin&&o(i)&&n.prop("crossOrigin")&&(i=h(i)),s.open("get",i),s.responseType="arraybuffer",s.send()}},read:function(t){var i=this.options,e=u(t),s=this.image,a=0,o=1,h=1;if(e>1)switch(this.url=m(t),e){case 2:o=-1;break;case 3:a=-180;break;case 4:h=-1;break;case 5:a=90,h=-1;break;case 6:a=90;break;case 7:a=90,o=-1;break;case 8:a=-90}i.rotatable&&(s.rotate=a),i.scalable&&(s.scaleX=o,s.scaleY=h),this.clone()},clone:function(){var i,e,s=this.options,a=this.$element,r=this.url,p="";s.checkCrossOrigin&&o(r)&&(p=a.prop("crossOrigin"),p?i=r:(p="anonymous",i=h(r))),this.crossOrigin=p,this.crossOriginUrl=i,this.$clone=e=t("'),this.isImg?a[0].complete?this.start():a.one(I,t.proxy(this.start,this)):e.one(I,t.proxy(this.start,this)).one(F,t.proxy(this.stop,this)).addClass(X).insertAfter(a)},start:function(){var i=this.$element,e=this.$clone;this.isImg||(e.off(F,this.stop),i=e),r(i[0],t.proxy(function(i,e){t.extend(this.image,{naturalWidth:i,naturalHeight:e,aspectRatio:i/e}),this.isLoaded=!0,this.build()},this))},stop:function(){this.$clone.remove(),this.$clone=null},build:function(){var i,e,s,a=this.options,o=this.$element,h=this.$clone;this.isLoaded&&(this.isBuilt&&this.unbuild(),this.$container=o.parent(),this.$cropper=i=t(v.TEMPLATE),this.$canvas=i.find(".cropper-canvas").append(h),this.$dragBox=i.find(".cropper-drag-box"),this.$cropBox=e=i.find(".cropper-crop-box"),this.$viewBox=i.find(".cropper-view-box"),this.$face=s=e.find(".cropper-face"),o.addClass(Y).after(i),this.isImg||h.removeClass(X),this.initPreview(),this.bind(),a.aspectRatio=xt(0,a.aspectRatio)||NaN,a.viewMode=xt(0,wt(3,Dt(a.viewMode)))||0,a.autoCrop?(this.isCropped=!0,a.modal&&this.$dragBox.addClass(T)):e.addClass(Y),a.guides||e.find(".cropper-dashed").addClass(Y),a.center||e.find(".cropper-center").addClass(Y),a.cropBoxMovable&&s.addClass(M).data(it,lt),a.highlight||s.addClass(k),a.background&&i.addClass(R),a.cropBoxResizable||e.find(".cropper-line, .cropper-point").addClass(Y),this.setDragMode(a.dragMode),this.render(),this.isBuilt=!0,this.setData(a.data),o.one(S,a.built),this.completing=setTimeout(t.proxy(function(){this.trigger(S),this.trigger(K,this.getData()),this.isCompleted=!0},this),0))},unbuild:function(){this.isBuilt&&(this.isCompleted||clearTimeout(this.completing),this.isBuilt=!1,this.isCompleted=!1,this.initialImage=null,this.initialCanvas=null,this.initialCropBox=null,this.container=null,this.canvas=null,this.cropBox=null,this.unbind(),this.resetPreview(),this.$preview=null,this.$viewBox=null,this.$cropBox=null,this.$dragBox=null,this.$canvas=null,this.$container=null,this.$cropper.remove(),this.$cropper=null)},render:function(){this.initContainer(),this.initCanvas(),this.initCropBox(),this.renderCanvas(),this.isCropped&&this.renderCropBox()},initContainer:function(){var t=this.options,i=this.$element,e=this.$container,s=this.$cropper;s.addClass(Y),i.removeClass(Y),s.css(this.container={width:xt(e.width(),vt(t.minContainerWidth)||200),height:xt(e.height(),vt(t.minContainerHeight)||100)}),i.addClass(Y),s.removeClass(Y)},initCanvas:function(){var i,e=this.options.viewMode,s=this.container,a=s.width,o=s.height,h=this.image,n=h.naturalWidth,r=h.naturalHeight,p=90===Ct(h.rotate),l=p?r:n,c=p?n:r,d=l/c,g=a,u=o;o*d>a?3===e?g=o*d:u=a/d:3===e?u=a/d:g=o*d,i={naturalWidth:l,naturalHeight:c,aspectRatio:d,width:g,height:u},i.oldLeft=i.left=(a-g)/2,i.oldTop=i.top=(o-u)/2,this.canvas=i,this.isLimited=1===e||2===e,this.limitCanvas(!0,!0),this.initialImage=t.extend({},h),this.initialCanvas=t.extend({},i)},limitCanvas:function(t,i){var e,s,a,o,h=this.options,n=h.viewMode,r=this.container,p=r.width,l=r.height,c=this.canvas,d=c.aspectRatio,g=this.cropBox,u=this.isCropped&&g;t&&(e=vt(h.minCanvasWidth)||0,s=vt(h.minCanvasHeight)||0,n&&(n>1?(e=xt(e,p),s=xt(s,l),3===n&&(s*d>e?e=s*d:s=e/d)):e?e=xt(e,u?g.width:0):s?s=xt(s,u?g.height:0):u&&(e=g.width,s=g.height,s*d>e?e=s*d:s=e/d)),e&&s?s*d>e?s=e/d:e=s*d:e?s=e/d:s&&(e=s*d),c.minWidth=e,c.minHeight=s,c.maxWidth=1/0,c.maxHeight=1/0),i&&(n?(a=p-c.width,o=l-c.height,c.minLeft=wt(0,a),c.minTop=wt(0,o),c.maxLeft=xt(0,a),c.maxTop=xt(0,o),u&&this.isLimited&&(c.minLeft=wt(g.left,g.left+g.width-c.width),c.minTop=wt(g.top,g.top+g.height-c.height),c.maxLeft=g.left,c.maxTop=g.top,2===n&&(c.width>=p&&(c.minLeft=wt(0,a),c.maxLeft=xt(0,a)),c.height>=l&&(c.minTop=wt(0,o),c.maxTop=xt(0,o))))):(c.minLeft=-c.width,c.minTop=-c.height,c.maxLeft=p,c.maxTop=l))},renderCanvas:function(t){var i,e,s=this.canvas,a=this.image,o=a.rotate,h=a.naturalWidth,n=a.naturalHeight;this.isRotated&&(this.isRotated=!1,e=l({width:a.width,height:a.height,degree:o}),i=e.width/e.height,i!==s.aspectRatio&&(s.left-=(e.width-s.width)/2,s.top-=(e.height-s.height)/2,s.width=e.width,s.height=e.height,s.aspectRatio=i,s.naturalWidth=h,s.naturalHeight=n,o%180&&(e=l({width:h,height:n,degree:o}),s.naturalWidth=e.width,s.naturalHeight=e.height),this.limitCanvas(!0,!1))),(s.width>s.maxWidth||s.widths.maxHeight||s.heighte.width?o.height=o.width/s:o.width=o.height*s),this.cropBox=o,this.limitCropBox(!0,!0),o.width=wt(xt(o.width,o.minWidth),o.maxWidth),o.height=wt(xt(o.height,o.minHeight),o.maxHeight),o.width=xt(o.minWidth,o.width*a),o.height=xt(o.minHeight,o.height*a),o.oldLeft=o.left=e.left+(e.width-o.width)/2,o.oldTop=o.top=e.top+(e.height-o.height)/2,this.initialCropBox=t.extend({},o)},limitCropBox:function(t,i){var e,s,a,o,h=this.options,n=h.aspectRatio,r=this.container,p=r.width,l=r.height,c=this.canvas,d=this.cropBox,g=this.isLimited;t&&(e=vt(h.minCropBoxWidth)||0,s=vt(h.minCropBoxHeight)||0,e=wt(e,p),s=wt(s,l),a=wt(p,g?c.width:p),o=wt(l,g?c.height:l),n&&(e&&s?s*n>e?s=e/n:e=s*n:e?s=e/n:s&&(e=s*n),o*n>a?o=a/n:a=o*n),d.minWidth=wt(e,a),d.minHeight=wt(s,o),d.maxWidth=a,d.maxHeight=o),i&&(g?(d.minLeft=xt(0,c.left),d.minTop=xt(0,c.top),d.maxLeft=wt(p,c.left+c.width)-d.width,d.maxTop=wt(l,c.top+c.height)-d.height):(d.minLeft=0,d.minTop=0,d.maxLeft=p-d.width,d.maxTop=l-d.height))},renderCropBox:function(){var t=this.options,i=this.container,e=i.width,s=i.height,a=this.cropBox;(a.width>a.maxWidth||a.widtha.maxHeight||a.height'),this.$viewBox.html(i),this.$preview.each(function(){var i=t(this);i.data(tt,{width:i.width(),height:i.height(),html:i.html()}),i.html("')})},resetPreview:function(){this.$preview.each(function(){var i=t(this),e=i.data(tt);i.css({width:e.width,height:e.height}).html(e.html).removeData(tt)})},preview:function(){var i=this.image,e=this.canvas,s=this.cropBox,a=s.width,o=s.height,h=i.width,n=i.height,r=s.left-e.left-i.left,l=s.top-e.top-i.top;this.isCropped&&!this.isDisabled&&(this.$clone2.css({width:h,height:n,marginLeft:-r,marginTop:-l,transform:p(i)}),this.$preview.each(function(){var e=t(this),s=e.data(tt),c=s.width,d=s.height,g=c,u=d,f=1;a&&(f=c/a,u=o*f),o&&u>d&&(f=d/o,g=a*f,u=d),e.css({width:g,height:u}).find("img").css({width:h*f,height:n*f,marginLeft:-r*f,marginTop:-l*f,transform:p(i)})}))},bind:function(){var i=this.options,e=this.$element,s=this.$cropper;t.isFunction(i.cropstart)&&e.on(N,i.cropstart),t.isFunction(i.cropmove)&&e.on(_,i.cropmove),t.isFunction(i.cropend)&&e.on(q,i.cropend),t.isFunction(i.crop)&&e.on(K,i.crop),t.isFunction(i.zoom)&&e.on(Z,i.zoom),s.on(z,t.proxy(this.cropStart,this)),i.zoomable&&i.zoomOnWheel&&s.on(E,t.proxy(this.wheel,this)),i.toggleDragModeOnDblclick&&s.on(U,t.proxy(this.dblclick,this)),x.on(O,this._cropMove=a(this.cropMove,this)).on(P,this._cropEnd=a(this.cropEnd,this)),i.responsive&&w.on(j,this._resize=a(this.resize,this))},unbind:function(){var i=this.options,e=this.$element,s=this.$cropper;t.isFunction(i.cropstart)&&e.off(N,i.cropstart),t.isFunction(i.cropmove)&&e.off(_,i.cropmove),t.isFunction(i.cropend)&&e.off(q,i.cropend),t.isFunction(i.crop)&&e.off(K,i.crop),t.isFunction(i.zoom)&&e.off(Z,i.zoom),s.off(z,this.cropStart),i.zoomable&&i.zoomOnWheel&&s.off(E,this.wheel),i.toggleDragModeOnDblclick&&s.off(U,this.dblclick),x.off(O,this._cropMove).off(P,this._cropEnd),i.responsive&&w.off(j,this._resize)},resize:function(){var i,e,s,a=this.options.restore,o=this.$container,h=this.container;!this.isDisabled&&h&&(s=o.width()/h.width,1===s&&o.height()===h.height||(a&&(i=this.getCanvasData(),e=this.getCropBoxData()),this.render(),a&&(this.setCanvasData(t.each(i,function(t,e){i[t]=e*s})),this.setCropBoxData(t.each(e,function(t,i){e[t]=i*s})))))},dblclick:function(){this.isDisabled||(this.$dragBox.hasClass(W)?this.setDragMode(dt):this.setDragMode(ct))},wheel:function(i){var e=i.originalEvent||i,s=vt(this.options.wheelZoomRatio)||.1,a=1;this.isDisabled||(i.preventDefault(),this.wheeling||(this.wheeling=!0,setTimeout(t.proxy(function(){this.wheeling=!1},this),50),e.deltaY?a=e.deltaY>0?1:-1:e.wheelDelta?a=-e.wheelDelta/120:e.detail&&(a=e.detail>0?1:-1),this.zoom(-a*s,i)))},cropStart:function(i){var e,s,a=this.options,o=i.originalEvent,h=o&&o.touches,n=i;if(!this.isDisabled){if(h){if(e=h.length,e>1){if(!a.zoomable||!a.zoomOnTouch||2!==e)return;n=h[1],this.startX2=n.pageX,this.startY2=n.pageY,s=gt}n=h[0]}if(s=s||t(n.target).data(it),Q.test(s)){if(this.trigger(N,{originalEvent:o,action:s}).isDefaultPrevented())return;i.preventDefault(),this.action=s,this.cropping=!1,this.startX=n.pageX||o&&o.pageX,this.startY=n.pageY||o&&o.pageY,s===ct&&(this.cropping=!0,this.$dragBox.addClass(T))}}},cropMove:function(t){var i,e=this.options,s=t.originalEvent,a=s&&s.touches,o=t,h=this.action;if(!this.isDisabled){if(a){if(i=a.length,i>1){if(!e.zoomable||!e.zoomOnTouch||2!==i)return;o=a[1],this.endX2=o.pageX,this.endY2=o.pageY}o=a[0]}if(h){if(this.trigger(_,{originalEvent:s,action:h}).isDefaultPrevented())return;t.preventDefault(),this.endX=o.pageX||s&&s.pageX,this.endY=o.pageY||s&&s.pageY,this.change(o.shiftKey,h===gt?t:null)}}},cropEnd:function(t){var i=t.originalEvent,e=this.action;this.isDisabled||e&&(t.preventDefault(),this.cropping&&(this.cropping=!1,this.$dragBox.toggleClass(T,this.isCropped&&this.options.modal)),this.action="",this.trigger(q,{originalEvent:i,action:e}))},change:function(t,i){var e,s,a=this.options,o=a.aspectRatio,h=this.action,n=this.container,r=this.canvas,p=this.cropBox,l=p.width,c=p.height,d=p.left,g=p.top,u=d+l,f=g+c,m=0,v=0,w=n.width,x=n.height,C=!0;switch(!o&&t&&(o=l&&c?l/c:1),this.isLimited&&(m=p.minLeft,v=p.minTop,w=m+wt(n.width,r.width,r.left+r.width),x=v+wt(n.height,r.height,r.top+r.height)),s={x:this.endX-this.startX,y:this.endY-this.startY},o&&(s.X=s.y*o,s.Y=s.x/o),h){case lt:d+=s.x,g+=s.y;break;case et:if(s.x>=0&&(u>=w||o&&(g<=v||f>=x))){C=!1;break}l+=s.x,o&&(c=l/o,g-=s.Y/2),l<0&&(h=st,l=0);break;case ot:if(s.y<=0&&(g<=v||o&&(d<=m||u>=w))){C=!1;break}c-=s.y,g+=s.y,o&&(l=c*o,d+=s.X/2),c<0&&(h=at,c=0);break;case st:if(s.x<=0&&(d<=m||o&&(g<=v||f>=x))){C=!1;break}l-=s.x,d+=s.x,o&&(c=l/o,g+=s.Y/2),l<0&&(h=et,l=0);break;case at:if(s.y>=0&&(f>=x||o&&(d<=m||u>=w))){C=!1;break}c+=s.y,o&&(l=c*o,d-=s.X/2),c<0&&(h=ot,c=0);break;case rt:if(o){if(s.y<=0&&(g<=v||u>=w)){C=!1;break}c-=s.y,g+=s.y,l=c*o}else s.x>=0?uv&&(c-=s.y,g+=s.y):(c-=s.y,g+=s.y);l<0&&c<0?(h=nt,c=0,l=0):l<0?(h=pt,l=0):c<0&&(h=ht,c=0);break;case pt:if(o){if(s.y<=0&&(g<=v||d<=m)){C=!1;break}c-=s.y,g+=s.y,l=c*o,d+=s.X}else s.x<=0?d>m?(l-=s.x,d+=s.x):s.y<=0&&g<=v&&(C=!1):(l-=s.x,d+=s.x),s.y<=0?g>v&&(c-=s.y,g+=s.y):(c-=s.y,g+=s.y);l<0&&c<0?(h=ht,c=0,l=0):l<0?(h=rt,l=0):c<0&&(h=nt,c=0);break;case nt:if(o){if(s.x<=0&&(d<=m||f>=x)){C=!1;break}l-=s.x,d+=s.x,c=l/o}else s.x<=0?d>m?(l-=s.x,d+=s.x):s.y>=0&&f>=x&&(C=!1):(l-=s.x,d+=s.x),s.y>=0?f=0&&(u>=w||f>=x)){C=!1;break}l+=s.x,c=l/o}else s.x>=0?u=0&&f>=x&&(C=!1):l+=s.x,s.y>=0?f0?h=s.y>0?ht:rt:s.x<0&&(d-=l,h=s.y>0?nt:pt),s.y<0&&(g-=c),this.isCropped||(this.$cropBox.removeClass(Y),this.isCropped=!0,this.isLimited&&this.limitCropBox(!0,!0))}C&&(p.width=l,p.height=c,p.left=d,p.top=g,this.action=h,this.renderCropBox()),this.startX=this.endX,this.startY=this.endY},crop:function(){this.isBuilt&&!this.isDisabled&&(this.isCropped||(this.isCropped=!0,this.limitCropBox(!0,!0),this.options.modal&&this.$dragBox.addClass(T),this.$cropBox.removeClass(Y)),this.setCropBoxData(this.initialCropBox))},reset:function(){this.isBuilt&&!this.isDisabled&&(this.image=t.extend({},this.initialImage),this.canvas=t.extend({},this.initialCanvas),this.cropBox=t.extend({},this.initialCropBox),this.renderCanvas(),this.isCropped&&this.renderCropBox())},clear:function(){this.isCropped&&!this.isDisabled&&(t.extend(this.cropBox,{left:0,top:0,width:0,height:0}),this.isCropped=!1,this.renderCropBox(),this.limitCanvas(!0,!0),this.renderCanvas(),this.$dragBox.removeClass(T),this.$cropBox.addClass(Y))},replace:function(t,i){!this.isDisabled&&t&&(this.isImg&&this.$element.attr("src",t),i?(this.url=t,this.$clone.attr("src",t),this.isBuilt&&this.$preview.find("img").add(this.$clone2).attr("src",t)):(this.isImg&&(this.isReplaced=!0),this.options.data=null,this.load(t)))},enable:function(){this.isBuilt&&(this.isDisabled=!1,this.$cropper.removeClass(H))},disable:function(){this.isBuilt&&(this.isDisabled=!0,this.$cropper.addClass(H))},destroy:function(){var t=this.$element;this.isLoaded?(this.isImg&&this.isReplaced&&t.attr("src",this.originalUrl),this.unbuild(),t.removeClass(Y)):this.isImg?t.off(I,this.start):this.$clone&&this.$clone.remove(),t.removeData(L)},move:function(t,i){var s=this.canvas;this.moveTo(e(t)?t:s.left+vt(t),e(i)?i:s.top+vt(i))},moveTo:function(t,s){var a=this.canvas,o=!1;e(s)&&(s=t),t=vt(t),s=vt(s),this.isBuilt&&!this.isDisabled&&this.options.movable&&(i(t)&&(a.left=t,o=!0),i(s)&&(a.top=s,o=!0),o&&this.renderCanvas(!0))},zoom:function(t,i){var e=this.canvas;t=vt(t),t=t<0?1/(1-t):1+t,this.zoomTo(e.width*t/e.naturalWidth,i)},zoomTo:function(t,i){var e,s,a,o,h,n=this.options,r=this.canvas,p=r.width,l=r.height,c=r.naturalWidth,g=r.naturalHeight;if(t=vt(t),t>=0&&this.isBuilt&&!this.isDisabled&&n.zoomable){if(s=c*t,a=g*t,i&&(e=i.originalEvent),this.trigger(Z,{originalEvent:e,oldRatio:p/c,ratio:s/c}).isDefaultPrevented())return;e?(o=this.$cropper.offset(),h=e.touches?d(e.touches):{pageX:i.pageX||e.pageX||0,pageY:i.pageY||e.pageY||0},r.left-=(s-p)*((h.pageX-o.left-r.left)/p),r.top-=(a-l)*((h.pageY-o.top-r.top)/l)):(r.left-=(s-p)/2,r.top-=(a-l)/2),r.width=s,r.height=a,this.renderCanvas(!0)}},rotate:function(t){this.rotateTo((this.image.rotate||0)+vt(t))},rotateTo:function(t){t=vt(t),i(t)&&this.isBuilt&&!this.isDisabled&&this.options.rotatable&&(this.image.rotate=t%360,this.isRotated=!0,this.renderCanvas(!0))},scale:function(t,s){var a=this.image,o=!1;e(s)&&(s=t),t=vt(t),s=vt(s),this.isBuilt&&!this.isDisabled&&this.options.scalable&&(i(t)&&(a.scaleX=t,o=!0),i(s)&&(a.scaleY=s,o=!0),o&&this.renderImage(!0))},scaleX:function(t){var e=this.image.scaleY;this.scale(t,i(e)?e:1)},scaleY:function(t){var e=this.image.scaleX;this.scale(i(e)?e:1,t)},getData:function(i){var e,s,a=this.options,o=this.image,h=this.canvas,n=this.cropBox;return this.isBuilt&&this.isCropped?(s={x:n.left-h.left,y:n.top-h.top,width:n.width,height:n.height},e=o.width/o.naturalWidth,t.each(s,function(t,a){a/=e,s[t]=i?Dt(a):a})):s={x:0,y:0,width:0,height:0},a.rotatable&&(s.rotate=o.rotate||0),a.scalable&&(s.scaleX=o.scaleX||1,s.scaleY=o.scaleY||1),s},setData:function(e){var s,a,o,h=this.options,n=this.image,r=this.canvas,p={};t.isFunction(e)&&(e=e.call(this.element)),this.isBuilt&&!this.isDisabled&&t.isPlainObject(e)&&(h.rotatable&&i(e.rotate)&&e.rotate!==n.rotate&&(n.rotate=e.rotate,this.isRotated=s=!0),h.scalable&&(i(e.scaleX)&&e.scaleX!==n.scaleX&&(n.scaleX=e.scaleX,a=!0),i(e.scaleY)&&e.scaleY!==n.scaleY&&(n.scaleY=e.scaleY,a=!0)),s?this.renderCanvas():a&&this.renderImage(),o=n.width/n.naturalWidth,i(e.x)&&(p.left=e.x*o+r.left),i(e.y)&&(p.top=e.y*o+r.top),i(e.width)&&(p.width=e.width*o),i(e.height)&&(p.height=e.height*o),this.setCropBoxData(p))},getContainerData:function(){return this.isBuilt?this.container:{}},getImageData:function(){return this.isLoaded?this.image:{}},getCanvasData:function(){var i=this.canvas,e={};return this.isBuilt&&t.each(["left","top","width","height","naturalWidth","naturalHeight"],function(t,s){e[s]=i[s]}),e},setCanvasData:function(e){var s=this.canvas,a=s.aspectRatio;t.isFunction(e)&&(e=e.call(this.$element)),this.isBuilt&&!this.isDisabled&&t.isPlainObject(e)&&(i(e.left)&&(s.left=e.left),i(e.top)&&(s.top=e.top),i(e.width)?(s.width=e.width,s.height=e.width/a):i(e.height)&&(s.height=e.height,s.width=e.height*a),this.renderCanvas(!0))},getCropBoxData:function(){var t,i=this.cropBox;return this.isBuilt&&this.isCropped&&(t={left:i.left,top:i.top,width:i.width,height:i.height}),t||{}},setCropBoxData:function(e){var s,a,o=this.cropBox,h=this.options.aspectRatio;t.isFunction(e)&&(e=e.call(this.$element)),this.isBuilt&&this.isCropped&&!this.isDisabled&&t.isPlainObject(e)&&(i(e.left)&&(o.left=e.left),i(e.top)&&(o.top=e.top),i(e.width)&&(s=!0,o.width=e.width),i(e.height)&&(a=!0,o.height=e.height),h&&(s?o.height=o.width/h:a&&(o.width=o.height*h)),this.renderCropBox())},getCroppedCanvas:function(i){var e,s,a,o,h,n,r,p,l,d,g;if(this.isBuilt&&ft)return this.isCropped?(t.isPlainObject(i)||(i={}),g=this.getData(),e=g.width,s=g.height,p=e/s,t.isPlainObject(i)&&(h=i.width,n=i.height,h?(n=h/p,r=h/e):n&&(h=n*p,r=n/s)),a=$t(h||e),o=$t(n||s),l=t("")[0],l.width=a,l.height=o,d=l.getContext("2d"),i.fillColor&&(d.fillStyle=i.fillColor,d.fillRect(0,0,a,o)),d.drawImage.apply(d,function(){var t,i,a,o,h,n,p=c(this.$clone[0],this.image),l=p.width,d=p.height,u=this.canvas,f=[p],m=g.x+u.naturalWidth*(Ct(g.scaleX||1)-1)/2,v=g.y+u.naturalHeight*(Ct(g.scaleY||1)-1)/2;return m<=-e||m>l?m=t=a=h=0:m<=0?(a=-m,m=0,t=h=wt(l,e+m)):m<=l&&(a=0,t=h=wt(e,l-m)),t<=0||v<=-s||v>d?v=i=o=n=0:v<=0?(o=-v,v=0,i=n=wt(d,s+v)):v<=d&&(o=0,i=n=wt(s,d-v)),f.push($t(m),$t(v),$t(t),$t(i)),r&&(a*=r,o*=r,h*=r,n*=r),h>0&&n>0&&f.push($t(a),$t(o),$t(h),$t(n)),f}.call(this)),l):c(this.$clone[0],this.image)},setAspectRatio:function(t){var i=this.options;this.isDisabled||e(t)||(i.aspectRatio=xt(0,t)||NaN,this.isBuilt&&(this.initCropBox(),this.isCropped&&this.renderCropBox()))},setDragMode:function(t){var i,e,s=this.options;this.isLoaded&&!this.isDisabled&&(i=t===ct,e=s.movable&&t===dt,t=i||e?t:ut,this.$dragBox.data(it,t).toggleClass(W,i).toggleClass(M,e),s.cropBoxMovable||this.$face.data(it,t).toggleClass(W,i).toggleClass(M,e))}},v.DEFAULTS={viewMode:0,dragMode:"crop",aspectRatio:NaN,data:null,preview:"",responsive:!0,restore:!0,checkCrossOrigin:!0,checkOrientation:!0,modal:!0,guides:!0,center:!0,highlight:!0,background:!0,autoCrop:!0,autoCropArea:.8,movable:!0,rotatable:!0,scalable:!0,zoomable:!0,zoomOnTouch:!0,zoomOnWheel:!0,wheelZoomRatio:.1,cropBoxMovable:!0,cropBoxResizable:!0,toggleDragModeOnDblclick:!0,minCanvasWidth:0,minCanvasHeight:0,minCropBoxWidth:0,minCropBoxHeight:0,minContainerWidth:200,minContainerHeight:100,build:null,built:null,cropstart:null,cropmove:null,cropend:null,crop:null,zoom:null},v.setDefaults=function(i){t.extend(v.DEFAULTS,i)},v.TEMPLATE='
',v.other=t.fn.cropper,t.fn.cropper=function(i){var a,o=s(arguments,1);return this.each(function(){var e,s,h=t(this),n=h.data(L);if(!n){if(/destroy/.test(i))return;e=t.extend({},h.data(),t.isPlainObject(i)&&i),h.data(L,n=new v(this,e))}"string"==typeof i&&t.isFunction(s=n[i])&&(a=s.apply(n,o))}),e(a)?this:a},t.fn.cropper.Constructor=v,t.fn.cropper.setDefaults=v.setDefaults,t.fn.cropper.noConflict=function(){return t.fn.cropper=v.other,this}}); -------------------------------------------------------------------------------- /bin/examples/avatar/js/main.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k9982874/stack/3c1ff3b9dfd96556b3a3e09487cb0befc5e926a7/bin/examples/avatar/js/main.js -------------------------------------------------------------------------------- /bin/examples/avatar/server.py: -------------------------------------------------------------------------------- 1 | import io 2 | import json 3 | import requests 4 | 5 | from os import curdir, sep 6 | 7 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 8 | from cgi import parse_header, parse_multipart, FieldStorage 9 | 10 | from PIL import Image 11 | 12 | STACK_URL = 'http://127.0.0.1:8000' 13 | 14 | 15 | class RequestHandler(BaseHTTPRequestHandler): 16 | 17 | def crop(self, x, y, w, h, r, file_data): 18 | img = Image.open(io.BytesIO(file_data)) 19 | 20 | src_w, src_h = img.size 21 | 22 | dst_x = max(x, 0) 23 | dst_y = max(y, 0) 24 | 25 | dst_w = min(dst_x + w, src_w) 26 | dst_h = min(dst_y + h, src_h) 27 | 28 | if r != 0: 29 | img = img.rotate(r) 30 | 31 | img = img.crop((dst_x, dst_y, dst_w, dst_h)) 32 | 33 | output = io.BytesIO() 34 | 35 | img.save(output, format='PNG') 36 | 37 | output.seek(0) 38 | 39 | contents = output.read() 40 | 41 | output.close() 42 | 43 | return contents 44 | 45 | def do_GET(self): 46 | if self.path == '/': 47 | self.path = '/index.html' 48 | 49 | try: 50 | if self.path.startswith('/stack/'): 51 | url = STACK_URL + '/file/' + self.path[7:] 52 | print(url) 53 | 54 | r = requests.get(url) 55 | 56 | self.send_response(200) 57 | self.send_header('Content-type', 'image/png') 58 | self.end_headers() 59 | self.wfile.write(r.content) 60 | 61 | return 62 | 63 | sendReply = False 64 | if self.path.endswith('.html'): 65 | mimetype = 'text/html' 66 | sendReply = True 67 | elif self.path.endswith('.png'): 68 | mimetype = 'image/png' 69 | sendReply = True 70 | elif self.path.endswith('.jpg'): 71 | mimetype = 'image/jpg' 72 | sendReply = True 73 | elif self.path.endswith('.gif'): 74 | mimetype = 'image/gif' 75 | sendReply = True 76 | elif self.path.endswith('.js'): 77 | mimetype = 'application/javascript' 78 | sendReply = True 79 | elif self.path.endswith('.css'): 80 | mimetype = 'text/css' 81 | sendReply = True 82 | 83 | if sendReply is True: 84 | f = open(curdir + sep + self.path) 85 | self.send_response(200) 86 | self.send_header('Content-type', mimetype) 87 | self.end_headers() 88 | self.wfile.write(f.read()) 89 | f.close() 90 | return 91 | 92 | except IOError: 93 | self.send_error(404, 'File Not Found: %s' % self.path) 94 | 95 | def do_POST(self): 96 | form = FieldStorage( 97 | fp=self.rfile, 98 | headers=self.headers, 99 | environ={ 100 | 'REQUEST_METHOD': 'POST', 101 | } 102 | ) 103 | 104 | data = json.loads(form['avatar_data'].value) 105 | 106 | contents = self.crop(data['x'], data['y'], data['width'], data['height'], data['rotate'], form['avatar_file'].value) 107 | 108 | url = STACK_URL + '/upload' 109 | files = {'file': (form['avatar_file'].filename, contents)} 110 | 111 | r = requests.post(url, files=files) 112 | 113 | data = json.loads(r.content) 114 | 115 | self.send_response(200) 116 | self.send_header('content-type', 'application/json') 117 | self.end_headers() 118 | self.wfile.write(json.dumps( 119 | {'state': 200, 'result': 'http://127.0.0.1:8080/stack/' + data['filename']})) 120 | 121 | server = HTTPServer(('', 8080), RequestHandler) 122 | server.serve_forever() 123 | -------------------------------------------------------------------------------- /bin/examples/simple/css/dropzone.css: -------------------------------------------------------------------------------- 1 | @-webkit-keyframes passing-through { 0% { opacity: 0; -webkit-transform: translateY(40px); -moz-transform: translateY(40px); -ms-transform: translateY(40px); -o-transform: translateY(40px); transform: translateY(40px); } 2 | 30%, 70% { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -ms-transform: translateY(0px); -o-transform: translateY(0px); transform: translateY(0px); } 3 | 100% { opacity: 0; -webkit-transform: translateY(-40px); -moz-transform: translateY(-40px); -ms-transform: translateY(-40px); -o-transform: translateY(-40px); transform: translateY(-40px); } } 4 | @-moz-keyframes passing-through { 0% { opacity: 0; -webkit-transform: translateY(40px); -moz-transform: translateY(40px); -ms-transform: translateY(40px); -o-transform: translateY(40px); transform: translateY(40px); } 5 | 30%, 70% { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -ms-transform: translateY(0px); -o-transform: translateY(0px); transform: translateY(0px); } 6 | 100% { opacity: 0; -webkit-transform: translateY(-40px); -moz-transform: translateY(-40px); -ms-transform: translateY(-40px); -o-transform: translateY(-40px); transform: translateY(-40px); } } 7 | @keyframes passing-through { 0% { opacity: 0; -webkit-transform: translateY(40px); -moz-transform: translateY(40px); -ms-transform: translateY(40px); -o-transform: translateY(40px); transform: translateY(40px); } 8 | 30%, 70% { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -ms-transform: translateY(0px); -o-transform: translateY(0px); transform: translateY(0px); } 9 | 100% { opacity: 0; -webkit-transform: translateY(-40px); -moz-transform: translateY(-40px); -ms-transform: translateY(-40px); -o-transform: translateY(-40px); transform: translateY(-40px); } } 10 | @-webkit-keyframes slide-in { 0% { opacity: 0; -webkit-transform: translateY(40px); -moz-transform: translateY(40px); -ms-transform: translateY(40px); -o-transform: translateY(40px); transform: translateY(40px); } 11 | 30% { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -ms-transform: translateY(0px); -o-transform: translateY(0px); transform: translateY(0px); } } 12 | @-moz-keyframes slide-in { 0% { opacity: 0; -webkit-transform: translateY(40px); -moz-transform: translateY(40px); -ms-transform: translateY(40px); -o-transform: translateY(40px); transform: translateY(40px); } 13 | 30% { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -ms-transform: translateY(0px); -o-transform: translateY(0px); transform: translateY(0px); } } 14 | @keyframes slide-in { 0% { opacity: 0; -webkit-transform: translateY(40px); -moz-transform: translateY(40px); -ms-transform: translateY(40px); -o-transform: translateY(40px); transform: translateY(40px); } 15 | 30% { opacity: 1; -webkit-transform: translateY(0px); -moz-transform: translateY(0px); -ms-transform: translateY(0px); -o-transform: translateY(0px); transform: translateY(0px); } } 16 | @-webkit-keyframes pulse { 0% { -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); -o-transform: scale(1); transform: scale(1); } 17 | 10% { -webkit-transform: scale(1.1); -moz-transform: scale(1.1); -ms-transform: scale(1.1); -o-transform: scale(1.1); transform: scale(1.1); } 18 | 20% { -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); -o-transform: scale(1); transform: scale(1); } } 19 | @-moz-keyframes pulse { 0% { -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); -o-transform: scale(1); transform: scale(1); } 20 | 10% { -webkit-transform: scale(1.1); -moz-transform: scale(1.1); -ms-transform: scale(1.1); -o-transform: scale(1.1); transform: scale(1.1); } 21 | 20% { -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); -o-transform: scale(1); transform: scale(1); } } 22 | @keyframes pulse { 0% { -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); -o-transform: scale(1); transform: scale(1); } 23 | 10% { -webkit-transform: scale(1.1); -moz-transform: scale(1.1); -ms-transform: scale(1.1); -o-transform: scale(1.1); transform: scale(1.1); } 24 | 20% { -webkit-transform: scale(1); -moz-transform: scale(1); -ms-transform: scale(1); -o-transform: scale(1); transform: scale(1); } } 25 | .dropzone, .dropzone * { box-sizing: border-box; } 26 | 27 | .dropzone { min-height: 150px; border: 2px solid rgba(0, 0, 0, 0.3); background: white; padding: 54px 54px; } 28 | .dropzone.dz-clickable { cursor: pointer; } 29 | .dropzone.dz-clickable * { cursor: default; } 30 | .dropzone.dz-clickable .dz-message, .dropzone.dz-clickable .dz-message * { cursor: pointer; } 31 | .dropzone.dz-started .dz-message { display: none; } 32 | .dropzone.dz-drag-hover { border-style: solid; } 33 | .dropzone.dz-drag-hover .dz-message { opacity: 0.5; } 34 | .dropzone .dz-message { text-align: center; margin: 2em 0; } 35 | .dropzone .dz-preview { position: relative; display: inline-block; vertical-align: top; margin: 16px; min-height: 100px; } 36 | .dropzone .dz-preview:hover { z-index: 1000; } 37 | .dropzone .dz-preview:hover .dz-details { opacity: 1; } 38 | .dropzone .dz-preview.dz-file-preview .dz-image { border-radius: 20px; background: #999; background: linear-gradient(to bottom, #eee, #ddd); } 39 | .dropzone .dz-preview.dz-file-preview .dz-details { opacity: 1; } 40 | .dropzone .dz-preview.dz-image-preview { background: white; } 41 | .dropzone .dz-preview.dz-image-preview .dz-details { -webkit-transition: opacity 0.2s linear; -moz-transition: opacity 0.2s linear; -ms-transition: opacity 0.2s linear; -o-transition: opacity 0.2s linear; transition: opacity 0.2s linear; } 42 | .dropzone .dz-preview .dz-remove { font-size: 14px; text-align: center; display: block; cursor: pointer; border: none; } 43 | .dropzone .dz-preview .dz-remove:hover { text-decoration: underline; } 44 | .dropzone .dz-preview:hover .dz-details { opacity: 1; } 45 | .dropzone .dz-preview .dz-details { z-index: 20; position: absolute; top: 0; left: 0; opacity: 0; font-size: 13px; min-width: 100%; max-width: 100%; padding: 2em 1em; text-align: center; color: rgba(0, 0, 0, 0.9); line-height: 150%; } 46 | .dropzone .dz-preview .dz-details .dz-size { margin-bottom: 1em; font-size: 16px; } 47 | .dropzone .dz-preview .dz-details .dz-filename { white-space: nowrap; } 48 | .dropzone .dz-preview .dz-details .dz-filename:hover span { border: 1px solid rgba(200, 200, 200, 0.8); background-color: rgba(255, 255, 255, 0.8); } 49 | .dropzone .dz-preview .dz-details .dz-filename:not(:hover) { overflow: hidden; text-overflow: ellipsis; } 50 | .dropzone .dz-preview .dz-details .dz-filename:not(:hover) span { border: 1px solid transparent; } 51 | .dropzone .dz-preview .dz-details .dz-filename span, .dropzone .dz-preview .dz-details .dz-size span { background-color: rgba(255, 255, 255, 0.4); padding: 0 0.4em; border-radius: 3px; } 52 | .dropzone .dz-preview:hover .dz-image img { -webkit-transform: scale(1.05, 1.05); -moz-transform: scale(1.05, 1.05); -ms-transform: scale(1.05, 1.05); -o-transform: scale(1.05, 1.05); transform: scale(1.05, 1.05); -webkit-filter: blur(8px); filter: blur(8px); } 53 | .dropzone .dz-preview .dz-image { border-radius: 20px; overflow: hidden; width: 120px; height: 120px; position: relative; display: block; z-index: 10; } 54 | .dropzone .dz-preview .dz-image img { display: block; } 55 | .dropzone .dz-preview.dz-success .dz-success-mark { -webkit-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); -moz-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); -ms-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); -o-animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); animation: passing-through 3s cubic-bezier(0.77, 0, 0.175, 1); } 56 | .dropzone .dz-preview.dz-error .dz-error-mark { opacity: 1; -webkit-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); -moz-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); -ms-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); -o-animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); animation: slide-in 3s cubic-bezier(0.77, 0, 0.175, 1); } 57 | .dropzone .dz-preview .dz-success-mark, .dropzone .dz-preview .dz-error-mark { pointer-events: none; opacity: 0; z-index: 500; position: absolute; display: block; top: 50%; left: 50%; margin-left: -27px; margin-top: -27px; } 58 | .dropzone .dz-preview .dz-success-mark svg, .dropzone .dz-preview .dz-error-mark svg { display: block; width: 54px; height: 54px; } 59 | .dropzone .dz-preview.dz-processing .dz-progress { opacity: 1; -webkit-transition: all 0.2s linear; -moz-transition: all 0.2s linear; -ms-transition: all 0.2s linear; -o-transition: all 0.2s linear; transition: all 0.2s linear; } 60 | .dropzone .dz-preview.dz-complete .dz-progress { opacity: 0; -webkit-transition: opacity 0.4s ease-in; -moz-transition: opacity 0.4s ease-in; -ms-transition: opacity 0.4s ease-in; -o-transition: opacity 0.4s ease-in; transition: opacity 0.4s ease-in; } 61 | .dropzone .dz-preview:not(.dz-processing) .dz-progress { -webkit-animation: pulse 6s ease infinite; -moz-animation: pulse 6s ease infinite; -ms-animation: pulse 6s ease infinite; -o-animation: pulse 6s ease infinite; animation: pulse 6s ease infinite; } 62 | .dropzone .dz-preview .dz-progress { opacity: 1; z-index: 1000; pointer-events: none; position: absolute; height: 16px; left: 50%; top: 50%; margin-top: -8px; width: 80px; margin-left: -40px; background: rgba(255, 255, 255, 0.9); -webkit-transform: scale(1); border-radius: 8px; overflow: hidden; } 63 | .dropzone .dz-preview .dz-progress .dz-upload { background: #333; background: linear-gradient(to bottom, #666, #444); position: absolute; top: 0; left: 0; bottom: 0; width: 0; -webkit-transition: width 300ms ease-in-out; -moz-transition: width 300ms ease-in-out; -ms-transition: width 300ms ease-in-out; -o-transition: width 300ms ease-in-out; transition: width 300ms ease-in-out; } 64 | .dropzone .dz-preview.dz-error .dz-error-message { display: block; } 65 | .dropzone .dz-preview.dz-error:hover .dz-error-message { opacity: 1; pointer-events: auto; } 66 | .dropzone .dz-preview .dz-error-message { pointer-events: none; z-index: 1000; position: absolute; display: block; display: none; opacity: 0; -webkit-transition: opacity 0.3s ease; -moz-transition: opacity 0.3s ease; -ms-transition: opacity 0.3s ease; -o-transition: opacity 0.3s ease; transition: opacity 0.3s ease; border-radius: 8px; font-size: 13px; top: 130px; left: -10px; width: 140px; background: #be2626; background: linear-gradient(to bottom, #be2626, #a92222); padding: 0.5em 1.2em; color: white; } 67 | .dropzone .dz-preview .dz-error-message:after { content: ''; position: absolute; top: -6px; left: 64px; width: 0; height: 0; border-left: 6px solid transparent; border-right: 6px solid transparent; border-bottom: 6px solid #be2626; } 68 | -------------------------------------------------------------------------------- /bin/examples/simple/css/style.css: -------------------------------------------------------------------------------- 1 | /** Eric Meyer's Reset CSS v2.0 (http://meyerweb.com/eric/tools/css/reset/) http://cssreset.com */ 2 | html, body, div, span, applet, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp, small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary, time, mark, audio, video { margin: 0; padding: 0; border: 0; font-size: 100%; font: inherit; vertical-align: baseline; } 3 | 4 | /* HTML5 display-role reset for older browsers */ 5 | article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section { display: block; } 6 | 7 | body { line-height: 1; } 8 | 9 | ol, ul { list-style: none; } 10 | 11 | blockquote, q { quotes: none; } 12 | 13 | blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } 14 | 15 | table { border-collapse: collapse; border-spacing: 0; } 16 | 17 | .hll { background-color: #ffffcc; } 18 | 19 | .c { color: #408080; font-style: italic; } 20 | 21 | /* Comment */ 22 | .err { border: 1px solid #FF0000; } 23 | 24 | /* Error */ 25 | .k { color: #008000; font-weight: bold; } 26 | 27 | /* Keyword */ 28 | .o { color: #666666; } 29 | 30 | /* Operator */ 31 | .cm { color: #9AA5AD; font-style: italic; } 32 | 33 | /* Comment.Multiline */ 34 | .cp { color: #BC7A00; } 35 | 36 | /* Comment.Preproc */ 37 | .c1 { color: #9AA5AD; font-style: italic; } 38 | 39 | /* Comment.Single */ 40 | .cs { color: #408080; font-style: italic; } 41 | 42 | /* Comment.Special */ 43 | .gd { color: #A00000; } 44 | 45 | /* Generic.Deleted */ 46 | .ge { font-style: italic; } 47 | 48 | /* Generic.Emph */ 49 | .gr { color: #FF0000; } 50 | 51 | /* Generic.Error */ 52 | .gh { color: #000080; font-weight: bold; } 53 | 54 | /* Generic.Heading */ 55 | .gi { color: #00A000; } 56 | 57 | /* Generic.Inserted */ 58 | .go { color: #808080; } 59 | 60 | /* Generic.Output */ 61 | .gp { color: #000080; font-weight: bold; } 62 | 63 | /* Generic.Prompt */ 64 | .gs { font-weight: bold; } 65 | 66 | /* Generic.Strong */ 67 | .gu { color: #800080; font-weight: bold; } 68 | 69 | /* Generic.Subheading */ 70 | .gt { color: #0040D0; } 71 | 72 | /* Generic.Traceback */ 73 | .kc { color: #008000; font-weight: bold; } 74 | 75 | /* Keyword.Constant */ 76 | .kd { color: #229EFF; font-weight: bold; } 77 | 78 | /* Keyword.Declaration */ 79 | .kn { color: #008000; font-weight: bold; } 80 | 81 | /* Keyword.Namespace */ 82 | .kp { color: #008000; } 83 | 84 | /* Keyword.Pseudo */ 85 | .kr { color: #008000; font-weight: bold; } 86 | 87 | /* Keyword.Reserved */ 88 | .kt { color: #B00040; } 89 | 90 | /* Keyword.Type */ 91 | .m { color: #666666; } 92 | 93 | /* Literal.Number */ 94 | .s { color: #CB0C6A; } 95 | 96 | /* Literal.String */ 97 | .na { color: #C38D00; } 98 | 99 | /* Name.Attribute */ 100 | .nb { color: #008000; } 101 | 102 | /* Name.Builtin */ 103 | .nc { color: #0000FF; font-weight: bold; } 104 | 105 | /* Name.Class */ 106 | .no { color: #880000; } 107 | 108 | /* Name.Constant */ 109 | .nd { color: #AA22FF; } 110 | 111 | /* Name.Decorator */ 112 | .ni { color: #999999; font-weight: bold; } 113 | 114 | /* Name.Entity */ 115 | .ne { color: #D2413A; font-weight: bold; } 116 | 117 | /* Name.Exception */ 118 | .nf { color: #0000FF; } 119 | 120 | /* Name.Function */ 121 | .nl { color: #A0A000; } 122 | 123 | /* Name.Label */ 124 | .nn { color: #0000FF; font-weight: bold; } 125 | 126 | /* Name.Namespace */ 127 | .nt { color: #0081E5; font-weight: bold; } 128 | 129 | /* Name.Tag */ 130 | .nv { color: #19177C; } 131 | 132 | /* Name.Variable */ 133 | .ow { color: #AA22FF; font-weight: bold; } 134 | 135 | /* Operator.Word */ 136 | .w { color: #bbbbbb; } 137 | 138 | /* Text.Whitespace */ 139 | .mf { color: #666666; } 140 | 141 | /* Literal.Number.Float */ 142 | .mh { color: #666666; } 143 | 144 | /* Literal.Number.Hex */ 145 | .mi { color: #666666; } 146 | 147 | /* Literal.Number.Integer */ 148 | .mo { color: #666666; } 149 | 150 | /* Literal.Number.Oct */ 151 | .sb { color: #BA2121; } 152 | 153 | /* Literal.String.Backtick */ 154 | .sc { color: #BA2121; } 155 | 156 | /* Literal.String.Char */ 157 | .sd { color: #BA2121; font-style: italic; } 158 | 159 | /* Literal.String.Doc */ 160 | .s2 { color: #D50069; } 161 | 162 | /* Literal.String.Double */ 163 | .se { color: #BB6622; font-weight: bold; } 164 | 165 | /* Literal.String.Escape */ 166 | .sh { color: #BA2121; } 167 | 168 | /* Literal.String.Heredoc */ 169 | .si { color: #BB6688; font-weight: bold; } 170 | 171 | /* Literal.String.Interpol */ 172 | .sx { color: #008000; } 173 | 174 | /* Literal.String.Other */ 175 | .sr { color: #BB6688; } 176 | 177 | /* Literal.String.Regex */ 178 | .s1 { color: #BA2121; } 179 | 180 | /* Literal.String.Single */ 181 | .ss { color: #19177C; } 182 | 183 | /* Literal.String.Symbol */ 184 | .bp { color: #008000; } 185 | 186 | /* Name.Builtin.Pseudo */ 187 | .vc { color: #19177C; } 188 | 189 | /* Name.Variable.Class */ 190 | .vg { color: #19177C; } 191 | 192 | /* Name.Variable.Global */ 193 | .vi { color: #19177C; } 194 | 195 | /* Name.Variable.Instance */ 196 | .il { color: #666666; } 197 | 198 | /* Literal.Number.Integer.Long */ 199 | .nx { color: #4C556B; } 200 | 201 | #dropzone { margin-bottom: 3rem; } 202 | 203 | .dropzone { border: 2px dashed #0087F7; border-radius: 5px; background: white; } 204 | .dropzone .dz-message { font-weight: 400; } 205 | .dropzone .dz-message .note { font-size: 0.8em; font-weight: 200; display: block; margin-top: 1.4rem; } 206 | 207 | *, *:before, *:after { box-sizing: border-box; } 208 | 209 | html, body { height: 100%; font-family: Roboto, "Open Sans", sans-serif; font-size: 20px; font-weight: 300; line-height: 1.4rem; background: #F3F4F5; color: #646C7F; text-rendering: optimizeLegibility; } 210 | @media (max-width: 600px) { html, body { font-size: 18px; } } 211 | @media (max-width: 400px) { html, body { font-size: 16px; } } 212 | 213 | h1, h2, h3, table th, table th .header { font-size: 1.8rem; color: #0087F7; -webkit-font-smoothing: antialiased; line-height: 2.2rem; } 214 | 215 | h1, h2, h3 { margin-top: 2.8rem; margin-bottom: 1.4rem; } 216 | 217 | h2 { font-size: 1.4rem; } 218 | 219 | h1.anchor, h2.anchor { margin: 0; padding: 0; height: 0; overflow: hidden; visibility: hidden; } 220 | 221 | table th { font-size: 1.4rem; color: #646C7F; } 222 | 223 | ul, ol { list-style-position: inside; } 224 | 225 | a { color: #0087F7; text-decoration: none; } 226 | a:hover { border-bottom: 2px solid #0087F7; } 227 | 228 | p { margin: 1.4rem 0; } 229 | 230 | strong { font-weight: 400; } 231 | 232 | em { font-style: italic; } 233 | 234 | code { font-family: Inconsolata, monospace; background: rgba(0, 135, 247, 0.04); padding: 0.2em 0.4em; } 235 | 236 | .highlight code, td:first-child code { background: none; padding: 0; } 237 | 238 | aside { font-size: 0.8em; color: rgba(0, 0, 0, 0.4); } 239 | 240 | hr { border: none; background: none; position: relative; height: 2.8rem; } 241 | hr:after { content: ""; position: absolute; top: 1.4rem; left: 0; right: 0; height: 1px; background: rgba(0, 0, 0, 0.1); } 242 | 243 | ul li { list-style-type: disc; padding-top: 0.7rem; padding-bottom: 0.7rem; border-bottom: 1px solid rgba(0, 0, 0, 0.1); } 244 | ul li:last-of-type { border: none; } 245 | 246 | .highlight { padding: 1.4rem; overflow: auto; background: rgba(100, 108, 128, 0.04); margin-top: 2.8rem; margin-bottom: 2.8rem; } 247 | 248 | .bitcoin { overflow: auto; } 249 | 250 | blockquote { color: #0087F7; font-size: 1.2rem; line-height: 2rem; -webkit-font-smoothing: antialiased; margin-top: 2.8rem; margin-bottom: 2.8rem; } 251 | blockquote a { border-bottom: 1px solid #0087F7; } 252 | 253 | body > header { position: relative; padding: 2.8rem 1.4rem; z-index: 10; } 254 | body > header .content { opacity: 1; background: #F3F4F5; z-index: 10; } 255 | body > header .content > * { max-width: 700px; } 256 | body > header .content h1 { margin-bottom: 2.8rem; margin-top: 0; } 257 | body > header .content h1 img { max-width: 100%; } 258 | body > header .content h1 span { display: none; } 259 | @media (min-width: 700px) { body > header #social-buttons { display: inline-block; position: absolute; top: 0.5em; right: 0; opacity: 0.5; -webkit-transition: opacity 0.2s ease; -moz-transition: opacity 0.2s ease; -ms-transition: opacity 0.2s ease; -o-transition: opacity 0.2s ease; transition: opacity 0.2s ease; } 260 | body > header #social-buttons:hover { opacity: 1; } } 261 | body > header #social-buttons .social-button { display: inline-block; } 262 | body > header #social-buttons .social-button.facebook-social-button .fb-like > span { vertical-align: top !important; top: 1px; } 263 | body > header .scroll-invitation { margin-top: 2.8rem; margin-bottom: 2.8rem; } 264 | body > header .scroll-invitation a { display: block; width: 56px; height: 56px; background: url("../images/arrow.svg") no-repeat; } 265 | body > header .scroll-invitation a:hover { text-decoration: none; border: none; background-image: url("../images/arrow-hover.svg"); } 266 | body > header .scroll-invitation a span { display: none; } 267 | @media (min-width: 700px) { body > header { height: 100vh; margin-bottom: 0; } 268 | body > header .content { position: relative; top: 50%; transform: translateY(-50%); -webkit-transform: translateY(-50%); -moz-transform: translateY(-50%); } } 269 | @media (min-width: 900px) { body > header { padding-left: 15%; } 270 | body > header .content h1 { margin-bottom: 4.2rem; } 271 | body > header .content h1 img { width: 550px; } 272 | body > header .content h2 { font-size: 1.5em; line-height: 1.4em; } } 273 | @media (min-width: 1100px) { body > header { font-size: 1em; line-height: 1.5em; } 274 | body > header .content h1 { margin-bottom: 5.6rem; } 275 | body > header .content h1 img { width: 700px; } 276 | body > header .content > * { max-width: 900px; } 277 | body > header h2 { margin-top: 2.8rem; margin-bottom: 2.8rem; } 278 | body > header .scroll-invitation { margin-top: 5.6rem; } } 279 | 280 | main > nav { position: absolute; top: 0; left: 0; bottom: 0; width: 220px; background: #028AF4; padding: 1.4rem 0; z-index: 200; overflow: auto; display: none; } 281 | main > nav.fixed { position: fixed; } 282 | main > nav img { margin: 0 0 1.4rem 1.4rem; width: 58px; height: 58px; } 283 | main > nav a:not(.logo) { display: block; line-height: 1.4rem; color: rgba(255, 255, 255, 0.9); border: none; padding: 0.7rem 1.4rem; font-size: 0.8rem; -webkit-font-smoothing: subpixel-antialiased; } 284 | main > nav a:not(.logo):hover { background: rgba(255, 255, 255, 0.3); } 285 | main > nav .sub-sections { height: 0; overflow: hidden; -webkit-transition: height 0.4s ease; -moz-transition: height 0.4s ease; -ms-transition: height 0.4s ease; -o-transition: height 0.4s ease; transition: height 0.4s ease; } 286 | main > nav .visible { background: rgba(255, 255, 255, 0.13); } 287 | main > nav .visible .sub-sections { display: block; } 288 | main > nav a.current { background: #4DADF7; } 289 | main > nav .level-0 > a { font-weight: 400; } 290 | main > nav .level-1 > a { padding-left: 1.9rem; color: rgba(255, 255, 255, 0.7); } 291 | 292 | @media (min-width: 940px) { main { padding-left: 220px; } 293 | main > nav { display: block; } } 294 | form.donate { display: inline-block; vertical-align: bottom; position: relative; top: 0.25em; margin: 0 0em 0 0.2em; } 295 | 296 | main { position: relative; z-index: 100; } 297 | main section { padding: 1.4rem 1.4rem 2.8rem 1.4rem; } 298 | main section:last-of-type { padding-bottom: 8.4rem; } 299 | main section h1, main section h2 { margin-top: 0; padding-top: 2.8rem; } 300 | main section > * { max-width: 720px; margin-left: auto; margin-right: auto; } 301 | main section > *.highlight { max-width: 900px; } 302 | main section > table { max-width: 80rem; } 303 | main section .embedded-video { position: relative; width: 100%; } 304 | main section .embedded-video:after { content: ''; padding-top: 56.25%; display: block; } 305 | main section .embedded-video iframe { display: block; position: absolute; width: 100%; height: 100%; } 306 | main section:nth-child(odd) { background: #F3F4F5; } 307 | main section:nth-child(even) { background: #E8E9EC; } 308 | main section.news { background: #646C7F; color: white; } 309 | main section.news h1, main section.news h2 { color: white; -webkit-font-smoothing: subpixel-antialiased; } 310 | main section.news a { color: #C0E3FE; border-color: #C0E3FE; } 311 | main table { font-size: 0.9rem; margin-top: 1.4rem; margin-bottom: 4.2rem; border: 1px solid #38A0FE; border-bottom: none; background: white; } 312 | main table th:first-of-type, main table td:first-of-type { text-align: right; } 313 | main table th, main table td { text-align: left; border-bottom: 1px solid #38A0FE; padding: 0.7rem 1.4rem; } 314 | main table td:first-of-type, main table th:first-of-type { border-right: 1px solid #38A0FE; } 315 | main table td:first-of-type { font-weight: bold; color: #0087F7; } 316 | main table th.title { text-align: center; padding-top: 2.8rem; padding-bottom: 2.8rem; } 317 | main table th.title p { margin-bottom: 0; } 318 | main table td.separator { font-weight: normal; text-align: left; color: #646C7F; } 319 | @media (max-width: 600px) { main table table, main table tbody, main table thead, main table tr, main table td, main table th { display: block; } 320 | main table td, main table th { overflow: auto; } 321 | main table td:first-of-type, main table th:first-of-type { text-align: left; border-right: none; } 322 | main table th.title { padding-top: 1.4rem; padding-bottom: 1.4rem; } 323 | main table th:not(.title) { display: none; } } 324 | 325 | footer { background: #2D3038; z-index: 5000; position: relative; display: block; padding: 1.4rem 1.4rem 2.8rem 1.4rem; font-size: 0.9rem; color: white; } 326 | footer * { color: white; } 327 | footer a:hover { border-color: white; } 328 | footer > * { max-width: 720px; margin-left: auto; margin-right: auto; } 329 | @media (min-width: 720px) { footer .license { text-align: justify; } } 330 | footer .logo { margin: 2.8rem 0; width: 270px; } 331 | 332 | .for-hire { text-align: center; padding: 1em 2em; background: rgba(255, 255, 255, 0.1); border-radius: 0.3rem; line-height: 1.5em; } 333 | .for-hire h1 { padding: 0; margin: 1.5rem 0 3rem; } 334 | .for-hire h1 img { max-width: 100%; height: auto; } 335 | -------------------------------------------------------------------------------- /bin/examples/simple/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Dropzone.js 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |

Try it out!

16 |
17 |
18 |
19 | Drop files here or click to upload.
20 | (This is just a demo dropzone. Selected files are not actually uploaded.) 21 |
22 |
23 |
24 |
25 |
26 | 27 | 73 | 74 | 107 | 108 | -------------------------------------------------------------------------------- /bin/examples/simple/main.css: -------------------------------------------------------------------------------- 1 | /* The MIT License */ 2 | .dropzone, 3 | .dropzone *, 4 | .dropzone-previews, 5 | .dropzone-previews * { 6 | -webkit-box-sizing: border-box; 7 | -moz-box-sizing: border-box; 8 | box-sizing: border-box; 9 | } 10 | .dropzone { 11 | position: relative; 12 | border: 1px solid rgba(0,0,0,0.08); 13 | background: rgba(0,0,0,0.02); 14 | padding: 1em; 15 | } 16 | .dropzone.dz-clickable { 17 | cursor: pointer; 18 | } 19 | .dropzone.dz-clickable .dz-message, 20 | .dropzone.dz-clickable .dz-message span { 21 | cursor: pointer; 22 | } 23 | .dropzone.dz-clickable * { 24 | cursor: default; 25 | } 26 | .dropzone .dz-message { 27 | opacity: 1; 28 | -ms-filter: none; 29 | filter: none; 30 | } 31 | .dropzone.dz-drag-hover { 32 | border-color: rgba(0,0,0,0.15); 33 | background: rgba(0,0,0,0.04); 34 | } 35 | .dropzone.dz-started .dz-message { 36 | display: none; 37 | } 38 | .dropzone .dz-preview, 39 | .dropzone-previews .dz-preview { 40 | background: rgba(255,255,255,0.8); 41 | position: relative; 42 | display: inline-block; 43 | margin: 17px; 44 | vertical-align: top; 45 | border: 1px solid #acacac; 46 | padding: 6px 6px 6px 6px; 47 | } 48 | .dropzone .dz-preview.dz-file-preview [data-dz-thumbnail], 49 | .dropzone-previews .dz-preview.dz-file-preview [data-dz-thumbnail] { 50 | display: none; 51 | } 52 | .dropzone .dz-preview .dz-details, 53 | .dropzone-previews .dz-preview .dz-details { 54 | width: 100px; 55 | height: 100px; 56 | position: relative; 57 | background: #ebebeb; 58 | padding: 5px; 59 | margin-bottom: 22px; 60 | } 61 | .dropzone .dz-preview .dz-details .dz-filename, 62 | .dropzone-previews .dz-preview .dz-details .dz-filename { 63 | overflow: hidden; 64 | height: 100%; 65 | } 66 | .dropzone .dz-preview .dz-details img, 67 | .dropzone-previews .dz-preview .dz-details img { 68 | position: absolute; 69 | top: 0; 70 | left: 0; 71 | width: 100px; 72 | height: 100px; 73 | } 74 | .dropzone .dz-preview .dz-details .dz-size, 75 | .dropzone-previews .dz-preview .dz-details .dz-size { 76 | position: absolute; 77 | bottom: -28px; 78 | left: 3px; 79 | height: 28px; 80 | line-height: 28px; 81 | } 82 | .dropzone .dz-preview.dz-error .dz-error-mark, 83 | .dropzone-previews .dz-preview.dz-error .dz-error-mark { 84 | display: block; 85 | } 86 | .dropzone .dz-preview.dz-success .dz-success-mark, 87 | .dropzone-previews .dz-preview.dz-success .dz-success-mark { 88 | display: block; 89 | } 90 | .dropzone .dz-preview:hover .dz-details img, 91 | .dropzone-previews .dz-preview:hover .dz-details img { 92 | display: none; 93 | } 94 | .dropzone .dz-preview .dz-success-mark, 95 | .dropzone-previews .dz-preview .dz-success-mark, 96 | .dropzone .dz-preview .dz-error-mark, 97 | .dropzone-previews .dz-preview .dz-error-mark { 98 | display: none; 99 | position: absolute; 100 | width: 40px; 101 | height: 40px; 102 | font-size: 30px; 103 | text-align: center; 104 | right: -10px; 105 | top: -10px; 106 | } 107 | .dropzone .dz-preview .dz-success-mark, 108 | .dropzone-previews .dz-preview .dz-success-mark { 109 | color: #8cc657; 110 | } 111 | .dropzone .dz-preview .dz-error-mark, 112 | .dropzone-previews .dz-preview .dz-error-mark { 113 | color: #ee162d; 114 | } 115 | .dropzone .dz-preview .dz-progress, 116 | .dropzone-previews .dz-preview .dz-progress { 117 | position: absolute; 118 | top: 100px; 119 | left: 6px; 120 | right: 6px; 121 | height: 6px; 122 | background: #d7d7d7; 123 | display: none; 124 | } 125 | .dropzone .dz-preview .dz-progress .dz-upload, 126 | .dropzone-previews .dz-preview .dz-progress .dz-upload { 127 | display: block; 128 | position: absolute; 129 | top: 0; 130 | bottom: 0; 131 | left: 0; 132 | width: 0%; 133 | background-color: #8cc657; 134 | } 135 | .dropzone .dz-preview.dz-processing .dz-progress, 136 | .dropzone-previews .dz-preview.dz-processing .dz-progress { 137 | display: block; 138 | } 139 | .dropzone .dz-preview .dz-error-message, 140 | .dropzone-previews .dz-preview .dz-error-message { 141 | display: none; 142 | position: absolute; 143 | top: -5px; 144 | left: -20px; 145 | background: rgba(245,245,245,0.8); 146 | padding: 8px 10px; 147 | color: #800; 148 | min-width: 140px; 149 | max-width: 500px; 150 | z-index: 500; 151 | } 152 | .dropzone .dz-preview:hover.dz-error .dz-error-message, 153 | .dropzone-previews .dz-preview:hover.dz-error .dz-error-message { 154 | display: block; 155 | } 156 | .dropzone { 157 | border: 1px solid rgba(0,0,0,0.03); 158 | min-height: 360px; 159 | -webkit-border-radius: 3px; 160 | border-radius: 3px; 161 | background: rgba(0,0,0,0.03); 162 | padding: 23px; 163 | } 164 | .dropzone .dz-default.dz-message { 165 | opacity: 1; 166 | -ms-filter: none; 167 | filter: none; 168 | -webkit-transition: opacity 0.3s ease-in-out; 169 | -moz-transition: opacity 0.3s ease-in-out; 170 | -o-transition: opacity 0.3s ease-in-out; 171 | -ms-transition: opacity 0.3s ease-in-out; 172 | transition: opacity 0.3s ease-in-out; 173 | background-image: url("../images/spritemap.png"); 174 | background-repeat: no-repeat; 175 | background-position: 0 0; 176 | position: absolute; 177 | width: 428px; 178 | height: 123px; 179 | margin-left: -214px; 180 | margin-top: -61.5px; 181 | top: 50%; 182 | left: 50%; 183 | } 184 | @media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) { 185 | .dropzone .dz-default.dz-message { 186 | background-image: url("../images/spritemap@2x.png"); 187 | -webkit-background-size: 428px 406px; 188 | -moz-background-size: 428px 406px; 189 | background-size: 428px 406px; 190 | } 191 | } 192 | .dropzone .dz-default.dz-message span { 193 | display: none; 194 | } 195 | .dropzone.dz-square .dz-default.dz-message { 196 | background-position: 0 -123px; 197 | width: 268px; 198 | margin-left: -134px; 199 | height: 174px; 200 | margin-top: -87px; 201 | } 202 | .dropzone.dz-drag-hover .dz-message { 203 | opacity: 0.15; 204 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=15)"; 205 | filter: alpha(opacity=15); 206 | } 207 | .dropzone.dz-started .dz-message { 208 | display: block; 209 | opacity: 0; 210 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 211 | filter: alpha(opacity=0); 212 | } 213 | .dropzone .dz-preview, 214 | .dropzone-previews .dz-preview { 215 | -webkit-box-shadow: 1px 1px 4px rgba(0,0,0,0.16); 216 | box-shadow: 1px 1px 4px rgba(0,0,0,0.16); 217 | font-size: 14px; 218 | } 219 | .dropzone .dz-preview.dz-image-preview:hover .dz-details img, 220 | .dropzone-previews .dz-preview.dz-image-preview:hover .dz-details img { 221 | display: block; 222 | opacity: 0.1; 223 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=10)"; 224 | filter: alpha(opacity=10); 225 | } 226 | .dropzone .dz-preview.dz-success .dz-success-mark, 227 | .dropzone-previews .dz-preview.dz-success .dz-success-mark { 228 | opacity: 1; 229 | -ms-filter: none; 230 | filter: none; 231 | } 232 | .dropzone .dz-preview.dz-error .dz-error-mark, 233 | .dropzone-previews .dz-preview.dz-error .dz-error-mark { 234 | opacity: 1; 235 | -ms-filter: none; 236 | filter: none; 237 | } 238 | .dropzone .dz-preview.dz-error .dz-progress .dz-upload, 239 | .dropzone-previews .dz-preview.dz-error .dz-progress .dz-upload { 240 | background: #ee1e2d; 241 | } 242 | .dropzone .dz-preview .dz-error-mark, 243 | .dropzone-previews .dz-preview .dz-error-mark, 244 | .dropzone .dz-preview .dz-success-mark, 245 | .dropzone-previews .dz-preview .dz-success-mark { 246 | display: block; 247 | opacity: 0; 248 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 249 | filter: alpha(opacity=0); 250 | -webkit-transition: opacity 0.4s ease-in-out; 251 | -moz-transition: opacity 0.4s ease-in-out; 252 | -o-transition: opacity 0.4s ease-in-out; 253 | -ms-transition: opacity 0.4s ease-in-out; 254 | transition: opacity 0.4s ease-in-out; 255 | background-image: url("../images/spritemap.png"); 256 | background-repeat: no-repeat; 257 | } 258 | @media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) { 259 | .dropzone .dz-preview .dz-error-mark, 260 | .dropzone-previews .dz-preview .dz-error-mark, 261 | .dropzone .dz-preview .dz-success-mark, 262 | .dropzone-previews .dz-preview .dz-success-mark { 263 | background-image: url("../images/spritemap@2x.png"); 264 | -webkit-background-size: 428px 406px; 265 | -moz-background-size: 428px 406px; 266 | background-size: 428px 406px; 267 | } 268 | } 269 | .dropzone .dz-preview .dz-error-mark span, 270 | .dropzone-previews .dz-preview .dz-error-mark span, 271 | .dropzone .dz-preview .dz-success-mark span, 272 | .dropzone-previews .dz-preview .dz-success-mark span { 273 | display: none; 274 | } 275 | .dropzone .dz-preview .dz-error-mark, 276 | .dropzone-previews .dz-preview .dz-error-mark { 277 | background-position: -268px -123px; 278 | } 279 | .dropzone .dz-preview .dz-success-mark, 280 | .dropzone-previews .dz-preview .dz-success-mark { 281 | background-position: -268px -163px; 282 | } 283 | .dropzone .dz-preview .dz-progress .dz-upload, 284 | .dropzone-previews .dz-preview .dz-progress .dz-upload { 285 | -webkit-animation: loading 0.4s linear infinite; 286 | -moz-animation: loading 0.4s linear infinite; 287 | -o-animation: loading 0.4s linear infinite; 288 | -ms-animation: loading 0.4s linear infinite; 289 | animation: loading 0.4s linear infinite; 290 | -webkit-transition: width 0.3s ease-in-out; 291 | -moz-transition: width 0.3s ease-in-out; 292 | -o-transition: width 0.3s ease-in-out; 293 | -ms-transition: width 0.3s ease-in-out; 294 | transition: width 0.3s ease-in-out; 295 | -webkit-border-radius: 2px; 296 | border-radius: 2px; 297 | position: absolute; 298 | top: 0; 299 | left: 0; 300 | width: 0%; 301 | height: 100%; 302 | background-image: url("../images/spritemap.png"); 303 | background-repeat: repeat-x; 304 | background-position: 0px -400px; 305 | } 306 | @media all and (-webkit-min-device-pixel-ratio:1.5),(min--moz-device-pixel-ratio:1.5),(-o-min-device-pixel-ratio:1.5/1),(min-device-pixel-ratio:1.5),(min-resolution:138dpi),(min-resolution:1.5dppx) { 307 | .dropzone .dz-preview .dz-progress .dz-upload, 308 | .dropzone-previews .dz-preview .dz-progress .dz-upload { 309 | background-image: url("../images/spritemap@2x.png"); 310 | -webkit-background-size: 428px 406px; 311 | -moz-background-size: 428px 406px; 312 | background-size: 428px 406px; 313 | } 314 | } 315 | .dropzone .dz-preview.dz-success .dz-progress, 316 | .dropzone-previews .dz-preview.dz-success .dz-progress { 317 | display: block; 318 | opacity: 0; 319 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 320 | filter: alpha(opacity=0); 321 | -webkit-transition: opacity 0.4s ease-in-out; 322 | -moz-transition: opacity 0.4s ease-in-out; 323 | -o-transition: opacity 0.4s ease-in-out; 324 | -ms-transition: opacity 0.4s ease-in-out; 325 | transition: opacity 0.4s ease-in-out; 326 | } 327 | .dropzone .dz-preview .dz-error-message, 328 | .dropzone-previews .dz-preview .dz-error-message { 329 | display: block; 330 | opacity: 0; 331 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 332 | filter: alpha(opacity=0); 333 | -webkit-transition: opacity 0.3s ease-in-out; 334 | -moz-transition: opacity 0.3s ease-in-out; 335 | -o-transition: opacity 0.3s ease-in-out; 336 | -ms-transition: opacity 0.3s ease-in-out; 337 | transition: opacity 0.3s ease-in-out; 338 | } 339 | .dropzone .dz-preview:hover.dz-error .dz-error-message, 340 | .dropzone-previews .dz-preview:hover.dz-error .dz-error-message { 341 | opacity: 1; 342 | -ms-filter: none; 343 | filter: none; 344 | } 345 | .dropzone a.dz-remove, 346 | .dropzone-previews a.dz-remove { 347 | background-image: -webkit-linear-gradient(top, #fafafa, #eee); 348 | background-image: -moz-linear-gradient(top, #fafafa, #eee); 349 | background-image: -o-linear-gradient(top, #fafafa, #eee); 350 | background-image: -ms-linear-gradient(top, #fafafa, #eee); 351 | background-image: linear-gradient(to bottom, #fafafa, #eee); 352 | -webkit-border-radius: 2px; 353 | border-radius: 2px; 354 | border: 1px solid #eee; 355 | text-decoration: none; 356 | display: block; 357 | padding: 4px 5px; 358 | text-align: center; 359 | color: #aaa; 360 | margin-top: 26px; 361 | } 362 | .dropzone a.dz-remove:hover, 363 | .dropzone-previews a.dz-remove:hover { 364 | color: #666; 365 | } 366 | @-moz-keyframes loading { 367 | from { 368 | background-position: 0 -400px; 369 | } 370 | to { 371 | background-position: -7px -400px; 372 | } 373 | } 374 | @-webkit-keyframes loading { 375 | from { 376 | background-position: 0 -400px; 377 | } 378 | to { 379 | background-position: -7px -400px; 380 | } 381 | } 382 | @-o-keyframes loading { 383 | from { 384 | background-position: 0 -400px; 385 | } 386 | to { 387 | background-position: -7px -400px; 388 | } 389 | } 390 | @keyframes loading { 391 | from { 392 | background-position: 0 -400px; 393 | } 394 | to { 395 | background-position: -7px -400px; 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /bin/examples/simple/main.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | Dropzone.options.myAwesomeDropzone = { 3 | maxFilesize: 5, 4 | addRemoveLinks: true, 5 | dictResponseError: 'Server not Configured', 6 | acceptedFiles: ".png,.jpg,.gif,.bmp,.jpeg", 7 | init:function(){ 8 | var self = this; 9 | // config 10 | self.options.addRemoveLinks = true; 11 | self.options.dictRemoveFile = "Delete"; 12 | //New file added 13 | self.on("addedfile", function (file) { 14 | console.log('new file added ', file); 15 | }); 16 | // Send file starts 17 | self.on("sending", function (file) { 18 | console.log('upload started', file); 19 | $('.meter').show(); 20 | }); 21 | 22 | // File upload Progress 23 | self.on("totaluploadprogress", function (progress) { 24 | console.log("progress ", progress); 25 | $('.roller').width(progress + '%'); 26 | }); 27 | 28 | self.on("queuecomplete", function (progress) { 29 | $('.meter').delay(999).slideUp(999); 30 | }); 31 | 32 | // On removing file 33 | self.on("removedfile", function (file) { 34 | console.log(file); 35 | }); 36 | } 37 | }; 38 | }) 39 | -------------------------------------------------------------------------------- /bin/examples/simple/server.py: -------------------------------------------------------------------------------- 1 | import io 2 | import json 3 | import requests 4 | 5 | from os import curdir, sep 6 | 7 | from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler 8 | from cgi import parse_header, parse_multipart, FieldStorage 9 | 10 | from PIL import Image 11 | 12 | STACK_URL = 'http://127.0.0.1:8000' 13 | 14 | 15 | class RequestHandler(BaseHTTPRequestHandler): 16 | def do_GET(self): 17 | if self.path == '/': 18 | self.path = '/index.html' 19 | 20 | try: 21 | if self.path.startswith('/stack/'): 22 | url = STACK_URL + '/file/' + self.path[7:] 23 | print(url) 24 | 25 | r = requests.get(url) 26 | 27 | self.send_response(200) 28 | self.send_header('Content-type', 'image/png') 29 | self.end_headers() 30 | self.wfile.write(r.content) 31 | 32 | return 33 | 34 | sendReply = False 35 | if self.path.endswith('.html'): 36 | mimetype = 'text/html' 37 | sendReply = True 38 | elif self.path.endswith('.png'): 39 | mimetype = 'image/png' 40 | sendReply = True 41 | elif self.path.endswith('.jpg'): 42 | mimetype = 'image/jpg' 43 | sendReply = True 44 | elif self.path.endswith('.gif'): 45 | mimetype = 'image/gif' 46 | sendReply = True 47 | elif self.path.endswith('.js'): 48 | mimetype = 'application/javascript' 49 | sendReply = True 50 | elif self.path.endswith('.css'): 51 | mimetype = 'text/css' 52 | sendReply = True 53 | 54 | if sendReply is True: 55 | f = open(curdir + sep + self.path) 56 | self.send_response(200) 57 | self.send_header('Content-type', mimetype) 58 | self.end_headers() 59 | self.wfile.write(f.read()) 60 | f.close() 61 | return 62 | 63 | except IOError: 64 | self.send_error(404, 'File Not Found: %s' % self.path) 65 | 66 | def do_POST(self): 67 | form = FieldStorage( 68 | fp=self.rfile, 69 | headers=self.headers, 70 | environ={ 71 | 'REQUEST_METHOD': 'POST', 72 | } 73 | ) 74 | 75 | url = STACK_URL + '/upload' 76 | files = {'file': (form['file'].filename, form['file'].value)} 77 | 78 | r = requests.post(url, files=files) 79 | 80 | data = json.loads(r.content) 81 | 82 | self.send_response(200) 83 | self.send_header('content-type', 'application/json') 84 | self.end_headers() 85 | self.wfile.write(json.dumps({'state': 200})) 86 | 87 | server = HTTPServer(('', 8080), RequestHandler) 88 | server.serve_forever() 89 | -------------------------------------------------------------------------------- /bin/examples/upload.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 |
7 | 8 | -------------------------------------------------------------------------------- /bin/stack: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | "use strict"; 3 | 4 | //module dependencies. 5 | var app = require("../app"); 6 | var debug = require("debug")("express:server"); 7 | var http = require("http"); 8 | 9 | //get port from environment and store in Express. 10 | var port = normalizePort(process.env.PORT || 8080); 11 | //app.set("port", port); 12 | 13 | 14 | //create http server 15 | var server = http.createServer(app); 16 | 17 | //listen on provided ports 18 | server.listen(port, (err) => { 19 | if (err) { 20 | return console.log('something bad happened', err) 21 | } 22 | 23 | console.log(`server is listening on ${port}`) 24 | }); 25 | 26 | //add error handler 27 | server.on("error", onError); 28 | 29 | //start listening on port 30 | server.on("listening", onListening); 31 | 32 | 33 | /** 34 | * Normalize a port into a number, string, or false. 35 | */ 36 | function normalizePort(val) { 37 | var port = parseInt(val, 10); 38 | 39 | if (isNaN(port)) { 40 | // named pipe 41 | return val; 42 | } 43 | 44 | if (port >= 0) { 45 | // port number 46 | return port; 47 | } 48 | 49 | return false; 50 | } 51 | 52 | /** 53 | * Event listener for HTTP server "error" event. 54 | */ 55 | function onError(error) { 56 | if (error.syscall !== "listen") { 57 | throw error; 58 | } 59 | 60 | var bind = typeof port === "string" 61 | ? "Pipe " + port 62 | : "Port " + port; 63 | 64 | // handle specific listen errors with friendly messages 65 | switch (error.code) { 66 | case "EACCES": 67 | console.error(bind + " requires elevated privileges"); 68 | process.exit(1); 69 | break; 70 | case "EADDRINUSE": 71 | console.error(bind + " is already in use"); 72 | process.exit(1); 73 | break; 74 | default: 75 | throw error; 76 | } 77 | } 78 | 79 | /** 80 | * Event listener for HTTP server "listening" event. 81 | */ 82 | function onListening() { 83 | var addr = server.address(); 84 | var bind = typeof addr === "string" 85 | ? "pipe " + addr 86 | : "port " + addr.port; 87 | debug("Listening on " + bind); 88 | } 89 | -------------------------------------------------------------------------------- /gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | "use strict"; 3 | 4 | grunt.initConfig({ 5 | ts: { 6 | app: { 7 | files: [{ 8 | src: ["src/\*\*/\*.ts", "!src/.baseDir.ts", "!src/_all.d.ts"], 9 | dest: "." 10 | }], 11 | options: { 12 | module: "commonjs", 13 | noLib: true, 14 | target: "es6", 15 | sourceMap: false 16 | } 17 | } 18 | }, 19 | tslint: { 20 | options: { 21 | configuration: "tslint.json" 22 | }, 23 | files: { 24 | src: ["src/\*\*/\*.ts"] 25 | } 26 | }, 27 | watch: { 28 | ts: { 29 | files: ["js/src/\*\*/\*.ts", "src/\*\*/\*.ts"], 30 | tasks: ["ts", "tslint"] 31 | } 32 | }, 33 | nodemon: { 34 | dev: { 35 | script: "./bin/stack" 36 | }, 37 | options: { 38 | ignore: ['node_modules/**', 'Gruntfile.js'], 39 | env: { 40 | PORT: '8000' 41 | } 42 | } 43 | }, 44 | concurrent: { 45 | watchers: { 46 | tasks: ['nodemon', 'watch'], 47 | options: { 48 | logConcurrentOutput: true 49 | } 50 | } 51 | } 52 | }); 53 | 54 | grunt.loadNpmTasks("grunt-contrib-watch"); 55 | grunt.loadNpmTasks("grunt-ts"); 56 | grunt.loadNpmTasks("grunt-tslint"); 57 | grunt.loadNpmTasks("grunt-nodemon"); 58 | grunt.loadNpmTasks("grunt-concurrent"); 59 | 60 | grunt.registerTask("default", [ 61 | "ts", 62 | "tslint" 63 | ]); 64 | 65 | grunt.registerTask("serve", [ "concurrent:watchers" ]); 66 | }; 67 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "grunt": "grunt", 9 | "start": "node ./bin/stack" 10 | }, 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "body-parser": "^1.15.2", 15 | "concat-stream": "^1.5.2", 16 | "debug": "^2.2.0", 17 | "express": "^4.14.0", 18 | "ftp": "^0.3.10", 19 | "grunt": "^1.0.1", 20 | "grunt-cli": "^1.2.0", 21 | "grunt-contrib-watch": "^1.0.0", 22 | "grunt-ts": "^6.0.0-beta.3", 23 | "grunt-tslint": "^3.3.0", 24 | "log4js": "^1.0.0", 25 | "mariasql": "^0.2.6", 26 | "memcached": "^2.2.2", 27 | "mkdirp": "^0.5.1", 28 | "mongodb": "^2.2.11", 29 | "multer": "^1.2.0", 30 | "pg": "^6.1.0", 31 | "redis": "^2.6.3", 32 | "tslint": "^3.15.1", 33 | "typescript": "^2.0.6", 34 | "typings": "^1.5.0", 35 | "webhdfs": "^1.1.0" 36 | }, 37 | "devDependencies": { 38 | "grunt-concurrent": "^2.3.1", 39 | "grunt-nodemon": "^0.4.2" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/.baseDir.ts: -------------------------------------------------------------------------------- 1 | // Ignore this file. See https://github.com/grunt-ts/grunt-ts/issues/77 -------------------------------------------------------------------------------- /src/_all.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | -------------------------------------------------------------------------------- /src/app.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | "use strict"; 4 | 5 | import * as bodyParser from "body-parser"; 6 | import * as express from "express"; 7 | import * as path from "path"; 8 | 9 | import * as log4js from "log4js"; 10 | 11 | import * as multer from "multer"; 12 | 13 | import * as Route from "./route"; 14 | 15 | import * as storage from "./storage"; 16 | 17 | /** 18 | * The server. 19 | * 20 | * @class Server 21 | */ 22 | class Server { 23 | public app: express.Application; 24 | 25 | public storage: storage.Instance; 26 | 27 | /** 28 | * Bootstrap the application. 29 | * 30 | * @class Server 31 | * @method bootstrap 32 | * @static 33 | * @return {ng.auto.IInjectorService} Returns the newly created injector for this app. 34 | */ 35 | public static bootstrap(): Server { 36 | return new Server(); 37 | } 38 | 39 | /** 40 | * Constructor. 41 | * 42 | * @class Server 43 | * @constructor 44 | */ 45 | constructor() { 46 | //create expressjs application 47 | this.app = express(); 48 | 49 | //configure application 50 | this.config(); 51 | 52 | //configure routes 53 | this.routes(); 54 | } 55 | 56 | /** 57 | * Configure application 58 | * 59 | * @class Server 60 | * @method config 61 | * @return void 62 | */ 63 | private config() { 64 | log4js.loadAppender("file"); 65 | log4js.addAppender(log4js.appenders.file("debug.log"), "app"); 66 | 67 | //configure jade 68 | //this.app.set("views", path.join(__dirname, "views")); 69 | //this.app.set("view engine", "jade"); 70 | 71 | //mount logger 72 | //this.app.use(logger("dev")); 73 | 74 | //mount json form parser 75 | this.app.use(bodyParser.json()); 76 | 77 | //mount query string parser 78 | this.app.use(bodyParser.urlencoded({ extended: true })); 79 | 80 | //add static paths 81 | this.app.use(express.static(path.join(__dirname, "public"))); 82 | 83 | // catch 404 and forward to error handler 84 | this.app.use(function(err: any, req: express.Request, res: express.Response, next: express.NextFunction) { 85 | var logger = log4js.getLogger("err"); 86 | logger.debug(req.ip + ": " + req.method + " - " + req.path); 87 | 88 | var error = new Error("Not Found"); 89 | err.status = 404; 90 | 91 | next(err); 92 | }); 93 | 94 | this.app.use(function(req: express.Request, res: express.Response, next: express.NextFunction) { 95 | var logger = log4js.getLogger("app"); 96 | logger.debug(req.ip + ": " + req.method + " - " + req.path); 97 | next(); 98 | }); 99 | 100 | this.storage = storage.disk(); 101 | //this.storage = storage.ftp(); 102 | //this.storage = storage.mariadb(); 103 | //this.storage = storage.redis(); 104 | //this.storage = storage.postgresql(); 105 | //this.storage = storage.memcached(); 106 | //this.storage = storage.mongo(); 107 | //this.storage = storage.hdfs(); 108 | } 109 | 110 | /** 111 | * Configure routes 112 | * 113 | * @class Server 114 | * @method routes 115 | * @return void 116 | */ 117 | private routes() { 118 | //get router 119 | var router: express.Router; 120 | 121 | router = express.Router(); 122 | 123 | // upload 124 | var upload: Route.Upload = new Route.Upload(); 125 | router.post("/upload", this.storage.upload("file"), upload.index.bind(upload.index)); 126 | 127 | // download 128 | var file: Route.File = new Route.File(); 129 | router.get("/file/*", this.storage.fetch(), file.index.bind(file.index)); 130 | 131 | //use router middleware 132 | this.app.use(router); 133 | } 134 | } 135 | 136 | var server = Server.bootstrap(); 137 | export = server.app; 138 | -------------------------------------------------------------------------------- /src/route.ts: -------------------------------------------------------------------------------- 1 | /// 2 | "use strict"; 3 | 4 | import * as fs from "fs"; 5 | 6 | import * as ftp from "ftp"; 7 | 8 | import * as express from "express"; 9 | import * as multer from "multer"; 10 | 11 | module Route { 12 | export class Upload { 13 | public index(req: Express.Request, res: express.Response, next: express.NextFunction) { 14 | try { 15 | var data = { 16 | originalname: req.file.originalname, 17 | encoding: req.file.encoding, 18 | mimetype: req.file.mimetype, 19 | filename: req.file.filename, 20 | size: req.file.size 21 | }; 22 | res.send(JSON.stringify(data)); 23 | } catch (e) { 24 | res.status(500).send(e.toString()); 25 | } 26 | } 27 | } 28 | 29 | export class File { 30 | public index(req: Express.Request, res: express.Response, next: express.NextFunction) { 31 | res.status(404).send("Not found"); 32 | } 33 | } 34 | } 35 | 36 | export = Route; 37 | -------------------------------------------------------------------------------- /src/storage.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as multer from "multer"; 4 | 5 | import * as ftpStorage from "./storage/ftp"; 6 | import * as diskStorage from "./storage/disk"; 7 | import * as mariaStorage from "./storage/maria"; 8 | import * as redisStorage from "./storage/redis"; 9 | import * as postgresStorage from "./storage/pg"; 10 | import * as memcachedStorage from "./storage/memcached"; 11 | import * as mongoStorage from "./storage/mongo"; 12 | import * as HDFSStorage from "./storage/hdfs"; 13 | 14 | module storage { 15 | export class Instance { 16 | private _opts: any; 17 | private _upload: any; 18 | private _fetch: any; 19 | 20 | disk() { 21 | this._opts = { destination: "uploads/" }; 22 | 23 | this._upload = multer({ storage: diskStorage.storage(this._opts) }); 24 | this._fetch = diskStorage.fetch; 25 | 26 | return this; 27 | } 28 | 29 | ftp() { 30 | this._opts = { 31 | host: "127.0.0.1", 32 | port: "21", 33 | user: "kevin", 34 | password: "111111" 35 | }; 36 | 37 | this._upload = multer({ storage: ftpStorage.storage(this._opts) }); 38 | this._fetch = ftpStorage.fetch; 39 | 40 | return this; 41 | } 42 | 43 | mariadb() { 44 | this._opts = { 45 | host: "127.0.0.1", 46 | user: "root", 47 | password: "111111", 48 | db: "test", 49 | destination: "files" 50 | }; 51 | 52 | this._upload = multer({ storage: mariaStorage.storage(this._opts) }); 53 | this._fetch = mariaStorage.fetch; 54 | 55 | return this; 56 | } 57 | 58 | redis() { 59 | this._opts = { 60 | host: "127.0.0.1", 61 | port: "6379", 62 | //password: "111111", 63 | db: 0 64 | }; 65 | 66 | this._upload = multer({ storage: redisStorage.storage(this._opts) }); 67 | this._fetch = redisStorage.fetch; 68 | 69 | return this; 70 | } 71 | 72 | postgresql() { 73 | this._opts = { 74 | host: "127.0.0.1", 75 | port: "5432", 76 | user: "dbuser", 77 | password: "111111", 78 | database: "test", 79 | destination: "files" 80 | }; 81 | 82 | this._upload = multer({ storage: postgresStorage.storage(this._opts) }); 83 | this._fetch = postgresStorage.fetch; 84 | 85 | return this; 86 | } 87 | 88 | memcached() { 89 | this._opts = { 90 | hosts: "127.0.0.1:11211", 91 | options: { 92 | maxKeySize: 250, 93 | maxExpiration: 2592000, 94 | maxValue: 1048576000, // default 1048576 95 | poolSize: 10, 96 | //algorithm: md5, 97 | reconnect: 18000000, 98 | timeout: 5000, 99 | retries: 5, 100 | failures: 5, 101 | retry: 30000, 102 | remove: false, 103 | failOverServers: undefined, 104 | keyCompression: true, 105 | idle: 5000, 106 | } 107 | }; 108 | 109 | this._upload = multer({ storage: memcachedStorage.storage(this._opts) }); 110 | this._fetch = memcachedStorage.fetch; 111 | 112 | return this; 113 | } 114 | 115 | mongo() { 116 | this._opts = { 117 | host: "127.0.0.1", 118 | port: "27017", 119 | db: "test", 120 | collection: "files" 121 | }; 122 | 123 | this._upload = multer({ storage: mongoStorage.storage(this._opts) }); 124 | this._fetch = mongoStorage.fetch; 125 | 126 | return this; 127 | } 128 | 129 | hdfs() { 130 | this._opts = { 131 | user: "root", 132 | host: "127.0.0.1", 133 | port: 50070, 134 | path: "/webhdfs/v1", 135 | destination: "/files" 136 | }; 137 | 138 | this._upload = multer({ storage: HDFSStorage.storage(this._opts) }); 139 | this._fetch = HDFSStorage.fetch; 140 | 141 | return this; 142 | } 143 | 144 | upload(key: string) { 145 | return this._upload.single(key); 146 | } 147 | 148 | fetch() { 149 | return this._fetch(this._opts); 150 | } 151 | } 152 | 153 | export function disk() { 154 | return (new storage.Instance).disk(); 155 | } 156 | 157 | export function ftp() { 158 | return (new storage.Instance).ftp(); 159 | } 160 | 161 | export function mariadb() { 162 | return (new storage.Instance).mariadb(); 163 | } 164 | 165 | export function redis() { 166 | return (new storage.Instance).redis(); 167 | } 168 | 169 | export function postgresql() { 170 | return (new storage.Instance).postgresql(); 171 | } 172 | 173 | export function memcached() { 174 | return (new storage.Instance).memcached(); 175 | } 176 | 177 | export function mongo() { 178 | return (new storage.Instance).mongo(); 179 | } 180 | 181 | export function hdfs() { 182 | return (new storage.Instance).hdfs(); 183 | } 184 | } 185 | 186 | export = storage; 187 | -------------------------------------------------------------------------------- /src/storage/disk.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as fs from "fs"; 4 | 5 | import * as os from "os"; 6 | 7 | import * as path from "path"; 8 | 9 | import * as crypto from "crypto"; 10 | import * as mkdirp from "mkdirp"; 11 | 12 | import * as express from "express"; 13 | import * as multer from "multer"; 14 | 15 | module storage { 16 | export class DiskStorage implements multer.StorageEngine { 17 | private opts: any; 18 | 19 | private getFilename: any; 20 | 21 | private getDestination: any; 22 | 23 | constructor(opts: any) { 24 | this.opts = opts; 25 | 26 | this.getFilename = (opts.filename || this._getFilename); 27 | 28 | if (typeof opts.destination === "string") { 29 | mkdirp.sync(opts.destination); 30 | this.getDestination = (callback) => { callback(null, opts.destination); }; 31 | } else { 32 | this.getDestination = (opts.destination || this._getDestination); 33 | } 34 | } 35 | 36 | _getFilename(callback: Function): void { 37 | crypto.pseudoRandomBytes(16, (err, raw) => { 38 | callback(err, err ? undefined : raw.toString("hex")); 39 | }); 40 | } 41 | 42 | _getDestination(callback: Function): void { 43 | callback(null, os.tmpdir()); 44 | } 45 | 46 | _handleFile(req: express.Request, file: any, callback: Function): void { 47 | var that = this; 48 | 49 | // 取保存路径 50 | var promise1 = () => { 51 | return new Promise((resolve, reject) => { 52 | that.getDestination((err, destination) => { 53 | err ? reject(err) : resolve(destination); 54 | }); 55 | }); 56 | }; 57 | 58 | // 取文件名 59 | var promise2 = () => { 60 | return new Promise((resolve, reject) => { 61 | that.getFilename((err, filename) => { 62 | err ? reject(err) : resolve(filename); 63 | }); 64 | }); 65 | }; 66 | 67 | // 保存文件 68 | var promise3 = (destination, filename) => { 69 | return new Promise((resolve, reject) => { 70 | var finalPath: string = path.join(destination, filename); 71 | 72 | var out: fs.WriteStream = fs.createWriteStream(finalPath); 73 | 74 | file.stream.pipe(out); 75 | 76 | out.on("error", (err) => { reject(err); }); 77 | out.on("finish", () => { resolve([ destination, filename, out.bytesWritten ]); }); 78 | }); 79 | }; 80 | 81 | // 保存metadata 82 | var promise4 = (destination, filename, bytesWritten) => { 83 | return new Promise((resolve, reject) => { 84 | var metadata = { 85 | filename: filename, 86 | originalname: file.originalname, 87 | encoding: file.encoding, 88 | mimetype: file.mimetype, 89 | size: bytesWritten 90 | }; 91 | 92 | var filePath: string = path.join(destination + filename) + ".metadata"; 93 | fs.writeFile(filePath, JSON.stringify(metadata), "utf8", (err) => { 94 | err ? reject(err) : resolve(metadata); 95 | }); 96 | }); 97 | }; 98 | 99 | Promise.all([ 100 | promise1(), 101 | promise2() 102 | ]).then(values => { 103 | console.dir(values); 104 | return promise3(values[0], values[1]); 105 | }).then(values => { 106 | return promise4(values[0], values[1], values[2]); 107 | }).then(metadata => { 108 | callback(null, metadata); 109 | }).catch(err => { 110 | callback(err); 111 | }); 112 | } 113 | 114 | _removeFile(req: express.Request, file: any, callback: (err: NodeJS.ErrnoException) => void): void { 115 | var that = this; 116 | 117 | // 取保存路径 118 | var promise1 = () => { 119 | return new Promise((resolve, reject) => { 120 | that.getDestination((err, destination) => { 121 | err ? reject(err) : resolve(destination); 122 | }); 123 | }); 124 | }; 125 | 126 | var promise2 = (destination) => { 127 | return new Promise((resolve, reject) => { 128 | var finalPath: string = path.join(destination, file.filename + ".metadata"); 129 | fs.unlink(finalPath, (err) => { err ? reject(err) : resolve(true); }); 130 | }); 131 | }; 132 | 133 | var promise3 = (destination) => { 134 | return new Promise((resolve, reject) => { 135 | var finalPath: string = path.join(destination, file.filename); 136 | fs.unlink(finalPath, (err) => { err ? reject(err) : resolve(true); }); 137 | }); 138 | }; 139 | 140 | promise1().then((destination) => { 141 | return Promise.all([ promise2(destination), promise3(destination) ]); 142 | }).then(() => { 143 | callback(null); 144 | }).catch(err => { 145 | callback(err); 146 | }); 147 | } 148 | } 149 | 150 | export function storage(opts: any) { 151 | return new DiskStorage(opts); 152 | } 153 | 154 | export function fetch(opts: any) { 155 | var getDestination; 156 | 157 | if (typeof opts.destination === "string") { 158 | mkdirp.sync(opts.destination); 159 | getDestination = (callback) => { callback(null, opts.destination); }; 160 | } else { 161 | getDestination = (opts.destination || ((callback) => { callback(null, os.tmpdir()); })); 162 | } 163 | 164 | return (req, res, next) => { 165 | var filename = req.params[0]; 166 | if (!filename) { 167 | return next(); 168 | } 169 | 170 | // 取保存路径 171 | var promise1 = () => { 172 | return new Promise((resolve, reject) => { 173 | getDestination((err, destination) => { 174 | err ? reject(err) : resolve(destination); 175 | }); 176 | }); 177 | }; 178 | 179 | // 查找metadata并下载 180 | var promise2 = (destination) => { 181 | return new Promise((resolve, reject) => { 182 | var filePath: string = path.join(destination, filename + ".metadata"); 183 | 184 | fs.readFile(filePath, "utf8", (err, data) => { 185 | if (err) { 186 | reject(err); 187 | } else { 188 | var metadata = JSON.parse(data); 189 | 190 | var finalPath: string = path.join(destination, filename); 191 | fs.exists(finalPath, (exists) => { 192 | if (exists) { 193 | res.download(finalPath, metadata.originalname); 194 | 195 | resolve(metadata); 196 | } else { 197 | reject(err); 198 | } 199 | }); 200 | } 201 | }); 202 | }); 203 | }; 204 | 205 | promise1().then(destination => { 206 | return promise2(destination); 207 | }).catch(err => { 208 | next(); 209 | }); 210 | }; 211 | } 212 | } 213 | 214 | export = storage; 215 | -------------------------------------------------------------------------------- /src/storage/ftp.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as fs from "fs"; 4 | 5 | import * as ftp from "ftp"; 6 | 7 | import * as crypto from "crypto"; 8 | 9 | import * as express from "express"; 10 | import * as multer from "multer"; 11 | 12 | module storage { 13 | export class FtpStorage implements multer.StorageEngine { 14 | private opts: any; 15 | 16 | constructor(opts: any) { 17 | this.opts = opts; 18 | } 19 | 20 | getFilename(callback: Function): void { 21 | crypto.pseudoRandomBytes(16, (err, raw) => { 22 | callback(err, err ? undefined : raw.toString("hex")); 23 | }); 24 | } 25 | 26 | _handleFile(req: express.Request, file: any, callback: Function): void { 27 | var that = this; 28 | 29 | // 获取文件名 30 | var promise1 = () => { 31 | return new Promise((resolve, reject) => { 32 | that.getFilename((err, filename) => { 33 | err ? reject(err) : resolve(filename); 34 | }); 35 | }); 36 | }; 37 | 38 | // 连接服务器 39 | var promise2 = () => { 40 | return new Promise((resolve, reject) => { 41 | var c = new ftp(); 42 | c.on("ready", () => { resolve(c); }); 43 | c.on("error", (err) => { reject(err); }); 44 | 45 | c.connect(that.opts); 46 | }); 47 | }; 48 | 49 | // 保存文件 50 | var promise3 = (c, filename) => { 51 | return new Promise((resolve, reject) => { 52 | c.put(file.stream, filename, (err) => { 53 | if (err) { 54 | c.end(); 55 | reject(err); 56 | } else { 57 | c.size(filename, (err, numBytes) => { 58 | if (err) { 59 | c.end(); 60 | reject(err); 61 | } else { 62 | resolve([ c, filename, numBytes ]); 63 | } 64 | }); 65 | } 66 | }); 67 | }); 68 | }; 69 | 70 | // 保存元数据 71 | var promise4 = (c, filename, numBytes) => { 72 | return new Promise((resolve, reject) => { 73 | var metadata = { 74 | filename: filename, 75 | originalname: file.originalname, 76 | encoding: file.encoding, 77 | mimetype: file.mimetype, 78 | size: numBytes 79 | }; 80 | 81 | var buf = new Buffer(JSON.stringify(metadata)); 82 | c.put(buf, filename + ".metadata", (err) => { 83 | c.end(); 84 | err ? reject(err) : resolve(metadata); 85 | }); 86 | }); 87 | }; 88 | 89 | Promise.all([ 90 | promise1(), 91 | promise2() 92 | ]).then(values => { 93 | return promise3(values[1], values[0]); 94 | }).then(values => { 95 | return promise4(values[0], values[1], values[2]); 96 | }).then(metadata => { 97 | callback(null, metadata); 98 | }).catch(err => { 99 | callback(err); 100 | }); 101 | } 102 | 103 | _removeFile(req: express.Request, file: any, callback: Function): void { 104 | var that = this; 105 | 106 | // 连接服务器 107 | var promise1 = () => { 108 | return new Promise((resolve, reject) => { 109 | var c = new ftp(); 110 | c.on("ready", () => { resolve(c); }); 111 | c.on("error", (err) => { reject(err); }); 112 | 113 | c.connect(that.opts); 114 | }); 115 | }; 116 | 117 | // 删除文件 118 | var promise2 = (c) => { 119 | return new Promise((resolve, reject) => { 120 | c.delete(file.filename + ".metadata", (err) => { 121 | if (err) { 122 | c.end(); 123 | reject(err); 124 | } else { 125 | resolve(c); 126 | } 127 | }); 128 | }); 129 | }; 130 | 131 | // 删除元数据文件 132 | var promise3 = (c) => { 133 | return new Promise((resolve, reject) => { 134 | c.delete(file.filename, (err) => { 135 | c.end(); 136 | 137 | err ? reject(err) : resolve(true); 138 | }); 139 | }); 140 | }; 141 | 142 | promise1().then(c => { 143 | return promise2(c); 144 | }).then(c => { 145 | return promise3(c); 146 | }).then(() => { 147 | callback(null); 148 | }).catch(err => { 149 | callback(err); 150 | }); 151 | } 152 | } 153 | 154 | export function storage(opts: any) { 155 | return new FtpStorage(opts); 156 | } 157 | 158 | export function fetch(opts: any) { 159 | return (req, res, next) => { 160 | var filename = req.params[0]; 161 | if (!filename) { 162 | return next(); 163 | } 164 | 165 | // 连接服务器 166 | var promise1 = () => { 167 | return new Promise((resolve, reject) => { 168 | var c = new ftp(); 169 | c.on("ready", () => { resolve(c); }); 170 | c.on("error", (err) => { reject(err); }); 171 | 172 | c.connect(opts); 173 | }); 174 | }; 175 | 176 | // 获取元数据 177 | var promise2 = (c) => { 178 | return new Promise((resolve, reject) => { 179 | c.get(filename + ".metadata", (err, stream) => { 180 | if (err) { 181 | c.end(); 182 | reject(err); 183 | } else { 184 | var data = ""; 185 | 186 | stream.on("data", (chunk) => { data = data.concat(chunk); }); 187 | stream.on("end", () => { resolve([ c, JSON.parse(data) ]); }); 188 | } 189 | }); 190 | }); 191 | }; 192 | 193 | // 获取文件 194 | var promise3 = (c, metadata) => { 195 | return new Promise((resolve, reject) => { 196 | c.get(filename, (err, stream) => { 197 | stream.once("close", () => { c.end(); }); 198 | 199 | if (err) { 200 | reject(err); 201 | } else { 202 | res.setHeader("Content-disposition", "attachment; filename=" + stream.originalname); 203 | res.setHeader("Content-type", "application/octet-stream"); 204 | 205 | stream.on("error", (err) => { reject(err); }); 206 | stream.on("finish", () => { resolve(metadata); }); 207 | 208 | stream.pipe(res); 209 | } 210 | }); 211 | }); 212 | }; 213 | 214 | promise1().then(c => { 215 | return promise2(c); 216 | }).then(values => { 217 | return promise3(values[0], values[1]); 218 | }).catch(err => { 219 | next(); 220 | }); 221 | }; 222 | } 223 | } 224 | 225 | export = storage; 226 | -------------------------------------------------------------------------------- /src/storage/hdfs.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as fs from "fs"; 4 | 5 | import * as path from "path"; 6 | 7 | import * as crypto from "crypto"; 8 | 9 | import * as express from "express"; 10 | import * as multer from "multer"; 11 | 12 | let webhdfs: any = require("webhdfs"); 13 | 14 | module storage { 15 | export class HDFSStorage implements multer.StorageEngine { 16 | private opts: any; 17 | 18 | constructor(opts: any) { 19 | this.opts = opts; 20 | 21 | var hdfs = webhdfs.createClient(opts); 22 | 23 | // 查看目录是否存在 24 | hdfs.exists(opts.destination, (err) => { 25 | if (typeof err === "object") { 26 | throw err; 27 | } 28 | 29 | if (err === false) { 30 | hdfs.mkdir(opts.destination, (err) => { 31 | if (err) { 32 | throw err; 33 | } 34 | }); 35 | } 36 | }); 37 | } 38 | 39 | getFilename(callback: Function): void { 40 | crypto.pseudoRandomBytes(16, (err, raw) => { 41 | callback(err, err ? undefined : raw.toString("hex")); 42 | }); 43 | } 44 | 45 | _handleFile(req: express.Request, file: any, callback: Function): void { 46 | var that: HDFSStorage = this; 47 | 48 | var hdfs = webhdfs.createClient(that.opts); 49 | 50 | // 获取文件名 51 | var promise1 = () => { 52 | return new Promise((resolve, reject) => { 53 | that.getFilename((err, filename) => { 54 | err ? reject(err) : resolve(filename); 55 | }); 56 | }); 57 | }; 58 | 59 | // 上传文件 60 | var promise2 = (filename) => { 61 | return new Promise((resolve, reject) => { 62 | var filePath = path.join(that.opts.destination, filename); 63 | 64 | var out = hdfs.createWriteStream(filePath); 65 | 66 | out.on("error", (err) => { reject(err); }); 67 | out.on("finish", () => { 68 | hdfs.stat(filePath, (err, stats) => { 69 | err ? reject(err) : resolve([ filename, stats.length ]); 70 | }); 71 | }); 72 | 73 | file.stream.pipe(out); 74 | }); 75 | }; 76 | 77 | // 上传metadata 78 | var promise3 = (filename, bytesWritten) => { 79 | return new Promise((resolve, reject) => { 80 | var metadata = { 81 | filename: filename, 82 | originalname: file.originalname, 83 | encoding: file.encoding, 84 | mimetype: file.mimetype, 85 | size: bytesWritten 86 | }; 87 | 88 | var filePath = path.join(that.opts.destination, filename) + ".metadata"; 89 | 90 | hdfs.writeFile(filePath, JSON.stringify(metadata), (err) => { 91 | err ? reject(err) : resolve(metadata); 92 | }); 93 | }); 94 | }; 95 | 96 | promise1().then(filename => { 97 | return promise2(filename); 98 | }).then(values => { 99 | return promise3(values[0], values[1]); 100 | }).then(metadata => { 101 | callback(null, metadata); 102 | }).catch(err => { 103 | callback(err); 104 | }); 105 | } 106 | 107 | _removeFile(req: express.Request, file: any, callback: (err: NodeJS.ErrnoException) => void): void { 108 | var that: HDFSStorage = this; 109 | 110 | var filename = path.join(that.opts.destination, file.filename); 111 | 112 | var hdfs = webhdfs.createClient(that.opts); 113 | 114 | hdfs.unlink(filename, (err) => { 115 | if (err) { 116 | callback(err); 117 | } else { 118 | hdfs.unlink(filename + ".metadata", (err) => { 119 | callback(null); 120 | }); 121 | } 122 | }); 123 | } 124 | } 125 | 126 | export function storage(opts: any) { 127 | return new HDFSStorage(opts); 128 | } 129 | 130 | export function fetch(opts: any) { 131 | return (req, res, next) => { 132 | var filename = req.params[0]; 133 | if (!filename) { 134 | return next(); 135 | } 136 | 137 | filename = path.join(opts.destination, filename); 138 | 139 | var hdfs = webhdfs.createClient(opts); 140 | 141 | // 数据文件是否存在 142 | var promise1 = () => { 143 | return new Promise((resolve, reject) => { 144 | hdfs.exists(filename, (err) => { 145 | (typeof err === "object") ? reject(err) : (err ? resolve(err) : reject(err)); 146 | }); 147 | }); 148 | }; 149 | 150 | // metadata文件是否存在 151 | var promise2 = () => { 152 | return new Promise((resolve, reject) => { 153 | hdfs.exists(filename + ".metadata", (err) => { 154 | (typeof err === "object") ? reject(err) : (err ? resolve(err) : reject(err)); 155 | }); 156 | }); 157 | }; 158 | 159 | // 下载metadata文件 160 | var promise3 = () => { 161 | return new Promise((resolve, reject) => { 162 | hdfs.readFile(filename + ".metadata", (err, data) => { 163 | err ? reject(err) : resolve(data); 164 | }); 165 | }); 166 | }; 167 | 168 | // 下载数据文件 169 | var promise4 = (data) => { 170 | return new Promise((resolve, reject) => { 171 | var metadata = JSON.parse(data); 172 | 173 | var remote = hdfs.createReadStream(filename); 174 | remote.on("error", (err) => { reject(err); }); 175 | remote.on("finish", () => { resolve(metadata); }); 176 | 177 | res.setHeader("Content-disposition", "attachment; filename=" + metadata.originalname); 178 | res.setHeader("Content-type", "application/octet-stream"); 179 | 180 | remote.pipe(res); 181 | }); 182 | }; 183 | 184 | Promise.all([ 185 | promise1(), 186 | promise2() 187 | ]).then(values => { 188 | return promise3(); 189 | }).then(data => { 190 | return promise4(data); 191 | }).catch(err => { 192 | console.dir(err); 193 | next(); 194 | }); 195 | }; 196 | }; 197 | } 198 | 199 | export = storage; 200 | -------------------------------------------------------------------------------- /src/storage/maria.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as fs from "fs"; 4 | import * as stream from "stream"; 5 | 6 | import * as crypto from "crypto"; 7 | 8 | import * as express from "express"; 9 | import * as multer from "multer"; 10 | 11 | let mariasql: any = require("mariasql"); 12 | 13 | /** 14 | CREATE TABLE `files` ( 15 | `filename` varchar(64) NOT NULL, 16 | `metadata` text, 17 | `content` longtext, 18 | PRIMARY KEY (`filename`) 19 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; 20 | **/ 21 | module storage { 22 | export class MariaStorage implements multer.StorageEngine { 23 | private opts: any; 24 | 25 | constructor(opts: any) { 26 | this.opts = opts; 27 | } 28 | 29 | getFilename(callback: Function): void { 30 | crypto.pseudoRandomBytes(16, (err, raw) => { 31 | callback(err, err ? undefined : raw.toString("hex")); 32 | }); 33 | } 34 | 35 | _handleFile(req: express.Request, file: any, callback: Function): void { 36 | var that: MariaStorage = this; 37 | 38 | // 获取文件名 39 | var promise1 = () => { 40 | return new Promise((resolve, reject) => { 41 | that.getFilename((err, filename) => { 42 | err ? reject(err) : resolve(filename); 43 | }); 44 | }); 45 | }; 46 | 47 | // 保存数据 48 | var promise2 = () => { 49 | return new Promise((resolve, reject) => { 50 | var data = []; 51 | 52 | var pt = new stream.PassThrough(); 53 | pt.on("data", (chunk) => { data.push(chunk); }); 54 | pt.on("error", (err) => { reject(err); }); 55 | pt.on("end", () => { resolve(data); }); 56 | 57 | file.stream.pipe(pt); 58 | }); 59 | }; 60 | 61 | // 上传文件 62 | var promise3 = (filename, data) => { 63 | return new Promise((resolve, reject) => { 64 | var content = Buffer.concat(data).toString("binary"); 65 | 66 | var metadata = { 67 | filename: filename, 68 | originalname: file.originalname, 69 | encoding: file.encoding, 70 | mimetype: file.mimetype, 71 | size: content.length 72 | }; 73 | 74 | var c = new mariasql(that.opts); 75 | 76 | c.query( 77 | "insert into `" + that.opts.destination + "` (`filename`, `metadata`, `content`) values " + 78 | "(:filename, :metadata, :content)", 79 | { filename: filename, metadata: JSON.stringify(metadata), content: content }, 80 | (err, result) => { 81 | err ? reject(err) : resolve(metadata); 82 | } 83 | ); 84 | 85 | c.end(); 86 | }); 87 | }; 88 | 89 | Promise.all([ 90 | promise1(), 91 | promise2() 92 | ]).then(values => { 93 | return promise3(values[0], values[1]); 94 | }).then(metadata => { 95 | callback(null, metadata); 96 | }).catch(err => { 97 | callback(err); 98 | }); 99 | } 100 | 101 | _removeFile(req: express.Request, file: any, callback: (err: NodeJS.ErrnoException) => void): void { 102 | var c = new mariasql(this.opts); 103 | 104 | c.query("delete from " + this.opts.destination + " where filename = ?", [file.filename], 105 | (err, rows) => { 106 | callback(err); 107 | }); 108 | 109 | c.end(); 110 | } 111 | } 112 | 113 | export function storage(opts: any) { 114 | return new MariaStorage(opts); 115 | } 116 | 117 | export function fetch(opts: any) { 118 | return (req, res, next) => { 119 | var filename = req.params[0]; 120 | if (!filename) { 121 | return next(); 122 | } 123 | 124 | var c = new mariasql(opts); 125 | 126 | c.query("select `metadata`, `content` from `" + opts.destination + "` where `filename` = ?", [filename], 127 | (err, rows) => { 128 | if (err || (rows.info.numRows < 1)) { 129 | return next(); 130 | } 131 | 132 | var metadata = JSON.parse(rows[0].metadata); 133 | 134 | res.setHeader("Content-disposition", "attachment; filename=" + metadata.originalname); 135 | res.setHeader("Content-type", "application/octet-stream"); 136 | 137 | res.send(new Buffer(rows[0].content, "binary")); 138 | }); 139 | c.end(); 140 | }; 141 | }; 142 | } 143 | 144 | export = storage; 145 | -------------------------------------------------------------------------------- /src/storage/memcached.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as fs from "fs"; 4 | import * as stream from "stream"; 5 | 6 | import * as crypto from "crypto"; 7 | 8 | import * as express from "express"; 9 | import * as multer from "multer"; 10 | 11 | let memcached: any = require("memcached"); 12 | 13 | module storage { 14 | export class MemcachedStorage implements multer.StorageEngine { 15 | private opts: any; 16 | 17 | constructor(opts: any) { 18 | this.opts = opts; 19 | } 20 | 21 | getFilename(callback: Function): void { 22 | crypto.pseudoRandomBytes(16, (err, raw) => { 23 | callback(err, err ? undefined : raw.toString("hex")); 24 | }); 25 | } 26 | 27 | _handleFile(req: express.Request, file: any, callback: Function): void { 28 | var that: MemcachedStorage = this; 29 | 30 | var data = []; 31 | 32 | var pt = new stream.PassThrough(); 33 | pt.on("data", (chunk) => { data.push(chunk); }); 34 | pt.on("error", (err) => { callback(err); }); 35 | pt.on("end", () => { 36 | that.getFilename((err, filename) => { 37 | var content = Buffer.concat(data).toString("hex"); 38 | 39 | var metadata = { 40 | filename: filename, 41 | originalname: file.originalname, 42 | encoding: file.encoding, 43 | mimetype: file.mimetype, 44 | size: content.length 45 | }; 46 | 47 | var c = new memcached(this.opts.hosts, this.opts.options); 48 | c.set(filename, { metadata: metadata, content: content }, 0, (err) => { 49 | c.end(); 50 | 51 | callback(err, err ? null : metadata); 52 | }); 53 | 54 | }); 55 | }); 56 | 57 | file.stream.pipe(pt); 58 | } 59 | 60 | _removeFile(req: express.Request, file: any, callback: (err: NodeJS.ErrnoException) => void): void { 61 | var c = new memcached(this.opts.hosts, this.opts.options); 62 | c.del(file.filename, (err) => { 63 | c.end(true); 64 | 65 | if (err) { 66 | return callback(err); 67 | } 68 | }); 69 | } 70 | } 71 | 72 | export function storage(opts: any) { 73 | return new MemcachedStorage(opts); 74 | } 75 | 76 | export function fetch(opts: any) { 77 | return (req, res, next) => { 78 | var filename = req.params[0]; 79 | if (!filename) { 80 | return next(); 81 | } 82 | 83 | var c = new memcached(opts.hosts, opts.options); 84 | c.get(filename, (err, result) => { 85 | c.end(); 86 | 87 | if (err) { 88 | return next(); 89 | } 90 | 91 | res.setHeader("Content-disposition", "attachment; filename=" + result.metadata.originalname); 92 | res.setHeader("Content-type", "application/octet-stream"); 93 | 94 | res.send(new Buffer(result.content, "hex")); 95 | }); 96 | }; 97 | }; 98 | } 99 | 100 | export = storage; 101 | -------------------------------------------------------------------------------- /src/storage/mongo.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as fs from "fs"; 4 | import * as stream from "stream"; 5 | 6 | import * as crypto from "crypto"; 7 | 8 | import * as express from "express"; 9 | import * as multer from "multer"; 10 | 11 | let mongodb: any = require("mongodb"); 12 | 13 | module storage { 14 | export class MongoStorage implements multer.StorageEngine { 15 | private opts: any; 16 | 17 | constructor(opts: any) { 18 | this.opts = opts; 19 | } 20 | 21 | getFilename(callback: Function): void { 22 | crypto.pseudoRandomBytes(16, (err, raw) => { 23 | callback(err, err ? undefined : raw.toString("hex")); 24 | }); 25 | } 26 | 27 | _handleFile(req: express.Request, file: any, callback: Function): void { 28 | var that: MongoStorage = this; 29 | 30 | // 获取文件名 31 | var promise1 = () => { 32 | return new Promise((resolve, reject) => { 33 | that.getFilename((err, filename) => { 34 | err ? reject(err) : resolve(filename); 35 | }); 36 | }); 37 | }; 38 | 39 | // 连接数据库 40 | var promise2 = () => { 41 | return new Promise((resolve, reject) => { 42 | var uri = "mongodb://" + that.opts.host + ":" + that.opts.port + "/" + that.opts.db; 43 | mongodb.MongoClient.connect(uri, (err, db) => { 44 | err ? reject(err) : resolve(db); 45 | }); 46 | }); 47 | }; 48 | 49 | // 插入文件 50 | var promise3 = (db, filename, stream) => { 51 | return new Promise((resolve, reject) => { 52 | var bucket = new mongodb.GridFSBucket(db); 53 | var bstream = bucket.openUploadStreamWithId(filename, filename); 54 | 55 | stream.pipe(bstream) 56 | .on("error", (err) => { 57 | db.close(); 58 | reject(err); 59 | }) 60 | .on("finish", () => { 61 | resolve([db, filename, bstream.length]); 62 | }); 63 | }); 64 | }; 65 | 66 | // 插入元数据 67 | var promise4 = (db, filename, bytesWritten) => { 68 | return new Promise((resolve, reject) => { 69 | var metadata = { 70 | "filename": filename, 71 | "originalname": file.originalname, 72 | "encoding": file.encoding, 73 | "mimetype": file.mimetype, 74 | "size": bytesWritten 75 | }; 76 | 77 | var col = db.collection(that.opts.collection); 78 | col.insertOne(metadata, (err, r) => { 79 | db.close(); 80 | 81 | err ? reject(err) : resolve(metadata); 82 | }); 83 | }); 84 | }; 85 | 86 | Promise.all([ 87 | promise1(), 88 | promise2() 89 | ]).then(values => { 90 | return promise3(values[1], values[0], file.stream); 91 | }).then(values => { 92 | return promise4(values[0], values[1], values[2]); 93 | }).then(metadata => { 94 | callback(null, metadata); 95 | }).catch(err => { 96 | callback(err); 97 | }); 98 | } 99 | 100 | _removeFile(req: express.Request, file: any, callback: (err: NodeJS.ErrnoException) => void): void { 101 | var that: MongoStorage = this; 102 | 103 | // 连接数据库 104 | var promise1 = () => { 105 | return new Promise((resolve, reject) => { 106 | var uri = "mongodb://" + that.opts.host + ":" + that.opts.port + "/" + that.opts.db; 107 | mongodb.MongoClient.connect(uri, (err, db) => { 108 | err ? reject(err) : resolve(db); 109 | }); 110 | }); 111 | }; 112 | 113 | // 删除元数据 114 | var promise2 = (db) => { 115 | return new Promise((resolve, reject) => { 116 | var col = db.collection(that.opts.collection); 117 | col.deleteOne({ filename: file.filename }, (err, r) => { 118 | if (err) { 119 | db.close(); 120 | reject(err); 121 | } else { 122 | resolve(db); 123 | } 124 | }); 125 | }); 126 | }; 127 | 128 | // 删除文件 129 | var promise3 = (db) => { 130 | return new Promise((resolve, reject) => { 131 | var bucket = new mongodb.GridFSBucket(db); 132 | bucket.delete(file.filename, err => { 133 | db.close(); 134 | err ? reject(err) : resolve(true); 135 | }); 136 | }); 137 | }; 138 | 139 | promise1().then(db => { 140 | return promise2(db); 141 | }).then(db => { 142 | return promise3(db); 143 | }).then(() => { 144 | callback(null); 145 | }).catch(err => { 146 | callback(err); 147 | }); 148 | } 149 | } 150 | 151 | export function storage(opts: any) { 152 | return new MongoStorage(opts); 153 | } 154 | 155 | export function fetch(opts: any) { 156 | return (req, res, next) => { 157 | var filename = req.params[0]; 158 | if (!filename) { 159 | return next(); 160 | } 161 | 162 | // 连接数据库 163 | var promise1 = () => { 164 | return new Promise((resolve, reject) => { 165 | var uri = "mongodb://" + opts.host + ":" + opts.port + "/" + opts.db; 166 | mongodb.MongoClient.connect(uri, (err, db) => { 167 | err ? reject(err) : resolve(db); 168 | }); 169 | }); 170 | }; 171 | 172 | // 检索元数据 173 | var promise2 = (db) => { 174 | return new Promise((resolve, reject) => { 175 | var col = db.collection(opts.collection); 176 | col.find({ filename: filename }).toArray((err, docs) => { 177 | if (err) { 178 | db.close(); 179 | reject(err); 180 | } else { 181 | resolve([ db, docs ]); 182 | } 183 | }); 184 | }); 185 | }; 186 | 187 | // 下载文件 188 | var promise3 = (db, docs) => { 189 | return new Promise((resolve, reject) => { 190 | var bucket = new mongodb.GridFSBucket(db); 191 | var stream = bucket.openDownloadStreamByName(docs[0].filename); 192 | 193 | stream.on("error", (err) => { db.close(); reject(err); }); 194 | stream.on("finish", () => { db.close(); resolve(docs[0]); }); 195 | 196 | res.setHeader("Content-disposition", "attachment; filename=" + docs[0].originalname); 197 | res.setHeader("Content-type", "application/octet-stream"); 198 | 199 | stream.pipe(res); 200 | }); 201 | }; 202 | 203 | promise1().then(db => { 204 | return promise2(db); 205 | }).then(values => { 206 | return promise3(values[0], values[1]); 207 | }).catch(err => { 208 | return next(); 209 | }); 210 | }; 211 | }; 212 | } 213 | 214 | export = storage; 215 | -------------------------------------------------------------------------------- /src/storage/pg.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as fs from "fs"; 4 | import * as stream from "stream"; 5 | 6 | import * as crypto from "crypto"; 7 | 8 | import * as express from "express"; 9 | import * as multer from "multer"; 10 | 11 | import * as pg from "pg"; 12 | 13 | /** 14 | CREATE TABLE files ( 15 | filename varchar(64) NOT NULL, 16 | metadata text, 17 | content bytea, 18 | PRIMARY KEY (filename) 19 | ); 20 | **/ 21 | module storage { 22 | export class PostgresStorage implements multer.StorageEngine { 23 | private opts: any; 24 | 25 | constructor(opts: any) { 26 | this.opts = opts; 27 | } 28 | 29 | getFilename(callback: Function): void { 30 | crypto.pseudoRandomBytes(16, (err, raw) => { 31 | callback(err, err ? undefined : raw.toString("hex")); 32 | }); 33 | } 34 | 35 | _handleFile(req: express.Request, file: any, callback: Function): void { 36 | var that: PostgresStorage = this; 37 | 38 | var data = []; 39 | 40 | var pt = new stream.PassThrough(); 41 | pt.on("data", (chunk) => { data.push(chunk); }); 42 | pt.on("error", (err) => { callback(err); }); 43 | pt.on("end", () => { 44 | that.getFilename((err, filename) => { 45 | var content = Buffer.concat(data).toString("hex"); 46 | 47 | var metadata = { 48 | filename: filename, 49 | originalname: file.originalname, 50 | encoding: file.encoding, 51 | mimetype: file.mimetype, 52 | size: content.length 53 | }; 54 | 55 | var c = new pg.Client(this.opts); 56 | c.connect((err) => { 57 | if (err) { 58 | return callback(err); 59 | } 60 | 61 | c.query( 62 | "insert into " + that.opts.destination + " (filename, metadata, content) values ($1, $2, $3)", 63 | [filename, JSON.stringify(metadata), "\\x" + content], 64 | (err, result) => { 65 | c.end(); 66 | callback(err, err ? null : metadata); 67 | }); 68 | }); 69 | }); 70 | }); 71 | 72 | file.stream.pipe(pt); 73 | } 74 | 75 | _removeFile(req: express.Request, file: any, callback: (err: NodeJS.ErrnoException) => void): void { 76 | var c = new pg.Client(this.opts); 77 | c.connect((err) => { 78 | if (err) { 79 | return callback(err); 80 | } 81 | 82 | c.query("delete from " + this.opts.destination + " where filename = $1", [file.filename], 83 | (err, result) => { 84 | c.end(); 85 | 86 | callback(err); 87 | }); 88 | }); 89 | } 90 | } 91 | 92 | export function storage(opts: any) { 93 | return new PostgresStorage(opts); 94 | } 95 | 96 | export function fetch(opts: any) { 97 | return (req, res, next) => { 98 | var filename = req.params[0]; 99 | if (!filename) { 100 | return next(); 101 | } 102 | 103 | var c = new pg.Client(opts); 104 | c.connect((err) => { 105 | if (err) { 106 | return next(); 107 | } 108 | 109 | c.query("select metadata, content from " + opts.destination + " where filename = $1", [ filename ], 110 | (err, result) => { 111 | c.end(); 112 | 113 | if (err || (result.rowCount < 1)) { 114 | return next(); 115 | } 116 | 117 | var metadata = JSON.parse(result.rows[0].metadata); 118 | 119 | res.setHeader("Content-disposition", "attachment; filename=" + metadata.originalname); 120 | res.setHeader("Content-type", "application/octet-stream"); 121 | 122 | res.send(new Buffer(result.rows[0].content, "hex")); 123 | }); 124 | }); 125 | }; 126 | }; 127 | } 128 | 129 | export = storage; 130 | -------------------------------------------------------------------------------- /src/storage/redis.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import * as fs from "fs"; 4 | import * as stream from "stream"; 5 | 6 | import * as crypto from "crypto"; 7 | 8 | import * as express from "express"; 9 | import * as multer from "multer"; 10 | 11 | let redis: any = require("redis"); 12 | 13 | module storage { 14 | export class RedisStorage implements multer.StorageEngine { 15 | private opts: any; 16 | 17 | constructor(opts: any) { 18 | this.opts = opts; 19 | } 20 | 21 | getFilename(callback: Function): void { 22 | crypto.pseudoRandomBytes(16, (err, raw) => { 23 | callback(err, err ? undefined : raw.toString("hex")); 24 | }); 25 | } 26 | 27 | _handleFile(req: express.Request, file: any, callback: Function): void { 28 | var that: RedisStorage = this; 29 | 30 | var data = []; 31 | 32 | var pt = new stream.PassThrough(); 33 | pt.on("data", (chunk) => { data.push(chunk); }); 34 | pt.on("error", (err) => { callback(err); }); 35 | pt.on("end", () => { 36 | that.getFilename((err, filename) => { 37 | var content = Buffer.concat(data).toString("binary"); 38 | 39 | var metadata = { 40 | filename: filename, 41 | originalname: file.originalname, 42 | encoding: file.encoding, 43 | mimetype: file.mimetype, 44 | size: content.length 45 | }; 46 | 47 | var c = redis.createClient(this.opts); 48 | c.on("error", (err) => { 49 | return callback(err); 50 | }); 51 | 52 | c.hmset(filename, [ "metadata", JSON.stringify(metadata), "content", content ], (err, res) => { 53 | c.end(true); 54 | callback(err, err ? null : metadata); 55 | }); 56 | }); 57 | }); 58 | 59 | file.stream.pipe(pt); 60 | } 61 | 62 | _removeFile(req: express.Request, file: any, callback: (err: NodeJS.ErrnoException) => void): void { 63 | var c = redis.createClient(this.opts); 64 | c.on("error", (err) => { 65 | return callback(err); 66 | }); 67 | 68 | c.del(file.filename, (err) => { 69 | c.end(true); 70 | 71 | if (err) { 72 | return callback(err); 73 | } 74 | }); 75 | } 76 | } 77 | 78 | export function storage(opts: any) { 79 | return new RedisStorage(opts); 80 | } 81 | 82 | export function fetch(opts: any) { 83 | return (req, res, next) => { 84 | var filename = req.params[0]; 85 | if (!filename) { 86 | return next(); 87 | } 88 | 89 | var c = redis.createClient(opts); 90 | c.on("error", (err) => { 91 | return next(); 92 | }); 93 | 94 | c.hgetall(filename, (err, reply) => { 95 | c.end(true); 96 | 97 | if (err || !reply) { 98 | return next(); 99 | } 100 | 101 | var metadata = JSON.parse(reply.metadata); 102 | 103 | res.setHeader("Content-disposition", "attachment; filename=" + metadata.originalname); 104 | res.setHeader("Content-type", "application/octet-stream"); 105 | 106 | res.send(new Buffer(reply.content, "binary")); 107 | }); 108 | }; 109 | }; 110 | } 111 | 112 | export = storage; 113 | -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "class-name": true, 4 | "curly": true, 5 | "eofline": false, 6 | "forin": true, 7 | "indent": false, 8 | "label-position": true, 9 | "label-undefined": true, 10 | "max-line-length": [true, 150], 11 | "no-arg": true, 12 | "no-bitwise": true, 13 | "no-console": false, 14 | "no-construct": true, 15 | "no-constructor-vars": false, 16 | "no-debugger": true, 17 | "no-duplicate-key": true, 18 | "no-duplicate-variable": true, 19 | "no-empty": true, 20 | "no-eval": true, 21 | "no-string-literal": true, 22 | "no-switch-case-fall-through": true, 23 | "no-trailing-whitespace": true, 24 | "no-unused-expression": true, 25 | "no-unused-variable": false, 26 | "no-unreachable": true, 27 | "no-use-before-declare": true, 28 | "no-var-requires": false, 29 | "one-line": [ 30 | true, 31 | "check-open-brace", 32 | "check-catch", 33 | "check-else", 34 | "check-whitespace" 35 | ], 36 | "quotemark": [true, "double"], 37 | "semicolon": true, 38 | "triple-equals": [true, "allow-null-check"], 39 | "typedef": [true, 40 | "callSignature", 41 | "indexSignature", 42 | "parameter", 43 | "propertySignature", 44 | "variableDeclarator", 45 | "memberVariableDeclarator"], 46 | "use-strict": false, 47 | "variable-name": [ 48 | true, 49 | "allow-leading-underscore" 50 | ], 51 | "whitespace": [ 52 | true, 53 | "check-branch", 54 | "check-decl", 55 | "check-operator", 56 | "check-separator", 57 | "check-type" 58 | ] 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /typings.json: -------------------------------------------------------------------------------- 1 | { 2 | "proxy": "http://127.0.0.1:1080", 3 | "dependencies": { 4 | "body-parser": "registry:npm/body-parser#1.15.2+20160815132839", 5 | "express": "registry:npm/express#4.14.0+20160925001530", 6 | "mime": "registry:npm/mime#1.3.0+20160723033700", 7 | "mkdirp": "registry:npm/mkdirp#0.5.0+20160723033700", 8 | "serve-static": "registry:npm/serve-static#1.11.1+20160810053450" 9 | }, 10 | "globalDependencies": { 11 | "express-serve-static-core": "registry:dt/express-serve-static-core#4.0.0+20161012061536", 12 | "ftp": "registry:dt/ftp#0.0.0+20160317120654", 13 | "log4js": "registry:dt/log4js#0.0.0+20160608042355", 14 | "mariasql": "registry:dt/mariasql#0.1.22+20160317120654", 15 | "multer": "registry:dt/multer#0.0.0+20160829120954", 16 | "node": "registry:env/node#6.0.0+20161019193037", 17 | "pg": "registry:dt/pg#6.1.0+20161019122652", 18 | "pg-types": "registry:dt/pg-types#1.11.0+20161019122652", 19 | "redis": "registry:dt/redis#0.12.1+20161011212150" 20 | } 21 | } 22 | --------------------------------------------------------------------------------