├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── LICENSE ├── README.md ├── README.ru.md ├── bower.json ├── crossdomain.xml ├── custom-tasks └── Gruntfile-ok.js ├── dist ├── FileAPI.flash.camera.swf ├── FileAPI.flash.image.swf ├── FileAPI.flash.swf ├── FileAPI.html5.js ├── FileAPI.html5.min.js ├── FileAPI.js ├── FileAPI.min.js └── jquery.fileapi.min.js ├── examples ├── caman.html ├── demo.html ├── thumbnails.html ├── toolkit.css ├── userpic.html ├── watermark.html └── webcam.html ├── flash ├── .gitignore ├── camera │ ├── .settings │ │ └── org.eclipse.core.resources.prefs │ ├── html-template │ │ ├── history │ │ │ ├── history.css │ │ │ ├── history.js │ │ │ └── historyFrame.html │ │ ├── index.template.html │ │ ├── playerProductInstall.swf │ │ └── swfobject.js │ └── src │ │ └── FileAPI_flash_camera.as ├── core │ ├── .settings │ │ └── org.eclipse.core.resources.prefs │ ├── html-template │ │ ├── history │ │ │ ├── history.css │ │ │ ├── history.js │ │ │ └── historyFrame.html │ │ ├── index.template.html │ │ ├── playerProductInstall.swf │ │ └── swfobject.js │ ├── lib │ │ ├── EnginesLibrary.swc │ │ └── blooddy_crypto.swc │ └── src │ │ ├── FileAPI_flash.as │ │ ├── net │ │ └── inspirit │ │ │ ├── MultipartURLLoader.as │ │ │ └── events │ │ │ └── MultipartURLLoaderEvent.as │ │ └── ru │ │ └── mail │ │ ├── commands │ │ ├── AbstractUploadFileCommand.as │ │ ├── DecodeBytesToBitmapCommand.as │ │ ├── LoadFileCommand.as │ │ ├── ResizeFileCommand.as │ │ ├── UploadCommand.as │ │ ├── UploadFileCommand.as │ │ ├── UploadImageCommand.as │ │ ├── graphicloader │ │ │ ├── IGraphicLoader.as │ │ │ ├── SimpleGraphicLoader.as │ │ │ └── events │ │ │ │ └── GraphicLoaderCompleteEvent.as │ │ └── textloader │ │ │ ├── ITextLoader.as │ │ │ ├── SimpleTextLoader.as │ │ │ └── events │ │ │ ├── LoaderProgressEvent.as │ │ │ └── TextLoaderCompleteEvent.as │ │ ├── communication │ │ ├── JSCallbackPresenter.as │ │ └── JSCaller.as │ │ ├── controller │ │ ├── AppController.as │ │ └── CameraController.as │ │ ├── data │ │ ├── AbstractImageFactory.as │ │ ├── AttachmentsModel.as │ │ ├── IImageFactory.as │ │ ├── ImageFactory.as │ │ ├── builder │ │ │ ├── AbstractDataBuilder.as │ │ │ └── FilesDataBuilder.as │ │ └── vo │ │ │ ├── BaseFileVO.as │ │ │ ├── ErrorVO.as │ │ │ ├── FakeFileVO.as │ │ │ ├── FileStatesEnum.as │ │ │ ├── FileVO.as │ │ │ ├── IFileVO.as │ │ │ ├── ImageTransformVO.as │ │ │ ├── OverlayVO.as │ │ │ └── PhotoFileVO.as │ │ ├── engines │ │ ├── chain │ │ │ ├── AbstractModelJSEngine.as │ │ │ ├── EnginesFactory.as │ │ │ ├── IJsCallerHolder.as │ │ │ ├── IModelHolder.as │ │ │ ├── manage │ │ │ │ └── SelectFilesEngine.as │ │ │ └── presentation │ │ │ │ └── MouseListenerEngine.as │ │ └── commands │ │ │ ├── MouseListenerEngineCommand.as │ │ │ └── SelectFilesCommand.as │ │ ├── events │ │ ├── CompleteEvent.as │ │ ├── DecodeBytesToBitmapCompleteEvent.as │ │ ├── ImageTransformCompleteEvent.as │ │ └── UploadCompleteEvent.as │ │ └── utils │ │ ├── BMPDecoder.as │ │ ├── ExifReader2.as │ │ └── LoggerJS.as └── image │ ├── .settings │ └── org.eclipse.core.resources.prefs │ ├── html-template │ ├── history │ │ ├── history.css │ │ ├── history.js │ │ └── historyFrame.html │ ├── index.template.html │ ├── playerProductInstall.swf │ └── swfobject.js │ ├── lib │ └── blooddy_crypto.swc │ └── src │ ├── Base64.as │ └── FileAPI_flash_image.as ├── index.html ├── lib ├── FileAPI.Camera.js ├── FileAPI.Flash.Camera.js ├── FileAPI.Flash.js ├── FileAPI.Form.js ├── FileAPI.Image.js ├── FileAPI.XHR.js ├── FileAPI.core.js ├── canvas-to-blob.js └── load-image-ios.js ├── node ├── file-api.js └── server.js ├── package.json ├── plugins ├── FileAPI.exif.js ├── FileAPI.id3.js ├── caman.full.min.js └── caman.min.js ├── server ├── FileAPI.class.php └── ctrl.php ├── statics ├── body.png ├── body__top.png ├── content.png ├── docs.json ├── logo.png ├── main.css ├── watermark.png └── xtpl.min.js ├── support.html └── tests ├── files ├── 1px.gif ├── big.jpg ├── dino.png ├── hello.txt ├── image.jpg ├── lebowski.json └── samples │ ├── chrome-dino-180deg-50x50.png │ ├── chrome-dino-50x50.jpeg │ ├── chrome-dino-90deg-100x100.png │ ├── chrome-dino-custom.png │ ├── chrome-image-auto-100x100.jpeg │ ├── firefox-dino-180deg-50x50.png │ ├── firefox-dino-50x50.jpeg │ ├── firefox-dino-90deg-100x100.png │ ├── firefox-dino-custom.png │ ├── firefox-image-auto-100x100.jpeg │ ├── firefox-vintage.png │ ├── phantomjs-dino-180deg-50x50.png │ ├── phantomjs-dino-50x50.jpeg │ ├── phantomjs-dino-90deg-100x100.png │ ├── phantomjs-dino-custom.png │ ├── phantomjs-image-auto-100x100.jpeg │ ├── phantomjs-vintage.png │ └── safari-dino-90deg-100x100.png ├── flash.html ├── grunt-task ├── phantomjs │ ├── bridge.js │ ├── grunt-lib-phantomjs-main.js │ └── grunt-lib-phantomjs.js └── qunit.js ├── index.html ├── json.html ├── pure-resize-tests.html ├── qunit ├── qunit.css └── qunit.js └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist/FileAPI.html5ok.js 3 | dist/FileAPI.html5ok.min.js 4 | dist/FileAPI.ok.js 5 | dist/FileAPI.ok.min.js 6 | .idea 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 4.5 4 | before_script: 5 | - npm install -g grunt-cli 6 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | // Project configuration. 5 | grunt.initConfig({ 6 | pkg: grunt.file.readJSON('package.json'), 7 | 8 | jshint: { 9 | all: [ 10 | 'Gruntfile.js' 11 | , 'lib/**/*.js' 12 | , 'plugins/jquery.fileapi.js' 13 | , 'node/**/*.js' 14 | ], 15 | 16 | options: { 17 | curly: true // + "Expected '{' and instead saw 'XXXX'." 18 | , immed: true 19 | , latedef: true 20 | , newcap: true // "Tolerate uncapitalized constructors" 21 | , noarg: true 22 | , sub: true 23 | , undef: true 24 | , unused: true 25 | , boss: true 26 | , eqnull: true 27 | 28 | , node: true 29 | , expr: true // - "Expected an assignment or function call and instead saw an expression." 30 | , supernew: true // - "Missing '()' invoking a constructor." 31 | , laxcomma: true 32 | , laxbreak: true 33 | , smarttabs: true 34 | } 35 | }, 36 | 37 | version: { 38 | src: 'lib/FileAPI.core.js' 39 | }, 40 | 41 | connect: { 42 | server: { 43 | options: { 44 | port: 9001, 45 | base: '.' 46 | } 47 | }, 48 | standalone: { 49 | options: { 50 | hostname: '*', 51 | keepalive: true, 52 | port: 9001, 53 | base: '.' 54 | } 55 | } 56 | }, 57 | 58 | curl: { 59 | jpg: { 60 | src: 'https://dl.dropboxusercontent.com/u/49592745/BigJPG.jpg', 61 | dest: 'tests/files/big.jpg' 62 | } 63 | }, 64 | 65 | qunit: { 66 | all: { 67 | options: { 68 | timeout: 5 * 60 * 1000, // 5min 69 | files: { 70 | '1px_gif': ['tests/files/1px.gif'] 71 | , 'big.jpg': ['tests/files/big.jpg'] 72 | , 'hello.txt': ['tests/files/hello.txt'] 73 | , 'image.jpg': ['tests/files/image.jpg'] 74 | , 'dino.png': ['tests/files/dino.png'] 75 | , 'multiple': ['tests/files/1px.gif', 'tests/files/hello.txt', 'tests/files/image.jpg', 'tests/files/dino.png', 'tests/files/lebowski.json'] 76 | }, 77 | urls: ['http://127.0.0.1:<%=connect.server.options.port%>/tests/index.html'] 78 | } 79 | } 80 | }, 81 | 82 | concat: { 83 | options: { 84 | banner: '/*! <%= pkg.exportName %> <%= pkg.version %> - <%= pkg.license %> | <%= pkg.repository.url %>\n' + 85 | ' * <%= pkg.description %>\n' + 86 | ' */\n\n', 87 | 88 | footer: 'if( typeof define === "function" && define.amd ){ define("<%= pkg.jam.name %>", [], function (){ return FileAPI; }); }' 89 | }, 90 | 91 | all: { 92 | src: [ 93 | 'lib/canvas-to-blob.js' 94 | , 'lib/FileAPI.core.js' 95 | , 'lib/FileAPI.Image.js' 96 | , 'lib/load-image-ios.js' 97 | , 'lib/FileAPI.Form.js' 98 | , 'lib/FileAPI.XHR.js' 99 | , 'lib/FileAPI.Camera.js' 100 | , 'lib/FileAPI.Flash.js' 101 | , 'lib/FileAPI.Flash.Camera.js' 102 | ], 103 | dest: 'dist/<%= pkg.exportName %>.js' 104 | }, 105 | 106 | html5: { 107 | src: [ 108 | 'lib/canvas-to-blob.js' 109 | , 'lib/FileAPI.core.js' 110 | , 'lib/FileAPI.Image.js' 111 | , 'lib/load-image-ios.js' 112 | , 'lib/FileAPI.Form.js' 113 | , 'lib/FileAPI.XHR.js' 114 | , 'lib/FileAPI.Camera.js' 115 | , 'lib/FileAPI.Flash.Camera.js' 116 | ], 117 | dest: 'dist/<%= pkg.exportName %>.html5.js' 118 | } 119 | }, 120 | 121 | uglify: { 122 | options: { banner: '/*! <%= pkg.exportName %> <%= pkg.version %> - <%= pkg.license %> | <%= pkg.repository.url %> */\n' }, 123 | dist: { 124 | files: { 125 | 'dist/<%= pkg.exportName %>.min.js': ['<%= concat.all.dest %>'] 126 | , 'dist/<%= pkg.exportName %>.html5.min.js': ['<%= concat.html5.dest %>'] 127 | } 128 | } 129 | }, 130 | 131 | mxmlc: { 132 | core: { 133 | options: { 134 | rawConfig: '-target-player=10.1 -static-link-runtime-shared-libraries=true -compiler.debug=false' + 135 | ' -library-path+=flash/core/lib/blooddy_crypto.swc -library-path+=flash/core/lib/EnginesLibrary.swc' 136 | }, 137 | files: { 138 | 'dist/<%= pkg.exportName %>.flash.swf': ['flash/core/src/FileAPI_flash.as'] 139 | } 140 | }, 141 | image: { 142 | options: { 143 | rawConfig: '-static-link-runtime-shared-libraries=true -compiler.debug=false' + 144 | ' -library-path+=flash/image/lib/blooddy_crypto.swc' 145 | }, 146 | files: { 147 | 'dist/<%= pkg.exportName %>.flash.image.swf': ['flash/image/src/FileAPI_flash_image.as'] 148 | } 149 | }, 150 | camera: { 151 | options: { 152 | rawConfig: '-static-link-runtime-shared-libraries=true -compiler.debug=false' 153 | }, 154 | files: { 155 | 'dist/<%= pkg.exportName %>.flash.camera.swf': ['flash/camera/src/FileAPI_flash_camera.as'] 156 | } 157 | } 158 | }, 159 | 160 | watch: { 161 | scripts: { 162 | files: 'lib/**/*.js', 163 | tasks: ['concat'], 164 | options: { interrupt: true } 165 | } 166 | } 167 | }); 168 | 169 | // These plugins provide necessary tasks. 170 | grunt.loadNpmTasks('grunt-version'); 171 | grunt.loadNpmTasks('grunt-contrib-jshint'); 172 | grunt.loadNpmTasks('grunt-contrib-concat'); 173 | grunt.loadNpmTasks('grunt-contrib-uglify'); 174 | grunt.loadNpmTasks('grunt-contrib-watch'); 175 | grunt.loadNpmTasks('grunt-contrib-connect'); 176 | grunt.loadNpmTasks('grunt-contrib-compress'); 177 | grunt.loadNpmTasks('grunt-mxmlc'); 178 | grunt.loadNpmTasks('grunt-curl'); 179 | 180 | // Load custom QUnit task, based on grunt-contrib-qunit, but support "files" option. 181 | grunt.loadTasks('./tests/grunt-task/'); 182 | grunt.loadTasks('./custom-tasks/'); 183 | 184 | // "npm build" runs these tasks 185 | grunt.registerTask('prepare-test-files', function (){ 186 | // big.jpg added to git 187 | /*if (!grunt.file.exists('tests/files/big.jpg')) { 188 | grunt.task.run('curl'); 189 | }*/ 190 | }); 191 | 192 | grunt.registerTask('express', 'Start a custom web server.', function() { 193 | var done = this.async(); 194 | 195 | require('./node/server.js').createServer(8000, function () { 196 | done(); 197 | }); 198 | }); 199 | 200 | grunt.registerTask('server', ['connect:server', 'express']); 201 | grunt.registerTask('dev', ['concat', 'server', 'watch']); 202 | grunt.registerTask('tests', ['jshint', 'concat', 'server', 'prepare-test-files', 'qunit']); 203 | grunt.registerTask('build', ['version', 'concat', 'uglify']); 204 | grunt.registerTask('build-all', ['build', 'mxmlc']); 205 | grunt.registerTask('default', ['tests', 'build']); 206 | }; 207 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012 FileAPI AUTHORS: 2 | Konstantin Lebedev, Demidov Vladimir. 3 | 4 | /* 5 | * Redistribution and use in source and binary forms, with or 6 | * without modification, are permitted provided that the following 7 | * conditions are met: 8 | * 9 | * 1. Redistributions of source code must retain the above 10 | * copyright notice, this list of conditions and the 11 | * following disclaimer. 12 | * 13 | * 2. Redistributions in binary form must reproduce the above 14 | * copyright notice, this list of conditions and the following 15 | * disclaimer in the documentation and/or other materials 16 | * provided with the distribution. 17 | * 18 | * THIS SOFTWARE IS PROVIDED BY MAILRU.RU GROUP ``AS IS'' AND 19 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 20 | * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 22 | * MAILRU.RU GROUP OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 23 | * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 26 | * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 27 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 29 | * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 30 | * SUCH DAMAGE. 31 | */ 32 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fileapi", 3 | "main": [ 4 | "./dist/FileAPI.flash.camera.swf", 5 | "./dist/FileAPI.flash.image.swf", 6 | "./dist/FileAPI.flash.swf", 7 | "./dist/FileAPI.html5.js", 8 | "./dist/FileAPI.js", 9 | "./dist/jquery.fileapi.min.js" 10 | ], 11 | "dependencies": { 12 | "jquery":">=1.8.2" 13 | }, 14 | "ignore": [ 15 | "custom-tasks/", 16 | "flash/", 17 | "lib/", 18 | "plugins/", 19 | "server/", 20 | "statics/", 21 | "tests/", 22 | "package.json", 23 | "bower.json", 24 | "README.md", 25 | ".*" 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /custom-tasks/Gruntfile-ok.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (grunt) { 4 | // Project configuration. 5 | grunt.config.set('concat.ok', { 6 | src: [ 7 | 'lib/FileAPI.header.js' 8 | , 'lib/canvas-to-blob.js' 9 | , 'lib/FileAPI.core.js' 10 | , 'lib/FileAPI.Image.js' 11 | , 'lib/load-image-ios.js' 12 | , 'lib/FileAPI.Form.js' 13 | , 'lib/FileAPI.XHR.js' 14 | , 'lib/FileAPI.Flash.js' 15 | , 'plugins/FileAPI.exif.js' 16 | ], 17 | dest: 'dist/<%= pkg.exportName %>.ok.js' 18 | }); 19 | grunt.config.set('concat.html5ok', { 20 | src: [ 21 | 'lib/FileAPI.header.js' 22 | , 'lib/canvas-to-blob.js' 23 | , 'lib/FileAPI.core.js' 24 | , 'lib/FileAPI.Image.js' 25 | , 'lib/load-image-ios.js' 26 | , 'lib/FileAPI.Form.js' 27 | , 'lib/FileAPI.XHR.js' 28 | , 'plugins/FileAPI.exif.js' 29 | ], 30 | dest: 'dist/<%= pkg.exportName %>.html5ok.js' 31 | }); 32 | 33 | grunt.config.set('uglify.distok', { 34 | files: { 35 | 'dist/<%= pkg.exportName %>.ok.min.js': ['<%= concat.ok.dest %>'], 'dist/<%= pkg.exportName %>.html5ok.min.js': ['<%= concat.html5ok.dest %>'] 36 | } 37 | }); 38 | 39 | grunt.config.set('compress.main', { 40 | options: { 41 | archive: '<%= pkg.name %>-<%= pkg.version.replace(/\\./g,"-") %>.zip' 42 | }, 43 | files: [ 44 | {cwd: 'dist/', expand: true, src: ['*'], dest: '<%= pkg.version.replace(/\\./g,"-") %>/'} 45 | ] 46 | }); 47 | 48 | 49 | grunt.registerTask('build-zip', ['build', 'compress']); 50 | 51 | }; 52 | -------------------------------------------------------------------------------- /dist/FileAPI.flash.camera.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/dist/FileAPI.flash.camera.swf -------------------------------------------------------------------------------- /dist/FileAPI.flash.image.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/dist/FileAPI.flash.image.swf -------------------------------------------------------------------------------- /dist/FileAPI.flash.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/dist/FileAPI.flash.swf -------------------------------------------------------------------------------- /examples/thumbnails.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | FileAPI :: Thumbnails :: example 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 75 | 76 | 77 | 78 |
79 |
80 | ← index | 81 | demo - 82 | userpic - 83 | thumbnails - 84 | watermark - 85 | webcam - 86 | caman.js 87 |
88 |
89 | 90 |
91 |

Thumbnails

92 | 93 |
94 |
95 | 96 | 97 |
98 | 99 | 100 |
101 | 102 |
 


103 |
104 |
105 | 106 | 172 | 173 | 174 | -------------------------------------------------------------------------------- /examples/toolkit.css: -------------------------------------------------------------------------------- 1 | .body { 2 | font-size: 14px; 3 | } 4 | 5 | .btn, .body { 6 | font-family: "Raleway","Helvetica Neue",Helvetica,Arial,sans-serif; 7 | } 8 | 9 | 10 | .btn { 11 | padding: 10px 50px; 12 | color: #fff; 13 | cursor: pointer; 14 | display: inline-block; 15 | text-decoration: none; 16 | font-size: 24px; 17 | border-radius: 4px; 18 | background-color: #80BD95; 19 | box-shadow: 0 3px 0 0 #72A884; 20 | text-shadow: 0 -2px 0 rgba(0,0,0,.2); 21 | } 22 | 23 | 24 | .loader { 25 | width: 30px; 26 | height: 30px; 27 | margin: 50px 0 50px -15px; 28 | border: 8px solid #000; 29 | border-right-color: transparent; 30 | display: inline-block; 31 | border-radius: 50%; 32 | box-shadow: 0 0 25px 2px #eee; 33 | border-right: 0 none; 34 | -webkit-animation: spin 1s linear infinite; 35 | -moz-animation: spin 1s linear infinite; 36 | -ms-animation: spin 1s linear infinite; 37 | -o-animation: spin 1s linear infinite; 38 | animation: spin 1s linear infinite; 39 | } 40 | 41 | .js-fileapi-wrapper { 42 | display: inline-block; 43 | *zoom: 1; 44 | *display: inline; 45 | } 46 | .js-fileapi-wrapper input { 47 | width: 0; 48 | height: 0; 49 | opacity: 0; 50 | overflow: 0; 51 | position: absolute; 52 | } 53 | 54 | @-webkit-keyframes spin { 55 | from { -webkit-transform: rotate(0deg); opacity: 0.4; } 56 | 50% { -webkit-transform: rotate(180deg); opacity: 1; } 57 | to { -webkit-transform: rotate(360deg); opacity: 0.4; } 58 | } 59 | 60 | @-moz-keyframes spin { 61 | from { -moz-transform: rotate(0deg); opacity: 0.4; } 62 | 50% { -moz-transform: rotate(180deg); opacity: 1; } 63 | to { -moz-transform: rotate(360deg); opacity: 0.4; } 64 | } 65 | 66 | @-ms-keyframes spin { 67 | from { -ms-transform: rotate(0deg); opacity: 0.4; } 68 | 50% { -ms-transform: rotate(180deg); opacity: 1; } 69 | to { -ms-transform: rotate(360deg); opacity: 0.4; } 70 | } 71 | 72 | @keyframes spin { 73 | from { transform: rotate(0deg); opacity: 0.2; } 74 | 50% { transform: rotate(180deg); opacity: 1; } 75 | to { transform: rotate(360deg); opacity: 0.2; } 76 | } 77 | -------------------------------------------------------------------------------- /examples/webcam.html: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | FileAPI :: WebCam :: example 22 | 23 | 31 | 32 | 33 | 34 | 35 | 45 | 46 | 47 | 48 |
49 |
50 | ← index | 51 | demo - 52 | userpic - 53 | thumbnails - 54 | watermark - 55 | webcam - 56 | caman.js 57 |
58 |
59 | 60 |
61 |

WebCam

62 |
63 | 64 | 75 |
76 | 77 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /flash/.gitignore: -------------------------------------------------------------------------------- 1 | #.gitignore 2 | 3 | core/bin-debug 4 | core/bin-release 5 | 6 | core/.actionScriptProperties 7 | core/.project 8 | 9 | camera/bin-debug 10 | camera/bin-release 11 | 12 | camera/.actionScriptProperties 13 | camera/.project 14 | 15 | image/bin-debug 16 | image/bin-release 17 | 18 | image/.actionScriptProperties 19 | image/.project 20 | -------------------------------------------------------------------------------- /flash/camera/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | #Thu May 02 20:14:08 MSD 2013 2 | eclipse.preferences.version=1 3 | encoding/=utf-8 4 | -------------------------------------------------------------------------------- /flash/camera/html-template/history/history.css: -------------------------------------------------------------------------------- 1 | /* This CSS stylesheet defines styles used by required elements in a flex application page that supports browser history */ 2 | 3 | #ie_historyFrame { width: 0px; height: 0px; display:none } 4 | #firefox_anchorDiv { width: 0px; height: 0px; display:none } 5 | #safari_formDiv { width: 0px; height: 0px; display:none } 6 | #safari_rememberDiv { width: 0px; height: 0px; display:none } 7 | -------------------------------------------------------------------------------- /flash/camera/html-template/history/historyFrame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | Hidden frame for Browser History support. 28 | 29 | 30 | -------------------------------------------------------------------------------- /flash/camera/html-template/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | ${title} 15 | 16 | 17 | 23 | 30 | 31 | 32 | 36 | 37 | 38 | 61 | 62 | 63 | 67 |
68 |

69 | To view this page ensure that Adobe Flash Player version 70 | ${version_major}.${version_minor}.${version_revision} or greater is installed. 71 |

72 | 77 |
78 | 79 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /flash/camera/html-template/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/flash/camera/html-template/playerProductInstall.swf -------------------------------------------------------------------------------- /flash/camera/src/FileAPI_flash_camera.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.BitmapData; 4 | import flash.display.Sprite; 5 | import flash.display.StageAlign; 6 | import flash.display.StageScaleMode; 7 | import flash.events.Event; 8 | import flash.events.StatusEvent; 9 | import flash.media.Camera; 10 | import flash.media.Video; 11 | import flash.utils.setTimeout; 12 | 13 | public class FileAPI_flash_camera extends Sprite 14 | { 15 | private var video:Video; 16 | private var camera:Camera; 17 | 18 | public function FileAPI_flash_camera() 19 | { 20 | if (stage) 21 | init(); 22 | else 23 | addEventListener(Event.ADDED_TO_STAGE, init); 24 | 25 | } 26 | 27 | public function init(e:Event = null):void 28 | { 29 | if(e) 30 | removeEventListener(Event.ADDED_TO_STAGE, init); 31 | 32 | stage.scaleMode = StageScaleMode.SHOW_ALL; 33 | stage.align = StageAlign.TOP_LEFT; 34 | 35 | // init camera 36 | camera = Camera.getCamera(); 37 | if (camera != null) { 38 | camera.addEventListener(StatusEvent.STATUS, onCameraStatus); 39 | // we need to show settings dialog, so we attach camera to a video 40 | video = new Video(); 41 | video.attachCamera(camera); 42 | if (!camera.muted) { 43 | onCameraStatus(new StatusEvent(StatusEvent.STATUS, false, false, 'Camera.Unmuted')); 44 | } 45 | else { 46 | setTimeout(function ():void { 47 | if (securityPanelIsClosed()) { 48 | onCameraStatus(new StatusEvent(StatusEvent.STATUS, false, false, 'Camera.Muted')); 49 | } 50 | }, 1000); 51 | } 52 | 53 | } else { 54 | // callback with error 55 | } 56 | } 57 | 58 | public function toggleCamera(on:Boolean):void { 59 | trace('toggleCamera',on); 60 | if (on) { 61 | if (video != null) { 62 | // turn current video off 63 | toggleCamera(false); 64 | } 65 | trace('stage width',stage.stageWidth,'stage w',stage.width,'camera width',camera.width); 66 | var w:Number = stage.stageWidth; 67 | var h:Number = stage.stageHeight; //stage.stageWidth * camera.height / camera.width; 68 | camera.setMode(w, h, camera.fps); 69 | video = new Video(w, h); 70 | video.attachCamera(camera); 71 | 72 | video.addEventListener(Event.ADDED_TO_STAGE, function(event:Event):void { 73 | trace('video addedToStage'); 74 | // now the camera is visible 75 | dispatchEvent(new Event('Camera.On')); 76 | }); 77 | addChildAt(video,0); 78 | } else { 79 | if(video) 80 | removeChild(video); 81 | video = null; 82 | } 83 | } 84 | 85 | public function shot():BitmapData { 86 | if (video) { 87 | trace('click!', video.width, stage.stageWidth); 88 | try{ 89 | var bm:BitmapData = new BitmapData(video.width,video.height); 90 | bm.draw(video); 91 | return bm; 92 | } catch(error:Error) { 93 | trace('error',error.toString() ); 94 | return null; 95 | } 96 | } 97 | return null; 98 | } 99 | 100 | private function onCameraStatus(event:StatusEvent):void { 101 | trace('status event:',event.toString() ); 102 | video = null; // turn off video 103 | // redispatch 104 | dispatchEvent(event.clone()); 105 | } 106 | 107 | /** 108 | * This code checks one time if the security panel is closed. 109 | * When you open the security panel, you should run this test 110 | * repeatedly with a timer (every 500ms seems to work well). 111 | * If the security panel is closed, you can then clean up your timers 112 | */ 113 | private function securityPanelIsClosed():Boolean 114 | { 115 | // Why not just wait for an event from the SettingsPanel to know that it's closed? Because there isn't one. 116 | // See http://bugs.adobe.com/jira/browse/FP-41 117 | var closed:Boolean = true; 118 | var hack:BitmapData = new BitmapData(1,1); 119 | try 120 | { 121 | // Trying to capture the stage triggers a Security error when the settings dialog box is open. 122 | hack.draw(stage); 123 | } 124 | catch (error:Error) 125 | { 126 | closed = false; 127 | } 128 | hack.dispose(); 129 | hack = null; 130 | return (closed); 131 | } 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /flash/core/.settings/org.eclipse.core.resources.prefs: -------------------------------------------------------------------------------- 1 | #Thu Aug 02 18:18:41 MSD 2012 2 | eclipse.preferences.version=1 3 | encoding/=utf-8 4 | -------------------------------------------------------------------------------- /flash/core/html-template/history/history.css: -------------------------------------------------------------------------------- 1 | /* This CSS stylesheet defines styles used by required elements in a flex application page that supports browser history */ 2 | 3 | #ie_historyFrame { width: 0px; height: 0px; display:none } 4 | #firefox_anchorDiv { width: 0px; height: 0px; display:none } 5 | #safari_formDiv { width: 0px; height: 0px; display:none } 6 | #safari_rememberDiv { width: 0px; height: 0px; display:none } 7 | -------------------------------------------------------------------------------- /flash/core/html-template/history/historyFrame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | Hidden frame for Browser History support. 28 | 29 | 30 | -------------------------------------------------------------------------------- /flash/core/html-template/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | ${title} 15 | 16 | 17 | 23 | 30 | 31 | 32 | 36 | 37 | 38 | 61 | 62 | 63 | 67 |
68 |

69 | To view this page ensure that Adobe Flash Player version 70 | ${version_major}.${version_minor}.${version_revision} or greater is installed. 71 |

72 | 77 |
78 | 79 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /flash/core/html-template/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/flash/core/html-template/playerProductInstall.swf -------------------------------------------------------------------------------- /flash/core/lib/EnginesLibrary.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/flash/core/lib/EnginesLibrary.swc -------------------------------------------------------------------------------- /flash/core/lib/blooddy_crypto.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/flash/core/lib/blooddy_crypto.swc -------------------------------------------------------------------------------- /flash/core/src/FileAPI_flash.as: -------------------------------------------------------------------------------- 1 | package 2 | { 3 | import flash.display.Sprite; 4 | import flash.display.StageAlign; 5 | import flash.display.StageQuality; 6 | import flash.display.StageScaleMode; 7 | import flash.events.Event; 8 | import flash.events.UncaughtErrorEvent; 9 | 10 | import ru.mail.controller.AppController; 11 | 12 | /** 13 | * 14 | * @author v.demidov https://github.com/im-saxo 15 | * 16 | */ 17 | public class FileAPI_flash extends Sprite 18 | { 19 | private var _controller:AppController; 20 | private var _graphicContext:Sprite = new Sprite(); 21 | 22 | public function FileAPI_flash() 23 | { 24 | if (stage) { 25 | init(); 26 | } 27 | else { 28 | addEventListener(Event.ADDED_TO_STAGE, init); 29 | } 30 | } 31 | 32 | /** 33 | * entry point 34 | * @param event 35 | * 36 | */ 37 | protected function init(event:Event = null):void 38 | { 39 | removeEventListener(Event.ADDED_TO_STAGE, init); 40 | 41 | // config stage 42 | stage.align = StageAlign.TOP_LEFT; 43 | stage.scaleMode = StageScaleMode.NO_SCALE; 44 | stage.quality = StageQuality.BEST; 45 | 46 | // add graphic context 47 | addChild(_graphicContext); 48 | 49 | // initiate controller 50 | _controller = new AppController(_graphicContext, loaderInfo.parameters); 51 | // add some global listeners 52 | stage.addEventListener(Event.RESIZE, _controller.onStageResize); 53 | loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, _controller.onUncaughtError); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /flash/core/src/net/inspirit/events/MultipartURLLoaderEvent.as: -------------------------------------------------------------------------------- 1 | package net.inspirit.events 2 | { 3 | import flash.events.Event; 4 | 5 | /** 6 | * MultipartURLLoader Event for async data prepare tracking 7 | * @author Eugene Zatepyakin 8 | */ 9 | public class MultipartURLLoaderEvent extends Event 10 | { 11 | public static const DATA_PREPARE_PROGRESS:String = 'dataPrepareProgress'; 12 | public static const DATA_PREPARE_COMPLETE:String = 'dataPrepareComplete'; 13 | 14 | public var bytesWritten:uint = 0; 15 | public var bytesTotal:uint = 0; 16 | 17 | public function MultipartURLLoaderEvent(type:String, w:uint = 0, t:uint = 0) 18 | { 19 | super(type); 20 | 21 | bytesTotal = t; 22 | bytesWritten = w; 23 | } 24 | 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/AbstractUploadFileCommand.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands 2 | { 3 | import flash.events.EventDispatcher; 4 | import flash.net.URLRequestHeader; 5 | import flash.net.URLVariables; 6 | 7 | import ru.mail.data.vo.ErrorVO; 8 | import ru.mail.events.UploadCompleteEvent; 9 | 10 | /** 11 | * Base class for UploadFileCommand and UploadImageCommand 12 | * @author v.demidov 13 | * 14 | */ 15 | public class AbstractUploadFileCommand extends EventDispatcher 16 | { 17 | protected var _url:String; 18 | protected var _uploadDataFieldName:String = "Filedata"; 19 | protected var _uploadPostData:URLVariables; 20 | protected var _contentType:String; 21 | protected var _requestHeaders:Array; 22 | 23 | public function AbstractUploadFileCommand(url:String, headers:Object, uploadPostData:Object, uploadDataFieldName:String = "Filedata") 24 | { 25 | super(); 26 | 27 | _url = url; 28 | if (uploadDataFieldName && uploadDataFieldName != "") 29 | _uploadDataFieldName = uploadDataFieldName; 30 | parsePostData(uploadPostData, headers); 31 | } 32 | 33 | public function execute():void {/* do nothing*/} 34 | 35 | public function dispose():void {} 36 | 37 | /** 38 | * it cancels upload and disposes the object 39 | */ 40 | public function cancel():void {} 41 | 42 | /** 43 | * prepare post data, content-type and other request headers for upload 44 | * @param uploadPostData 45 | * @param headers 46 | * 47 | */ 48 | protected function parsePostData(uploadPostData:Object, headers:Object):void 49 | { 50 | // create URLVariables 51 | _uploadPostData = new URLVariables(); 52 | var prop:String; 53 | if (uploadPostData != null) 54 | { 55 | for (prop in uploadPostData) 56 | { 57 | _uploadPostData[prop] = uploadPostData[prop]; 58 | } 59 | } 60 | 61 | if (headers != null) 62 | { 63 | _requestHeaders = new Array(); 64 | 65 | for (prop in headers) 66 | { 67 | if (prop == "Content-Type") 68 | { 69 | // handle Content-Type apart from other headers 70 | _contentType = headers[prop]; 71 | } 72 | else { 73 | _requestHeaders.push( new URLRequestHeader(prop, headers[prop]) ); 74 | } 75 | } 76 | } 77 | } 78 | 79 | protected function complete(isSuccess:Boolean, result:String, error:ErrorVO = null):void 80 | { 81 | dispatchEvent( new UploadCompleteEvent(isSuccess, result, error) ); 82 | 83 | try{ 84 | dispose(); 85 | } catch (e:Error) {}; 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/DecodeBytesToBitmapCommand.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands 2 | { 3 | 4 | import flash.display.Bitmap; 5 | import flash.display.BitmapData; 6 | import flash.display.Loader; 7 | import flash.display.LoaderInfo; 8 | import flash.events.Event; 9 | import flash.events.EventDispatcher; 10 | import flash.events.IOErrorEvent; 11 | import flash.events.SecurityErrorEvent; 12 | import flash.events.TimerEvent; 13 | import flash.utils.ByteArray; 14 | import flash.utils.Timer; 15 | 16 | import ru.mail.data.vo.ErrorVO; 17 | import ru.mail.events.DecodeBytesToBitmapCompleteEvent; 18 | import ru.mail.utils.BMPDecoder; 19 | 20 | 21 | /** 22 | * The command decodes bytes to image with the Loader object. png, jpg, bmp and unanimated gif only. 23 | * 24 | * Animated gif - currently there is no check for it, so it will be decoded, but the image might be incorrect 25 | * 26 | * @author ivanova 27 | */ 28 | public class DecodeBytesToBitmapCommand extends EventDispatcher 29 | { 30 | private var bmpDecoder:BMPDecoder = new BMPDecoder(); 31 | private var _bytes:ByteArray ; 32 | private var _loader:Loader = new Loader() ; 33 | private var _terminateTimer:Timer = new Timer( 20 * 1000 ); 34 | private var _isTerminated:Boolean = false; 35 | 36 | /** 37 | * ctor 38 | * @param bytes: the bytes collection to decode 39 | */ 40 | public function DecodeBytesToBitmapCommand( bytes:ByteArray ) 41 | { 42 | if ( null == bytes ) 43 | throw new Error( "DecodeBytesToBitmapCommand bytes is null" ) ; 44 | 45 | _bytes = bytes ; 46 | 47 | _loader.contentLoaderInfo.addEventListener( Event.COMPLETE, _onDecodeImageComplete ) ; 48 | _loader.contentLoaderInfo.addEventListener( IOErrorEvent.IO_ERROR, _onDecodeImageError ) ; 49 | _loader.contentLoaderInfo.addEventListener( SecurityErrorEvent.SECURITY_ERROR, _onDecodeImageError ) ; 50 | 51 | _terminateTimer.addEventListener( TimerEvent.TIMER, _onTerminateTimer); 52 | } 53 | 54 | public function dispose():void 55 | { 56 | _loader.contentLoaderInfo.removeEventListener( Event.COMPLETE, _onDecodeImageComplete ) ; 57 | _loader.contentLoaderInfo.removeEventListener( SecurityErrorEvent.SECURITY_ERROR, _onDecodeImageError ) ; 58 | _loader.contentLoaderInfo.removeEventListener( IOErrorEvent.IO_ERROR, _onDecodeImageError ) ; 59 | 60 | _bytes = null ; 61 | try 62 | { 63 | if ( ( _loader.contentLoaderInfo.content as Bitmap ) != null ) 64 | ( _loader.contentLoaderInfo.content as Bitmap ).bitmapData.dispose() ; 65 | } 66 | catch ( e:Error ) { } 67 | 68 | bmpDecoder = null; 69 | _loader = null ; 70 | _terminateTimer.removeEventListener( TimerEvent.TIMER, _onTerminateTimer); 71 | _terminateTimer = null; 72 | } 73 | 74 | public function execute():void 75 | { 76 | try 77 | { 78 | _terminateTimer.start(); 79 | _loader.loadBytes( _bytes ) ; 80 | } 81 | catch ( e:Error ) 82 | { 83 | complete( false, null, new ErrorVO( e.toString() ) ); ; 84 | } 85 | } 86 | 87 | private function _onDecodeImageComplete( e:Event ):void 88 | { 89 | 90 | try 91 | { 92 | var image:Bitmap = ( e.target as LoaderInfo ).content as Bitmap ; 93 | var isSuccess:Boolean = ( image != null ) ; 94 | complete( isSuccess, image ) ; 95 | } 96 | catch ( e:Error ) { complete( false, null, new ErrorVO( e.toString() ) ); } 97 | } 98 | 99 | private function _onDecodeImageError( e:Event ):void 100 | { 101 | 102 | try { 103 | var bd:BitmapData = bmpDecoder.decode(_bytes); 104 | var image:Bitmap = new Bitmap(bd); 105 | var isSuccess:Boolean = ( image != null && ( !bmpDecoder.decodeError || bmpDecoder.decodeError == "" ) ) ; 106 | complete( isSuccess, image, bmpDecoder.decodeError? new ErrorVO(bmpDecoder.decodeError) : null ) ; 107 | } catch(e:Error) { 108 | complete( false, null, new ErrorVO( e.toString() ) ); 109 | } 110 | } 111 | 112 | private function _onTerminateTimer( e:TimerEvent ):void 113 | { 114 | complete( false, null, new ErrorVO("DecodeBytesToImageCommand timeout") ); 115 | } 116 | 117 | private function complete( isSuccess:Boolean, image:Bitmap, error:ErrorVO = null ):void 118 | { 119 | if ( !_isTerminated ) 120 | { 121 | _isTerminated = true; 122 | _terminateTimer.stop(); 123 | dispatchEvent( new DecodeBytesToBitmapCompleteEvent( isSuccess, image, error ) ) ; 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/LoadFileCommand.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands 2 | { 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | import flash.events.IOErrorEvent; 6 | import flash.events.ProgressEvent; 7 | import flash.net.FileReference; 8 | import flash.utils.ByteArray; 9 | 10 | import ru.mail.data.vo.ErrorVO; 11 | import ru.mail.data.vo.FileStatesEnum; 12 | import ru.mail.data.vo.FileVO; 13 | import ru.mail.events.ImageTransformCompleteEvent; 14 | 15 | /** 16 | * 17 | * Load bytes from FileReference 18 | * 19 | * @author v.demidov 20 | * 21 | */ 22 | public class LoadFileCommand extends EventDispatcher 23 | { 24 | private var fileRef:FileReference; 25 | private var file:FileVO; 26 | protected var isCancelled:Boolean = false; 27 | 28 | public function LoadFileCommand(file:FileVO) 29 | { 30 | super(); 31 | 32 | if ( null == file ) 33 | throw new Error( "LoadFileCommand fileVO is null" ) ; 34 | 35 | this.file = file; 36 | fileRef = file.fileRef; // shortcut 37 | 38 | fileRef.addEventListener(IOErrorEvent.IO_ERROR, onLoadError); 39 | fileRef.addEventListener(ProgressEvent.PROGRESS, onProgress); 40 | fileRef.addEventListener(Event.COMPLETE, onLoadComplete); 41 | } 42 | 43 | public function execute():void 44 | { 45 | try 46 | { 47 | // change file status to prevent simultanious uploading and loading 48 | file.status = FileStatesEnum.LOADING; 49 | // load 50 | fileRef.load() ; 51 | } 52 | catch ( e:Error ){ 53 | complete( false, null, new ErrorVO(e.toString()) ); 54 | } 55 | } 56 | 57 | /** 58 | * it cancels load and disposes the object 59 | */ 60 | public function cancel():void 61 | { 62 | try{ 63 | isCancelled = true; 64 | 65 | fileRef.cancel(); 66 | 67 | dispose(); 68 | }catch(e:Error){ 69 | trace ("LoadFileCommand cancel() error: "+e.toString()); 70 | } 71 | 72 | file.status = FileStatesEnum.SELECTED; 73 | } 74 | 75 | public function dispose():void 76 | { 77 | fileRef.removeEventListener( Event.COMPLETE, onLoadComplete); 78 | fileRef.removeEventListener( IOErrorEvent.IO_ERROR, onLoadError ); 79 | fileRef.removeEventListener(ProgressEvent.PROGRESS, onProgress); 80 | } 81 | 82 | private function onLoadError(event:IOErrorEvent):void 83 | { 84 | complete( false, null, new ErrorVO(event.toString(), IOErrorEvent.IO_ERROR) ); 85 | } 86 | 87 | private function onProgress(event:ProgressEvent):void 88 | { 89 | dispatchEvent(event.clone()); 90 | } 91 | 92 | /** 93 | * step 1 complete, 94 | * step 2: create bitmap 95 | * @param event 96 | * 97 | */ 98 | private function onLoadComplete(event:Event):void 99 | { 100 | if (isCancelled) { 101 | return; 102 | } 103 | else if (file.fileData != null) 104 | { 105 | complete( true, file.fileData ); 106 | } 107 | else { 108 | complete( false, null, new ErrorVO("Error #1009: Loaded data is null.") ); 109 | } 110 | } 111 | 112 | private function complete(isSuccess:Boolean, data:ByteArray, error:ErrorVO = null):void 113 | { 114 | dispatchEvent( new ImageTransformCompleteEvent(isSuccess, data, error) ); 115 | } 116 | } 117 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/UploadFileCommand.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands 2 | { 3 | import flash.events.DataEvent; 4 | import flash.events.Event; 5 | import flash.events.HTTPStatusEvent; 6 | import flash.events.IOErrorEvent; 7 | import flash.events.ProgressEvent; 8 | import flash.events.SecurityErrorEvent; 9 | import flash.events.TextEvent; 10 | import flash.net.FileReference; 11 | import flash.net.URLRequest; 12 | import flash.net.URLRequestMethod; 13 | 14 | import ru.mail.data.vo.ErrorVO; 15 | import ru.mail.utils.LoggerJS; 16 | 17 | /** 18 | * Upload using fileReference.upload() 19 | * @author v.demidov 20 | * 21 | */ 22 | public class UploadFileCommand extends AbstractUploadFileCommand 23 | { 24 | private var fileRef:FileReference; 25 | private var status:String = null; // httpStatus. in case of upload error we get httpStatus event followed by ioError event. add status to error event using this temp variable 26 | 27 | public function UploadFileCommand(fileRef:FileReference, url:String, headers:Object, uploadPostData:Object, uploadDataFieldName:String) 28 | { 29 | super(url, headers, uploadPostData, uploadDataFieldName); 30 | 31 | this.fileRef = fileRef; 32 | } 33 | 34 | override public function dispose():void 35 | { 36 | fileRef.removeEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadCompleteData); 37 | fileRef.removeEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus); 38 | fileRef.removeEventListener(IOErrorEvent.IO_ERROR, onError); 39 | fileRef.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); 40 | fileRef.removeEventListener(ProgressEvent.PROGRESS, onProgress); 41 | } 42 | 43 | override public function execute():void 44 | { 45 | if ( _url == null ) { 46 | complete(false, null, new ErrorVO("UploadFileCommand: upload url is null") ); 47 | return; 48 | } 49 | 50 | // add listeners 51 | fileRef.addEventListener(DataEvent.UPLOAD_COMPLETE_DATA, onUploadCompleteData); 52 | fileRef.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus); 53 | fileRef.addEventListener(IOErrorEvent.IO_ERROR, onError); 54 | fileRef.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); 55 | fileRef.addEventListener(ProgressEvent.PROGRESS, onProgress); 56 | 57 | // create request 58 | var request:URLRequest = new URLRequest(_url); 59 | 60 | // data 61 | request.method = URLRequestMethod.POST; 62 | request.data = _uploadPostData; 63 | 64 | LoggerJS.log("upload file with FileReference, url = " + request.url); 65 | try 66 | { 67 | fileRef.upload(request, _uploadDataFieldName) ; 68 | } 69 | catch ( e:Error ){ 70 | trace ("UploadFileCommand execute err", e); 71 | complete( false, null, new ErrorVO( e.toString() ) ); 72 | } 73 | } 74 | 75 | /** 76 | * Cancels upload and disposes the object 77 | * 78 | * Known issue: do not call cancel when the progress is 100% but uploadCompleteData hasn't received 79 | * in this case the file fill not be deleted from server 80 | */ 81 | override public function cancel():void 82 | { 83 | try{ 84 | trace ("UploadFileCommand:cancel"); 85 | fileRef.cancel(); 86 | 87 | dispose(); 88 | }catch(e:Error){ 89 | trace ("UploadFileCommand cancel() error: "+e.message); 90 | } 91 | } 92 | 93 | /** 94 | * Get the responce from server 95 | */ 96 | private function onUploadCompleteData(event:DataEvent):void 97 | { 98 | trace ("onUploadCompleteData", event); 99 | complete(true, event.data); 100 | } 101 | 102 | private function onHTTPStatus(event:HTTPStatusEvent):void 103 | { 104 | trace ("onHTTPStatus", event); 105 | LoggerJS.log("fileReference.upload HTTPStatusEvent: " +event.status); 106 | status = event.status.toString(); 107 | dispatchEvent(new TextEvent("httpStatus", false, false, event.status.toString() ) ); 108 | } 109 | 110 | private function onError(event:Event):void 111 | { 112 | // choose error type 113 | var errorType:String = null; 114 | if (event is IOErrorEvent) { 115 | errorType = "IOError"; 116 | } else if (event is SecurityErrorEvent) { 117 | errorType = "SecurityError"; 118 | } 119 | 120 | LoggerJS.log("fileReference.upload onError: " +event.toString()); 121 | 122 | trace ("onError", event); 123 | complete(false, null, new ErrorVO(event.toString(), errorType, status) ); 124 | } 125 | 126 | private function onProgress(event:ProgressEvent):void 127 | { 128 | // because of bug (loaded:123123, total:0), use fileRef.size as total 129 | dispatchEvent(new ProgressEvent( ProgressEvent.PROGRESS, false, false, event.bytesLoaded, fileRef.size) ); 130 | } 131 | 132 | } 133 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/UploadImageCommand.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands 2 | { 3 | import flash.events.Event; 4 | import flash.events.HTTPStatusEvent; 5 | import flash.events.IOErrorEvent; 6 | import flash.events.ProgressEvent; 7 | import flash.events.SecurityErrorEvent; 8 | import flash.events.TextEvent; 9 | 10 | import net.inspirit.MultipartURLLoader; 11 | import net.inspirit.events.MultipartURLLoaderEvent; 12 | 13 | import ru.mail.data.vo.ErrorVO; 14 | import ru.mail.utils.LoggerJS; 15 | 16 | /** 17 | * Upload using MultipartURLLoader 18 | * 19 | * @author v.demidov 20 | * 21 | */ 22 | public class UploadImageCommand extends AbstractUploadFileCommand 23 | { 24 | private var _files:Object; 25 | private var _totalSize:int = 0; 26 | private var _loader:MultipartURLLoader; 27 | private var status:String = null; // httpStatus. in case of upload error we get httpStatus event followed by ioError event. add status to error event using this temp variable 28 | 29 | public function UploadImageCommand(files:Object, url:String, headers:Object, uploadPostData:Object) 30 | { 31 | super(url, headers, uploadPostData); 32 | 33 | _files = files; 34 | } 35 | 36 | override public function dispose():void 37 | { 38 | _loader.removeEventListener(Event.COMPLETE, onUploadComplete); 39 | _loader.loader.removeEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus); 40 | _loader.loader.removeEventListener(IOErrorEvent.IO_ERROR, onError); 41 | _loader.loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); 42 | _loader.loader.removeEventListener(ProgressEvent.PROGRESS, onProgress); 43 | 44 | _loader.dispose(); 45 | _loader = null; 46 | _files = null; 47 | } 48 | 49 | override public function execute():void 50 | { 51 | if ( _url == null ) { 52 | complete(false, null, new ErrorVO("UploadImageCommand: upload url is null") ); 53 | return; 54 | } 55 | 56 | _loader = new MultipartURLLoader(); 57 | // add listeners 58 | _loader.addEventListener(Event.COMPLETE, onUploadComplete); 59 | _loader.loader.addEventListener(ProgressEvent.PROGRESS, onProgress); 60 | _loader.loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, onHTTPStatus); 61 | _loader.loader.addEventListener(IOErrorEvent.IO_ERROR, onError); 62 | _loader.loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onError); 63 | 64 | // post data 65 | for (var s:String in _uploadPostData) { 66 | _loader.addVariable(s, _uploadPostData[s]); 67 | } 68 | // headers 69 | if (_requestHeaders) { 70 | _loader.requestHeaders = _requestHeaders; 71 | } 72 | 73 | // files data 74 | addFiles(); 75 | 76 | try 77 | { 78 | LoggerJS.log("Load file with multipartURLLoader. url = "+_url ); 79 | _loader.addEventListener(MultipartURLLoaderEvent.DATA_PREPARE_COMPLETE, function(event:MultipartURLLoaderEvent):void { 80 | event.currentTarget.removeEventListener(event.type, arguments.callee); 81 | _loader.startLoad(); 82 | }); 83 | _loader.load(_url, true); 84 | } 85 | catch ( e:Error ){ 86 | trace ("UploadImageCommand execute err", e); 87 | complete( false, null, new ErrorVO( e.toString() ) ); 88 | } 89 | } 90 | 91 | /** 92 | * add files 93 | */ 94 | private function addFiles():void 95 | { 96 | trace ("addFiles"); 97 | for (var s:String in _files ) 98 | { 99 | for (var filename:String in _files[s]) { 100 | trace ("s="+s+ ", filename = " + filename); 101 | if ( _files[s][filename] ) { 102 | _totalSize += _files[s][filename].length; 103 | _loader.addFile(_files[s][filename], filename, s); 104 | } 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Cancels upload and disposes the object 111 | */ 112 | override public function cancel():void 113 | { 114 | try{ 115 | trace ("UploadImageCommand:cancel"); 116 | _loader.close(); 117 | 118 | dispose(); 119 | }catch(e:Error){ 120 | trace ("UploadImageCommand cancel() error: "+e.message); 121 | } 122 | } 123 | 124 | /** 125 | * Get the responce from server 126 | */ 127 | private function onUploadComplete(event:Event):void 128 | { 129 | trace ("onUploadCompleteData", event); 130 | complete(true, _loader.loader.data); 131 | } 132 | 133 | private function onHTTPStatus(event:HTTPStatusEvent):void 134 | { 135 | trace ("onHTTPStatus", event); 136 | LoggerJS.log("urlloader.upload onHTTPStatus: " +event.status); 137 | status = event.status.toString(); 138 | dispatchEvent(new TextEvent("httpStatus", false, false, event.status.toString() ) ); 139 | } 140 | 141 | private function onError(event:Event):void 142 | { 143 | // choose error type 144 | var errorType:String = null; 145 | if (event is IOErrorEvent) { 146 | errorType = "IOError"; 147 | } else if (event is SecurityErrorEvent) { 148 | errorType = "SecurityError"; 149 | } 150 | 151 | LoggerJS.log("fileReference.upload onError: " +event.toString()); 152 | 153 | trace ("onError", event); 154 | complete(false, null, new ErrorVO(event.toString(), errorType, status) ); 155 | } 156 | 157 | private function onProgress(event:ProgressEvent):void 158 | { 159 | // because of bug (loaded:123123, total:0), use _totalSize as total 160 | dispatchEvent(new ProgressEvent( ProgressEvent.PROGRESS, false, false, event.bytesLoaded, _totalSize) ); 161 | } 162 | 163 | } 164 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/graphicloader/IGraphicLoader.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands.graphicloader 2 | { 3 | import flash.net.URLRequest; 4 | 5 | /** 6 | */ 7 | public interface IGraphicLoader 8 | { 9 | function loadGraphic( request:URLRequest ):void; 10 | 11 | function cancel():void; 12 | } 13 | 14 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/graphicloader/SimpleGraphicLoader.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands.graphicloader 2 | { 3 | import flash.display.Loader; 4 | import flash.events.Event; 5 | import flash.events.EventDispatcher; 6 | import flash.events.IEventDispatcher; 7 | import flash.events.IOErrorEvent; 8 | import flash.events.ProgressEvent; 9 | import flash.events.SecurityErrorEvent; 10 | import flash.events.TimerEvent; 11 | import flash.net.URLRequest; 12 | import flash.utils.Timer; 13 | 14 | import ru.mail.commands.graphicloader.events.GraphicLoaderCompleteEvent; 15 | import ru.mail.commands.textloader.events.LoaderProgressEvent; 16 | import ru.mail.data.vo.ErrorVO; 17 | import ru.mail.utils.LoggerJS; 18 | 19 | 20 | public class SimpleGraphicLoader extends EventDispatcher implements IGraphicLoader 21 | { 22 | private var _timeoutTimer:Timer = new Timer( _TIMEOUT, 1 ); // to keep off endless waiting 23 | private var _loader:Loader = new Loader(); 24 | 25 | private var _TIMEOUT:uint = 60 * 1000; // timeout in millisecond: minutes * secs per min * millisecs per sec 26 | private const _SUCCESS:Boolean = true; 27 | 28 | public function SimpleGraphicLoader( timeOutInSeconds:uint = 60 ) 29 | { 30 | _addURLLoaderListeners( _loader.contentLoaderInfo ) ; 31 | 32 | _TIMEOUT = timeOutInSeconds * 1000; 33 | _timeoutTimer = new Timer( _TIMEOUT, 1 ) ; 34 | _timeoutTimer.addEventListener( TimerEvent.TIMER 35 | , function( e:TimerEvent ):void{ _complete( _getContent() != null, new ErrorVO('SimpleGraphicLoader loadGraphic timeout') ); } ) ; 36 | } 37 | 38 | public function cancel():void 39 | { 40 | try 41 | { 42 | _loader.close(); 43 | }catch ( e:Error ) { } 44 | } 45 | 46 | /** 47 | * method load data by request 48 | */ 49 | public function loadGraphic( request:URLRequest ):void 50 | { 51 | //trace( "SimpleGraphicLoader.loadGraphic() ", request.url ) ; 52 | LoggerJS.log('SimpleGraphicLoader loadGraphic '+request.url); 53 | _timeoutTimer.start() ; 54 | try 55 | { 56 | import flash.system.LoaderContext ; 57 | _loader.load( request, new LoaderContext( true ) ) ; 58 | } 59 | catch( e:Error ) 60 | { 61 | LoggerJS.log('SimpleGraphicLoader Error '+e.toString()); 62 | _complete( !_SUCCESS, new ErrorVO( e.toString() ) ); 63 | } 64 | } 65 | 66 | private function _complete( isSuccess:Boolean, error:ErrorVO = null ):void 67 | { //trace( "SimpleGraphicLoader._complete() ", isSuccess ) ; 68 | LoggerJS.log('SimpleGraphicLoader _complete, isSuccess = '+isSuccess+', error = '+(error?error.error:"")); 69 | _timeoutTimer.stop() ; 70 | 71 | dispatchEvent( new GraphicLoaderCompleteEvent( isSuccess, _getContent(), error ) ) ; 72 | } 73 | 74 | private function _getContent():* 75 | { 76 | var content:* = null 77 | try 78 | { 79 | content = _loader.content; 80 | } 81 | catch ( e:Error ) { 82 | LoggerJS.log("SimpleGraphicLoader._getContent() Error "+ e.message); 83 | } 84 | 85 | return content; 86 | } 87 | 88 | 89 | private function _addURLLoaderListeners( dispatcher:IEventDispatcher ):void 90 | { 91 | dispatcher.addEventListener( Event.INIT, function( e:Event ):void{ _complete( _SUCCESS ); } ); 92 | dispatcher.addEventListener( SecurityErrorEvent.SECURITY_ERROR, function( e:Event ):void{ _complete( !_SUCCESS, new ErrorVO(e.toString()) ); } ); 93 | dispatcher.addEventListener( IOErrorEvent.IO_ERROR, function( e:Event ):void{ _complete( !_SUCCESS, new ErrorVO(e.toString()) ); } ); 94 | dispatcher.addEventListener( ProgressEvent.PROGRESS 95 | , function( e:ProgressEvent ):void { 96 | dispatchEvent( new LoaderProgressEvent( e.bytesLoaded, e.bytesTotal ) ) 97 | } ) ; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/graphicloader/events/GraphicLoaderCompleteEvent.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands.graphicloader.events 2 | { 3 | import flash.events.Event; 4 | 5 | import ru.mail.data.vo.ErrorVO; 6 | 7 | ; 8 | 9 | public class GraphicLoaderCompleteEvent extends Event 10 | { 11 | public static const TYPE:String = "GraphicLoaderCompleteEvent" ; 12 | 13 | public function GraphicLoaderCompleteEvent( isSuccess:Boolean, content:*, error:ErrorVO = null ) 14 | { 15 | super( TYPE ) ; 16 | _isSuccess = isSuccess ; 17 | _content = content ; 18 | _error = error; 19 | } 20 | 21 | public function get isSuccess():Boolean 22 | { 23 | return _isSuccess ; 24 | } 25 | 26 | public function get content():* 27 | { 28 | return _content ; 29 | } 30 | 31 | public function get error():ErrorVO 32 | { 33 | return _error; 34 | } 35 | 36 | private var _isSuccess:Boolean ; 37 | private var _content:* ; 38 | private var _error:ErrorVO; 39 | } 40 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/textloader/ITextLoader.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands.textloader 2 | { 3 | import flash.net.URLRequest; 4 | 5 | /** 6 | * Simple util loader 7 | */ 8 | public interface ITextLoader 9 | { 10 | function loadText( request:URLRequest ):void; 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/textloader/SimpleTextLoader.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands.textloader 2 | { 3 | import flash.events.Event; 4 | import flash.events.EventDispatcher; 5 | import flash.events.IEventDispatcher; 6 | import flash.events.IOErrorEvent; 7 | import flash.events.ProgressEvent; 8 | import flash.events.SecurityErrorEvent; 9 | import flash.events.TimerEvent; 10 | import flash.net.URLLoader; 11 | import flash.net.URLRequest; 12 | import flash.utils.Timer; 13 | 14 | import ru.mail.commands.textloader.events.*; 15 | import ru.mail.data.vo.ErrorVO; 16 | 17 | public class SimpleTextLoader extends EventDispatcher implements ITextLoader 18 | { 19 | private var _timeoutTimer:Timer = new Timer( _TIMEOUT, 1 ) ; // to keep off endless waiting 20 | private var _loader:URLLoader= new URLLoader() ; 21 | 22 | private var _TIMEOUT:uint = 1 * 60 * 1000 ; // timeout in millisecond: minutes * secs per min * millisecs per sec 23 | private const _SUCCESS:Boolean = true ; 24 | 25 | public function SimpleTextLoader( timeoutDelayInSecs:uint = 60 ) 26 | { 27 | _addURLLoaderListeners( _loader ) ; 28 | 29 | _timeoutTimer.delay = timeoutDelayInSecs * 1000; 30 | _timeoutTimer.addEventListener( TimerEvent.TIMER 31 | , function( e:TimerEvent ):void{ _complete( !_SUCCESS ); } ) ; 32 | } 33 | 34 | /** 35 | * method load data by request 36 | */ 37 | public function loadText( request:URLRequest ):void 38 | { 39 | _timeoutTimer.start() ; 40 | try 41 | { 42 | _loader.load( request ) ; 43 | } 44 | catch( e:Error ) 45 | { 46 | _complete( !_SUCCESS, new ErrorVO( e.toString() ) ); 47 | } 48 | } 49 | 50 | private function _complete( isSuccess:Boolean, error:ErrorVO = null ):void 51 | { 52 | _timeoutTimer.stop() ; 53 | dispatchEvent( new TextLoaderCompleteEvent( isSuccess, _loader.data, error ) ) ; 54 | } 55 | 56 | private function _addURLLoaderListeners( dispatcher:IEventDispatcher ):void 57 | { 58 | dispatcher.addEventListener( Event.COMPLETE, function( e:Event ):void{ _complete( _SUCCESS ); } ); 59 | dispatcher.addEventListener( SecurityErrorEvent.SECURITY_ERROR, function( e:Event ):void{ _complete( !_SUCCESS, new ErrorVO(e.toString()) ); } ); 60 | dispatcher.addEventListener( IOErrorEvent.IO_ERROR, function( e:Event ):void{ _complete( !_SUCCESS, new ErrorVO(e.toString()) ); } ); 61 | dispatcher.addEventListener( ProgressEvent.PROGRESS 62 | , function( e:ProgressEvent ):void 63 | { dispatchEvent( new LoaderProgressEvent( e.bytesLoaded, e.bytesTotal ) ) } ) ; 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands.textloader.events 2 | { 3 | import flash.events.Event; 4 | 5 | public class LoaderProgressEvent extends Event 6 | { 7 | public static const TYPE:String = "LoaderProgressEvent" ; 8 | 9 | public function LoaderProgressEvent( loaded:uint, total:uint ) 10 | { 11 | super( TYPE ) ; 12 | _loaded = loaded ; 13 | _total = total ; 14 | } 15 | 16 | public function get total():uint 17 | { 18 | return _total ; 19 | } 20 | 21 | public function get loaded():uint 22 | { 23 | return _loaded ; 24 | } 25 | 26 | private var _loaded:uint ; 27 | private var _total:uint ; 28 | } 29 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as: -------------------------------------------------------------------------------- 1 | package ru.mail.commands.textloader.events 2 | { 3 | import flash.events.Event; 4 | 5 | import ru.mail.data.vo.ErrorVO; 6 | 7 | public class TextLoaderCompleteEvent extends Event 8 | { 9 | public static const TYPE:String = "TextLoaderCompleteEvent" ; 10 | 11 | public function TextLoaderCompleteEvent( isSuccess:Boolean, content:String, error:ErrorVO = null ) 12 | { 13 | super( TYPE ) ; 14 | _isSuccess = isSuccess ; 15 | _content = content ; 16 | _error = error; 17 | } 18 | 19 | public function get isSuccess():Boolean 20 | { 21 | return _isSuccess ; 22 | } 23 | 24 | public function get content():String 25 | { 26 | return _content ; 27 | } 28 | 29 | public function get error():ErrorVO 30 | { 31 | return _error; 32 | } 33 | 34 | private var _isSuccess:Boolean ; 35 | private var _content:String ; 36 | private var _error:ErrorVO; 37 | } 38 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/communication/JSCallbackPresenter.as: -------------------------------------------------------------------------------- 1 | package ru.mail.communication 2 | { 3 | import flash.external.ExternalInterface; 4 | import flash.system.Security; 5 | 6 | import ru.mail.controller.AppController; 7 | import ru.mail.utils.LoggerJS; 8 | 9 | /** 10 | * Configure js callback functions, redirect all of them to app controller. 11 | * Controller will decide what to do with them 12 | * @author v.demidov 13 | * 14 | */ 15 | public class JSCallbackPresenter 16 | { 17 | private var appController:AppController; 18 | 19 | public function JSCallbackPresenter(appController:AppController) 20 | { 21 | this.appController = appController; 22 | 23 | try 24 | { 25 | Security.allowDomain("*"); 26 | 27 | ExternalInterface.addCallback("cmd", parseCmd); 28 | } 29 | catch (e:Error) { 30 | LoggerJS.log('add js cmd callback error: '+e.toString() ); 31 | trace ("{JSCallbackPresenter} - unable to set callback, error:", e.message); 32 | } 33 | } 34 | 35 | /** 36 | * cmd('commandType', { ... }) 37 | * 38 | * @param command - string to determine what to do 39 | * @param data - details - an object or a string, depending on command 40 | * @return 41 | * 42 | */ 43 | protected function parseCmd(command:String, data:Object):Object 44 | { 45 | LoggerJS.log('parseCmd, command: '+command); 46 | switch (command) 47 | { 48 | case "accept": 49 | appController.setTypeFilter(data.toString()); 50 | break; 51 | case "upload": 52 | /* cmd("upload", {url:, data:URLVariables, headers:Object 53 | ,files: {'filename[original]': { 54 | id 55 | , name 56 | , matrix:{ 57 | sx , ... 58 | ,dh 59 | ,deg 60 | , type:'image/png' 61 | , quality: 1 // качество jpeg 62 | ,overlay: [ // массив изображений, которые нужно разместить: 63 | { x: 0, y: 0, opacity: .5, src: '...' } 64 | , { x: 0, y: 0, w: 120, h: 30, opacity: 1, src: '...' } 65 | ] 66 | } } 67 | , 'filename[XL]': { id, name, matrix:null } } 68 | , callback:jsHandler}) */ 69 | // headers: { 'Content-Type': 'application/x-mru-upload' , 'Content-Disposition': '...' , ...} 70 | appController.uploadFile(data.url 71 | , data.data, data.headers 72 | , data.files 73 | , data.callback); 74 | break; 75 | case "abort": 76 | // cmd('abort', { id: '...' }) 77 | appController.cancelFile(data.id); 78 | break; 79 | case "hitTest": 80 | // cmd("hitTest") 81 | // return whether mouse is over the flash 82 | return appController.hitTest(); 83 | break; 84 | case "multiple": 85 | appController.setMultipleSelect(data.toString() == "true"); 86 | break; 87 | case "clear": 88 | // remove all files 89 | appController.clear(); 90 | break; 91 | case "clearError": 92 | // clear shared object error data 93 | appController.clearError(); 94 | break; 95 | case "getFileInfo": 96 | // cmd('getFileInfo', { id: '...', callback: '...' }); 97 | appController.getFileInfo(data.id, data.callback) 98 | break; 99 | case "imageTransform": 100 | // cmd('imageTransform', { 101 | // id: '...', 102 | // matrix: { 103 | // sx: Number, // s* — original image region 104 | // sy: Number, 105 | // sw: Number, // if 0, then w - sx 106 | // sh: Number, // if 0, then h - sy 107 | // dw: Number, // if 0, then sw 108 | // dh: Number, // if 0, then sh 109 | // deg: Number 110 | // resize: String, // min, max OR preview 111 | // }, 112 | // callback: '...' 113 | //}); 114 | appController.imageTransform(data.id, data.matrix, data.callback); 115 | break; 116 | // camera: 117 | case "camera.on": 118 | appController.cameraController.cameraOn(data.callback); 119 | break; 120 | case "camera.off": 121 | appController.cameraController.cameraOff(); 122 | break; 123 | case "shot": 124 | return appController.cameraController.shot(); 125 | break; 126 | default: 127 | LoggerJS.log("cannot parse command: "+command); 128 | break; 129 | } 130 | 131 | // by default function doesn't have to return anything 132 | // If it have to, return it inside switch case 133 | return false; 134 | } 135 | 136 | } 137 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/controller/CameraController.as: -------------------------------------------------------------------------------- 1 | package ru.mail.controller 2 | { 3 | import flash.display.BitmapData; 4 | import flash.display.Sprite; 5 | import flash.events.Event; 6 | import flash.events.StatusEvent; 7 | import flash.net.URLRequest; 8 | 9 | import ru.mail.commands.graphicloader.SimpleGraphicLoader; 10 | import ru.mail.commands.graphicloader.events.GraphicLoaderCompleteEvent; 11 | import ru.mail.communication.JSCaller; 12 | import ru.mail.data.AttachmentsModel; 13 | import ru.mail.data.vo.PhotoFileVO; 14 | import ru.mail.utils.LoggerJS; 15 | 16 | public class CameraController 17 | { 18 | private var _jsCaller:JSCaller; 19 | private var _model:AttachmentsModel; 20 | private var _view:Sprite; 21 | 22 | private var _cameraSwf:*; 23 | 24 | public function CameraController(view:Sprite) 25 | { 26 | LoggerJS.log('Camera Controller - init'); 27 | 28 | _view = view; 29 | _jsCaller = JSCaller.jsCaller; 30 | _model = AttachmentsModel.model; 31 | 32 | loadCamera(); 33 | } 34 | 35 | public function cameraOn(callback:String):void 36 | { 37 | LoggerJS.log('camera.on called'); 38 | try { 39 | _cameraSwf.addEventListener('Camera.On', function(event:Event):void { 40 | _jsCaller.callJS(callback, {error:null}, null, true); 41 | }); 42 | _cameraSwf.toggleCamera(true); 43 | } catch (e:Error) { 44 | _jsCaller.callJS(callback, {error:e.toString()}, null, true); 45 | } 46 | } 47 | 48 | public function cameraOff():void 49 | { 50 | LoggerJS.log('camera.off called'); 51 | _cameraSwf.toggleCamera(false); 52 | } 53 | 54 | public function shot():Object 55 | { 56 | LoggerJS.log('smile please!'); 57 | var bm:BitmapData = _cameraSwf.shot(); 58 | var result:Object = {}; 59 | if (bm == null) { 60 | LoggerJS.log('shot image error'); 61 | result.error = 'create shot fail'; 62 | } 63 | else { 64 | LoggerJS.log('shot image w:'+bm.width+', h:'+bm.height); 65 | var fileVO:PhotoFileVO = _model.filesBuilder.createPhotoFileVO(bm); 66 | result.id = fileVO.fileID; 67 | result.type = fileVO.fileType; 68 | result.size = fileVO.fileSize; 69 | result.width = fileVO.imageData.width; 70 | result.height = fileVO.imageData.height; 71 | } 72 | 73 | return result; 74 | } 75 | 76 | // ============= init ================ 77 | 78 | private function loadCamera():void 79 | { 80 | var loader:SimpleGraphicLoader = new SimpleGraphicLoader(10); 81 | loader.addEventListener(GraphicLoaderCompleteEvent.TYPE, function(evt:GraphicLoaderCompleteEvent):void { 82 | if (evt.isSuccess) { 83 | LoggerJS.log('load camera swf complete'); 84 | try { 85 | _cameraSwf = evt.content; 86 | _cameraSwf.addEventListener(StatusEvent.STATUS, onCameraStatus); 87 | _view.addChild(_cameraSwf); 88 | } catch (e:Error) { 89 | LoggerJS.log('attach camera fail: '+e.toString()); 90 | initComplete(false, evt.error.getError()); 91 | } 92 | 93 | } 94 | else { 95 | LoggerJS.log('load camera swf complete, _isSuccess='+evt.isSuccess+', error='+evt.error.getError()); 96 | // What a pity 97 | initComplete(false, evt.error.getError()); 98 | } 99 | }); 100 | 101 | loader.loadGraphic(new URLRequest(_model.useCamera || 'FileAPI.flash.camera.swf')); 102 | } 103 | 104 | private function onCameraStatus(event:StatusEvent):void 105 | { 106 | LoggerJS.log('onCameraStatus '+event.code); 107 | if (event.code == 'Camera.Unmuted') { 108 | // report ok 109 | initComplete(true); 110 | } else if (event.code == 'Camera.Muted') { 111 | // report user denied 112 | initComplete(false, 'user denied access to camera'); 113 | } else { 114 | // report this strange thing 115 | initComplete(false, 'unknown camera status: '+event.code); 116 | } 117 | } 118 | 119 | private function initComplete(success:Boolean, error:String = null):void 120 | { 121 | _jsCaller.notifyCameraStatus(success? null : error); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/AbstractImageFactory.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data 2 | { 3 | import ru.mail.data.vo.IFileVO; 4 | 5 | /** 6 | * Produce factories for file 7 | * 8 | * @author v.demidov 9 | * 10 | */ 11 | public class AbstractImageFactory 12 | { 13 | private var file:IFileVO; 14 | 15 | public function AbstractImageFactory(target:IFileVO) 16 | { 17 | if (!target) { 18 | throw new Error("{ImageFactory} - init: target is null"); 19 | } 20 | 21 | file = target; 22 | } 23 | 24 | public function getImageFactory():IImageFactory 25 | { 26 | return new ImageFactory(file); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/AttachmentsModel.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data 2 | { 3 | import flash.events.EventDispatcher; 4 | import flash.net.FileFilter; 5 | import flash.net.SharedObject; 6 | 7 | import ru.mail.data.builder.FilesDataBuilder; 8 | 9 | public class AttachmentsModel extends EventDispatcher 10 | { 11 | 12 | //================================================= 13 | // singleton 14 | //================================================= 15 | 16 | protected static var _instance:AttachmentsModel = null; 17 | 18 | /** 19 | * reference to Singleton 20 | * @return 21 | * 22 | */ 23 | public static function get model():AttachmentsModel 24 | { 25 | if (!_instance) 26 | { 27 | _instance = new AttachmentsModel(); 28 | } 29 | 30 | return _instance 31 | } 32 | 33 | //================================================= 34 | // consts 35 | //================================================= 36 | 37 | protected const DEFAULT_FILTER:FileFilter = new FileFilter( "All types", "*.*"); 38 | 39 | //================================================= 40 | // vars 41 | //================================================= 42 | 43 | // TODO: remove unused vars 44 | 45 | public var fileFilters:Array = [DEFAULT_FILTER]; 46 | 47 | protected var _filesBuilder:FilesDataBuilder = new FilesDataBuilder() 48 | 49 | public function get filesBuilder():FilesDataBuilder 50 | { 51 | return _filesBuilder; 52 | } 53 | 54 | public var useCamera:String = null; 55 | 56 | /** 57 | * if true, user can select multiple files 58 | */ 59 | public var useMultipleSelect:Boolean = true; 60 | 61 | public var storeKey:String = ""; 62 | protected var _hasError:Boolean = false; 63 | /** 64 | * read from sharedObject, whether application experienced error #2038 - problems with authorisation 65 | * while uploading through proxy. 66 | * @return 67 | * 68 | */ 69 | public function get hasError():Boolean 70 | { 71 | return _hasError; 72 | } 73 | 74 | public function set hasError(value:Boolean):void 75 | { 76 | _hasError = value; 77 | writeError(value); 78 | } 79 | 80 | public function clearError():void { 81 | _clearError(); 82 | _hasError = false; 83 | } 84 | 85 | private var _timeout:int = 0; 86 | /** 87 | * timeout for removing file 88 | * When calling cmd.abort(), do not delete file immediately, but wait for some time. 89 | * If file is called for upload or something, cancel timeout and do not remove file 90 | * @return 91 | * 92 | */ 93 | public function get timeout():int 94 | { 95 | return _timeout; 96 | } 97 | 98 | public function set timeout(value:int):void 99 | { 100 | _timeout = int(value) || 0; 101 | } 102 | 103 | 104 | /** 105 | * @private Constructor 106 | */ 107 | public function AttachmentsModel() 108 | { 109 | super(); 110 | 111 | // lock 112 | if (_instance) 113 | { 114 | throw new Error("AttachmentsModel is singleton class, use get model method instead"); 115 | } 116 | 117 | _hasError = readError(); 118 | } 119 | 120 | public function updateHasError():void { 121 | _hasError = readError(); 122 | } 123 | 124 | /** 125 | * whether error #2038 occured or not 126 | * @return 127 | */ 128 | private function readError():Boolean { 129 | try 130 | { 131 | var so:SharedObject = SharedObject.getLocal("flashfileapi", '/'); 132 | var error:String = so.data[storeKey+"savedError"]; 133 | 134 | trace ("attachmetsModel read sharedobject, error = " + error); 135 | 136 | return (error == "1"); 137 | } 138 | catch (e:Error) { 139 | trace ("read shared object error: "+e); 140 | } 141 | 142 | return false; 143 | } 144 | 145 | private function writeError(isError:Boolean):void { 146 | try 147 | { 148 | var so:SharedObject = SharedObject.getLocal("flashfileapi", '/'); 149 | so.data[storeKey+"savedError"] = isError? "1" : "0"; 150 | so.flush(); 151 | } 152 | catch (e:Error) { 153 | trace ("write shared object error: "+e); 154 | } 155 | } 156 | 157 | /** 158 | * clear shared object data 159 | */ 160 | private function _clearError():void { 161 | var so:SharedObject = SharedObject.getLocal("flashfileapi", '/'); 162 | so.clear(); 163 | } 164 | 165 | 166 | } 167 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/IImageFactory.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data 2 | { 3 | import ru.mail.data.vo.ImageTransformVO; 4 | 5 | /** 6 | * produces transformed images from target's source 7 | * @author v.demidov 8 | * 9 | */ 10 | public interface IImageFactory 11 | { 12 | /** 13 | * Create transformed image using imageTransform params. 14 | * if imageTransform is null, return original image. 15 | * If original image has not been loaded, first load it. 16 | * The result image is returned async via completeEvent 17 | * @param imageTransform 18 | * 19 | */ 20 | function createImage(imageTransform:ImageTransformVO):void; 21 | /** 22 | * try to read file's exif. return object with "Orientation" value 23 | * @return 24 | * 25 | */ 26 | function readExif():Object; 27 | } 28 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/builder/AbstractDataBuilder.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.builder 2 | { 3 | import flash.events.EventDispatcher; 4 | import flash.utils.clearTimeout; 5 | 6 | import ru.mail.data.vo.BaseFileVO; 7 | 8 | public class AbstractDataBuilder extends EventDispatcher 9 | { 10 | protected var _type:String = "AbstractDataBuilder" 11 | 12 | public function get type():String 13 | { 14 | return _type; 15 | } 16 | 17 | protected var _items:Vector. = new Vector.(); 18 | /** 19 | * Return just a copy of items vector 20 | * @return 21 | * 22 | */ 23 | public function get items():Vector. 24 | { 25 | return _items.slice(); 26 | } 27 | 28 | public function AbstractDataBuilder(type:String) 29 | { 30 | super(); 31 | 32 | _type = type; 33 | } 34 | 35 | /** 36 | * Check id and add file to the collection. 37 | * @param file 38 | * @return file that have been added 39 | * 40 | */ 41 | public function addFile(file:BaseFileVO):BaseFileVO 42 | { 43 | _items.push(file); 44 | validateID(file) 45 | 46 | return file; 47 | } 48 | 49 | /** 50 | * Search among all items and return file that matches given fileID 51 | * @param fileID 52 | * @return 53 | * 54 | */ 55 | public function getFileByID(fileID:String):BaseFileVO 56 | { 57 | var result:BaseFileVO = null; 58 | 59 | for (var i:uint = 0; i < _items.length; i++) 60 | { 61 | if (_items[i].fileID == fileID) { 62 | result = _items[i]; 63 | if (result.timeout) { 64 | clearTimeout(result.timeout); // cancel file remove 65 | } 66 | break; 67 | } 68 | } 69 | 70 | return result; 71 | } 72 | 73 | /** 74 | * Return file at given index. 75 | * @param index 76 | * @return 77 | * 78 | */ 79 | public function getFileAt(index:int):BaseFileVO 80 | { 81 | return _items[index]; 82 | } 83 | 84 | /** 85 | * Remove given file 86 | * @param file 87 | * @return removed file 88 | * 89 | */ 90 | public function removeFile(file:BaseFileVO):BaseFileVO 91 | { 92 | var index:int = _items.indexOf(file); 93 | 94 | return removeFileAt(index); 95 | } 96 | 97 | /** 98 | * 99 | * @param fileID - id of the file to be removed 100 | * @return removed file 101 | * 102 | */ 103 | public function removeFileByID(fileID:String):BaseFileVO 104 | { 105 | var file:BaseFileVO = getFileByID(fileID); 106 | 107 | if (file != null) 108 | { 109 | removeFile(file); 110 | } 111 | 112 | return file; 113 | } 114 | 115 | /** 116 | * If index is valid, remove file from this index 117 | * @param index 118 | * @return removed file 119 | * 120 | */ 121 | public function removeFileAt(index:int):BaseFileVO 122 | { 123 | var result:BaseFileVO = null; 124 | 125 | if (index > -1 && index < _items.length) 126 | { 127 | result = _items.splice(index, 1)[0]; 128 | result.loadCommand = null; 129 | result.uploadCommand = null; 130 | if(result.timeout) { 131 | clearTimeout(result.timeout); 132 | } 133 | } 134 | 135 | return result; 136 | } 137 | 138 | /** 139 | * 140 | * 141 | */ 142 | public function removeAllFiles():void 143 | { 144 | for (var i:uint = 0; i < _items.length; i++) { // for garbage collector 145 | if (_items[i].timeout) { 146 | clearTimeout(_items[i].timeout); 147 | } 148 | } 149 | _items.length = 0; 150 | } 151 | 152 | /** 153 | * Return files array length 154 | * @return 155 | * 156 | */ 157 | public function getFilesCount():int 158 | { 159 | return _items.length; 160 | } 161 | 162 | /** 163 | * returns random id string 164 | * @return 165 | * 166 | */ 167 | public function generateID():String 168 | { 169 | return new String().concat( new Date().valueOf(), Math.floor( Math.random() * 1000 ) ); 170 | } 171 | 172 | /** 173 | * Validate that file's id is not null and differs from other file ids. 174 | * 175 | * If id is not valid, generate new one and assign it to the file. 176 | * @param file 177 | * @return true if id had been changed 178 | * 179 | */ 180 | protected function validateID(file:BaseFileVO):Boolean 181 | { 182 | var isIdChanged:Boolean = false; 183 | 184 | // check null 185 | if (file.fileID == null || file.fileID == ""){ 186 | isIdChanged = true; 187 | file.fileID = generateID(); 188 | } 189 | 190 | // compare with other files 191 | for (var i:uint = 0; i < _items.length; i++) 192 | { 193 | if (_items[i] == file) 194 | continue; 195 | 196 | if (_items[i].fileID == file.fileID) { 197 | // generate new id 198 | isIdChanged = true; 199 | file.fileID = generateID(); 200 | } 201 | } 202 | 203 | return isIdChanged; 204 | } 205 | } 206 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/builder/FilesDataBuilder.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.builder 2 | { 3 | import flash.display.BitmapData; 4 | import flash.net.FileReference; 5 | import flash.utils.ByteArray; 6 | 7 | import ru.mail.data.AbstractImageFactory; 8 | import ru.mail.data.vo.FakeFileVO; 9 | import ru.mail.data.vo.FileVO; 10 | import ru.mail.data.vo.PhotoFileVO; 11 | 12 | /** 13 | * Create and store files 14 | * 15 | * @author v.demidov 16 | * 17 | */ 18 | public class FilesDataBuilder extends AbstractDataBuilder 19 | { 20 | public static const TYPE:String = "FilesDataBuilder"; 21 | 22 | public function FilesDataBuilder() 23 | { 24 | super(TYPE); 25 | } 26 | 27 | /** 28 | * Create fake file with empty data 29 | * @param fileID 30 | * @return 31 | * 32 | */ 33 | public function createFakeFileVO(fileID:String = ''):FakeFileVO 34 | { 35 | // create 36 | var fileVO:FakeFileVO = new FakeFileVO(); 37 | // set props 38 | fileVO.fileData = new ByteArray(); 39 | fileVO.imageData = new BitmapData(1,1); 40 | fileVO.fileID = fileID; 41 | 42 | fileVO.abstractImageFactory = new AbstractImageFactory(fileVO); 43 | 44 | return fileVO; 45 | } 46 | 47 | public function createFileVO(fileReference:FileReference, fileID:String = '', addToCollection:Boolean = true):FileVO 48 | { 49 | if (fileReference == null) { 50 | throw new Error("FilesDataBuilder - createFileVO: fileReference is null"); 51 | } 52 | // create 53 | var fileVO:FileVO = new FileVO(); 54 | // set props 55 | fileVO.fileRef = fileReference; 56 | fileVO.fileID = fileID; 57 | //fileVO.imageFactory = new ImageFactory(fileVO, true); 58 | fileVO.abstractImageFactory = new AbstractImageFactory(fileVO); 59 | // add 60 | if(addToCollection) 61 | { 62 | addFile(fileVO); 63 | } 64 | 65 | return fileVO; 66 | } 67 | 68 | /*public function createRestoredFileVO(url:String, fileID:String = '', addToCollection:Boolean = true):RestoredFileVO 69 | { 70 | if (!url || url == "") { 71 | throw new Error("FilesDataBuilder - createRestoredFileVO: empty url"); 72 | } 73 | // create 74 | var fileVO:RestoredFileVO = new RestoredFileVO(); 75 | // set props 76 | fileVO.url = url; 77 | fileVO.fileID = fileID; 78 | // add 79 | if(addToCollection) 80 | { 81 | addFile(fileVO); 82 | } 83 | 84 | return fileVO; 85 | }*/ 86 | 87 | public function createPhotoFileVO(image:BitmapData, fileID:String = '', addToCollection:Boolean = true):PhotoFileVO 88 | { 89 | if (image == null) { 90 | throw new Error("FilesDataBuilder - createPhotoFileVO: image BitmapData is null"); 91 | } 92 | 93 | // create 94 | var fileVO:PhotoFileVO = new PhotoFileVO(); 95 | // set props 96 | fileVO.imageData = image; 97 | fileVO.fileID = fileID; 98 | //fileVO.imageFactory = new ImageFactory(fileVO, true); 99 | fileVO.abstractImageFactory = new AbstractImageFactory(fileVO); 100 | // add 101 | if(addToCollection) 102 | { 103 | addFile(fileVO); 104 | } 105 | 106 | return fileVO; 107 | } 108 | 109 | } 110 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/BaseFileVO.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | import ru.mail.data.AbstractImageFactory; 4 | import ru.mail.data.IImageFactory; 5 | 6 | /** 7 | * This class contains almost all information about the file. The difference is only the source of data - 8 | * it can be fileReference or loaded from url or image from web camera. 9 | * load from url isn't implemented now. 10 | * 11 | * @author v.demidov 12 | */ 13 | public class BaseFileVO 14 | { 15 | protected var _fileID:String = ""; 16 | 17 | public function get fileID():String 18 | { 19 | return _fileID; 20 | } 21 | 22 | public function set fileID(value:String):void 23 | { 24 | _fileID = value; 25 | } 26 | 27 | // TODO: refactor 28 | private var _status:String = "selected"; 29 | 30 | public function get status():String { 31 | return _status; 32 | } 33 | public function set status(value:String):void { 34 | _status = value; 35 | } 36 | 37 | public var loadCommand:Object; 38 | public var uploadCommand:Object; 39 | 40 | public var timeout:uint = 0; // timeout that can remove file 41 | 42 | private var _abstractImageFactory:AbstractImageFactory; 43 | 44 | public function set abstractImageFactory(value:AbstractImageFactory):void 45 | { 46 | _abstractImageFactory = value; 47 | } 48 | /** 49 | * factory for producing images 50 | * 51 | * @return 52 | * 53 | */ 54 | public function get imageFactory():IImageFactory 55 | { 56 | return _abstractImageFactory.getImageFactory(); 57 | } 58 | 59 | public function BaseFileVO() 60 | { 61 | 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/ErrorVO.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | /** 4 | * 5 | * Try to get error type, message and ID from given string. 6 | * 7 | * Input string can be Error.toString() or ErrorEvent.toString() or anything else 8 | * 9 | * @author v.demidov 10 | * 11 | */ 12 | public class ErrorVO 13 | { 14 | public var error:String = ""; 15 | public var errorType:String = "error"; 16 | public var errorID:String = ""; 17 | public var errorMessage:String = ""; 18 | public var httpStatus:String = ''; 19 | 20 | public function ErrorVO(error:String, errorType:String = null, httpStatus:String = null) 21 | { 22 | super(); 23 | parseError(error); 24 | if (errorType) { 25 | this.errorType = errorType; 26 | } 27 | if (httpStatus) { 28 | this.httpStatus = httpStatus; 29 | } 30 | } 31 | 32 | public function parseError(str:String):void { 33 | if (!str) { 34 | return; 35 | } 36 | error = str; 37 | 38 | var idIndex:int = str.indexOf("#"); 39 | var msgIndex:int = str.indexOf(":", idIndex); 40 | var msgEndIndex:int = str.lastIndexOf('"'); 41 | // #1234: 42 | errorID = str.substring(idIndex+1, msgIndex); 43 | //: Error details 44 | errorMessage = str.substring(msgIndex+1, msgEndIndex == -1? str.length : msgEndIndex); 45 | } 46 | 47 | public function getError():String { 48 | return errorType + " " + errorID + ": " + errorMessage; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/FakeFileVO.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | import flash.display.BitmapData; 4 | import flash.utils.ByteArray; 5 | 6 | /** 7 | * Restored file (from url or any else bytearray). If it is image, it cannot be scaled or rotated. 8 | * @author v.demidov 9 | * 10 | */ 11 | public class FakeFileVO extends BaseFileVO implements IFileVO 12 | { 13 | private var _fileData:ByteArray; 14 | 15 | public function get fileData():ByteArray 16 | { 17 | return _fileData; 18 | } 19 | 20 | public function set fileData(value:ByteArray):void 21 | { 22 | _fileData = value; 23 | } 24 | 25 | public function get fileSize():Number { 26 | return _fileData? _fileData.length : 0; 27 | } 28 | 29 | public function get fileName():String { 30 | return fileID; 31 | } 32 | 33 | public function get fileNameModified():String 34 | { 35 | return fileID; 36 | } 37 | 38 | public function get fileType():String { 39 | return ""; 40 | } 41 | 42 | private var _imageData:BitmapData; 43 | /** 44 | * original image bitmapData; 45 | * @return 46 | * 47 | */ 48 | public function get imageData():BitmapData 49 | { 50 | return _imageData; 51 | } 52 | 53 | public function set imageData(bd:BitmapData):void 54 | { 55 | _imageData = bd; 56 | } 57 | 58 | public function FakeFileVO() 59 | { 60 | super(); 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/FileStatesEnum.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | public class FileStatesEnum 4 | { 5 | public static const SELECTED:String = "selected"; 6 | public static const LOADING:String = "loading"; 7 | public static const LOADED:String = "loaded"; 8 | public static const RESIZING:String = "resizing"; 9 | public static const UPLOADING:String = "uploading"; 10 | public static const UPLOADED:String = "uploaded"; 11 | public static const READY:String = "ready"; 12 | 13 | public function FileStatesEnum() 14 | { 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/FileVO.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | import flash.display.BitmapData; 4 | import flash.net.FileReference; 5 | import flash.utils.ByteArray; 6 | 7 | /** 8 | * Value object contains file reference, unique ID etc 9 | * 10 | * @author v.demidov 11 | * 12 | */ 13 | public class FileVO extends BaseFileVO implements IFileVO 14 | { 15 | public var fileRef:FileReference; 16 | 17 | public function get fileData():ByteArray 18 | { 19 | return fileRef.data; 20 | } 21 | 22 | public function get fileSize():Number { 23 | var fileSize:Number = 0; 24 | try 25 | { 26 | fileSize = fileRef.size; 27 | } 28 | catch ( e:Error ) { } 29 | 30 | return fileSize; 31 | } 32 | 33 | public function get fileName():String 34 | { 35 | return fileRef.name; 36 | } 37 | 38 | public function get fileType():String 39 | { 40 | var fileNameParts:Array = fileName.split( "." ); 41 | if ( fileNameParts.length < 2 ) 42 | return ""; 43 | 44 | return ( fileNameParts[ fileNameParts.length - 1 ] ).toLowerCase(); 45 | } 46 | 47 | private var _imageData:BitmapData; 48 | /** 49 | * original image bitmapData; 50 | * @return 51 | * 52 | */ 53 | public function get imageData():BitmapData 54 | { 55 | return _imageData; 56 | } 57 | 58 | public function set imageData(bd:BitmapData):void 59 | { 60 | _imageData = bd; 61 | } 62 | 63 | /** 64 | * for modified images we change filetype, because we encode bmp and gif with png encoder. 65 | * for jpg - do not modify, because we use jpg encoder for them 66 | * @return 67 | */ 68 | public function get fileNameModified():String 69 | { 70 | if (fileType == 'jpg' || fileType == 'jpeg') { 71 | return fileName; 72 | } 73 | else { 74 | var fileNameParts:Array = fileName.split( "." ); 75 | if ( fileNameParts.length < 2 ) 76 | return fileName; 77 | 78 | return fileNameParts[0] + '.png'; 79 | } 80 | } 81 | 82 | public function FileVO() 83 | { 84 | super(); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/IFileVO.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | import flash.display.BitmapData; 4 | import flash.utils.ByteArray; 5 | 6 | import ru.mail.data.IImageFactory; 7 | 8 | public interface IFileVO 9 | { 10 | function get fileID():String; 11 | function get fileData():ByteArray; 12 | function get fileSize():Number; 13 | function get fileName():String; 14 | function get fileNameModified():String; 15 | /** 16 | * it gets fileRef name, cuts the dot and returns the lowerCase file extenation. 17 | * it returns empty string if type is null 18 | */ 19 | function get fileType():String; 20 | 21 | function get status():String; 22 | function set status(value:String):void; 23 | 24 | function get imageData():BitmapData; 25 | function set imageData(bd:BitmapData):void; 26 | 27 | function get imageFactory():IImageFactory; 28 | } 29 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/ImageTransformVO.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | /** 4 | * Value object with transformation matrix 5 | * 6 | * @author v.demidov 7 | * 8 | */ 9 | public class ImageTransformVO 10 | { 11 | public static const TYPE_PNG:String = 'image/png'; 12 | public static const TYPE_JPEG:String = 'image/jpeg'; 13 | 14 | public var sx:Number = 0; 15 | public var sy:Number = 0; 16 | public var sw:Number = 0; 17 | public var sh:Number = 0; 18 | public var dw:Number = 0; 19 | public var dh:Number = 0; 20 | public var deg:Number = 0; 21 | public var type:String = 'image/png'; // encoded image type. If type value is unknown, png is used 22 | public var quality:Number = 1; // encode quality (jpeg only) 23 | public var overlay:Array = []; // array of OverlayVO instances 24 | public var multiPassResize: Boolean = true; 25 | 26 | public function ImageTransformVO(sx:Number = 0, sy:Number = 0, sw:Number = 0, sh:Number = 0, dw:Number = 0, dh:Number = 0, deg:Number = 0 27 | , type:String = null, quality:Number = 1, overlay:Array = null, multiPassResize:Boolean = true) 28 | { 29 | super(); 30 | 31 | if ( !isNaN(sx) ) 32 | this.sx = sx; 33 | if ( !isNaN(sy) ) 34 | this.sy = sy; 35 | if ( !isNaN(sw) ) 36 | this.sw = sw; 37 | if ( !isNaN(sh) ) 38 | this.sh = sh; 39 | if ( !isNaN(dw) ) 40 | this.dw = dw; 41 | if ( !isNaN(dh) ) 42 | this.dh = dh; 43 | if ( !isNaN(deg) ) 44 | this.deg = deg; 45 | if ( type ) 46 | this.type = type; 47 | if ( !isNaN(quality) ) 48 | this.quality = quality; 49 | 50 | this.multiPassResize = multiPassResize; 51 | 52 | if ( overlay ) 53 | setOverlay( overlay ); 54 | } 55 | 56 | /** 57 | * @private fill overlay array with value objects 58 | * @param overlay 59 | * 60 | */ 61 | private function setOverlay(overlay:Array):void 62 | { 63 | var item:Object; 64 | var overlayVO:OverlayVO; 65 | for (var i:uint = 0; i < overlay.length; i++) { 66 | item = overlay[i]; 67 | if ( item && item.src ) { 68 | overlayVO = new OverlayVO(item); 69 | this.overlay.push(overlayVO); 70 | } 71 | } 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/OverlayVO.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | import flash.display.BitmapData; 4 | 5 | /** 6 | * Value object with params for overlay 7 | * 8 | * @author demidov 9 | * 10 | */ 11 | public class OverlayVO 12 | { 13 | public var x:Number; 14 | public var y:Number; 15 | public var w:Number; 16 | public var h:Number; 17 | /** 18 | * 0 1 2
19 | * 3 4 5
20 | * 6 7 8 21 | */ 22 | public var rel:uint; 23 | public var opacity:Number; 24 | public var src:String; 25 | 26 | private var _imageData:BitmapData; 27 | public function get imageData():BitmapData 28 | { 29 | return _imageData; 30 | } 31 | public function set imageData(value:BitmapData):void 32 | { 33 | _imageData = value; 34 | // update default w, h 35 | if (w == 0) { 36 | w = _imageData.width; 37 | } 38 | if (h == 0) { 39 | h = _imageData.height; 40 | } 41 | } 42 | 43 | 44 | public function OverlayVO(item:Object) 45 | { 46 | this.x = item.x|0; 47 | this.y = item.y|0; 48 | this.w = item.w|0; 49 | this.h = item.h|0; 50 | this.rel = uint(item.rel)|0; 51 | this.opacity = item.opacity||1; 52 | this.src = item.src; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/data/vo/PhotoFileVO.as: -------------------------------------------------------------------------------- 1 | package ru.mail.data.vo 2 | { 3 | import flash.display.BitmapData; 4 | import flash.geom.Rectangle; 5 | import flash.utils.ByteArray; 6 | 7 | public class PhotoFileVO extends BaseFileVO implements IFileVO 8 | { 9 | private var _image:BitmapData = null; 10 | private var _filedata:ByteArray = null; 11 | 12 | public function PhotoFileVO() 13 | { 14 | super(); 15 | } 16 | 17 | public function get fileData():ByteArray 18 | { 19 | return _filedata; 20 | } 21 | 22 | public function get fileSize():Number 23 | { 24 | return _filedata? _filedata.length : 0; 25 | } 26 | 27 | public function get fileName():String 28 | { 29 | return _fileID+'.png'; 30 | } 31 | 32 | /** 33 | * use fileName, because modified filename makes sense only for fileRef files. 34 | * @return 35 | * 36 | */ 37 | public function get fileNameModified():String 38 | { 39 | return fileName; 40 | } 41 | 42 | public function get fileType():String 43 | { 44 | return 'png'; 45 | } 46 | 47 | public function get imageData():BitmapData 48 | { 49 | return _image; 50 | } 51 | 52 | public function set imageData(bd:BitmapData):void 53 | { 54 | _image = bd; 55 | if (_image) { 56 | // get bytearray now to avoid this operation every time we need it. 57 | _filedata = _image.getPixels(new Rectangle(0, 0, _image.width, _image.height)); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/engines/chain/AbstractModelJSEngine.as: -------------------------------------------------------------------------------- 1 | package ru.mail.engines.chain 2 | { 3 | import ru.mail.communication.JSCaller; 4 | import ru.mail.data.AttachmentsModel; 5 | 6 | public class AbstractModelJSEngine extends AbstractEngine implements IJsCallerHolder, IModelHolder 7 | { 8 | protected var _jsCaller:JSCaller; 9 | /** 10 | * reference to JS communicator 11 | * @return 12 | * 13 | */ 14 | public function get jsCaller():JSCaller 15 | { 16 | return _jsCaller; 17 | } 18 | public function set jsCaller(value:JSCaller):void 19 | { 20 | _jsCaller = value; 21 | } 22 | 23 | protected var _model:AttachmentsModel; 24 | /** 25 | * reference to the application model 26 | * @return 27 | * 28 | */ 29 | public function get model():AttachmentsModel 30 | { 31 | return _model; 32 | } 33 | public function set model(value:AttachmentsModel):void 34 | { 35 | _model = value; 36 | } 37 | 38 | public function AbstractModelJSEngine(engineType:String) 39 | { 40 | super(engineType); 41 | 42 | // default 43 | model = AttachmentsModel.model; 44 | jsCaller = JSCaller.jsCaller; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/engines/chain/EnginesFactory.as: -------------------------------------------------------------------------------- 1 | package ru.mail.engines.chain 2 | { 3 | import ru.mail.engines.chain.manage.SelectFilesEngine; 4 | import ru.mail.engines.chain.presentation.MouseListenerEngine; 5 | 6 | /** 7 | * concreate engines factory for attachments uploader 8 | */ 9 | public class EnginesFactory extends AbstractEnginesFactory 10 | { 11 | protected static var _instance:EnginesFactory = null; 12 | 13 | /** 14 | * should be overrided in derived classes 15 | * @return 16 | * 17 | */ 18 | public static function getEnginesFactory():EnginesFactory { 19 | if ( !_instance ) { 20 | _instance = new EnginesFactory(); 21 | } 22 | 23 | return _instance; 24 | } 25 | 26 | public function EnginesFactory() { 27 | super(); 28 | 29 | if ( _instance ) { 30 | throw new Error("EnginesFactory is singleton class, use get engineFactory method instead"); 31 | } 32 | } 33 | 34 | override protected function registerEngines():void { 35 | _registeredEngines.push(new SelectFilesEngine()); 36 | _registeredEngines.push(new MouseListenerEngine()); 37 | // _registeredEngines.push(new UploadEngine()); 38 | 39 | super.registerEngines(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/engines/chain/IJsCallerHolder.as: -------------------------------------------------------------------------------- 1 | package ru.mail.engines.chain 2 | { 3 | import ru.mail.communication.JSCaller; 4 | 5 | /** 6 | * For Engines that have jsCaller property 7 | * @author v.demidov 8 | * 9 | */ 10 | public interface IJsCallerHolder 11 | { 12 | function get jsCaller():JSCaller; 13 | function set jsCaller(value:JSCaller):void 14 | } 15 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/engines/chain/IModelHolder.as: -------------------------------------------------------------------------------- 1 | package ru.mail.engines.chain 2 | { 3 | import ru.mail.data.AttachmentsModel; 4 | 5 | /** 6 | * For Engines that have link to the Attachments model 7 | * @author v.demidov 8 | * 9 | */ 10 | public interface IModelHolder 11 | { 12 | function get model():AttachmentsModel; 13 | function set model(value:AttachmentsModel):void; 14 | } 15 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/engines/chain/manage/SelectFilesEngine.as: -------------------------------------------------------------------------------- 1 | package ru.mail.engines.chain.manage 2 | { 3 | import flash.errors.IllegalOperationError; 4 | import flash.events.Event; 5 | import flash.events.EventDispatcher; 6 | import flash.net.FileReference; 7 | import flash.net.FileReferenceList; 8 | 9 | import ru.mail.data.vo.ErrorVO; 10 | import ru.mail.data.vo.FileVO; 11 | import ru.mail.engines.chain.AbstractModelJSEngine; 12 | import ru.mail.engines.commands.AbstractEngineCommand; 13 | import ru.mail.engines.commands.SelectFilesCommand; 14 | import ru.mail.engines.events.CommandCompleteEvent; 15 | import ru.mail.engines.exceptions.WrongEngineCommandType; 16 | 17 | /** 18 | * Select files, add files to model, send filesInfo 19 | * 20 | * @author v.demidov 21 | * 22 | */ 23 | public class SelectFilesEngine extends AbstractModelJSEngine 24 | { 25 | public static const TYPE:String = "SelectFilesEngine"; 26 | 27 | /** 28 | * It can be whether FileReferenceList or FileReference. It depends on model.useMultipleSelect value 29 | */ 30 | protected var _fileRefObj:EventDispatcher = null; 31 | 32 | protected var _isActive:Boolean = false; 33 | 34 | public function SelectFilesEngine() 35 | { 36 | super(TYPE); 37 | } 38 | 39 | override public function handle(e:AbstractEngineCommand):void 40 | { 41 | var command:SelectFilesCommand = e as SelectFilesCommand; 42 | 43 | if (command == null){ 44 | throw new WrongEngineCommandType("{SelectFilesEngine} - handle: command type is: " + command.type + " engine to handle is: " + command.engineToHandleCommandType); 45 | } 46 | 47 | super.handle(command); 48 | selectFiles(command.filesFilters); 49 | } 50 | 51 | /** 52 | * browse for files on disk 53 | * @param filesFilter 54 | * 55 | */ 56 | private function selectFiles(filesFilters:Array):void 57 | { 58 | // mail-8225 do not allow multiple sessions 59 | if (_isActive) 60 | return; 61 | 62 | var useMultiple:Boolean = _model.useMultipleSelect; 63 | 64 | if (useMultiple) 65 | _fileRefObj = new FileReferenceList(); 66 | else 67 | _fileRefObj = new FileReference(); 68 | 69 | _fileRefObj.addEventListener( Event.SELECT, onFilesSelected) ; 70 | _fileRefObj.addEventListener( Event.CANCEL, onFilesSelectionCanceled) ; 71 | 72 | try { 73 | if (useMultiple) 74 | (_fileRefObj as FileReferenceList).browse( filesFilters ) ; 75 | else 76 | (_fileRefObj as FileReference).browse( filesFilters ) ; 77 | 78 | jsCaller.notifyJSFilesEvents("browse"); 79 | _isActive = true; 80 | } 81 | catch(error:Error) { 82 | trace ("selectFiles error", error); 83 | 84 | // call js 85 | jsCaller.notifyJSErrors( new ErrorVO(error.toString(), 'browseError') ); 86 | 87 | if ( error is IllegalOperationError) { 88 | complete(false, _fileRefObj); 89 | } 90 | else if ( error is ArgumentError ) { 91 | complete(false, _fileRefObj); 92 | } 93 | else if ( error is Error ) { 94 | complete(false, _fileRefObj); 95 | } 96 | } 97 | } 98 | 99 | private function onFilesSelected(event:Event):void { 100 | complete( true, event.target as EventDispatcher); 101 | } 102 | 103 | private function onFilesSelectionCanceled(event:Event):void { 104 | complete( true, event.target as EventDispatcher, true); 105 | } 106 | 107 | /** 108 | * add files to model, notify js 109 | * 110 | * @param fileList 111 | * 112 | */ 113 | private function processFiles(fileList:Array):void 114 | { 115 | var fileVO:FileVO; 116 | var filesListForJS:Vector. = new Vector.(); 117 | 118 | for each (var fileRef:FileReference in fileList) 119 | { 120 | fileVO = _model.filesBuilder.createFileVO(fileRef); 121 | 122 | filesListForJS.push(fileVO); 123 | } 124 | 125 | // notifyJS - collect all files in a vector and then send them all at once 126 | jsCaller.notifyJSFilesEvents("select", filesListForJS); 127 | } 128 | 129 | /** 130 | * 131 | * @param isSuccess 132 | * @param fileRefrenceList 133 | * @param isCanceled 134 | * 135 | */ 136 | private function complete( isSuccess:Boolean, fileRefrenceObj:EventDispatcher, isCanceled:Boolean = false ):void { 137 | //remove listeners 138 | fileRefrenceObj.removeEventListener( Event.SELECT, onFilesSelected) ; 139 | fileRefrenceObj.removeEventListener( Event.CANCEL, onFilesSelectionCanceled) ; 140 | 141 | _isActive = false; 142 | 143 | var fileList:Array; 144 | 145 | if (isCanceled) { 146 | // notify about cancel 147 | _jsCaller.notifyJSFilesEvents("cancel"); 148 | } 149 | else { 150 | if (fileRefrenceObj is FileReferenceList) { 151 | fileList = (fileRefrenceObj as FileReferenceList).fileList; 152 | } 153 | else { 154 | fileList = [fileRefrenceObj]; 155 | } 156 | 157 | processFiles(fileList); 158 | } 159 | 160 | var event:CommandCompleteEvent = new CommandCompleteEvent(isSuccess); 161 | dispatchEvent(event); 162 | } 163 | 164 | } 165 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as: -------------------------------------------------------------------------------- 1 | package ru.mail.engines.chain.presentation 2 | { 3 | import flash.display.InteractiveObject; 4 | import flash.events.MouseEvent; 5 | 6 | import ru.mail.communication.JSCaller; 7 | import ru.mail.data.AttachmentsModel; 8 | import ru.mail.engines.chain.AbstractEngine; 9 | import ru.mail.engines.commands.AbstractEngineCommand; 10 | import ru.mail.engines.commands.MouseListenerEngineCommand; 11 | import ru.mail.engines.commands.SelectFilesCommand; 12 | 13 | public class MouseListenerEngine extends AbstractEngine 14 | { 15 | public static const TYPE:String = "MouseListenerEngine"; 16 | 17 | private var rollOutFlag:Boolean = false; 18 | 19 | protected var _view:InteractiveObject; 20 | 21 | /** 22 | * we need link to the target object to subscribe to mouseEvents 23 | * @param value 24 | * 25 | */ 26 | public function get view():InteractiveObject 27 | { 28 | return _view; 29 | } 30 | 31 | public function set view(value:InteractiveObject):void 32 | { 33 | if (_view != null) 34 | { 35 | // remove event listeners from old target 36 | unsubscribeTarget(); 37 | } 38 | 39 | // set new value 40 | _view = value; 41 | 42 | try { 43 | // add listeners to new target 44 | subscribeTarget(); 45 | } catch (e:Error) { 46 | trace ("{MouseListenerEngine} - set target: cannot subscribe target", e.message); 47 | } 48 | } 49 | 50 | public function MouseListenerEngine() 51 | { 52 | super(TYPE); 53 | } 54 | 55 | override public function handle(command:AbstractEngineCommand):void 56 | { 57 | super.handle(command); 58 | 59 | if (command is MouseListenerEngineCommand) 60 | { 61 | rollOutFlag = !(command as MouseListenerEngineCommand).isDispatchRollout; 62 | } 63 | } 64 | 65 | protected function subscribeTarget():void 66 | { 67 | _view.addEventListener(MouseEvent.ROLL_OVER, onMouseEvent); 68 | _view.addEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent); 69 | _view.addEventListener(MouseEvent.MOUSE_UP, onMouseEvent); 70 | _view.addEventListener(MouseEvent.ROLL_OUT, onMouseEvent); 71 | } 72 | 73 | protected function unsubscribeTarget():void 74 | { 75 | _view.removeEventListener(MouseEvent.ROLL_OVER, onMouseEvent); 76 | _view.removeEventListener(MouseEvent.MOUSE_DOWN, onMouseEvent); 77 | _view.removeEventListener(MouseEvent.MOUSE_UP, onMouseEvent); 78 | _view.removeEventListener(MouseEvent.ROLL_OUT, onMouseEvent); 79 | } 80 | 81 | protected function onMouseEvent(event:MouseEvent):void 82 | { 83 | // баг: открывается окно выбора файлов (browse() ) и от этого бросается эвент rollout, 84 | // и от этого жс сворачивает флешку и больше ее не слушает 85 | if (event.type == MouseEvent.ROLL_OUT && rollOutFlag) 86 | { 87 | // and 88 | return; 89 | // ha-ha! 90 | } 91 | // call js 92 | JSCaller.jsCaller.notifyJSMouseEvents(event.type); 93 | 94 | if (event.type == MouseEvent.MOUSE_UP) 95 | { 96 | rollOutFlag = true; 97 | 98 | // get file filter from model, and dispatch select files event 99 | var fileFilters:Array = AttachmentsModel.model.fileFilters; 100 | dispatchEvent(new SelectFilesCommand(fileFilters)); 101 | } 102 | 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/engines/commands/MouseListenerEngineCommand.as: -------------------------------------------------------------------------------- 1 | package ru.mail.engines.commands 2 | { 3 | import flash.events.Event; 4 | 5 | import ru.mail.engines.chain.presentation.MouseListenerEngine; 6 | 7 | public class MouseListenerEngineCommand extends AbstractEngineCommand 8 | { 9 | public static const TYPE:String = "MouseListenerEngineCommand"; 10 | 11 | private var _dispatchRollout:Boolean; 12 | /** 13 | * true - dispatch js event mouseleave on rollout 14 | * false - do not dispatch 15 | * @return 16 | * 17 | */ 18 | public function get isDispatchRollout():Boolean 19 | { 20 | return _dispatchRollout; 21 | } 22 | 23 | 24 | public function MouseListenerEngineCommand(dispatchRollout:Boolean) 25 | { 26 | super(TYPE, MouseListenerEngine.TYPE); 27 | 28 | _dispatchRollout = dispatchRollout; 29 | } 30 | 31 | override public function clone():Event 32 | { 33 | return new MouseListenerEngineCommand(_dispatchRollout); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/engines/commands/SelectFilesCommand.as: -------------------------------------------------------------------------------- 1 | package ru.mail.engines.commands 2 | { 3 | import flash.events.Event; 4 | 5 | import ru.mail.engines.chain.manage.SelectFilesEngine; 6 | 7 | /** 8 | * command to activate SelectFilesCommand engine 9 | * 10 | * store filesFilter to use in fileReference.browse 11 | * 12 | * @author s.osipov 13 | * 14 | */ 15 | public class SelectFilesCommand extends AbstractEngineCommand 16 | { 17 | public static const TYPE:String = "SelectFilesCommand"; 18 | 19 | private var _filesFilters:Array = null; 20 | /** 21 | * file filter to use in fileReferenceList 22 | * @return 23 | * 24 | */ 25 | public function get filesFilters():Array 26 | { 27 | return _filesFilters; 28 | } 29 | 30 | public function SelectFilesCommand(filters:Array) 31 | { 32 | super(TYPE, SelectFilesEngine.TYPE); 33 | 34 | _filesFilters = filters; 35 | } 36 | 37 | override public function clone():Event 38 | { 39 | return new SelectFilesCommand(_filesFilters); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/events/CompleteEvent.as: -------------------------------------------------------------------------------- 1 | package ru.mail.events 2 | { 3 | import flash.events.Event; 4 | 5 | import ru.mail.data.vo.ErrorVO; 6 | 7 | /** 8 | * the event that is triggered by Command on its executing completion. 9 | * it provides access to execution result flag, that indicates whether command executed successfully 10 | * @author ivanova 11 | */ 12 | public class CompleteEvent extends Event 13 | { 14 | public static const TYPE:String = "CompleteEvent" ; 15 | 16 | private var _isSuccess:Boolean; 17 | public function get isSuccess():Boolean 18 | { 19 | return _isSuccess ; 20 | } 21 | 22 | private var _error:ErrorVO 23 | public function get error():ErrorVO 24 | { 25 | return _error; 26 | } 27 | 28 | /** 29 | * ctor 30 | * @param isSuccess: indicates whether command executed successfully. 31 | */ 32 | public function CompleteEvent( isSuccess:Boolean, error:ErrorVO = null, type:String = null ) 33 | { 34 | super( type? type : TYPE ) ; 35 | _isSuccess = isSuccess ; 36 | _error = error; 37 | } 38 | 39 | override public function clone():Event 40 | { 41 | return new CompleteEvent(_isSuccess, error); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as: -------------------------------------------------------------------------------- 1 | package ru.mail.events 2 | { 3 | import flash.display.Bitmap; 4 | import flash.events.Event; 5 | 6 | import ru.mail.data.vo.ErrorVO; 7 | 8 | public class DecodeBytesToBitmapCompleteEvent extends CompleteEvent 9 | { 10 | public static const TYPE:String = "DecodeBytesToBitmapCompletedEvent"; 11 | 12 | private var _decodedBitmap:Bitmap; 13 | /** 14 | * transformed image data 15 | * @return 16 | */ 17 | public function get decodedBitmap():Bitmap 18 | { 19 | return _decodedBitmap; 20 | } 21 | 22 | 23 | public function DecodeBytesToBitmapCompleteEvent(isSuccess:Boolean, decodedBitmap:Bitmap, error:ErrorVO = null) 24 | { 25 | super(isSuccess, error, TYPE); 26 | 27 | _decodedBitmap = decodedBitmap; 28 | } 29 | 30 | override public function clone():Event 31 | { 32 | return new DecodeBytesToBitmapCompleteEvent(isSuccess, decodedBitmap, error); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/events/ImageTransformCompleteEvent.as: -------------------------------------------------------------------------------- 1 | package ru.mail.events 2 | { 3 | import flash.events.Event; 4 | import flash.utils.ByteArray; 5 | 6 | import ru.mail.data.vo.ErrorVO; 7 | 8 | public class ImageTransformCompleteEvent extends CompleteEvent 9 | { 10 | public static const TYPE:String = "ImageTransformCompleteEvent"; 11 | 12 | private var _data:ByteArray; 13 | /** 14 | * transformed image data 15 | * @return 16 | */ 17 | public function get data():ByteArray 18 | { 19 | return _data; 20 | } 21 | 22 | public function ImageTransformCompleteEvent(isSuccess:Boolean, data:ByteArray, error:ErrorVO = null) 23 | { 24 | super(isSuccess, error, TYPE); 25 | 26 | _data = data; 27 | } 28 | 29 | override public function clone():Event 30 | { 31 | return new ImageTransformCompleteEvent(isSuccess, data, error); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/events/UploadCompleteEvent.as: -------------------------------------------------------------------------------- 1 | package ru.mail.events 2 | { 3 | import flash.events.Event; 4 | 5 | import ru.mail.data.vo.ErrorVO; 6 | 7 | public class UploadCompleteEvent extends CompleteEvent 8 | { 9 | public static const TYPE:String = "UploadCompleteEvent"; 10 | 11 | private var _result:String; 12 | /** 13 | * Upload server responce 14 | */ 15 | public function get result():String 16 | { 17 | return _result; 18 | } 19 | 20 | public function UploadCompleteEvent(isSuccess:Boolean, result:String, error:ErrorVO=null) 21 | { 22 | super(isSuccess, error, TYPE); 23 | 24 | _result = result; 25 | } 26 | 27 | override public function clone():Event 28 | { 29 | return new UploadCompleteEvent(isSuccess, result, error); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /flash/core/src/ru/mail/utils/ExifReader2.as: -------------------------------------------------------------------------------- 1 | package ru.mail.utils 2 | { 3 | 4 | import flash.utils.ByteArray; 5 | 6 | /** 7 | * Get data from exif. 8 | * Only orientation tag 9 | * If need something else, add tags. 10 | * 11 | */ 12 | public class ExifReader2 13 | { 14 | private var m_data:ByteArray = new ByteArray(); 15 | private var m_exif:Object = new Object; 16 | private var m_exifKeys:Array = new Array(); 17 | 18 | private var m_intel:Boolean=true; 19 | private var m_loc:uint=0; 20 | 21 | private var DATASIZES:Object = new Object; 22 | private var TAGS:Object = new Object; 23 | 24 | public function getKeys():Array{ 25 | return m_exifKeys; 26 | } 27 | public function hasKey(key:String):Boolean{ 28 | return m_exif[key] != undefined; 29 | } 30 | public function getValue(key:String):Object{ 31 | if(m_exif[key] == undefined) return null; 32 | return m_exif[key]; 33 | } 34 | 35 | public function ExifReader2(){ 36 | DATASIZES[1] = 1; 37 | DATASIZES[2] = 1; 38 | DATASIZES[3] = 2; 39 | DATASIZES[4] = 4; 40 | DATASIZES[5] = 8; 41 | DATASIZES[6] = 1; 42 | DATASIZES[7] = 1; 43 | DATASIZES[8] = 2; 44 | DATASIZES[9] = 4; 45 | DATASIZES[10] = 8; 46 | DATASIZES[11] = 4; 47 | DATASIZES[12] = 8; 48 | 49 | TAGS[0x0112] = 'Orientation'; 50 | 51 | //... add more if you like. 52 | //See http://park2.wakwak.com/~tsuruzoh/Computer/Digicams/exif-e.html 53 | } 54 | 55 | 56 | public function processData( data:ByteArray ):Boolean { 57 | 58 | m_data = data ; 59 | m_data.position = 0 ; 60 | 61 | var iter:int=0; 62 | 63 | //confirm JPG type 64 | if(!(m_data.readUnsignedByte()==0xff && m_data.readUnsignedByte()==0xd8)) 65 | return false ; 66 | 67 | //Locate APP1 MARKER 68 | var ff:uint=0; 69 | var marker:uint=0; 70 | for(iter=0;iter<5;++iter){ //cap iterations 71 | ff = m_data.readUnsignedByte(); 72 | marker = m_data.readUnsignedByte(); 73 | var size:uint = (m_data.readUnsignedByte()<<8) + m_data.readUnsignedByte(); 74 | if(marker == 0x00e1) break; 75 | else{ 76 | for(var x:int=0;x100) numEntries=100; //cap entries 112 | 113 | m_loc+=2; 114 | for(iter=0; iter=utf-8 4 | -------------------------------------------------------------------------------- /flash/image/html-template/history/history.css: -------------------------------------------------------------------------------- 1 | /* This CSS stylesheet defines styles used by required elements in a flex application page that supports browser history */ 2 | 3 | #ie_historyFrame { width: 0px; height: 0px; display:none } 4 | #firefox_anchorDiv { width: 0px; height: 0px; display:none } 5 | #safari_formDiv { width: 0px; height: 0px; display:none } 6 | #safari_rememberDiv { width: 0px; height: 0px; display:none } 7 | -------------------------------------------------------------------------------- /flash/image/html-template/history/historyFrame.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 27 | Hidden frame for Browser History support. 28 | 29 | 30 | -------------------------------------------------------------------------------- /flash/image/html-template/index.template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 13 | 14 | ${title} 15 | 16 | 17 | 23 | 30 | 31 | 32 | 36 | 37 | 38 | 61 | 62 | 63 | 67 |
68 |

69 | To view this page ensure that Adobe Flash Player version 70 | ${version_major}.${version_minor}.${version_revision} or greater is installed. 71 |

72 | 77 |
78 | 79 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /flash/image/html-template/playerProductInstall.swf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/flash/image/html-template/playerProductInstall.swf -------------------------------------------------------------------------------- /flash/image/lib/blooddy_crypto.swc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/flash/image/lib/blooddy_crypto.swc -------------------------------------------------------------------------------- /flash/image/src/Base64.as: -------------------------------------------------------------------------------- 1 | package{ 2 | import flash.utils.ByteArray; 3 | public class Base64 { 4 | private static const decodeChars:Array = 5 | [-1, -1, -1, -1, -1, -1, -1, -1, 6 | -1, -1, -1, -1, -1, -1, -1, -1, 7 | -1, -1, -1, -1, -1, -1, -1, -1, 8 | -1, -1, -1, -1, -1, -1, -1, -1, 9 | -1, -1, -1, -1, -1, -1, -1, -1, 10 | -1, -1, -1, 62, -1, -1, -1, 63, 11 | 52, 53, 54, 55, 56, 57, 58, 59, 12 | 60, 61, -1, -1, -1, -1, -1, -1, 13 | -1, 0, 1, 2, 3, 4, 5, 6, 14 | 7, 8, 9, 10, 11, 12, 13, 14, 15 | 15, 16, 17, 18, 19, 20, 21, 22, 16 | 23, 24, 25, -1, -1, -1, -1, -1, 17 | -1, 26, 27, 28, 29, 30, 31, 32, 18 | 33, 34, 35, 36, 37, 38, 39, 40, 19 | 41, 42, 43, 44, 45, 46, 47, 48, 20 | 49, 50, 51, -1, -1, -1, -1, -1]; 21 | public static function decode(str:String):ByteArray { 22 | var c1:int; 23 | var c2:int; 24 | var c3:int; 25 | var c4:int; 26 | var i:int; 27 | var len:int; 28 | var out:ByteArray; 29 | len = str.length; 30 | i = 0; 31 | out = new ByteArray(); 32 | while (i < len) { 33 | // c1 34 | do { 35 | c1 = decodeChars[str.charCodeAt(i++) & 0xff]; 36 | } while (i < len && c1 == -1); 37 | if (c1 == -1) { 38 | break; 39 | } 40 | // c2 41 | do { 42 | c2 = decodeChars[str.charCodeAt(i++) & 0xff]; 43 | } while (i < len && c2 == -1); 44 | if (c2 == -1) { 45 | break; 46 | } 47 | out.writeByte((c1 << 2) | ((c2 & 0x30) >> 4)); 48 | // c3 49 | do { 50 | c3 = str.charCodeAt(i++) & 0xff; 51 | if (c3 == 61) { 52 | return out; 53 | } 54 | c3 = decodeChars[c3]; 55 | } while (i < len && c3 == -1); 56 | if (c3 == -1) { 57 | break; 58 | } 59 | out.writeByte(((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2)); 60 | // c4 61 | do { 62 | c4 = str.charCodeAt(i++) & 0xff; 63 | if (c4 == 61) { 64 | return out; 65 | } 66 | c4 = decodeChars[c4]; 67 | } while (i < len && c4 == -1); 68 | if (c4 == -1) { 69 | break; 70 | } 71 | out.writeByte(((c3 & 0x03) << 6) | c4); 72 | } 73 | return out; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /lib/FileAPI.Flash.Camera.js: -------------------------------------------------------------------------------- 1 | /** 2 | * FileAPI fallback to Flash 3 | * 4 | * @flash-developer "Vladimir Demidov" 5 | */ 6 | 7 | /*global window, FileAPI */ 8 | (function (window, jQuery, api) { 9 | "use strict"; 10 | 11 | var _each = api.each, 12 | _cameraQueue = []; 13 | 14 | if (api.support.flash && (api.media && (!api.support.media || !api.html5 || api.insecureChrome))) { 15 | (function () { 16 | function _wrap(fn) { 17 | var id = fn.wid = api.uid(); 18 | api.Flash._fn[id] = fn; 19 | return 'FileAPI.Flash._fn.' + id; 20 | } 21 | 22 | 23 | function _unwrap(fn) { 24 | try { 25 | api.Flash._fn[fn.wid] = null; 26 | delete api.Flash._fn[fn.wid]; 27 | } catch (e) { 28 | } 29 | } 30 | 31 | var flash = api.Flash; 32 | api.extend(api.Flash, { 33 | 34 | patchCamera: function () { 35 | api.Camera.fallback = function (el, options, callback) { 36 | var camId = api.uid(); 37 | api.log('FlashAPI.Camera.publish: ' + camId); 38 | flash.publish(el, camId, api.extend(options, { 39 | camera: true, 40 | onEvent: _wrap(function _(evt) { 41 | if (evt.type === 'camera') { 42 | _unwrap(_); 43 | 44 | if (evt.error) { 45 | api.log('FlashAPI.Camera.publish.error: ' + evt.error); 46 | callback(evt.error); 47 | } else { 48 | api.log('FlashAPI.Camera.publish.success: ' + camId); 49 | callback(null); 50 | } 51 | } 52 | }) 53 | })); 54 | }; 55 | // Run 56 | _each(_cameraQueue, function (args) { 57 | api.Camera.fallback.apply(api.Camera, args); 58 | }); 59 | _cameraQueue = []; 60 | 61 | 62 | // FileAPI.Camera:proto 63 | api.extend(api.Camera.prototype, { 64 | _id: function () { 65 | return this.video.id; 66 | }, 67 | 68 | start: function (callback) { 69 | var _this = this; 70 | flash.cmd(this._id(), 'camera.on', { 71 | callback: _wrap(function _(evt) { 72 | _unwrap(_); 73 | 74 | if (evt.error) { 75 | api.log('FlashAPI.camera.on.error: ' + evt.error); 76 | callback(evt.error, _this); 77 | } else { 78 | api.log('FlashAPI.camera.on.success: ' + _this._id()); 79 | _this._active = true; 80 | callback(null, _this); 81 | } 82 | }) 83 | }); 84 | }, 85 | 86 | stop: function () { 87 | this._active = false; 88 | flash.cmd(this._id(), 'camera.off'); 89 | }, 90 | 91 | shot: function () { 92 | api.log('FlashAPI.Camera.shot:', this._id()); 93 | 94 | var shot = api.Flash.cmd(this._id(), 'shot', {}); 95 | shot.type = 'image/png'; 96 | shot.flashId = this._id(); 97 | shot.isShot = true; 98 | 99 | return new api.Camera.Shot(shot); 100 | } 101 | }); 102 | } 103 | }); 104 | 105 | api.Camera.fallback = function () { 106 | _cameraQueue.push(arguments); 107 | }; 108 | 109 | }()); 110 | } 111 | }(window, window.jQuery, FileAPI)); 112 | -------------------------------------------------------------------------------- /lib/FileAPI.Form.js: -------------------------------------------------------------------------------- 1 | /*global window, FileAPI */ 2 | 3 | (function (api, window){ 4 | "use strict"; 5 | 6 | var 7 | document = window.document 8 | , FormData = window.FormData 9 | , Form = function (){ this.items = []; } 10 | , encodeURIComponent = window.encodeURIComponent 11 | ; 12 | 13 | 14 | Form.prototype = { 15 | 16 | append: function (name, blob, file, type){ 17 | this.items.push({ 18 | name: name 19 | , blob: blob && blob.blob || (blob == void 0 ? '' : blob) 20 | , file: blob && (file || blob.name) 21 | , type: blob && (type || blob.type) 22 | }); 23 | }, 24 | 25 | each: function (fn){ 26 | var i = 0, n = this.items.length; 27 | for( ; i < n; i++ ){ 28 | fn.call(this, this.items[i]); 29 | } 30 | }, 31 | 32 | toData: function (fn, options){ 33 | // allow chunked transfer if we have only one file to send 34 | // flag is used below and in XHR._send 35 | options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1; 36 | 37 | if( !api.support.html5 ){ 38 | api.log('FileAPI.Form.toHtmlData'); 39 | this.toHtmlData(fn); 40 | } 41 | else if( !api.formData || this.multipart || !FormData ){ 42 | api.log('FileAPI.Form.toMultipartData'); 43 | this.toMultipartData(fn); 44 | } 45 | else if( options._chunked ){ 46 | api.log('FileAPI.Form.toPlainData'); 47 | this.toPlainData(fn); 48 | } 49 | else { 50 | api.log('FileAPI.Form.toFormData'); 51 | this.toFormData(fn); 52 | } 53 | }, 54 | 55 | _to: function (data, complete, next, arg){ 56 | var queue = api.queue(function (){ 57 | complete(data); 58 | }); 59 | 60 | this.each(function (file){ 61 | try{ 62 | next(file, data, queue, arg); 63 | } 64 | catch( err ){ 65 | api.log('FileAPI.Form._to: ' + err.message); 66 | complete(err); 67 | } 68 | }); 69 | 70 | queue.check(); 71 | }, 72 | 73 | 74 | toHtmlData: function (fn){ 75 | this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){ 76 | var blob = file.blob, hidden; 77 | 78 | if( file.file ){ 79 | api.reset(blob, true); 80 | // set new name 81 | blob.name = file.name; 82 | blob.disabled = false; 83 | data.appendChild(blob); 84 | } 85 | else { 86 | hidden = document.createElement('input'); 87 | hidden.name = file.name; 88 | hidden.type = 'hidden'; 89 | hidden.value = blob; 90 | data.appendChild(hidden); 91 | } 92 | }); 93 | }, 94 | 95 | toPlainData: function (fn){ 96 | this._to({}, fn, function (file, data, queue){ 97 | if( file.file ){ 98 | data.type = file.file; 99 | } 100 | 101 | if( file.blob.toBlob ){ 102 | // canvas 103 | queue.inc(); 104 | _convertFile(file, function (file, blob){ 105 | data.name = file.name; 106 | data.file = blob; 107 | data.size = blob.length; 108 | data.type = file.type; 109 | queue.next(); 110 | }); 111 | } 112 | else if( file.file ){ 113 | // file 114 | data.name = file.blob.name; 115 | data.file = file.blob; 116 | data.size = file.blob.size; 117 | data.type = file.type; 118 | } 119 | else { 120 | // additional data 121 | if( !data.params ){ 122 | data.params = []; 123 | } 124 | data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob)); 125 | } 126 | 127 | data.start = -1; 128 | data.end = data.file && data.file.FileAPIReadPosition || -1; 129 | data.retry = 0; 130 | }); 131 | }, 132 | 133 | toFormData: function (fn){ 134 | this._to(new FormData, fn, function (file, data, queue){ 135 | if( file.blob && file.blob.toBlob ){ 136 | queue.inc(); 137 | _convertFile(file, function (file, blob){ 138 | data.append(file.name, blob, file.file); 139 | queue.next(); 140 | }); 141 | } 142 | else if( file.file ){ 143 | data.append(file.name, file.blob, file.file); 144 | } 145 | else { 146 | data.append(file.name, file.blob); 147 | } 148 | 149 | if( file.file ){ 150 | data.append('_'+file.name, file.file); 151 | } 152 | }); 153 | }, 154 | 155 | 156 | toMultipartData: function (fn){ 157 | this._to([], fn, function (file, data, queue, boundary){ 158 | queue.inc(); 159 | _convertFile(file, function (file, blob){ 160 | data.push( 161 | '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '') 162 | + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '') 163 | + '\r\n' 164 | + '\r\n'+ (file.file ? blob : encodeURIComponent(blob)) 165 | + '\r\n') 166 | ); 167 | queue.next(); 168 | }, true); 169 | }, api.expando); 170 | } 171 | }; 172 | 173 | 174 | function _convertFile(file, fn, useBinaryString){ 175 | var blob = file.blob, filename = file.file; 176 | 177 | if( filename ){ 178 | if( !blob.toDataURL ){ 179 | // The Blob is not an image. 180 | api.readAsBinaryString(blob, function (evt){ 181 | if( evt.type == 'load' ){ 182 | fn(file, evt.result); 183 | } 184 | }); 185 | return; 186 | } 187 | 188 | var 189 | mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' } 190 | , type = mime[file.type] ? file.type : 'image/png' 191 | , ext = mime[type] || '.png' 192 | , quality = blob.quality || 1 193 | ; 194 | 195 | if( !filename.match(new RegExp(ext+'$', 'i')) ){ 196 | // Does not change the current extension, but add a new one. 197 | filename += ext.replace('?', ''); 198 | } 199 | 200 | file.file = filename; 201 | file.type = type; 202 | 203 | if( !useBinaryString && blob.toBlob ){ 204 | blob.toBlob(function (blob){ 205 | fn(file, blob); 206 | }, type, quality); 207 | } 208 | else { 209 | fn(file, api.toBinaryString(blob.toDataURL(type, quality))); 210 | } 211 | } 212 | else { 213 | fn(file, blob); 214 | } 215 | } 216 | 217 | 218 | // @export 219 | api.Form = Form; 220 | })(FileAPI, window); 221 | -------------------------------------------------------------------------------- /lib/canvas-to-blob.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Canvas to Blob 2.0.5 3 | * https://github.com/blueimp/JavaScript-Canvas-to-Blob 4 | * 5 | * Copyright 2012, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * Licensed under the MIT license: 9 | * http://www.opensource.org/licenses/MIT 10 | * 11 | * Based on stackoverflow user Stoive's code snippet: 12 | * http://stackoverflow.com/q/4998908 13 | */ 14 | 15 | /*jslint nomen: true, regexp: true */ 16 | /*global window, atob, Blob, ArrayBuffer, Uint8Array */ 17 | 18 | (function (window) { 19 | 'use strict'; 20 | var CanvasPrototype = window.HTMLCanvasElement && 21 | window.HTMLCanvasElement.prototype, 22 | hasBlobConstructor = window.Blob && (function () { 23 | try { 24 | return Boolean(new Blob()); 25 | } catch (e) { 26 | return false; 27 | } 28 | }()), 29 | hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array && 30 | (function () { 31 | try { 32 | return new Blob([new Uint8Array(100)]).size === 100; 33 | } catch (e) { 34 | return false; 35 | } 36 | }()), 37 | BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder || 38 | window.MozBlobBuilder || window.MSBlobBuilder, 39 | dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob && 40 | window.ArrayBuffer && window.Uint8Array && function (dataURI) { 41 | var byteString, 42 | arrayBuffer, 43 | intArray, 44 | i, 45 | mimeString, 46 | bb; 47 | if (dataURI.split(',')[0].indexOf('base64') >= 0) { 48 | // Convert base64 to raw binary data held in a string: 49 | byteString = atob(dataURI.split(',')[1]); 50 | } else { 51 | // Convert base64/URLEncoded data component to raw binary data: 52 | byteString = decodeURIComponent(dataURI.split(',')[1]); 53 | } 54 | // Write the bytes of the string to an ArrayBuffer: 55 | arrayBuffer = new ArrayBuffer(byteString.length); 56 | intArray = new Uint8Array(arrayBuffer); 57 | for (i = 0; i < byteString.length; i += 1) { 58 | intArray[i] = byteString.charCodeAt(i); 59 | } 60 | // Separate out the mime component: 61 | mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; 62 | // Write the ArrayBuffer (or ArrayBufferView) to a blob: 63 | if (hasBlobConstructor) { 64 | return new Blob( 65 | [hasArrayBufferViewSupport ? intArray : arrayBuffer], 66 | {type: mimeString} 67 | ); 68 | } 69 | bb = new BlobBuilder(); 70 | bb.append(arrayBuffer); 71 | return bb.getBlob(mimeString); 72 | }; 73 | if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) { 74 | if (CanvasPrototype.mozGetAsFile) { 75 | CanvasPrototype.toBlob = function (callback, type, quality) { 76 | if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) { 77 | callback(dataURLtoBlob(this.toDataURL(type, quality))); 78 | } else { 79 | callback(this.mozGetAsFile('blob', type)); 80 | } 81 | }; 82 | } else if (CanvasPrototype.toDataURL && dataURLtoBlob) { 83 | CanvasPrototype.toBlob = function (callback, type, quality) { 84 | callback(dataURLtoBlob(this.toDataURL(type, quality))); 85 | }; 86 | } 87 | } 88 | window.dataURLtoBlob = dataURLtoBlob; 89 | })(window); 90 | -------------------------------------------------------------------------------- /lib/load-image-ios.js: -------------------------------------------------------------------------------- 1 | /* 2 | * JavaScript Load Image iOS scaling fixes 1.0.3 3 | * https://github.com/blueimp/JavaScript-Load-Image 4 | * 5 | * Copyright 2013, Sebastian Tschan 6 | * https://blueimp.net 7 | * 8 | * iOS image scaling fixes based on 9 | * https://github.com/stomita/ios-imagefile-megapixel 10 | * 11 | * Licensed under the MIT license: 12 | * http://www.opensource.org/licenses/MIT 13 | */ 14 | 15 | /*jslint nomen: true, bitwise: true */ 16 | /*global FileAPI, window, document */ 17 | 18 | (function (factory) { 19 | 'use strict'; 20 | factory(FileAPI); 21 | }(function (loadImage) { 22 | 'use strict'; 23 | 24 | // Only apply fixes on the iOS platform: 25 | if (!window.navigator || !window.navigator.platform || 26 | !(/iP(hone|od|ad)/).test(window.navigator.platform)) { 27 | return; 28 | } 29 | 30 | var originalRenderMethod = loadImage.renderImageToCanvas; 31 | 32 | // Detects subsampling in JPEG images: 33 | loadImage.detectSubsampling = function (img) { 34 | var canvas, 35 | context; 36 | if (img.width * img.height > 1024 * 1024) { // only consider mexapixel images 37 | canvas = document.createElement('canvas'); 38 | canvas.width = canvas.height = 1; 39 | context = canvas.getContext('2d'); 40 | context.drawImage(img, -img.width + 1, 0); 41 | // subsampled image becomes half smaller in rendering size. 42 | // check alpha channel value to confirm image is covering edge pixel or not. 43 | // if alpha value is 0 image is not covering, hence subsampled. 44 | return context.getImageData(0, 0, 1, 1).data[3] === 0; 45 | } 46 | return false; 47 | }; 48 | 49 | // Detects vertical squash in JPEG images: 50 | loadImage.detectVerticalSquash = function (img, subsampled) { 51 | var naturalHeight = img.naturalHeight || img.height, 52 | canvas = document.createElement('canvas'), 53 | context = canvas.getContext('2d'), 54 | data, 55 | sy, 56 | ey, 57 | py, 58 | alpha; 59 | if (subsampled) { 60 | naturalHeight /= 2; 61 | } 62 | canvas.width = 1; 63 | canvas.height = naturalHeight; 64 | context.drawImage(img, 0, 0); 65 | data = context.getImageData(0, 0, 1, naturalHeight).data; 66 | // search image edge pixel position in case it is squashed vertically: 67 | sy = 0; 68 | ey = naturalHeight; 69 | py = naturalHeight; 70 | while (py > sy) { 71 | alpha = data[(py - 1) * 4 + 3]; 72 | if (alpha === 0) { 73 | ey = py; 74 | } else { 75 | sy = py; 76 | } 77 | py = (ey + sy) >> 1; 78 | } 79 | return (py / naturalHeight) || 1; 80 | }; 81 | 82 | // Renders image to canvas while working around iOS image scaling bugs: 83 | // https://github.com/blueimp/JavaScript-Load-Image/issues/13 84 | loadImage.renderImageToCanvas = function ( 85 | canvas, 86 | img, 87 | sourceX, 88 | sourceY, 89 | sourceWidth, 90 | sourceHeight, 91 | destX, 92 | destY, 93 | destWidth, 94 | destHeight 95 | ) { 96 | if (img._type === 'image/jpeg') { 97 | var context = canvas.getContext('2d'), 98 | tmpCanvas = document.createElement('canvas'), 99 | tileSize = 1024, 100 | tmpContext = tmpCanvas.getContext('2d'), 101 | subsampled, 102 | vertSquashRatio, 103 | tileX, 104 | tileY; 105 | tmpCanvas.width = tileSize; 106 | tmpCanvas.height = tileSize; 107 | context.save(); 108 | subsampled = loadImage.detectSubsampling(img); 109 | if (subsampled) { 110 | sourceX /= 2; 111 | sourceY /= 2; 112 | sourceWidth /= 2; 113 | sourceHeight /= 2; 114 | } 115 | vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled); 116 | if (subsampled || vertSquashRatio !== 1) { 117 | sourceY *= vertSquashRatio; 118 | destWidth = Math.ceil(tileSize * destWidth / sourceWidth); 119 | destHeight = Math.ceil( 120 | tileSize * destHeight / sourceHeight / vertSquashRatio 121 | ); 122 | destY = 0; 123 | tileY = 0; 124 | while (tileY < sourceHeight) { 125 | destX = 0; 126 | tileX = 0; 127 | while (tileX < sourceWidth) { 128 | tmpContext.clearRect(0, 0, tileSize, tileSize); 129 | tmpContext.drawImage( 130 | img, 131 | sourceX, 132 | sourceY, 133 | sourceWidth, 134 | sourceHeight, 135 | -tileX, 136 | -tileY, 137 | sourceWidth, 138 | sourceHeight 139 | ); 140 | context.drawImage( 141 | tmpCanvas, 142 | 0, 143 | 0, 144 | tileSize, 145 | tileSize, 146 | destX, 147 | destY, 148 | destWidth, 149 | destHeight 150 | ); 151 | tileX += tileSize; 152 | destX += destWidth; 153 | } 154 | tileY += tileSize; 155 | destY += destHeight; 156 | } 157 | context.restore(); 158 | return canvas; 159 | } 160 | } 161 | return originalRenderMethod( 162 | canvas, 163 | img, 164 | sourceX, 165 | sourceY, 166 | sourceWidth, 167 | sourceHeight, 168 | destX, 169 | destY, 170 | destWidth, 171 | destHeight 172 | ); 173 | }; 174 | 175 | })); 176 | -------------------------------------------------------------------------------- /node/file-api.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var qs = require('qs'); 3 | var imageSize = require('image-size'); 4 | 5 | function convertToBase64(buffer, mimetype) { 6 | return 'data:' + mimetype + ';base64,' + buffer.toString('base64'); 7 | } 8 | 9 | function fileApi() { 10 | return function (req, res, next) { 11 | var queryString = ''; 12 | 13 | req.files = {}; 14 | req.images = {}; 15 | 16 | req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) { 17 | var buffersArray = []; 18 | 19 | file.on('data', function (data) { 20 | buffersArray.push(data); 21 | }); 22 | 23 | file.on('end', function () { 24 | var bufferResult = Buffer.concat(buffersArray); 25 | var fileObj = { 26 | name: filename, 27 | type: mimetype, 28 | mime: mimetype, 29 | size: bufferResult.length, 30 | dataURL: convertToBase64(bufferResult, mimetype) 31 | }; 32 | 33 | req.files[fieldname] = fileObj; 34 | 35 | if (mimetype.indexOf('image/') === 0) { 36 | fs.writeFileSync(filename, bufferResult); 37 | 38 | var size = imageSize(filename); 39 | 40 | fileObj.width = size.width; 41 | fileObj.height = size.height; 42 | 43 | req.images[fieldname] = fileObj; 44 | 45 | fs.unlinkSync(filename); 46 | } 47 | }); 48 | }); 49 | 50 | req.busboy.on('field', function (key, value) { 51 | queryString += encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&'; 52 | }); 53 | 54 | req.busboy.on('finish', function () { 55 | req.body = qs.parse(queryString); 56 | 57 | next(); 58 | }); 59 | }; 60 | } 61 | 62 | module.exports = fileApi; 63 | -------------------------------------------------------------------------------- /node/server.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var busboy = require('connect-busboy'); 3 | var fileApi = require('./file-api'); 4 | var app = express(); 5 | 6 | app.use(express.static('.', {index: 'index.html'})); 7 | 8 | app.use(function (req, res, next) { 9 | // Enable CORS for non static files 10 | var origin = req.get('Origin'); 11 | 12 | if (origin) { 13 | res.set({ 14 | 'Access-Control-Allow-Origin': origin, 15 | 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', 16 | 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, X-Foo, X-Rnd', 17 | 'Access-Control-Allow-Credentials': 'true' 18 | }); 19 | } 20 | next(); 21 | }); 22 | 23 | var uploadPath = '/upload'; 24 | 25 | app.options(uploadPath, function (req, res) { 26 | res.end(); 27 | }); 28 | 29 | app.post( 30 | uploadPath, 31 | busboy({immediate: true}), // parse post data 32 | fileApi(), // prepare req.body, req.files and req.images 33 | function (req, res) { 34 | var jsonp = req.query.callback || null; 35 | 36 | res[jsonp ? 'jsonp' : 'json']({ 37 | status: 200, 38 | statusText: 'OK', 39 | images: req.images, 40 | data: { 41 | HEADERS: req.headers, 42 | _REQUEST: req.body, 43 | _FILES: req.files 44 | } 45 | }); 46 | } 47 | ); 48 | 49 | // Export 50 | module.exports.createServer = function (port, callback) { 51 | var server = app.listen(port, function () { 52 | var host = server.address().address; 53 | var port = server.address().port; 54 | 55 | console.log('Test server listening at http://%s:%s', host, port); 56 | 57 | callback(server); 58 | }); 59 | }; 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fileapi", 3 | "exportName": "FileAPI", 4 | "version": "2.2.0", 5 | "devDependencies": { 6 | "connect-busboy": "~0.0.2", 7 | "eventemitter2": "~0.4.13", 8 | "express": "~4.12.3", 9 | "flex-sdk": "^4.6.0-0", 10 | "grunt": "^0.4.5", 11 | "grunt-cli": "^1.3.2", 12 | "grunt-contrib-compress": "~0.9.1", 13 | "grunt-contrib-concat": "~0.4.0", 14 | "grunt-contrib-connect": "~0.8.0", 15 | "grunt-contrib-jshint": "~0.10.0", 16 | "grunt-contrib-uglify": "~0.5.0", 17 | "grunt-contrib-watch": "~0.6.1", 18 | "grunt-curl": "~2.0.2", 19 | "grunt-mxmlc": "~0.5.2", 20 | "grunt-version": "~0.3.0", 21 | "image-size": "~0.3.5", 22 | "phantomjs": "~1.9.7-9", 23 | "qs": "~2.4.1", 24 | "semver": "~5.0.0", 25 | "temporary": "~0.0.8" 26 | }, 27 | "peerDependencies": {}, 28 | "description": "FileAPI — a set of javascript tools for working with files. Multiupload, drag'n'drop and chunked file upload. Images: crop, resize and auto orientation by EXIF.", 29 | "main": "dist/FileAPI.js", 30 | "files": [ 31 | "dist", 32 | "plugins", 33 | "*.js" 34 | ], 35 | "jam": { 36 | "name": "FileAPI" 37 | }, 38 | "scripts": { 39 | "test": "grunt tests --verbose", 40 | "build": "grunt build" 41 | }, 42 | "repository": { 43 | "type": "git", 44 | "url": "git://github.com/mailru/FileAPI.git" 45 | }, 46 | "keywords": [ 47 | "FileAPI", 48 | "upload", 49 | "file", 50 | "html5", 51 | "chunked" 52 | ], 53 | "author": "Konstantin Lebedev ", 54 | "contributors": [ 55 | "Vladimir Demidov ", 56 | "Ilya Lebedev ", 57 | "Mikhail Bezoyan " 58 | ], 59 | "license": "BSD", 60 | "dependencies": {} 61 | } 62 | -------------------------------------------------------------------------------- /server/FileAPI.class.php: -------------------------------------------------------------------------------- 1 | $mixedValue ){ 10 | self::rRestructuringFilesArray($arrayForFill[$currentKey], 11 | $nameKey, 12 | $mixedValue, 13 | $fileDescriptionParam); 14 | } 15 | } else { 16 | $arrayForFill[$currentKey][$fileDescriptionParam] = $currentMixedValue; 17 | } 18 | } 19 | 20 | 21 | private static function determineMimeType(&$file){ 22 | if( function_exists('mime_content_type') ){ 23 | if( isset($file['tmp_name']) && is_string($file['tmp_name']) ){ 24 | if( $file['type'] == 'application/octet-stream' ){ 25 | $mime = mime_content_type($file['tmp_name']); 26 | if( !empty($mime) ){ 27 | $file['type'] = $mime; 28 | } 29 | } 30 | } 31 | else if( is_array($file) ){ 32 | foreach( $file as &$entry ){ 33 | self::determineMimeType($entry); 34 | } 35 | } 36 | } 37 | } 38 | 39 | 40 | /** 41 | * Enable CORS -- http://enable-cors.org/ 42 | * @param array [$options] 43 | */ 44 | public static function enableCORS($options = null){ 45 | if( is_null($options) ){ 46 | $options = array(); 47 | } 48 | 49 | if( !isset($options['origin']) ){ 50 | $options['origin'] = $_SERVER['HTTP_ORIGIN']; 51 | } 52 | 53 | if( !isset($options['methods']) ){ 54 | $options['methods'] = 'POST, GET'; 55 | } 56 | 57 | if( !isset($options['headers']) ){ 58 | $options['headers'] = array(); 59 | } 60 | 61 | header('Access-Control-Allow-Origin: ' . $options['origin']); 62 | header('Access-Control-Allow-Methods: ' . $options['methods']); 63 | header('Access-Control-Allow-Headers: ' . implode(', ', array_merge($options['headers'], array('X-Requested-With', 'Content-Range', 'Content-Disposition')))); 64 | 65 | if( !isset($options['cookie']) || $options['cookie'] ){ 66 | header('Access-Control-Allow-Credentials: true'); 67 | } 68 | } 69 | 70 | 71 | /** 72 | * Request header 73 | * @return array 74 | */ 75 | public static function getRequestHeaders(){ 76 | $headers = array(); 77 | 78 | foreach( $_SERVER as $key => $value ){ 79 | if( substr($key, 0, 5) == 'HTTP_' ){ 80 | $header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5))))); 81 | $headers[$header] = $value; 82 | } 83 | } 84 | 85 | return $headers; 86 | } 87 | 88 | 89 | /** 90 | * Retrieve File List 91 | * @return array 92 | */ 93 | public static function getFiles(){ 94 | $files = array(); 95 | 96 | // http://www.php.net/manual/ru/reserved.variables.files.php#106558 97 | foreach( $_FILES as $firstNameKey => $arFileDescriptions ){ 98 | foreach( $arFileDescriptions as $fileDescriptionParam => $mixedValue ){ 99 | self::rRestructuringFilesArray($files, $firstNameKey, $_FILES[$firstNameKey][$fileDescriptionParam], $fileDescriptionParam); 100 | } 101 | } 102 | 103 | self::determineMimeType($files); 104 | 105 | return $files; 106 | } 107 | 108 | 109 | /** 110 | * Make server response 111 | * @param array $res 112 | * @param string [$jsonp] 113 | */ 114 | public static function makeResponse(array $res, $jsonp = null){ 115 | $body = $res['body']; 116 | $json = is_array($body) ? json_encode($body) : $body; 117 | 118 | $httpStatus = isset($res['status']) ? $res['status'] : self::OK; 119 | $httpStatusText = addslashes(isset($res['statusText']) ? $res['statusText'] : 'OK'); 120 | $httpHeaders = isset($res['headers']) ? $res['headers'] : array(); 121 | 122 | if( empty($jsonp) ){ 123 | header("HTTP/1.1 $httpStatus $httpStatusText"); 124 | $httpHeaders['Content-Type'] = 'application/json'; 125 | foreach( $httpHeaders as $header => $value ){ 126 | header("$header: $value"); 127 | } 128 | echo $json; 129 | } 130 | else { 131 | $json = addslashes($json); 132 | 133 | echo << 135 | (function (ctx, jsonp){ 136 | 'use strict'; 137 | var status = $httpStatus, statusText = "$httpStatusText", response = "$json"; 138 | try { 139 | ctx[jsonp](status, statusText, response); 140 | } catch (e){ 141 | var data = "{\"id\":\"$jsonp\",\"status\":"+status+",\"statusText\":\""+statusText+"\",\"response\":\""+response.replace(/\"/g, '\\\\\"')+"\"}"; 142 | try { 143 | ctx.postMessage(data, document.referrer); 144 | } catch (e){} 145 | } 146 | })(window.parent, '$jsonp'); 147 | 148 | END; 149 | } 150 | } 151 | 152 | } 153 | -------------------------------------------------------------------------------- /server/ctrl.php: -------------------------------------------------------------------------------- 1 | $images 40 | , 'data' => array('_REQUEST' => $_REQUEST, '_FILES' => $files) 41 | ); 42 | 43 | 44 | // Server response: "HTTP/1.1 200 OK" 45 | FileAPI::makeResponse(array( 46 | 'status' => FileAPI::OK 47 | , 'statusText' => 'OK' 48 | , 'body' => $json 49 | ), $jsonp); 50 | exit; 51 | } 52 | 53 | 54 | 55 | 56 | function fetchImages($files, &$images, $name = 'file'){ 57 | if( isset($files['tmp_name']) ){ 58 | $filename = $files['tmp_name']; 59 | list($mime) = explode(';', @mime_content_type($filename)); 60 | 61 | if( strpos($mime, 'image') !== false ){ 62 | $size = getimagesize($filename); 63 | $base64 = base64_encode(file_get_contents($filename)); 64 | 65 | $images[$name] = array( 66 | 'width' => $size[0] 67 | , 'height' => $size[1] 68 | , 'mime' => $mime 69 | , 'size' => filesize($filename) 70 | , 'dataURL' => 'data:'. $mime .';base64,'. $base64 71 | ); 72 | } 73 | } 74 | else { 75 | foreach( $files as $name => $file ){ 76 | fetchImages($file, $images, $name); 77 | } 78 | } 79 | } 80 | ?> 81 | -------------------------------------------------------------------------------- /statics/body.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/statics/body.png -------------------------------------------------------------------------------- /statics/body__top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/statics/body__top.png -------------------------------------------------------------------------------- /statics/content.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/statics/content.png -------------------------------------------------------------------------------- /statics/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/statics/logo.png -------------------------------------------------------------------------------- /statics/watermark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/statics/watermark.png -------------------------------------------------------------------------------- /support.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | FileAPI :: Support 14 | 15 | 22 | 23 | 24 | 25 |
Single:
26 |
27 |
28 | 29 |
Multiple:
30 |
31 | 32 | 33 |
34 |
35 | 36 | 49 | 50 | 51 | 52 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /tests/files/1px.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/1px.gif -------------------------------------------------------------------------------- /tests/files/big.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/big.jpg -------------------------------------------------------------------------------- /tests/files/dino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/dino.png -------------------------------------------------------------------------------- /tests/files/hello.txt: -------------------------------------------------------------------------------- 1 | Hello FileAPI! 2 | -------------------------------------------------------------------------------- /tests/files/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/image.jpg -------------------------------------------------------------------------------- /tests/files/lebowski.json: -------------------------------------------------------------------------------- 1 | { 2 | "adverts":[ 3 | { 4 | "title":"Walter", 5 | "text":"You see what happens, Larry?", 6 | "url":"http://www.imdb.com/title/tt0118715/quotes" 7 | }, 8 | { 9 | "title":"Walter", 10 | "text":"I don't roll on Shabbos!", 11 | "url":"http://www.imdb.com/title/tt0118715/quotes" 12 | }, 13 | { 14 | "title":"Blond Thug", 15 | "text":"Where's the money, Lebowski?", 16 | "url":"http://www.imdb.com/title/tt0118715/quotes" 17 | }, 18 | { 19 | "title":"Nihilist", 20 | "text":"We believe in nothing, Lebowski.", 21 | "url":"http://www.imdb.com/title/tt0118715/quotes" 22 | }, 23 | { 24 | "title":"Walter", 25 | "text":"Is this your homework, Larry?", 26 | "url":"http://www.imdb.com/title/tt0118715/quotes" 27 | }, 28 | { 29 | "title":"Nihilist", 30 | "text":"Ve vant ze money, Lebowski", 31 | "url":"http://www.imdb.com/title/tt0118715/quotes" 32 | } 33 | ], 34 | 35 | "sections":[ 36 | { 37 | "id":1234, 38 | "title":"The Dude", 39 | "rip":0 40 | }, 41 | { 42 | "id":2345, 43 | "title":"Walter Sobchak", 44 | "rip":0 45 | }, 46 | { 47 | "id":3456, 48 | "title":"Donny", 49 | "rip":1 50 | }, 51 | { 52 | "id":4567, 53 | "title":"Maude Lebowski", 54 | "rip":0 55 | }, 56 | { 57 | "id":5678, 58 | "title":"The Big Lebowski", 59 | "rip":0 60 | }, 61 | { 62 | "id":6789, 63 | "title":"Brandt", 64 | "rip":0 65 | }, 66 | { 67 | "id":7890, 68 | "title":"Jesus Quintana", 69 | "rip":0 70 | } 71 | ], 72 | 73 | "total":654329, 74 | "online":[ 75 | { "name":"true" }, 76 | { "name":"false" }, 77 | { "name":"short" }, 78 | { "name":"long" }, 79 | { "name":"apha" }, 80 | { "name":"omega" }, 81 | { "name":"drag" }, 82 | { "name":"drop" }, 83 | { "name":"make" }, 84 | { "name":"clean" }, 85 | { "name":"east" }, 86 | { "name":"west" }, 87 | { "name":"up" }, 88 | { "name":"down" }, 89 | { "name":"sun" }, 90 | { "name":"rain" }, 91 | { "name":"secondary" }, 92 | { "name":"main" } 93 | ], 94 | 95 | "news":[ 96 | { 97 | "time":"03:45", 98 | "id":987, 99 | "title":"The Stranger", 100 | "text":"See, they call Los Angeles the \"City Of Angels\"; but I didn't find it to be that, exactly. But I'll allow it as there are s ome nice folks there. 'Course I ain't never been to London, and I ain't never seen France. And I ain't never seen no queen in her damned undies, so the feller says. But I'll tell you what - after seeing Los Angeles, and this here story I'm about to unfold, well, I guess I seen somethin' every bit as stupefyin' as you'd seen in any of them other places. And in English , too. So I can die with a smile on my face, without feelin' like the good Lord gypped me. Now this here story I'm about to unfold took place in the early '90s - just about the time of our conflict with Sad'm and the I-raqis. I only mention it be cause sometimes there's a man..." 101 | }, 102 | { 103 | "time":"03:48", 104 | "id":876, 105 | "title":"The Stranger", 106 | "text":"...I won't say a hero, 'cause, what's a hero? Sometimes, there's a man. And I'm talkin' about the Dude here - the Dude from Los Angeles. Sometimes, there's a man, well, he's the man for his time and place. He fits right in there. And that's the Dude. The Dude, from Los Angeles. And even if he's a lazy man - and the Dude was most certainly that. Quite possibly the laziest in all of Los Angeles County, which would place him high in the runnin' for laziest worldwide. Sometimes there's a man , sometimes, there's a man. Well, I lost my train of thought here. But... aw, hell. I've done introduced it enough." 107 | }, 108 | { 109 | "time":"03:50", 110 | "id":765, 111 | "title":"Walter Sobchak", 112 | "text":"Donny was a good bowler, and a good man. He was one of us. He was a man who loved the outdoors... and bowling, and as a surfer he explored the beaches of Southern California, from La Jolla to Leo Carrillo and... up to... Pismo. He died, like so many young men of his generation, he died before his time. In your wisdom, Lord, you took him, as you took so many bright flowering young men at Khe Sanh, at Langdok, at Hill 364. These young men gave their lives. And so would Donny. Donny, who loved bowling. And so, Theodore Donald Karabotsos, in accordance with what we think your dying wishes might well have been, we commit your final mortal remains to the bosom of the Pacific Ocean, which you loved so well. Good night, sweet prince." 113 | }, 114 | { 115 | "time":"03:52", 116 | "id":654, 117 | "title":"The Dude", 118 | "text":"God damn you Walter! You fuckin' asshole! Everything's a fuckin' travesty with you, man! And what was all that shit about Vietnam? What the FUCK, has anything got to do with Vietnam? What the fuck are you talking about?" 119 | }, 120 | { 121 | "time":"03:57", 122 | "id":543, 123 | "title":"Jesus Quintana", 124 | "text":"What's this day of rest shit? What's this bullshit? I don't fuckin' care! It don't matter to Jesus. But you're not foolin'me, man. You might fool the fucks in the league office, but you don't fool Jesus. This bush league psyche-out stuff. Laughable, man - ha ha! I would have fucked you in the ass Saturday. I fuck you in the ass next Wednesday instead. Wooo! You gotadate Wednesday, baby!" 125 | }, 126 | { 127 | "time":"03:59", 128 | "id":432, 129 | "title":"Jesus Quintana", 130 | "text":"Let me tell you something, pendejo. You pull any of your crazy shit with us, you flash a piece out on the lanes, I'll take it away from you, stick it up your ass and pull the fucking trigger 'til it goes \"click.\"" 131 | }, 132 | { 133 | "time":"04:01", 134 | "id":321, 135 | "title":"The Dude", 136 | "text":"Let me explain something to you. Um, I am not \"Mr. Lebowski\". You're Mr. Lebowski. I'm the Dude. So that's what you call me. You know, that or, uh, His Dudeness, or uh, Duder, or El Duderino if you're not into the whole brevity thing." 137 | } 138 | ] 139 | } 140 | -------------------------------------------------------------------------------- /tests/files/samples/chrome-dino-180deg-50x50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/chrome-dino-180deg-50x50.png -------------------------------------------------------------------------------- /tests/files/samples/chrome-dino-50x50.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/chrome-dino-50x50.jpeg -------------------------------------------------------------------------------- /tests/files/samples/chrome-dino-90deg-100x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/chrome-dino-90deg-100x100.png -------------------------------------------------------------------------------- /tests/files/samples/chrome-dino-custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/chrome-dino-custom.png -------------------------------------------------------------------------------- /tests/files/samples/chrome-image-auto-100x100.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/chrome-image-auto-100x100.jpeg -------------------------------------------------------------------------------- /tests/files/samples/firefox-dino-180deg-50x50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/firefox-dino-180deg-50x50.png -------------------------------------------------------------------------------- /tests/files/samples/firefox-dino-50x50.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/firefox-dino-50x50.jpeg -------------------------------------------------------------------------------- /tests/files/samples/firefox-dino-90deg-100x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/firefox-dino-90deg-100x100.png -------------------------------------------------------------------------------- /tests/files/samples/firefox-dino-custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/firefox-dino-custom.png -------------------------------------------------------------------------------- /tests/files/samples/firefox-image-auto-100x100.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/firefox-image-auto-100x100.jpeg -------------------------------------------------------------------------------- /tests/files/samples/firefox-vintage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/firefox-vintage.png -------------------------------------------------------------------------------- /tests/files/samples/phantomjs-dino-180deg-50x50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/phantomjs-dino-180deg-50x50.png -------------------------------------------------------------------------------- /tests/files/samples/phantomjs-dino-50x50.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/phantomjs-dino-50x50.jpeg -------------------------------------------------------------------------------- /tests/files/samples/phantomjs-dino-90deg-100x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/phantomjs-dino-90deg-100x100.png -------------------------------------------------------------------------------- /tests/files/samples/phantomjs-dino-custom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/phantomjs-dino-custom.png -------------------------------------------------------------------------------- /tests/files/samples/phantomjs-image-auto-100x100.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/phantomjs-image-auto-100x100.jpeg -------------------------------------------------------------------------------- /tests/files/samples/phantomjs-vintage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/phantomjs-vintage.png -------------------------------------------------------------------------------- /tests/files/samples/safari-dino-90deg-100x100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mailru/FileAPI/5b50e8ed012e089eb578e586d860a6fd035e16d8/tests/files/samples/safari-dino-90deg-100x100.png -------------------------------------------------------------------------------- /tests/flash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FileAPI :: Tests 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 |
30 |
31 | 32 |
33 | 34 |
35 | 36 |
 37 | 			
 38 | 		
39 |
40 | 41 | 42 | 43 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /tests/grunt-task/phantomjs/bridge.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-contrib-qunit 3 | * http://gruntjs.com/ 4 | * 5 | * Copyright (c) 2012 "Cowboy" Ben Alman, contributors 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | /*global QUnit:true, alert:true*/ 10 | 11 | 'use strict'; 12 | 13 | 14 | // Don't re-order tests. 15 | QUnit.config.reorder = false; 16 | // Run tests serially, not in parallel. 17 | QUnit.config.autorun = false; 18 | 19 | // Send messages to the parent PhantomJS process via alert! Good times!! 20 | function sendMessage() { 21 | var args = [].slice.call(arguments); 22 | alert(JSON.stringify(args)); 23 | } 24 | 25 | // These methods connect QUnit to PhantomJS. 26 | QUnit.log(function(obj) { 27 | // What is this I don’t even 28 | if (obj.message === '[object Object], undefined:undefined') { return; } 29 | // Parse some stuff before sending it. 30 | var actual = QUnit.jsDump.parse(obj.actual); 31 | var expected = QUnit.jsDump.parse(obj.expected); 32 | // Send it. 33 | sendMessage('qunit.log', obj.result, actual, expected, obj.message, obj.source); 34 | }); 35 | 36 | QUnit.testStart(function(obj) { 37 | sendMessage('qunit.testStart', obj.name); 38 | }); 39 | 40 | QUnit.testDone(function(obj) { 41 | sendMessage('qunit.testDone', obj.name, obj.failed, obj.passed, obj.total); 42 | }); 43 | 44 | QUnit.moduleStart(function(obj) { 45 | sendMessage('qunit.moduleStart', obj.name); 46 | }); 47 | 48 | QUnit.moduleDone(function(obj) { 49 | sendMessage('qunit.moduleDone', obj.name, obj.failed, obj.passed, obj.total); 50 | }); 51 | 52 | QUnit.begin(function() { 53 | sendMessage('qunit.begin'); 54 | }); 55 | 56 | QUnit.done(function(obj) { 57 | sendMessage('qunit.done', obj.failed, obj.passed, obj.total, obj.runtime); 58 | }); 59 | -------------------------------------------------------------------------------- /tests/grunt-task/phantomjs/grunt-lib-phantomjs-main.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-lib-phantomjs 3 | * http://gruntjs.com/ 4 | * 5 | * Copyright (c) 2012 "Cowboy" Ben Alman, contributors 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | /*global phantom:true*/ 10 | 11 | 'use strict'; 12 | 13 | var fs = require('fs'); 14 | 15 | // The temporary file used for communications. 16 | var tmpfile = phantom.args[0]; 17 | // The page .html file to load. 18 | var url = phantom.args[1]; 19 | // Extra, optionally overridable stuff. 20 | var options = JSON.parse(phantom.args[2] || {}); 21 | 22 | // Default options. 23 | if (!options.timeout) { options.timeout = 5000; } 24 | 25 | // Keep track of the last time a client message was sent. 26 | var last = new Date(); 27 | 28 | // Messages are sent to the parent by appending them to the tempfile. 29 | var sendMessage = function(arg) { 30 | var args = Array.isArray(arg) ? arg : [].slice.call(arguments); 31 | last = new Date(); 32 | fs.write(tmpfile, JSON.stringify(args) + '\n', 'a'); 33 | }; 34 | 35 | // This allows grunt to abort if the PhantomJS version isn't adequate. 36 | sendMessage('private', 'version', phantom.version); 37 | 38 | // Abort if the page doesn't send any messages for a while. 39 | setInterval(function() { 40 | if (new Date() - last > options.timeout) { 41 | sendMessage('fail.timeout'); 42 | phantom.exit(); 43 | } 44 | }, 100); 45 | 46 | // Create a new page. 47 | var page = require('webpage').create(); 48 | 49 | // Inject bridge script into client page. 50 | var injected; 51 | var inject = function() { 52 | if (injected) { return; } 53 | // Inject client-side helper script. 54 | var scripts = Array.isArray(options.inject) ? options.inject : [options.inject]; 55 | sendMessage('inject', options.inject); 56 | scripts.forEach(page.injectJs); 57 | injected = true; 58 | }; 59 | 60 | // Keep track if the client-side helper script already has been injected. 61 | page.onUrlChanged = function(newUrl) { 62 | injected = false; 63 | sendMessage('onUrlChanged', newUrl); 64 | }; 65 | 66 | // The client page must send its messages via alert(jsonstring). 67 | page.onAlert = function(str) { 68 | // The only thing that should ever alert "inject" is the custom event 69 | // handler this script adds to be executed on DOMContentLoaded. 70 | if (str === 'inject') { 71 | inject(); 72 | return; 73 | } 74 | 75 | if (str.indexOf('qunit.testStart') == 2) { 76 | // Setup files 77 | var files = options.files, name; 78 | if( files ){ 79 | for( name in files ){ 80 | page.uploadFile('input[name="'+name+'"]', files[name]); 81 | } 82 | } 83 | } 84 | // Otherwise, parse the specified message string and send it back to grunt. 85 | // Unless there's a parse error. Then, complain. 86 | try { 87 | sendMessage(JSON.parse(str)); 88 | } catch(err) { 89 | sendMessage('error.invalidJSON', str); 90 | } 91 | }; 92 | 93 | // Relay console logging messages. 94 | page.onConsoleMessage = function(message) { 95 | sendMessage('console', message); 96 | }; 97 | 98 | // For debugging. 99 | page.onResourceRequested = function(request) { 100 | sendMessage('onResourceRequested', request.url); 101 | }; 102 | 103 | page.onResourceReceived = function(request) { 104 | if (request.stage === 'end') { 105 | sendMessage('onResourceReceived', request.url); 106 | } 107 | }; 108 | 109 | page.onError = function(msg, trace) { 110 | sendMessage('error.onError', msg, trace); 111 | }; 112 | 113 | // Run before the page is loaded. 114 | page.onInitialized = function() { 115 | sendMessage('onInitialized'); 116 | // Abort if there is no bridge to inject. 117 | if (!options.inject) { return; } 118 | // Tell the client that when DOMContentLoaded fires, it needs to tell this 119 | // script to inject the bridge. This should ensure that the bridge gets 120 | // injected before any other DOMContentLoaded or window.load event handler. 121 | page.evaluate(function() { 122 | /*jshint browser:true, devel:true */ 123 | document.addEventListener('DOMContentLoaded', function() { 124 | alert('inject'); 125 | }, false); 126 | }); 127 | }; 128 | 129 | // Run when the page has finished loading. 130 | page.onLoadFinished = function(status) { 131 | // The window has loaded. 132 | sendMessage('onLoadFinished', status); 133 | if (status !== 'success') { 134 | // File loading failure. 135 | sendMessage('fail.load', url); 136 | phantom.exit(); 137 | } 138 | }; 139 | 140 | // Actually load url. 141 | page.open(url); 142 | -------------------------------------------------------------------------------- /tests/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FileAPI :: Tests 8 | 9 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 | 32 |
33 |
34 | 35 |
36 |
1px.gif --
37 |
big.jpg --
38 |
hello.txt --
39 |
image.jpg --
40 |
dino.png --
41 |
multiple --
42 |
43 |
44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /tests/json.html: -------------------------------------------------------------------------------- 1 | {"status":200,"statusText":"OK","response":"done"} 2 | -------------------------------------------------------------------------------- /tests/pure-resize-tests.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 83 | 84 | 85 | 86 |  Time to load: n/ams 87 |  Time to render: n/ams 88 |  Time to encode: n/ams 89 |
90 | 91 |
92 | 93 |
94 | 95 |
96 | 97 | -------------------------------------------------------------------------------- /tests/qunit/qunit.css: -------------------------------------------------------------------------------- 1 | /** 2 | * QUnit v1.11.0 - A JavaScript Unit Testing Framework 3 | * 4 | * http://qunitjs.com 5 | * 6 | * Copyright 2012 jQuery Foundation and other contributors 7 | * Released under the MIT license. 8 | * http://jquery.org/license 9 | */ 10 | 11 | /** Font Family and Sizes */ 12 | 13 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult { 14 | font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif; 15 | } 16 | 17 | #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; } 18 | #qunit-tests { font-size: smaller; } 19 | 20 | 21 | /** Resets */ 22 | 23 | #qunit-tests, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter { 24 | margin: 0; 25 | padding: 0; 26 | } 27 | 28 | 29 | /** Header */ 30 | 31 | #qunit-header { 32 | padding: 0.5em 0 0.5em 1em; 33 | 34 | color: #8699a4; 35 | background-color: #0d3349; 36 | 37 | font-size: 1.5em; 38 | line-height: 1em; 39 | font-weight: normal; 40 | 41 | border-radius: 5px 5px 0 0; 42 | -moz-border-radius: 5px 5px 0 0; 43 | -webkit-border-top-right-radius: 5px; 44 | -webkit-border-top-left-radius: 5px; 45 | } 46 | 47 | #qunit-header a { 48 | text-decoration: none; 49 | color: #c2ccd1; 50 | } 51 | 52 | #qunit-header a:hover, 53 | #qunit-header a:focus { 54 | color: #fff; 55 | } 56 | 57 | #qunit-testrunner-toolbar label { 58 | display: inline-block; 59 | padding: 0 .5em 0 .1em; 60 | } 61 | 62 | #qunit-banner { 63 | height: 5px; 64 | } 65 | 66 | #qunit-testrunner-toolbar { 67 | padding: 0.5em 0 0.5em 2em; 68 | color: #5E740B; 69 | background-color: #eee; 70 | overflow: hidden; 71 | } 72 | 73 | #qunit-userAgent { 74 | padding: 0.5em 0 0.5em 2.5em; 75 | background-color: #2b81af; 76 | color: #fff; 77 | text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px; 78 | } 79 | 80 | #qunit-modulefilter-container { 81 | float: right; 82 | } 83 | 84 | /** Tests: Pass/Fail */ 85 | 86 | #qunit-tests { 87 | list-style-position: inside; 88 | } 89 | 90 | #qunit-tests li { 91 | padding: 0.4em 0.5em 0.4em 2.5em; 92 | border-bottom: 1px solid #fff; 93 | list-style-position: inside; 94 | } 95 | 96 | #qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running { 97 | display: none; 98 | } 99 | 100 | #qunit-tests li strong { 101 | cursor: pointer; 102 | } 103 | 104 | #qunit-tests li a { 105 | padding: 0.5em; 106 | color: #c2ccd1; 107 | text-decoration: none; 108 | } 109 | #qunit-tests li a:hover, 110 | #qunit-tests li a:focus { 111 | color: #000; 112 | } 113 | 114 | #qunit-tests li .runtime { 115 | float: right; 116 | font-size: smaller; 117 | } 118 | 119 | .qunit-assert-list { 120 | margin-top: 0.5em; 121 | padding: 0.5em; 122 | 123 | background-color: #fff; 124 | 125 | border-radius: 5px; 126 | -moz-border-radius: 5px; 127 | -webkit-border-radius: 5px; 128 | } 129 | 130 | .qunit-collapsed { 131 | display: none; 132 | } 133 | 134 | #qunit-tests table { 135 | border-collapse: collapse; 136 | margin-top: .2em; 137 | } 138 | 139 | #qunit-tests th { 140 | text-align: right; 141 | vertical-align: top; 142 | padding: 0 .5em 0 0; 143 | } 144 | 145 | #qunit-tests td { 146 | vertical-align: top; 147 | } 148 | 149 | #qunit-tests pre { 150 | margin: 0; 151 | white-space: pre-wrap; 152 | word-wrap: break-word; 153 | } 154 | 155 | #qunit-tests del { 156 | background-color: #e0f2be; 157 | color: #374e0c; 158 | text-decoration: none; 159 | } 160 | 161 | #qunit-tests ins { 162 | background-color: #ffcaca; 163 | color: #500; 164 | text-decoration: none; 165 | } 166 | 167 | /*** Test Counts */ 168 | 169 | #qunit-tests b.counts { color: black; } 170 | #qunit-tests b.passed { color: #5E740B; } 171 | #qunit-tests b.failed { color: #710909; } 172 | 173 | #qunit-tests li li { 174 | padding: 5px; 175 | background-color: #fff; 176 | border-bottom: none; 177 | list-style-position: inside; 178 | } 179 | 180 | /*** Passing Styles */ 181 | 182 | #qunit-tests li li.pass { 183 | color: #3c510c; 184 | background-color: #fff; 185 | border-left: 10px solid #C6E746; 186 | } 187 | 188 | #qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; } 189 | #qunit-tests .pass .test-name { color: #366097; } 190 | 191 | #qunit-tests .pass .test-actual, 192 | #qunit-tests .pass .test-expected { color: #999999; } 193 | 194 | #qunit-banner.qunit-pass { background-color: #C6E746; } 195 | 196 | /*** Failing Styles */ 197 | 198 | #qunit-tests li li.fail { 199 | color: #710909; 200 | background-color: #fff; 201 | border-left: 10px solid #EE5757; 202 | white-space: pre; 203 | } 204 | 205 | #qunit-tests > li:last-child { 206 | border-radius: 0 0 5px 5px; 207 | -moz-border-radius: 0 0 5px 5px; 208 | -webkit-border-bottom-right-radius: 5px; 209 | -webkit-border-bottom-left-radius: 5px; 210 | } 211 | 212 | #qunit-tests .fail { color: #000000; background-color: #EE5757; } 213 | #qunit-tests .fail .test-name, 214 | #qunit-tests .fail .module-name { color: #000000; } 215 | 216 | #qunit-tests .fail .test-actual { color: #EE5757; } 217 | #qunit-tests .fail .test-expected { color: green; } 218 | 219 | #qunit-banner.qunit-fail { background-color: #EE5757; } 220 | 221 | 222 | /** Result */ 223 | 224 | #qunit-testresult { 225 | padding: 0.5em 0.5em 0.5em 2.5em; 226 | 227 | color: #2b81af; 228 | background-color: #D2E0E6; 229 | 230 | border-bottom: 1px solid white; 231 | } 232 | #qunit-testresult .module-name { 233 | font-weight: bold; 234 | } 235 | 236 | /** Fixture */ 237 | 238 | #qunit-fixture { 239 | position: absolute; 240 | top: -10000px; 241 | left: -10000px; 242 | width: 1000px; 243 | height: 1000px; 244 | } 245 | --------------------------------------------------------------------------------