-
Upload files
-
+
```
-
+---
+
+
+
### Link
+Button like link.
+
```html
-
- Upload photo
-
+
+ Upload photo
+
```
+---
+
+
+## Installation, testing, assembling
+`npm install fileapi`
+`cd fileapi`
+`npm install`
+`grunt`
+
---
+
## Changelog
- * + iOS fix (https://github.com/blueimp/JavaScript-Load-Image)
- * [#121](https://github.com/mailru/FileAPI/issues/121): + FileAPI.`postNameConcat:Function(name, idx)`
- * [#116](https://github.com/mailru/FileAPI/issues/116): + `cache:false` option for FileAPI.upload
+
+### 2.0.20
+
+
+
+### 2.0.19
+
+ #367: * [flash] allow gif and bmp to resize
+
+
+
+### 2.0.18
+
+ #364: * Camera#stop
+ #363: * support `Blob` in `FileAPI.getInfo`
+ #361: + upload zero-files
+
+
+
+### 2.0.16-2.0.17
+
+ #353: debug mode vs. IE
+ #352: correct filename via flash-uploading
+
+
+
+### 2.0.12-2.0.15 (!)
+
+ #346, #342, #344: fixes for XSS into Flash-transport
+
+
+
+### 2.0.11
+
+ #322, #308: dnd & safari + $.fn.dnd (store all dropped items)
+ #319: NodeJS tesing
+ #317, #313: fixed "malformed entry.name (OSX Unicode NFD)"
+ #311: fixed "Arithmetic result exceeded 32 bits"
+
+
+
+### 2.0.10
+
+ #289: * WebCam & html5 == false
+ #199, #265: flash fix 2015 error with BitmapData
+ #177: IE9, IE11 flash.camera remembered settigns
+ #254: check 'onLoadFnName' before call
+ #272: fixed `entry.createReader().readEntries`
+
+
+
+### 2.0.9
+
+ #253: fixed `proxyXHR.loaded`
+ #250: + check `disabled`-attr
+
+
+
+### 2.0.8
+
+ Two new resize strategies `width` and `height`
+
+
+
+### 2.0.7
+
+ #214: iframe transport under IE8
+ Fixed iframe-transport (remove `disabled`-attr for input)
+
+
+
+### 2.0.6
+
+ #240: Fixed `FileAPI.event.dnd.off`
+
+
+
+### 2.0.5
+
+ + #228: check callbacks with regexp
+ * Updated devDepending
+ + #207: support EXIF.Orientation == 4, 5 & 7
+
+
+
+### 2.0.4
+
+ + #176: Add params to the beginning of form
+ + #190: Add 204 as a successful response
+ + #192: many bugfixes; + `retry` & `multipass` options; + QUnit-tests for BigSizeImage
+
+
+### 2.0.3
+
+ + QUnit-tests for iframe-transport
+ + `postMessage` for iframe-transport
+ + `jsonp: "callback"` option
+ * resize: `imageTransform.type` rename to `imageTransform.strategy` (!!!)
+ + https://github.com/mailru/FileAPI/pull/165 (#140: fix)
+
+
+### 2.0.2
+
+ + test: upload headers
+ + test: upload + camanjs
+ + test: upload + autoOrientation
+ FileAPI.class.php: + HTTP header Content-Type: application/json
+ #143: + `FileAPI.flashWebcamUrl` option
+ * merge v1.2.7
+ + `FileAPI.formData: true` option
+
+
+### 2.0.1
+
+ + support 'filter' prop in imageTransform
+
+
+### 2.0.0
+
+ + FileAPI.Camera (HTML5 and Flash fallback)
+ + jquery.fileapi.js, see demo
+ + npm support
+ + grunt support
+ + requirejs support
+ + [#80](https://https://github.com/mailru/FileAPI/issues/80): FileAPI.Image.fn.overlay
+ `imageTransform` — now supports: `crop`, `type`, `quality` and `overlay` properties.
+ Improved the documentation
+ +iOS fix (https://github.com/blueimp/JavaScript-Load-Image)
+ [#121](https://github.com/mailru/FileAPI/issues/121): + FileAPI.`postNameConcat:Function(name, idx)`
+ [#116](https://github.com/mailru/FileAPI/issues/116): + `cache:false` option for FileAPI.upload
+
### 1.2.6
- * [#91](https://github.com/mailru/FileAPI/issues/91): replace `new Image` to `FileAPI.newImage`
- * + FileAPI.withCredentials: true
- * [#90](https://github.com/mailru/FileAPI/issues/90): Fixed `progress` event
- * [#105](https://github.com/mailru/FileAPI/issues/105): Fixed `image/jpg` -> `image/jpeg`
- * [#108](https://github.com/mailru/FileAPI/issues/108): Check width/height before resize by type(min/max)
+
+ [#91](https://github.com/mailru/FileAPI/issues/91): replace `new Image` to `FileAPI.newImage`
+ + FileAPI.withCredentials: true
+ [#90](https://github.com/mailru/FileAPI/issues/90): Fixed `progress` event
+ [#105](https://github.com/mailru/FileAPI/issues/105): Fixed `image/jpg` -> `image/jpeg`
+ [#108](https://github.com/mailru/FileAPI/issues/108): Check width/height before resize by type(min/max)
+
### 1.2.5
- * [#86](https://github.com/mailru/FileAPI/issues/86): Smarter upload recovery
- * [#87](https://github.com/mailru/FileAPI/issues/87): Fixed upload files into browsers that do not support FormData
- * Fixed support "accept" attribute for Flash.
- * Fixed detection of HTML5 support for FireFox 3.6
- * + FileAPI.html5 option, default "true"
+
+ [#86](https://github.com/mailru/FileAPI/issues/86): Smarter upload recovery
+ [#87](https://github.com/mailru/FileAPI/issues/87): Fixed upload files into browsers that do not support FormData
+ Fixed support "accept" attribute for Flash.
+ Fixed detection of HTML5 support for FireFox 3.6
+ + FileAPI.html5 option, default "true"
+
### 1.2.4
- * Fixed auto orientation image by EXIF (Flash)
- * Fixed image dimensions after rotate (Flash)
- * [#82](https://github.com/mailru/FileAPI/issues/82): "undefined" data-fields cause exceptions
- * [#83](https://github.com/mailru/FileAPI/issues/83): Allow requests without files
- * [#84](https://github.com/mailru/FileAPI/pull/84): Fixed connection abort when waiting for connection recovery
+
+ Fixed auto orientation image by EXIF (Flash)
+ Fixed image dimensions after rotate (Flash)
+ [#82](https://github.com/mailru/FileAPI/issues/82): "undefined" data-fields cause exceptions
+ [#83](https://github.com/mailru/FileAPI/issues/83): Allow requests without files
+ [#84](https://github.com/mailru/FileAPI/pull/84): Fixed connection abort when waiting for connection recovery
+
### 1.2.3
- * [#77](https://github.com/mailru/FileAPI/pull/77): Fixed flash.abort(), [#75](https://github.com/mailru/FileAPI/issues/75)
- * - `FileAPI.addMime`
- * + `FileAPI.accept` — fallback for flash.
+
+ [#77](https://github.com/mailru/FileAPI/pull/77): Fixed flash.abort(), [#75](https://github.com/mailru/FileAPI/issues/75)
+ - `FileAPI.addMime`
+ + `FileAPI.accept` — fallback for flash.
+
### 1.2.2
- * [#67](https://github.com/mailru/FileAPI/pull/67): Added correct httpStatus for upload fail, [#62](https://github.com/mailru/FileAPI/pull/68)
- * [#68](https://github.com/mailru/FileAPI/pull/68) Added "Content-Type" for chunked upload, [#65](https://github.com/mailru/FileAPI/pull/65)
- * [#69](https://github.com/mailru/FileAPI/issues/69): Fixed network down recovery
- * Fixed progress event, [#66](https://github.com/mailru/FileAPI/issues/66)
- * Increase flash stage size, [#73](https://github.com/mailru/FileAPI/pull/73)
- * - array index from POST-param "name", [#72](https://github.com/mailru/FileAPI/issues/72)
- * - dependency on FileAPI.Image for FileAPI.Flash
+
+ [#67](https://github.com/mailru/FileAPI/pull/67): Added correct httpStatus for upload fail, [#62](https://github.com/mailru/FileAPI/pull/68)
+ [#68](https://github.com/mailru/FileAPI/pull/68) Added "Content-Type" for chunked upload, [#65](https://github.com/mailru/FileAPI/pull/65)
+ [#69](https://github.com/mailru/FileAPI/issues/69): Fixed network down recovery
+ Fixed progress event, [#66](https://github.com/mailru/FileAPI/issues/66)
+ Increase flash stage size, [#73](https://github.com/mailru/FileAPI/pull/73)
+ - array index from POST-param "name", [#72](https://github.com/mailru/FileAPI/issues/72)
+ - dependency on FileAPI.Image for FileAPI.Flash
+
### 1.2.1
- * [#64](https://github.com/mailru/FileAPI/issues/64): Bufixed for [#63](https://github.com/mailru/FileAPI/issues/63)
-
+
+ [#64](https://github.com/mailru/FileAPI/issues/64): Bufixed for [#63](https://github.com/mailru/FileAPI/issues/63)
+
### 1.2.0
- * [#57](https://github.com/mailru/FileAPI/issues/57): Chunked file upload
+
+ [#57](https://github.com/mailru/FileAPI/issues/57): Chunked file upload
+
### 1.1.0
- * [#54](https://github.com/mailru/FileAPI/issues/54): added `FileAPI.flashUrl` and `FileAPI.flashImageUrl`
+
+ [#54](https://github.com/mailru/FileAPI/issues/54): added `FileAPI.flashUrl` and `FileAPI.flashImageUrl`
+
### 1.0.1
- * [#51](https://github.com/mailru/FileAPI/issues/51): remove circular references from `file-objects` (Flash transport)
- * added `changelog`
+
+ [#51](https://github.com/mailru/FileAPI/issues/51): remove circular references from `file-objects` (Flash transport)
+ added `changelog`
+
### 1.0.0
- * first release
+
diff --git a/README.ru.md b/README.ru.md
new file mode 100644
index 00000000..0a278886
--- /dev/null
+++ b/README.ru.md
@@ -0,0 +1,1383 @@
+
+## FileAPI
+Набор JavaScript инструментов для работы с файлами.
+
+
+### Get started
+
+```html
+
+
+
+
+
+```
+
+---
+
+
+### Setup options
+Отредактируйте файл `crossdomain.xml` и разместите его в корне домена, на который будут загружаться файлы.
+
+```html
+
+
+
+
+
+
+```
+
+---
+
+
+
+### getFiles(input`:HTMLInputElement|Event|$.Event`)`:Array`
+Получить список файлов из `input` элемента, или `event`, также поддерживается `jQuery`.
+
+* input — `HTMLInputElement`, `change` и `drop` события, `jQuery` коллекция или `jQuery.Event`
+
+```js
+var el = document.getElement('my-input');
+FileAPI.event.on(el, function (evt/**Event*/){
+ // Получить список файлов из `input`
+ var files = FileAPI.getFiles(el);
+
+ // или события
+ var files = FileAPI.getFiles(evt);
+});
+```
+
+---
+
+
+### getInfo(file`:Object`, callback`:Function`)`:void`
+Получить информацию о файле (см. FileAPI.addInfoReader).
+
+* file — объект файла (https://developer.mozilla.org/en-US/docs/DOM/File)
+* callback — функция, вызывается по завершению сбора информации
+
+```js
+// Получить информацию о изображении (FileAPI.exif.js подключен)
+FileAPI.getInfo(file, function (err/**String*/, info/**Object*/){
+ if( !err ){
+ console.log(info); // { width: 800, height: 600, exif: {..} }
+ }
+});
+
+// Получить информацию о mp3 файле (FileAPI.id3.js included)
+FileAPI.getInfo(file, function (err/**String*/, info/**Object*/){
+ if( !err ){
+ console.log(info); // { title: "...", album: "...", artists: "...", ... }
+ }
+});
+```
+
+---
+
+
+### filterFiles(files`:Array`, filter`:Function`, callback`:Function`)`:void`
+Отфильтровать список файлов, используя дополнительную информацию о них.
+см. FileAPI.getInfo или FileAPI.addInfoReader.
+
+* files — оригинальный список файлов
+* filter — функция, принимает два аргумента: `file` — сам файл, `info` — дополнительная информация
+* callback — функция: `list` — список файлов, подошедшие под условия, `other` — все остальные.
+
+```js
+// Получаем список файлов
+var files = FileAPI.getFiles(input);
+
+// Фильтруем список
+FileAPI.filterFiles(files, function (file/**Object*/, info/**Object*/){
+ if( /^image/.test(file.type) && info ){
+ return info.width > 320 && info.height > 240;
+ } else {
+ return file.size < 20 * FileAPI.MB;
+ }
+}, function (list/**Array*/, other/**Array*/){
+ if( list.length ){
+ // ..
+ }
+});
+```
+
+---
+
+
+### getDropFiles(evt`:Event|$.Event`, callback`:Function`)`:void`
+Получить весь список файлов, включая директории.
+
+* evt — `drop` event
+* callback — функция, принимает один аргумент — список файлов
+
+```js
+FileAPI.event.on(document, 'drop', function (evt/**Event*/){
+ evt.preventDefault();
+
+ // Получаем все файлы
+ FileAPI.getDropFiles(evt, function (files/**Array*/){
+ // ...
+ });
+});
+```
+
+---
+
+
+### upload(opts`:Object`)`:XmlHttpRequest`
+Загрузка файлов на сервер (последовательно). Возвращает XHR-подобный объект.
+Помните, для корректной работы flash-транспорта, тело ответа сервера не должно быть пустым,
+например можно ответить простым текстом "ok".
+
+* opts — объект настроек, см. раздел [Upload options](#options)
+
+```js
+var el = document.getElementById('my-input');
+FileAPI.event.on(el, 'change', function (evt/**Event*/){
+ var files = FileAPI.getFiles(evt);
+ var xhr = FileAPI.upload({
+ url: 'http://rubaxa.org/FileAPI/server/ctrl.php',
+ files: { file: files[0] },
+ complete: function (err, xhr){
+ if( !err ){
+ var result = xhr.responseText;
+ // ...
+ }
+ }
+ });
+});
+```
+
+---
+
+
+### addInfoReader(mime`:RegExp`, handler`:Function`)`:void`
+Добавить обработчик, для сбора информации о файле.
+см. также: FileAPI.getInfo и FileAPI.filterFiles.
+
+* mime — маска mime-type
+* handler — функция, принимает два аргумента: `file` объект и `complete` функция обратного вызова
+
+```js
+FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
+ // http://www.nihilogic.dk/labs/exif/exif.js
+ // http://www.nihilogic.dk/labs/binaryajax/binaryajax.js
+ FileAPI.readAsBinaryString(file, function (evt/**Object*/){
+ if( evt.type == 'load' ){
+ var binaryString = evt.result;
+ var oFile = new BinaryFile(binaryString, 0, file.size);
+ var exif = EXIF.readFromBinaryFile(oFile);
+ callback(false, { 'exif': exif || {} });
+ }
+ else if( evt.type == 'error' ){
+ callback('read_as_binary_string');
+ }
+ else if( evt.type == 'progress' ){
+ // ...
+ }
+ });
+});
+```
+
+---
+
+
+### readAsDataURL(file`:Object`, callback`:Function`)`:void`
+Чтение содержимого указанного файла как dataURL.
+
+* file — файл для чтения
+* callback — функция обработчик
+
+```js
+FileAPI.readAsDataURL(file, function (evt/**Object*/){
+ if( evt.type == 'load' ){
+ // Всё хорошо
+ var dataURL = evt.result;
+ } else if( evt.type =='progress' ){
+ var pr = evt.loaded/evt.total * 100;
+ } else {
+ // Ошибка
+ }
+})
+```
+
+---
+
+
+### readAsBinaryString(file`:Object`, callback`:Function`)`:void`
+Чтение содержимого указанного файла как `BinaryString`.
+
+* file — файл для чтения
+* callback — функция обработчик
+
+```js
+FileAPI.readAsBinaryString(file, function (evt/**Object*/){
+ if( evt.type == 'load' ){
+ // Всё хорошо
+ var binaryString = evt.result;
+ } else if( evt.type =='progress' ){
+ var pr = evt.loaded/evt.total * 100;
+ } else {
+ // Ошибка
+ }
+})
+```
+
+---
+
+
+### readAsArrayBuffer(file`:Object`, callback`:Function`)`:void`
+Чтение содержимого указанного файла как `ArrayBuffer`.
+
+* file — файл для чтения
+* callback — функция обработчик
+
+```js
+FileAPI.readAsArrayBuffer(file, function (evt/**Object*/){
+ if( evt.type == 'load' ){
+ // Всё хорошо
+ var arrayBuffer = evt.result;
+ } else if( evt.type =='progress' ){
+ var pr = evt.loaded/evt.total * 100;
+ } else {
+ // Ошибка
+ }
+})
+```
+
+---
+
+
+### readAsText(file`:Object`, callback`:Function`)`:void`
+Чтение содержимого указанного файла как `text`.
+
+* file — файл для чтения
+* callback — функция обработчик
+
+```js
+FileAPI.readAsText(file, function (evt/**Object*/){
+ if( evt.type == 'load' ){
+ // Всё хорошо
+ var text = evt.result;
+ } else if( evt.type =='progress' ){
+ var pr = evt.loaded/evt.total * 100;
+ } else {
+ // Ошибка
+ }
+})
+```
+
+---
+
+
+### readAsText(file`:Object`, encoding`:String`, callback`:Function`)`:void`
+Чтение содержимого указанного файла как `text` в нужной кодировке.
+
+* encoding — строкой с указанием кодировки. По умолчанию UTF-8.
+
+```js
+FileAPI.readAsText(file, "utf-8", function (evt/**Object*/){
+ if( evt.type == 'load' ){
+ // Всё хорошо
+ var text = evt.result;
+ } else if( evt.type =='progress' ){
+ var pr = evt.loaded/evt.total * 100;
+ } else {
+ // Ошибка
+ }
+})
+```
+
+---
+
+
+
+## Опции загрузки
+
+
+### url`:String`
+Строка, содержащая адрес, на который отправляется запрос.
+
+---
+
+
+### data`:Object`
+Дополнительные данные, которые должны быть отправлены вместе с файлом.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ data: { 'session-id': 123 },
+ files: { ... },
+});
+```
+
+---
+
+
+### uploadMethod`:String`
+Метод запроса, только HTML5.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ uploadMethod: 'PUT',
+ files: { ... },
+});
+```
+
+---
+
+
+### uploadCredentials`:Boolean`
+Передавать ли куки в запросе, только HTML5.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ uploadCredentials: false,
+ files: { ... },
+});
+```
+
+---
+
+
+### headers`:Object`
+Дополнительные заголовки запроса, только HTML5.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ headers: { 'x-upload': 'fileapi' },
+ files: { .. },
+});
+```
+
+---
+
+
+### chunkSize`:Number`
+Размер части файла в байтах, только HTML5.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { images: fileList },
+ chunkSize: 0.5 * FileAPI.MB
+});
+```
+
+---
+
+
+### chunkUploadRetry`:Number`
+Количество попыток загрузки одной части, только HTML5.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { images: fileList },
+ chunkSize: 0.5 * FileAPI.MB,
+ chunkUploadRetry: 3
+});
+```
+
+--
+
+
+### imageTransform`:Object`
+Правила модификации оригинально изображения.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { image: imageFiles },
+ // Changes the original image
+ imageTransform: {
+ // Ресайз по боьшой строне
+ maxWidth: 800,
+ maxHeight: 600,
+ // Добавляем водяной знак
+ overlay: [{ x: 10, y: 10, src: '/i/watemark.png', rel: FileAPI.Image.RIGHT_BOTTOM }]
+ }
+});
+```
+
+--
+
+
+### imageTransform`:Object`
+Правила для нарезки дополнительных изображения на клиенте.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { image: imageFiles },
+ imageTransform: {
+ // Ресайз по большой строне
+ 'huge': { maxWidth: 800, maxHeight: 600 },
+ // Ресайз и кроп
+ 'medium': { width: 320, height: 240, preview: true },
+ // ресайз и кроп + водяной знак
+ 'small': {
+ width: 100, height: 100,
+ // Добавляем водяной знак
+ overlay: [{ x: 5, y: 5, src: '/i/watemark.png', rel: FileAPI.Image.RIGHT_BOTTOM }]
+ }
+ }
+});
+```
+
+--
+
+
+### imageTransform`:Object`
+Конвертация всех изображений в jpeg или png.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { image: imageFiles },
+ imageTransform: {
+ type: 'image/jpeg',
+ quality: 0.86 // качество jpeg
+ }
+});
+```
+
+
+### imageOriginal`:Boolean`
+Отправлять исходное изображение на сервер или нет, если определен `imageTransform` вариант.
+
+--
+
+
+### imageAutoOrientation`:Boolean`
+Автоматический поворот изображения на основе EXIF.
+
+--
+
+
+### prepare`:Function`
+Подготовка опций загрузки для конкретного файла.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { .. }
+ prepare: function (file/**Object*/, options/**Object*/){
+ options.data.secret = utils.getSecretKey(file.name);
+ }
+});
+```
+
+--
+
+
+### upload`:Function`
+Начало загрузки
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { .. }
+ upload: function (xhr/**Object*/, options/**Object*/){
+ // ...
+ }
+});
+```
+
+--
+
+
+### fileupload`:Function`
+Начало загрузки файла
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { .. }
+ fileupload: function (file/**Object*/, xhr/**Object*/, options/**Object*/){
+ // ...
+ }
+});
+```
+
+--
+
+
+### progress`:Function`
+Общий прогресс загрузки файлов.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { .. }
+ progress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){
+ var pr = evt.loaded/evt.total * 100;
+ }
+});
+```
+
+--
+
+
+### fileprogress`:Function`
+Прогресс загрузки файла.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { .. }
+ fileprogress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){
+ var pr = evt.loaded/evt.total * 100;
+ }
+});
+```
+
+--
+
+
+### complete`:Function`
+Завершение загрузки всех файлов.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { .. }
+ complete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){
+ if( !err ){
+ // Все файлы загружены успешно
+ }
+ }
+});
+```
+
+--
+
+
+### filecomplete`:Function`
+Конец загрузки файла.
+
+```js
+var xhr = FileAPI.upload({
+ url: '...',
+ files: { .. }
+ filecomplete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){
+ if( !err ){
+ // Файл загружен успешно
+ var result = xhr.responseText;
+ }
+ }
+});
+```
+
+---
+
+
+## File object
+
+
+### name
+Имя файла.
+
+
+### type
+MIME type
+
+
+### size
+Размер файла в байтах.
+
+
+---
+
+
+
+## FileAPI.event
+
+
+### on(el`:HTMLElement`, events`:String`, handler`:Function`)`:void`
+Добавить функцию обработки события.
+
+* el — DOM элемент.
+* events — одно или нескольких разделенных пробелами типов событий.
+* handler — функция обработчик события.
+
+---
+
+
+### off(el`:HTMLElement`, events`:String`, handler`:Function`)`:void`
+Удалить обработчик события.
+
+* el — DOM элемент
+* events — одно или нескольких разделенных пробелами типов событий.
+* handler — функции обработчика ранее назначения на `event`.
+
+---
+
+
+### one(el`:HTMLElement`, events`:String`, handler`:Function`)`:void`
+Добавить функцию обработки события. Обработчик выполняется не более одного раза.
+
+* el — DOM элемент.
+* events — одно или нескольких разделенных пробелами типов событий.
+* handler — функция обработчик события.
+
+---
+
+
+### dnd(el`:HTMLElement`, hover`:Function`, handler`:Function`)`:void`
+Добавить функцию обработки событий `drag` и `drop`.
+
+* el — DOM элемент
+* hover — `dragenter` и `dragleave` слушатель
+* handler — обработчик события `drop`
+
+```js
+var el = document.getElementById('dropzone');
+FileAPI.event.dnd(el, function (over){
+ el.style.backgroundColor = over ? '#f60': '';
+}, function (files){
+ if( files.length ){
+ // Загружаем их.
+ }
+});
+
+// или jQuery
+$('#dropzone').dnd(hoverFn, dropFn);
+```
+
+---
+
+
+### dnd.off(el`:HTMLElement`, hover`:Function`, handler`:Function`)`:void`
+Удалить функцию обработки событий `drag` и `drop`.
+
+* el — DOM элемент
+* hover — `dragenter` и `dragleave` слушатель
+* handler — обработчик события `drop`
+
+```js
+// Native
+FileAPI.event.dnd.off(el, hoverFn, dropFn);
+
+// jQuery
+$('#dropzone').dndoff(hoverFn, dropFn);
+```
+
+--
+
+
+## FileAPI.Image
+Класс для работы с изображениями
+
+### constructor(file`:Object`)`:void`
+Конструктор получает только один параметр, файл.
+
+* file — файл изображения
+
+```js
+FileAPI.Image(imageFile).get(function (err/**String*/, img/**HTMLElement*/){
+ if( !err ){
+ document.body.appendChild( img );
+ }
+});
+```
+
+---
+
+
+### crop(width`:Number`, height`:Number`)`:FileAPI.Image`
+Кроп изображения по ширине и высоте.
+
+* width — новая ширина изображения
+* height — новая высота изображения
+
+```js
+FileAPI.Image(imageFile)
+ .crop(640, 480)
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+```
+
+### crop(x`:Number`, y`:Number`, width`:Number`, height`:Number`)`:FileAPI.Image`
+Кроп изображения по ширине и высоте, а также смещению по x и y.
+
+* x — смещение относительно по x левого угла
+* y — смещение относительно по y левого угла
+
+```js
+FileAPI.Image(imageFile)
+ .crop(100, 50, 320, 240)
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+```
+
+---
+
+
+### resize(width`:Number`, height`:Number`[, strategy`:String`])`:FileAPI.Image`
+Ресайз.
+
+* width — новая ширина
+* height — новая высота
+* strategy — enum: `min`, `max`, `preview`, `width`, `height`. По умолчанию `undefined`.
+
+```js
+FileAPI.Image(imageFile)
+ .resize(320, 240)
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+
+// По большей стороне
+FileAPI.Image(imageFile)
+ .resize(320, 240, 'max')
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+
+// По заданной высоте.
+FileAPI.Image(imageFile)
+ .resize(240, 'height')
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+```
+
+---
+
+
+### preview(width`:Number`[, height`:Number`])`:FileAPI.Image`
+Кроп и ресайз изображения.
+
+* width — новая ширина
+* height — новая высота
+
+```js
+FileAPI.Image(imageFile)
+ .preview(100, 100)
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+```
+
+---
+
+
+### rotate(deg`:Number`)`:FileAPI.Image`
+Поворот.
+
+* deg — угол поворота в градусах
+
+```js
+FileAPI.Image(imageFile)
+ .rotate(90)
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+```
+
+---
+
+
+### filter(callback`:Function`)`:FileAPI.Image`
+Применить фильтр функцию. Только `HTML5`.
+
+* callback — принимает два рагумента, `canvas` элемент и метод `done`.
+
+```js
+FileAPI.Image(imageFile)
+ .filter(function (canvas/**HTMLCanvasElement*/, doneFn/**Function*/){
+ // бла-бла-бла
+ doneFn(); // вызываем по завершению
+ })
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+```
+
+
+---
+
+### filter(name`:String`)`:FileAPI.Image`
+Используется [CamanJS](http://camanjs.com/), подключите его перед библиотекой FileAPI.
+
+* name — название CamanJS фильтра (произвольный, либо предустановленный)
+
+```js
+Caman.Filter.register("my-funky-filter", function () {
+ // http://camanjs.com/guides/#Extending
+});
+
+FileAPI.Image(imageFile)
+ .filter("my-funky-filter") // или .filter("vintage")
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+```
+
+---
+
+
+### overlay(images`:Array`)`:FileAPI.Image`
+Добавить наложение, например: водяной знак.
+
+* images — массив наложений
+
+```js
+FileAPI.Image(imageFile)
+ .overlay([
+ // Левый угл.
+ { x: 10, y: 10, w: 100, h: 10, src: '/i/watermark.png' },
+
+ // Правый нижний угл.
+ { x: 10, y: 10, src: '/i/watermark.png', rel: FileAPI.Image.RIGHT_BOTTOM }
+ ])
+ .get(function (err/**String*/, img/**HTMLElement*/){
+
+ })
+;
+```
+
+---
+
+
+### get(fn`:Function`)`:FileAPI.Image`
+Получить итоговое изображение.
+
+* fn — функция обратного вызова
+
+---
+
+
+## FileAPI.Camera
+Для работы с веб-камерой, обязательно установить параметр `FileAPI.media: true`.
+
+
+
+### publish(el`:HTMLElement`, options`:Object`, callback`:Function`)`:void`
+Публикация камеры.
+
+* el — куда публикуем
+* options — { `width: 100%`, `height: 100%`, `start: true` }
+* callback — первый параметр возможная ошибка, второй экземпляр FileAPI.Camera
+
+```js
+var el = document.getElementById('cam');
+FileAPI.Camera.publish(el, { width: 320, height: 240 }, function (err, cam/**FileAPI.Camera*/){
+ if( !err ){
+ // Камера готова, можно использовать
+ }
+});
+```
+
+---
+
+
+### start(callback`:Function`)`:void`
+Включить камеру
+
+* callback — будет вызван в момент готовности камеры
+
+```js
+var el = document.getElementById('cam');
+FileAPI.Camera.publish(el, { start: false }, function (err, cam/**FileAPI.Camera*/){
+ if( !err ){
+ // Включаем камеру
+ cam.start(function (err){
+ if( !err ){
+ // камера готова к использованию
+ }
+ });
+ }
+});
+```
+
+---
+
+
+### stop()`:void`
+Выключить камеру
+
+---
+
+
+### shot()`:FileAPI.Image`
+Сделать снимок с камеры
+
+```js
+var el = document.getElementById('cam');
+FileAPI.Camera.publish(el, function (err, cam/**FileAPI.Camera*/){
+ if( !err ){
+ var shot = cam.shot(); // делаем снимок
+
+ // создаем предпросмотр 100x100
+ shot.preview(100).get(function (err, img){
+ previews.appendChild(img);
+ });
+
+ // и/или загружаем
+ FileAPI.upload({
+ url: '...',
+ files: { cam: shot
+ });
+ }
+});
+```
+
+---
+
+
+
+## Константы
+
+
+### FileAPI.KB`:Number`
+1024 байт
+
+
+### FileAPI.MB`:Number`
+1048576 байт
+
+
+### FileAPI.GB`:Number`
+1073741824 байт
+
+
+### FileAPI.TB`:Number`
+1.0995116e+12 байт
+
+---
+
+
+## Utils
+
+
+### FileAPI.each(obj`:Object|Array`, callback`:Function`[, thisObject`:Mixed`])`:void`
+Перебор объект или массив, выполняя функцию для каждого элемента.
+
+* obj — массив или объект
+* callback — функция, выполняется для каждого элемента.
+* thisObject — объект для использования в качестве `this` при выполнении `callback`.
+
+--
+
+
+### FileAPI.extend(dst`:Object`, src`:Object`)`:Object`
+Объединить содержимое двух объектов вместе.
+
+* dst — объект, который получит новые свойства
+* src — объект, содержащий дополнительные свойства для объединения
+
+--
+
+
+### FileAPI.filter(array`:Array`, callback`:Function`[, thisObject`:Mixed`)`:Object`
+Создает новый массив со всеми элементами, которые соответствуют условиям.
+
+* array — оригинальный массив
+* callback — функция для проверки каждого элемента массива.
+* thisObject — объект для использования в качестве `this` при выполнении `callback`.
+
+---
+
+
+## Support
+
+ Multiupload: все браузеры поддерживающие HTML5 или Flash
+ Drag'n'Drop загрузка: файлы (HTML5) и директории (Chrome 21+)
+ Загрузка файлов по частям, только HTML5
+ Загрузка одно файла: все браузеры, даже очень старые
+
+ Работа с изображениями: IE6+, FF 3.6+, Chrome 10+, Opera 11.1+, Safari 5.4+
+
+ crop, resize, preview & rotate (HTML5 или Flash)
+ авто ориентация на основе EXIF (HTML5, если подключен FileAPI.exif.js или Flash)
+
+
+
+
+
+### FileAPI.support.html5`:Boolean`
+Поддержка HTML5.
+
+
+### FileAPI.support.cors`:Boolean`
+Поддержка кроссдоменных запросов.
+
+
+### FileAPI.support.dnd`:Boolean`
+Поддержка Drag'n'drop событий.
+
+
+### FileAPI.support.flash`:Boolean`
+Наличие Flash плагина.
+
+
+### FileAPI.support.canvas`:Boolean`
+Поддержка canvas.
+
+
+### FileAPI.support.dataURI`:Boolean`
+Поддержка dataURI в качестве src для изображений.
+
+
+### FileAPI.support.chunked`:Boolean`
+Возможность загрузки по частям.
+
+---
+
+
+## Flash
+Флеш очень "глючная" штука :]
+Поэтому в случае успешной загрузки http status должен быть только `200 OK`.
+
+
+### Settings
+Настройки для flash части.
+Желательно, разместить flash на том же сервере, куда будут загружаться файлы.
+
+```html
+
+
+```
+
+---
+
+
+### crossdomain.xml
+Обязательно создайте этот файл на сервере, куда будут загружаться файлы.
+Не забудьте заменить `youdomain.com` на имя вашего домена.
+
+```xml
+
+
+
+
+
+
+
+
+```
+
+---
+
+
+### request
+Пример запроса, который отправляет flash player.
+
+```xml
+POST /server/ctrl.php HTTP/1.1
+Accept: text/*
+Content-Type: multipart/form-data;
+boundary=----------Ij5ae0ae0KM7GI3KM7
+User-Agent: Shockwave Flash
+Host: www.youdomain.com
+Content-Length: 421
+Connection: Keep-Alive
+Cache-Control: no-cache
+
+------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
+Content-Disposition: form-data; name="Filename"
+
+MyFile.jpg
+------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
+Content-Disposition: form-data; name="Filedata"; filename="MyFile.jpg"
+Content-Type: application/octet-stream
+
+[[..FILE_DATA_HERE..]]
+------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7
+Content-Disposition: form-data; name="Upload"
+
+Submit Query
+------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7--
+```
+
+---
+
+
+### Security
+По умолчанию `FileAPI.flash.swf` разрешает доступ с любых доменов `Security.allowDomain("*")`.
+Это может привести к уязвимости same origin bypass, если flash лежит на том же домене, что и критичные данные.
+Чтобы этого избежать, нужно разрешить доступ только к своим доменам [здесь](https://github.com/mailru/FileAPI/blob/master/flash/core/src/ru/mail/communication/JSCallbackPresenter.as#L25) и пересобрать flash.
+
+---
+
+
+## Server settings
+
+
+### IFrame/JSONP
+
+```php
+
+
+
+
+ FileAPI::OK
+ , 'statusText' => 'OK'
+ , 'body' => array('count' => sizeof($files))
+ ), $jsonp);
+ exit;
+ }
+?>
+```
+
+---
+
+
+### CORS
+Включение CORS.
+
+```php
+
+### Chunked file upload
+Всё общение между клиентом и сервером ведётся на уровне HTTP заголовков.
+Для передачи отдельного chunk'а клиент устанавливает заголовки:
+
+ Content-Range: bytes <start-offset>-<end-offset>/<total>
+ Content-Disposition: attachment; filename=<file-name>
+
+Другие заголовки не используются, отслеживание уникальности имени передаваемого файла не реализуется и оставлено на усмотрение разработчика.
+В ответ на передаваемый chunk сервер может отвечать следующими кодами:
+
+ 200, 201 — chunk сохранён успешно
+ 416, 500 — восстановимая ошибка
+
+Остальные коды — фатальная ошибка, требуется вмешательство пользователя.
+
+
+
+---
+
+## Buttons examples
+
+### Base
+Простой input[type="file"]
+
+```html
+
+
+
+```
+
+---
+
+### Button
+Стилизованная кнопка.
+
+```html
+
+
+```
+
+---
+
+### Link
+Кнопка в виде ссылки
+
+```html
+
+
+ Upload photo
+
+
+```
+
+
+
+
diff --git a/bower.json b/bower.json
new file mode 100644
index 00000000..7143a66a
--- /dev/null
+++ b/bower.json
@@ -0,0 +1,27 @@
+{
+ "name": "fileapi",
+ "main": [
+ "./dist/FileAPI.flash.camera.swf",
+ "./dist/FileAPI.flash.image.swf",
+ "./dist/FileAPI.flash.swf",
+ "./dist/FileAPI.html5.js",
+ "./dist/FileAPI.js",
+ "./dist/jquery.fileapi.min.js"
+ ],
+ "dependencies": {
+ "jquery":">=1.8.2"
+ },
+ "ignore": [
+ "custom-tasks/",
+ "flash/",
+ "lib/",
+ "plugins/",
+ "server/",
+ "statics/",
+ "tests/",
+ "package.json",
+ "bower.json",
+ "README.md",
+ ".*"
+ ]
+}
diff --git a/custom-tasks/Gruntfile-ok.js b/custom-tasks/Gruntfile-ok.js
new file mode 100644
index 00000000..1ff81290
--- /dev/null
+++ b/custom-tasks/Gruntfile-ok.js
@@ -0,0 +1,51 @@
+'use strict';
+
+module.exports = function (grunt) {
+ // Project configuration.
+ grunt.config.set('concat.ok', {
+ src: [
+ 'lib/FileAPI.header.js'
+ , 'lib/canvas-to-blob.js'
+ , 'lib/FileAPI.core.js'
+ , 'lib/FileAPI.Image.js'
+ , 'lib/load-image-ios.js'
+ , 'lib/FileAPI.Form.js'
+ , 'lib/FileAPI.XHR.js'
+ , 'lib/FileAPI.Flash.js'
+ , 'plugins/FileAPI.exif.js'
+ ],
+ dest: 'dist/<%= pkg.exportName %>.ok.js'
+ });
+ grunt.config.set('concat.html5ok', {
+ src: [
+ 'lib/FileAPI.header.js'
+ , 'lib/canvas-to-blob.js'
+ , 'lib/FileAPI.core.js'
+ , 'lib/FileAPI.Image.js'
+ , 'lib/load-image-ios.js'
+ , 'lib/FileAPI.Form.js'
+ , 'lib/FileAPI.XHR.js'
+ , 'plugins/FileAPI.exif.js'
+ ],
+ dest: 'dist/<%= pkg.exportName %>.html5ok.js'
+ });
+
+ grunt.config.set('uglify.distok', {
+ files: {
+ 'dist/<%= pkg.exportName %>.ok.min.js': ['<%= concat.ok.dest %>'], 'dist/<%= pkg.exportName %>.html5ok.min.js': ['<%= concat.html5ok.dest %>']
+ }
+ });
+
+ grunt.config.set('compress.main', {
+ options: {
+ archive: '<%= pkg.name %>-<%= pkg.version.replace(/\\./g,"-") %>.zip'
+ },
+ files: [
+ {cwd: 'dist/', expand: true, src: ['*'], dest: '<%= pkg.version.replace(/\\./g,"-") %>/'}
+ ]
+ });
+
+
+ grunt.registerTask('build-zip', ['build', 'compress']);
+
+};
diff --git a/dist/FileAPI.flash.camera.swf b/dist/FileAPI.flash.camera.swf
new file mode 100644
index 00000000..4c953e95
Binary files /dev/null and b/dist/FileAPI.flash.camera.swf differ
diff --git a/dist/FileAPI.flash.image.swf b/dist/FileAPI.flash.image.swf
new file mode 100644
index 00000000..001ff0f9
Binary files /dev/null and b/dist/FileAPI.flash.image.swf differ
diff --git a/dist/FileAPI.flash.swf b/dist/FileAPI.flash.swf
new file mode 100644
index 00000000..738f60d4
Binary files /dev/null and b/dist/FileAPI.flash.swf differ
diff --git a/dist/FileAPI.html5.js b/dist/FileAPI.html5.js
new file mode 100644
index 00000000..0ac8698e
--- /dev/null
+++ b/dist/FileAPI.html5.js
@@ -0,0 +1,3613 @@
+/*! FileAPI 2.1.1 - BSD | git://github.com/mailru/FileAPI.git
+ * 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.
+ */
+
+/*
+ * JavaScript Canvas to Blob 2.0.5
+ * https://github.com/blueimp/JavaScript-Canvas-to-Blob
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Based on stackoverflow user Stoive's code snippet:
+ * http://stackoverflow.com/q/4998908
+ */
+
+/*jslint nomen: true, regexp: true */
+/*global window, atob, Blob, ArrayBuffer, Uint8Array */
+
+(function (window) {
+ 'use strict';
+ var CanvasPrototype = window.HTMLCanvasElement &&
+ window.HTMLCanvasElement.prototype,
+ hasBlobConstructor = window.Blob && (function () {
+ try {
+ return Boolean(new Blob());
+ } catch (e) {
+ return false;
+ }
+ }()),
+ hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
+ (function () {
+ try {
+ return new Blob([new Uint8Array(100)]).size === 100;
+ } catch (e) {
+ return false;
+ }
+ }()),
+ BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
+ window.MozBlobBuilder || window.MSBlobBuilder,
+ dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
+ window.ArrayBuffer && window.Uint8Array && function (dataURI) {
+ var byteString,
+ arrayBuffer,
+ intArray,
+ i,
+ mimeString,
+ bb;
+ if (dataURI.split(',')[0].indexOf('base64') >= 0) {
+ // Convert base64 to raw binary data held in a string:
+ byteString = atob(dataURI.split(',')[1]);
+ } else {
+ // Convert base64/URLEncoded data component to raw binary data:
+ byteString = decodeURIComponent(dataURI.split(',')[1]);
+ }
+ // Write the bytes of the string to an ArrayBuffer:
+ arrayBuffer = new ArrayBuffer(byteString.length);
+ intArray = new Uint8Array(arrayBuffer);
+ for (i = 0; i < byteString.length; i += 1) {
+ intArray[i] = byteString.charCodeAt(i);
+ }
+ // Separate out the mime component:
+ mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
+ // Write the ArrayBuffer (or ArrayBufferView) to a blob:
+ if (hasBlobConstructor) {
+ return new Blob(
+ [hasArrayBufferViewSupport ? intArray : arrayBuffer],
+ {type: mimeString}
+ );
+ }
+ bb = new BlobBuilder();
+ bb.append(arrayBuffer);
+ return bb.getBlob(mimeString);
+ };
+ if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
+ if (CanvasPrototype.mozGetAsFile) {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
+ callback(dataURLtoBlob(this.toDataURL(type, quality)));
+ } else {
+ callback(this.mozGetAsFile('blob', type));
+ }
+ };
+ } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ callback(dataURLtoBlob(this.toDataURL(type, quality)));
+ };
+ }
+ }
+ window.dataURLtoBlob = dataURLtoBlob;
+})(window);
+
+/*jslint evil: true */
+/*global window, URL, webkitURL, ActiveXObject */
+
+(function (window, undef){
+ 'use strict';
+
+ var
+ gid = 1,
+ noop = function (){},
+
+ document = window.document,
+ doctype = document.doctype || {},
+ userAgent = window.navigator.userAgent,
+ safari = /safari\//i.test(userAgent) && !/chrome\//i.test(userAgent),
+ iemobile = /iemobile\//i.test(userAgent),
+ insecureChrome = !safari && /chrome\//i.test(userAgent) && window.location.protocol === 'http:',
+
+ // https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
+ apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
+
+ Blob = window.Blob,
+ File = window.File,
+ FileReader = window.FileReader,
+ FormData = window.FormData,
+
+
+ XMLHttpRequest = window.XMLHttpRequest,
+ jQuery = window.jQuery,
+
+ html5 = !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
+ && !(safari && /windows/i.test(userAgent) && !iemobile), // BugFix: https://github.com/mailru/FileAPI/issues/25
+
+ cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
+
+ chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
+
+ normalize = ('' + ''.normalize).indexOf('[native code]') > 0,
+
+ // https://github.com/blueimp/JavaScript-Canvas-to-Blob
+ dataURLtoBlob = window.dataURLtoBlob,
+
+
+ _rimg = /img/i,
+ _rcanvas = /canvas/i,
+ _rimgcanvas = /img|canvas/i,
+ _rinput = /input/i,
+ _rdata = /^data:[^,]+,/,
+
+ _toString = {}.toString,
+ _supportConsoleLog,
+ _supportConsoleLogApply,
+
+
+ Math = window.Math,
+
+ _SIZE_CONST = function (pow){
+ pow = new window.Number(Math.pow(1024, pow));
+ pow.from = function (sz){ return Math.round(sz * this); };
+ return pow;
+ },
+
+ _elEvents = {}, // element event listeners
+ _infoReader = [], // list of file info processors
+
+ _readerEvents = 'abort progress error load loadend',
+ _xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
+
+ currentTarget = 'currentTarget', // for minimize
+ preventDefault = 'preventDefault', // and this too
+
+ _isArray = function (ar) {
+ return ar && ('length' in ar);
+ },
+
+ /**
+ * Iterate over a object or array
+ */
+ _each = function (obj, fn, ctx){
+ if( obj ){
+ if( _isArray(obj) ){
+ for( var i = 0, n = obj.length; i < n; i++ ){
+ if( i in obj ){
+ fn.call(ctx, obj[i], i, obj);
+ }
+ }
+ }
+ else {
+ for( var key in obj ){
+ if( obj.hasOwnProperty(key) ){
+ fn.call(ctx, obj[key], key, obj);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Merge the contents of two or more objects together into the first object
+ */
+ _extend = function (dst){
+ var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
+ for( ; i < args.length; i++ ){
+ _each(args[i], _ext);
+ }
+ return dst;
+ },
+
+ /**
+ * Add event listener
+ */
+ _on = function (el, type, fn){
+ if( el ){
+ var uid = api.uid(el);
+
+ if( !_elEvents[uid] ){
+ _elEvents[uid] = {};
+ }
+
+ var isFileReader = (FileReader && el) && (el instanceof FileReader);
+ _each(type.split(/\s+/), function (type){
+ if( jQuery && !isFileReader){
+ jQuery.event.add(el, type, fn);
+ } else {
+ if( !_elEvents[uid][type] ){
+ _elEvents[uid][type] = [];
+ }
+
+ _elEvents[uid][type].push(fn);
+
+ if( el.addEventListener ){ el.addEventListener(type, fn, false); }
+ else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
+ else { el['on'+type] = fn; }
+ }
+ });
+ }
+ },
+
+
+ /**
+ * Remove event listener
+ */
+ _off = function (el, type, fn){
+ if( el ){
+ var uid = api.uid(el), events = _elEvents[uid] || {};
+
+ var isFileReader = (FileReader && el) && (el instanceof FileReader);
+ _each(type.split(/\s+/), function (type){
+ if( jQuery && !isFileReader){
+ jQuery.event.remove(el, type, fn);
+ }
+ else {
+ var fns = events[type] || [], i = fns.length;
+
+ while( i-- ){
+ if( fns[i] === fn ){
+ fns.splice(i, 1);
+ break;
+ }
+ }
+
+ if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
+ else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
+ else { el['on'+type] = null; }
+ }
+ });
+ }
+ },
+
+
+ _one = function(el, type, fn){
+ _on(el, type, function _(evt){
+ _off(el, type, _);
+ fn(evt);
+ });
+ },
+
+
+ _fixEvent = function (evt){
+ if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
+ if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
+ return evt;
+ },
+
+
+ _supportInputAttr = function (attr){
+ var input = document.createElement('input');
+ input.setAttribute('type', "file");
+ return attr in input;
+ },
+
+
+ /**
+ * FileAPI (core object)
+ */
+ api = {
+ version: '2.1.1',
+
+ cors: false,
+ html5: true,
+ media: false,
+ formData: true,
+ multiPassResize: true,
+ insecureChrome: insecureChrome,
+
+ debug: false,
+ pingUrl: false,
+ multiFlash: false,
+ flashAbortTimeout: 0,
+ withCredentials: true,
+
+ staticPath: './dist/',
+
+ flashUrl: 0, // @default: './FileAPI.flash.swf'
+ flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
+
+ postNameConcat: function (name, idx){
+ return name + (idx != null ? '['+ idx +']' : '');
+ },
+
+ ext2mime: {
+ jpg: 'image/jpeg'
+ , tif: 'image/tiff'
+ , txt: 'text/plain'
+ },
+
+ // Fallback for flash
+ accept: {
+ 'image/*': 'art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd'
+ , 'audio/*': 'm4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs'
+ , 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl'
+ },
+
+ uploadRetry : 0,
+ networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
+
+ chunkSize : 0,
+ chunkUploadRetry : 0,
+ chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
+
+ KB: _SIZE_CONST(1),
+ MB: _SIZE_CONST(2),
+ GB: _SIZE_CONST(3),
+ TB: _SIZE_CONST(4),
+
+ EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
+
+ expando: 'fileapi' + (new Date).getTime(),
+
+ uid: function (obj){
+ return obj
+ ? (obj[api.expando] = obj[api.expando] || api.uid())
+ : (++gid, api.expando + gid)
+ ;
+ },
+
+ log: function (){
+ if( api.debug && _supportConsoleLog ){
+ if( _supportConsoleLogApply ){
+ console.log.apply(console, arguments);
+ }
+ else {
+ console.log([].join.call(arguments, ' '));
+ }
+ }
+ },
+
+ /**
+ * Create new image
+ *
+ * @param {String} [src]
+ * @param {Function} [fn] 1. error -- boolean, 2. img -- Image element
+ * @returns {HTMLElement}
+ */
+ newImage: function (src, fn){
+ var img = document.createElement('img');
+ if( fn ){
+ api.event.one(img, 'error load', function (evt){
+ fn(evt.type == 'error', img);
+ img = null;
+ });
+ }
+ img.src = src;
+ return img;
+ },
+
+ /**
+ * Get XHR
+ * @returns {XMLHttpRequest}
+ */
+ getXHR: function (){
+ var xhr;
+
+ if( XMLHttpRequest ){
+ xhr = new XMLHttpRequest;
+ }
+ else if( window.ActiveXObject ){
+ try {
+ xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
+ } catch (e) {
+ xhr = new ActiveXObject('Microsoft.XMLHTTP');
+ }
+ }
+
+ return xhr;
+ },
+
+ isArray: _isArray,
+
+ support: {
+ dnd: cors && ('ondrop' in document.createElement('div')),
+ cors: cors,
+ html5: html5,
+ chunked: chunked,
+ dataURI: true,
+ accept: _supportInputAttr('accept'),
+ multiple: _supportInputAttr('multiple')
+ },
+
+ event: {
+ on: _on
+ , off: _off
+ , one: _one
+ , fix: _fixEvent
+ },
+
+
+ throttle: function(fn, delay) {
+ var id, args;
+
+ return function _throttle(){
+ args = arguments;
+
+ if( !id ){
+ fn.apply(window, args);
+ id = setTimeout(function (){
+ id = 0;
+ fn.apply(window, args);
+ }, delay);
+ }
+ };
+ },
+
+
+ F: function (){},
+
+
+ parseJSON: function (str){
+ var json;
+ if( window.JSON && JSON.parse ){
+ json = JSON.parse(str);
+ }
+ else {
+ json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
+ }
+ return json;
+ },
+
+
+ trim: function (str){
+ str = String(str);
+ return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
+ },
+
+ /**
+ * Simple Defer
+ * @return {Object}
+ */
+ defer: function (){
+ var
+ list = []
+ , result
+ , error
+ , defer = {
+ resolve: function (err, res){
+ defer.resolve = noop;
+ error = err || false;
+ result = res;
+
+ while( res = list.shift() ){
+ res(error, result);
+ }
+ },
+
+ then: function (fn){
+ if( error !== undef ){
+ fn(error, result);
+ } else {
+ list.push(fn);
+ }
+ }
+ };
+
+ return defer;
+ },
+
+ queue: function (fn){
+ var
+ _idx = 0
+ , _length = 0
+ , _fail = false
+ , _end = false
+ , queue = {
+ inc: function (){
+ _length++;
+ },
+
+ next: function (){
+ _idx++;
+ setTimeout(queue.check, 0);
+ },
+
+ check: function (){
+ (_idx >= _length) && !_fail && queue.end();
+ },
+
+ isFail: function (){
+ return _fail;
+ },
+
+ fail: function (){
+ !_fail && fn(_fail = true);
+ },
+
+ end: function (){
+ if( !_end ){
+ _end = true;
+ fn();
+ }
+ }
+ }
+ ;
+ return queue;
+ },
+
+
+ /**
+ * For each object
+ *
+ * @param {Object|Array} obj
+ * @param {Function} fn
+ * @param {*} [ctx]
+ */
+ each: _each,
+
+
+ /**
+ * Async for
+ * @param {Array} array
+ * @param {Function} callback
+ */
+ afor: function (array, callback){
+ var i = 0, n = array.length;
+
+ if( _isArray(array) && n-- ){
+ (function _next(){
+ callback(n != i && _next, array[i], i++);
+ })();
+ }
+ else {
+ callback(false);
+ }
+ },
+
+
+ /**
+ * Merge the contents of two or more objects together into the first object
+ *
+ * @param {Object} dst
+ * @return {Object}
+ */
+ extend: _extend,
+
+
+ /**
+ * Is file?
+ * @param {File} file
+ * @return {Boolean}
+ */
+ isFile: function (file){
+ return _toString.call(file) === '[object File]';
+ },
+
+
+ /**
+ * Is blob?
+ * @param {Blob} blob
+ * @returns {Boolean}
+ */
+ isBlob: function (blob) {
+ return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
+ },
+
+
+ /**
+ * Is canvas element
+ *
+ * @param {HTMLElement} el
+ * @return {Boolean}
+ */
+ isCanvas: function (el){
+ return el && _rcanvas.test(el.nodeName);
+ },
+
+
+ getFilesFilter: function (filter){
+ filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
+ return filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
+ },
+
+
+
+ /**
+ * Read as DataURL
+ *
+ * @param {File|Element} file
+ * @param {Function} fn
+ */
+ readAsDataURL: function (file, fn){
+ if( api.isCanvas(file) ){
+ _emit(file, fn, 'load', api.toDataURL(file));
+ }
+ else {
+ _readAs(file, fn, 'DataURL');
+ }
+ },
+
+
+ /**
+ * Read as Binary string
+ *
+ * @param {File} file
+ * @param {Function} fn
+ */
+ readAsBinaryString: function (file, fn){
+ if( _hasSupportReadAs('BinaryString') ){
+ _readAs(file, fn, 'BinaryString');
+ } else {
+ // Hello IE10!
+ _readAs(file, function (evt){
+ if( evt.type == 'load' ){
+ try {
+ // dataURL -> binaryString
+ evt.result = api.toBinaryString(evt.result);
+ } catch (e){
+ evt.type = 'error';
+ evt.message = e.toString();
+ }
+ }
+ fn(evt);
+ }, 'DataURL');
+ }
+ },
+
+
+ /**
+ * Read as ArrayBuffer
+ *
+ * @param {File} file
+ * @param {Function} fn
+ */
+ readAsArrayBuffer: function(file, fn){
+ _readAs(file, fn, 'ArrayBuffer');
+ },
+
+
+ /**
+ * Read as text
+ *
+ * @param {File} file
+ * @param {String} encoding
+ * @param {Function} [fn]
+ */
+ readAsText: function(file, encoding, fn){
+ if( !fn ){
+ fn = encoding;
+ encoding = 'utf-8';
+ }
+
+ _readAs(file, fn, 'Text', encoding);
+ },
+
+
+ /**
+ * Convert image or canvas to DataURL
+ *
+ * @param {Element} el Image or Canvas element
+ * @param {String} [type] mime-type
+ * @return {String}
+ */
+ toDataURL: function (el, type){
+ if( typeof el == 'string' ){
+ return el;
+ }
+ else if( el.toDataURL ){
+ return el.toDataURL(type || 'image/png');
+ }
+ },
+
+
+ /**
+ * Canvert string, image or canvas to binary string
+ *
+ * @param {String|Element} val
+ * @return {String}
+ */
+ toBinaryString: function (val){
+ return window.atob(api.toDataURL(val).replace(_rdata, ''));
+ },
+
+
+ /**
+ * Read file or DataURL as ImageElement
+ *
+ * @param {File|String} file
+ * @param {Function} fn
+ * @param {Boolean} [progress]
+ */
+ readAsImage: function (file, fn, progress){
+ if( api.isBlob(file) ){
+ if( apiURL ){
+ /** @namespace apiURL.createObjectURL */
+ var data = apiURL.createObjectURL(file);
+ if( data === undef ){
+ _emit(file, fn, 'error');
+ }
+ else {
+ api.readAsImage(data, fn, progress);
+ }
+ }
+ else {
+ api.readAsDataURL(file, function (evt){
+ if( evt.type == 'load' ){
+ api.readAsImage(evt.result, fn, progress);
+ }
+ else if( progress || evt.type == 'error' ){
+ _emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
+ }
+ });
+ }
+ }
+ else if( api.isCanvas(file) ){
+ _emit(file, fn, 'load', file);
+ }
+ else if( _rimg.test(file.nodeName) ){
+ if( file.complete ){
+ _emit(file, fn, 'load', file);
+ }
+ else {
+ var events = 'error abort load';
+ _one(file, events, function _fn(evt){
+ if( evt.type == 'load' && apiURL ){
+ /** @namespace apiURL.revokeObjectURL */
+ apiURL.revokeObjectURL(file.src);
+ }
+
+ _off(file, events, _fn);
+ _emit(file, fn, evt, file);
+ });
+ }
+ }
+ else if( file.iframe ){
+ _emit(file, fn, { type: 'error' });
+ }
+ else {
+ // Created image
+ var img = api.newImage(file.dataURL || file);
+ api.readAsImage(img, fn, progress);
+ }
+ },
+
+
+ /**
+ * Make file by name
+ *
+ * @param {String} name
+ * @return {Array}
+ */
+ checkFileObj: function (name){
+ var file = {}, accept = api.accept;
+
+ if( typeof name == 'object' ){
+ file = name;
+ }
+ else {
+ file.name = (name + '').split(/\\|\//g).pop();
+ }
+
+ if( file.type == null ){
+ file.type = file.name.split('.').pop();
+ }
+
+ _each(accept, function (ext, type){
+ ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
+ if( ext.test(file.type) || api.ext2mime[file.type] ){
+ file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
+ }
+ });
+
+ return file;
+ },
+
+
+ /**
+ * Get drop files
+ *
+ * @param {Event} evt
+ * @param {Function} callback
+ */
+ getDropFiles: function (evt, callback){
+ var
+ files = []
+ , all = []
+ , items
+ , dataTransfer = _getDataTransfer(evt)
+ , transFiles = dataTransfer.files
+ , transItems = dataTransfer.items
+ , entrySupport = _isArray(transItems) && transItems[0] && _getAsEntry(transItems[0])
+ , queue = api.queue(function (){ callback(files, all); })
+ ;
+
+ if( entrySupport ){
+ if( normalize && transFiles ){
+ var
+ i = transFiles.length
+ , file
+ , entry
+ ;
+
+ items = new Array(i);
+ while( i-- ){
+ file = transFiles[i];
+
+ try {
+ entry = _getAsEntry(transItems[i]);
+ }
+ catch( err ){
+ api.log('[err] getDropFiles: ', err);
+ entry = null;
+ }
+
+ if( _isEntry(entry) ){
+ // OSX filesystems use Unicode Normalization Form D (NFD),
+ // and entry.file(…) can't read the files with the same names
+ if( entry.isDirectory || (entry.isFile && file.name == file.name.normalize('NFC')) ){
+ items[i] = entry;
+ }
+ else {
+ items[i] = file;
+ }
+ }
+ else {
+ items[i] = file;
+ }
+ }
+ }
+ else {
+ items = transItems;
+ }
+ }
+ else {
+ items = transFiles;
+ }
+
+ _each(items || [], function (item){
+ queue.inc();
+
+ try {
+ if( entrySupport && _isEntry(item) ){
+ _readEntryAsFiles(item, function (err, entryFiles, allEntries){
+ if( err ){
+ api.log('[err] getDropFiles:', err);
+ } else {
+ files.push.apply(files, entryFiles);
+ }
+ all.push.apply(all, allEntries);
+
+ queue.next();
+ });
+ }
+ else {
+ _isRegularFile(item, function (yes, err){
+ if( yes ){
+ files.push(item);
+ }
+ else {
+ item.error = err;
+ }
+ all.push(item);
+
+ queue.next();
+ });
+ }
+ }
+ catch( err ){
+ queue.next();
+ api.log('[err] getDropFiles: ', err);
+ }
+ });
+
+ queue.check();
+ },
+
+
+ /**
+ * Get file list
+ *
+ * @param {HTMLInputElement|Event} input
+ * @param {String|Function} [filter]
+ * @param {Function} [callback]
+ * @return {Array|Null}
+ */
+ getFiles: function (input, filter, callback){
+ var files = [];
+
+ if( callback ){
+ api.filterFiles(api.getFiles(input), filter, callback);
+ return null;
+ }
+
+ if( input.jquery ){
+ // jQuery object
+ input.each(function (){
+ files = files.concat(api.getFiles(this));
+ });
+ input = files;
+ files = [];
+ }
+
+ if( typeof filter == 'string' ){
+ filter = api.getFilesFilter(filter);
+ }
+
+ if( input.originalEvent ){
+ // jQuery event
+ input = _fixEvent(input.originalEvent);
+ }
+ else if( input.srcElement ){
+ // IE Event
+ input = _fixEvent(input);
+ }
+
+
+ if( input.dataTransfer ){
+ // Drag'n'Drop
+ input = input.dataTransfer;
+ }
+ else if( input.target ){
+ // Event
+ input = input.target;
+ }
+
+ if( input.files ){
+ // Input[type="file"]
+ files = input.files;
+
+ if( !html5 ){
+ // Partial support for file api
+ files[0].blob = input;
+ files[0].iframe = true;
+ }
+ }
+ else if( !html5 && isInputFile(input) ){
+ if( api.trim(input.value) ){
+ files = [api.checkFileObj(input.value)];
+ files[0].blob = input;
+ files[0].iframe = true;
+ }
+ }
+ else if( _isArray(input) ){
+ files = input;
+ }
+
+ return api.filter(files, function (file){ return !filter || filter.test(file.name); });
+ },
+
+
+ /**
+ * Get total file size
+ * @param {Array} files
+ * @return {Number}
+ */
+ getTotalSize: function (files){
+ var size = 0, i = files && files.length;
+ while( i-- ){
+ size += files[i].size;
+ }
+ return size;
+ },
+
+
+ /**
+ * Get image information
+ *
+ * @param {File} file
+ * @param {Function} fn
+ */
+ getInfo: function (file, fn){
+ var info = {}, readers = _infoReader.concat();
+
+ if( api.isBlob(file) ){
+ (function _next(){
+ var reader = readers.shift();
+ if( reader ){
+ if( reader.test(file.type) ){
+ reader(file, function (err, res){
+ if( err ){
+ fn(err);
+ }
+ else {
+ _extend(info, res);
+ _next();
+ }
+ });
+ }
+ else {
+ _next();
+ }
+ }
+ else {
+ fn(false, info);
+ }
+ })();
+ }
+ else {
+ fn('not_support_info', info);
+ }
+ },
+
+
+ /**
+ * Add information reader
+ *
+ * @param {RegExp} mime
+ * @param {Function} fn
+ */
+ addInfoReader: function (mime, fn){
+ fn.test = function (type){ return mime.test(type); };
+ _infoReader.push(fn);
+ },
+
+
+ /**
+ * Filter of array
+ *
+ * @param {Array} input
+ * @param {Function} fn
+ * @return {Array}
+ */
+ filter: function (input, fn){
+ var result = [], i = 0, n = input.length, val;
+
+ for( ; i < n; i++ ){
+ if( i in input ){
+ val = input[i];
+ if( fn.call(val, val, i, input) ){
+ result.push(val);
+ }
+ }
+ }
+
+ return result;
+ },
+
+
+ /**
+ * Filter files
+ *
+ * @param {Array} files
+ * @param {Function} eachFn
+ * @param {Function} resultFn
+ */
+ filterFiles: function (files, eachFn, resultFn){
+ if( files.length ){
+ // HTML5 or Flash
+ var queue = files.concat(), file, result = [], deleted = [];
+
+ (function _next(){
+ if( queue.length ){
+ file = queue.shift();
+ api.getInfo(file, function (err, info){
+ (eachFn(file, err ? false : info) ? result : deleted).push(file);
+ _next();
+ });
+ }
+ else {
+ resultFn(result, deleted);
+ }
+ })();
+ }
+ else {
+ resultFn([], files);
+ }
+ },
+
+
+ upload: function (options){
+ options = _extend({
+ jsonp: 'callback'
+ , prepare: api.F
+ , beforeupload: api.F
+ , upload: api.F
+ , fileupload: api.F
+ , fileprogress: api.F
+ , filecomplete: api.F
+ , progress: api.F
+ , complete: api.F
+ , pause: api.F
+ , imageOriginal: true
+ , chunkSize: api.chunkSize
+ , chunkUploadRetry: api.chunkUploadRetry
+ , uploadRetry: api.uploadRetry
+ }, options);
+
+
+ if( options.imageAutoOrientation && !options.imageTransform ){
+ options.imageTransform = { rotate: 'auto' };
+ }
+
+
+ var
+ proxyXHR = new api.XHR(options)
+ , dataArray = this._getFilesDataArray(options.files)
+ , _this = this
+ , _total = 0
+ , _loaded = 0
+ , _nextFile
+ , _complete = false
+ ;
+
+
+ // calc total size
+ _each(dataArray, function (data){
+ _total += data.size;
+ });
+
+ // Array of files
+ proxyXHR.files = [];
+ _each(dataArray, function (data){
+ proxyXHR.files.push(data.file);
+ });
+
+ // Set upload status props
+ proxyXHR.total = _total;
+ proxyXHR.loaded = 0;
+ proxyXHR.filesLeft = dataArray.length;
+
+ // emit "beforeupload" event
+ options.beforeupload(proxyXHR, options);
+
+ // Upload by file
+ _nextFile = function (){
+ var
+ data = dataArray.shift()
+ , _file = data && data.file
+ , _fileLoaded = false
+ , _fileOptions = _simpleClone(options)
+ ;
+
+ proxyXHR.filesLeft = dataArray.length;
+
+ if( _file && _file.name === api.expando ){
+ _file = null;
+ api.log('[warn] FileAPI.upload() — called without files');
+ }
+
+ if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
+ // Mark active job
+ _complete = false;
+
+ // Set current upload file
+ proxyXHR.currentFile = _file;
+
+ // Prepare file options
+ if (_file && options.prepare(_file, _fileOptions) === false) {
+ _nextFile.call(_this);
+ return;
+ }
+ _fileOptions.file = _file;
+
+ _this._getFormData(_fileOptions, data, function (form){
+ if( !_loaded ){
+ // emit "upload" event
+ options.upload(proxyXHR, options);
+ }
+
+ var xhr = new api.XHR(_extend({}, _fileOptions, {
+
+ upload: _file ? function (){
+ // emit "fileupload" event
+ options.fileupload(_file, xhr, _fileOptions);
+ } : noop,
+
+ progress: _file ? function (evt){
+ if( !_fileLoaded ){
+ // For ignore the double calls.
+ _fileLoaded = (evt.loaded === evt.total);
+
+ // emit "fileprogress" event
+ options.fileprogress({
+ type: 'progress'
+ , total: data.total = evt.total
+ , loaded: data.loaded = evt.loaded
+ }, _file, xhr, _fileOptions);
+
+ // emit "progress" event
+ options.progress({
+ type: 'progress'
+ , total: _total
+ , loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total)) || 0
+ }, _file, xhr, _fileOptions);
+ }
+ } : noop,
+
+ complete: function (err){
+ _each(_xhrPropsExport, function (name){
+ proxyXHR[name] = xhr[name];
+ });
+
+ if( _file ){
+ data.total = (data.total || data.size);
+ data.loaded = data.total;
+
+ if( !err ) {
+ // emulate 100% "progress"
+ this.progress(data);
+
+ // fixed throttle event
+ _fileLoaded = true;
+
+ // bytes loaded
+ _loaded += data.size; // data.size != data.total, it's desirable fix this
+ proxyXHR.loaded = _loaded;
+ }
+
+ // emit "filecomplete" event
+ options.filecomplete(err, xhr, _file, _fileOptions);
+ }
+
+ // upload next file
+ setTimeout(function () {_nextFile.call(_this);}, 0);
+ }
+ })); // xhr
+
+
+ // ...
+ proxyXHR.abort = function (current){
+ if (!current) { dataArray.length = 0; }
+ this.current = current;
+ xhr.abort();
+ };
+
+ // Start upload
+ xhr.send(form);
+ });
+ }
+ else {
+ var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
+ options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
+ // Mark done state
+ _complete = true;
+ }
+ };
+
+
+ // Next tick
+ setTimeout(_nextFile, 0);
+
+
+ // Append more files to the existing request
+ // first - add them to the queue head/tail
+ proxyXHR.append = function (files, first) {
+ files = api._getFilesDataArray([].concat(files));
+
+ _each(files, function (data) {
+ _total += data.size;
+ proxyXHR.files.push(data.file);
+ if (first) {
+ dataArray.unshift(data);
+ } else {
+ dataArray.push(data);
+ }
+ });
+
+ proxyXHR.statusText = "";
+
+ if( _complete ){
+ _nextFile.call(_this);
+ }
+ };
+
+
+ // Removes file from queue by file reference and returns it
+ proxyXHR.remove = function (file) {
+ var i = dataArray.length, _file;
+ while( i-- ){
+ if( dataArray[i].file == file ){
+ _file = dataArray.splice(i, 1);
+ _total -= _file.size;
+ }
+ }
+ return _file;
+ };
+
+ return proxyXHR;
+ },
+
+
+ _getFilesDataArray: function (data){
+ var files = [], oFiles = {};
+
+ if( isInputFile(data) ){
+ var tmp = api.getFiles(data);
+ oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
+ }
+ else if( _isArray(data) && isInputFile(data[0]) ){
+ _each(data, function (input){
+ oFiles[input.name || 'file'] = api.getFiles(input);
+ });
+ }
+ else {
+ oFiles = data;
+ }
+
+ _each(oFiles, function add(file, name){
+ if( _isArray(file) ){
+ _each(file, function (file){
+ add(file, name);
+ });
+ }
+ else if( file && (file.name || file.image) ){
+ files.push({
+ name: name
+ , file: file
+ , size: file.size
+ , total: file.size
+ , loaded: 0
+ });
+ }
+ });
+
+ if( !files.length ){
+ // Create fake `file` object
+ files.push({ file: { name: api.expando } });
+ }
+
+ return files;
+ },
+
+
+ _getFormData: function (options, data, fn){
+ var
+ file = data.file
+ , name = data.name
+ , filename = file.name
+ , filetype = file.type
+ , trans = api.support.transform && options.imageTransform
+ , Form = new api.Form
+ , queue = api.queue(function (){ fn(Form); })
+ , isOrignTrans = trans && _isOriginTransform(trans)
+ , postNameConcat = api.postNameConcat
+ ;
+
+ // Append data
+ _each(options.data, function add(val, name){
+ if( typeof val == 'object' ){
+ _each(val, function (v, i){
+ add(v, postNameConcat(name, i));
+ });
+ }
+ else {
+ Form.append(name, val);
+ }
+ });
+
+ (function _addFile(file/**Object*/){
+ if( file.image ){ // This is a FileAPI.Image
+ queue.inc();
+
+ file.toData(function (err, image){
+ // @todo: требует рефакторинга и обработки ошибки
+ if (file.file) {
+ image.type = file.file.type;
+ image.quality = file.matrix.quality;
+ filename = file.file && file.file.name;
+ }
+
+ filename = filename || (new Date).getTime()+'.png';
+
+ _addFile(image);
+ queue.next();
+ });
+ }
+ else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
+ queue.inc();
+
+ if( isOrignTrans ){
+ // Convert to array for transform function
+ trans = [trans];
+ }
+
+ api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
+ if( isOrignTrans && !err ){
+ if( !dataURLtoBlob && !api.flashEngine ){
+ // Canvas.toBlob or Flash not supported, use multipart
+ Form.multipart = true;
+ }
+
+ Form.append(name, images[0], filename, trans[0].type || filetype);
+ }
+ else {
+ var addOrigin = 0;
+
+ if( !err ){
+ _each(images, function (image, idx){
+ if( !dataURLtoBlob && !api.flashEngine ){
+ Form.multipart = true;
+ }
+
+ if( !trans[idx].postName ){
+ addOrigin = 1;
+ }
+
+ Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
+ });
+ }
+
+ if( err || options.imageOriginal ){
+ Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
+ }
+ }
+
+ queue.next();
+ });
+ }
+ else if( filename !== api.expando ){
+ Form.append(name, file, filename);
+ }
+ })(file);
+
+ queue.check();
+ },
+
+
+ reset: function (inp, notRemove){
+ var parent, clone;
+
+ if( jQuery ){
+ clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
+ if( !notRemove ){
+ jQuery(inp).remove();
+ }
+ } else {
+ parent = inp.parentNode;
+ clone = parent.insertBefore(inp.cloneNode(true), inp);
+ clone.value = '';
+
+ if( !notRemove ){
+ parent.removeChild(inp);
+ }
+
+ _each(_elEvents[api.uid(inp)], function (fns, type){
+ _each(fns, function (fn){
+ _off(inp, type, fn);
+ _on(clone, type, fn);
+ });
+ });
+ }
+
+ return clone;
+ },
+
+
+ /**
+ * Load remote file
+ *
+ * @param {String} url
+ * @param {Function} fn
+ * @return {XMLHttpRequest}
+ */
+ load: function (url, fn){
+ var xhr = api.getXHR();
+ if( xhr ){
+ xhr.open('GET', url, true);
+
+ if( xhr.overrideMimeType ){
+ xhr.overrideMimeType('text/plain; charset=x-user-defined');
+ }
+
+ _on(xhr, 'progress', function (/**Event*/evt){
+ /** @namespace evt.lengthComputable */
+ if( evt.lengthComputable ){
+ fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
+ }
+ });
+
+ xhr.onreadystatechange = function(){
+ if( xhr.readyState == 4 ){
+ xhr.onreadystatechange = null;
+ if( xhr.status == 200 ){
+ url = url.split('/');
+ /** @namespace xhr.responseBody */
+ var file = {
+ name: url[url.length-1]
+ , size: xhr.getResponseHeader('Content-Length')
+ , type: xhr.getResponseHeader('Content-Type')
+ };
+ file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
+ fn({ type: 'load', result: file }, xhr);
+ }
+ else {
+ fn({ type: 'error' }, xhr);
+ }
+ }
+ };
+ xhr.send(null);
+ } else {
+ fn({ type: 'error' });
+ }
+
+ return xhr;
+ },
+
+ encode64: function (str){
+ var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
+
+ if( typeof str !== 'string' ){
+ str = String(str);
+ }
+
+ while( i < str.length ){
+ //all three "& 0xff" added below are there to fix a known bug
+ //with bytes returned by xhr.responseText
+ var
+ byte1 = str.charCodeAt(i++) & 0xff
+ , byte2 = str.charCodeAt(i++) & 0xff
+ , byte3 = str.charCodeAt(i++) & 0xff
+ , enc1 = byte1 >> 2
+ , enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
+ , enc3, enc4
+ ;
+
+ if( isNaN(byte2) ){
+ enc3 = enc4 = 64;
+ } else {
+ enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
+ enc4 = isNaN(byte3) ? 64 : byte3 & 63;
+ }
+
+ outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
+ }
+
+ return outStr;
+ }
+
+ } // api
+ ;
+
+
+ function _emit(target, fn, name, res, ext){
+ var evt = {
+ type: name.type || name
+ , target: target
+ , result: res
+ };
+ _extend(evt, ext);
+ fn(evt);
+ }
+
+
+ function _hasSupportReadAs(method){
+ return FileReader && !!FileReader.prototype['readAs' + method];
+ }
+
+
+ function _readAs(file, fn, method, encoding){
+ if( api.isBlob(file) && _hasSupportReadAs(method) ){
+ var Reader = new FileReader;
+
+ // Add event listener
+ _on(Reader, _readerEvents, function _fn(evt){
+ var type = evt.type;
+ if( type == 'progress' ){
+ _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
+ }
+ else if( type == 'loadend' ){
+ _off(Reader, _readerEvents, _fn);
+ Reader = null;
+ }
+ else {
+ _emit(file, fn, evt, evt.target.result);
+ }
+ });
+
+
+ try {
+ // ReadAs ...
+ if( encoding ){
+ Reader['readAs' + method](file, encoding);
+ }
+ else {
+ Reader['readAs' + method](file);
+ }
+ }
+ catch (err){
+ _emit(file, fn, 'error', undef, { error: err.toString() });
+ }
+ }
+ else {
+ _emit(file, fn, 'error', undef, { error: 'filreader_not_support_' + method });
+ }
+ }
+
+
+ function _isRegularFile(file, callback){
+ // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
+ if( !file.type && (safari || ((file.size % 4096) === 0 && (file.size <= 102400))) ){
+ if( FileReader ){
+ try {
+ var reader = new FileReader();
+
+ _one(reader, _readerEvents, function (evt){
+ var isFile = evt.type != 'error';
+ if( isFile ){
+ if ( reader.readyState == null || reader.readyState === reader.LOADING ) {
+ reader.abort();
+ }
+ callback(isFile);
+ }
+ else {
+ callback(false, reader.error);
+ }
+ });
+
+ reader.readAsDataURL(file);
+ } catch( err ){
+ callback(false, err);
+ }
+ }
+ else {
+ callback(null, new Error('FileReader is not supported'));
+ }
+ }
+ else {
+ callback(true);
+ }
+ }
+
+
+ function _isEntry(item){
+ return item && (item.isFile || item.isDirectory);
+ }
+
+
+ function _getAsEntry(item){
+ var entry;
+ if( item.getAsEntry ){ entry = item.getAsEntry(); }
+ else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
+ return entry;
+ }
+
+
+ function _readEntryAsFiles(entry, callback){
+ if( !entry ){
+ // error
+ var err = new Error('invalid entry');
+ entry = new Object(entry);
+ entry.error = err;
+ callback(err.message, [], [entry]);
+ }
+ else if( entry.isFile ){
+ // Read as file
+ entry.file(function (file){
+ // success
+ file.fullPath = entry.fullPath;
+ callback(false, [file], [file]);
+ }, function (err){
+ // error
+ entry.error = err;
+ callback('FileError.code: ' + err.code, [], [entry]);
+ });
+ }
+ else if( entry.isDirectory ){
+ var
+ reader = entry.createReader()
+ , firstAttempt = true
+ , files = []
+ , all = [entry]
+ ;
+
+ var onerror = function (err){
+ // error
+ entry.error = err;
+ callback('DirectoryError.code: ' + err.code, files, all);
+ };
+ var ondone = function ondone(entries){
+ if( firstAttempt ){
+ firstAttempt = false;
+ if( !entries.length ){
+ entry.error = new Error('directory is empty');
+ }
+ }
+
+ // success
+ if( entries.length ){
+ api.afor(entries, function (next, entry){
+ _readEntryAsFiles(entry, function (err, entryFiles, allEntries){
+ if( !err ){
+ files = files.concat(entryFiles);
+ }
+ all = all.concat(allEntries);
+
+ if( next ){
+ next();
+ }
+ else {
+ reader.readEntries(ondone, onerror);
+ }
+ });
+ });
+ }
+ else {
+ callback(false, files, all);
+ }
+ };
+
+ reader.readEntries(ondone, onerror);
+ }
+ else {
+ _readEntryAsFiles(_getAsEntry(entry), callback);
+ }
+ }
+
+
+ function _simpleClone(obj){
+ var copy = {};
+ _each(obj, function (val, key){
+ if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
+ val = _extend({}, val);
+ }
+ copy[key] = val;
+ });
+ return copy;
+ }
+
+
+ function isInputFile(el){
+ return _rinput.test(el && el.tagName);
+ }
+
+
+ function _getDataTransfer(evt){
+ return (evt.originalEvent || evt || '').dataTransfer || {};
+ }
+
+
+ function _isOriginTransform(trans){
+ var key;
+ for( key in trans ){
+ if( trans.hasOwnProperty(key) ){
+ if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ // Add default image info reader
+ api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
+ if( !file.__dimensions ){
+ var defer = file.__dimensions = api.defer();
+
+ api.readAsImage(file, function (evt){
+ var img = evt.target;
+ defer.resolve(evt.type == 'load' ? false : 'error', {
+ width: img.width
+ , height: img.height
+ });
+ img.src = api.EMPTY_PNG;
+ img = null;
+ });
+ }
+
+ file.__dimensions.then(callback);
+ });
+
+
+ /**
+ * Drag'n'Drop special event
+ *
+ * @param {HTMLElement} el
+ * @param {Function} onHover
+ * @param {Function} onDrop
+ */
+ api.event.dnd = function (el, onHover, onDrop){
+ var _id, _type;
+
+ if( !onDrop ){
+ onDrop = onHover;
+ onHover = api.F;
+ }
+
+ if( FileReader ){
+ // Hover
+ _on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
+ var
+ types = _getDataTransfer(evt).types
+ , i = types && types.length
+ , debounceTrigger = false
+ ;
+
+ while( i-- ){
+ if( ~types[i].indexOf('File') ){
+ evt[preventDefault]();
+
+ if( _type !== evt.type ){
+ _type = evt.type; // Store current type of event
+
+ if( _type != 'dragleave' ){
+ onHover.call(evt[currentTarget], true, evt);
+ }
+
+ debounceTrigger = true;
+ }
+
+ break; // exit from "while"
+ }
+ }
+
+ if( debounceTrigger ){
+ clearTimeout(_id);
+ _id = setTimeout(function (){
+ onHover.call(evt[currentTarget], _type != 'dragleave', evt);
+ }, 50);
+ }
+ });
+
+
+ // Drop
+ _on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
+ evt[preventDefault]();
+
+ _type = 0;
+
+ api.getDropFiles(evt, function (files, all){
+ onDrop.call(evt[currentTarget], files, all, evt);
+ });
+
+ onHover.call(evt[currentTarget], false, evt);
+ });
+ }
+ else {
+ api.log("Drag'n'Drop -- not supported");
+ }
+ };
+
+
+ /**
+ * Remove drag'n'drop
+ * @param {HTMLElement} el
+ * @param {Function} onHover
+ * @param {Function} onDrop
+ */
+ api.event.dnd.off = function (el, onHover, onDrop){
+ _off(el, 'dragenter dragleave dragover', onHover.ff);
+ _off(el, 'drop', onDrop.ff);
+ };
+
+
+ // Support jQuery
+ if( jQuery && !jQuery.fn.dnd ){
+ jQuery.fn.dnd = function (onHover, onDrop){
+ return this.each(function (){
+ api.event.dnd(this, onHover, onDrop);
+ });
+ };
+
+ jQuery.fn.offdnd = function (onHover, onDrop){
+ return this.each(function (){
+ api.event.dnd.off(this, onHover, onDrop);
+ });
+ };
+ }
+
+ // @export
+ window.FileAPI = _extend(api, window.FileAPI);
+
+
+ // Debug info
+ api.log('FileAPI: ' + api.version);
+ api.log('protocol: ' + window.location.protocol);
+ api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
+
+
+ // @detect 'x-ua-compatible'
+ _each(document.getElementsByTagName('meta'), function (meta){
+ if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
+ api.log('meta.http-equiv: ' + meta.getAttribute('content'));
+ }
+ });
+
+
+ // Configuration
+ try {
+ _supportConsoleLog = !!console.log;
+ _supportConsoleLogApply = !!console.log.apply;
+ }
+ catch (err) {}
+
+ if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
+ if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
+ if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
+})(window, void 0);
+
+/*global window, FileAPI, document */
+
+(function (api, document, undef) {
+ 'use strict';
+
+ var
+ min = Math.min,
+ round = Math.round,
+ getCanvas = function () { return document.createElement('canvas'); },
+ support = false,
+ exifOrientation = {
+ 8: 270
+ , 3: 180
+ , 6: 90
+ , 7: 270
+ , 4: 180
+ , 5: 90
+ }
+ ;
+
+ try {
+ support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
+ }
+ catch (e){}
+
+
+ function Image(file){
+ if( file instanceof Image ){
+ var img = new Image(file.file);
+ api.extend(img.matrix, file.matrix);
+ return img;
+ }
+ else if( !(this instanceof Image) ){
+ return new Image(file);
+ }
+
+ this.file = file;
+ this.size = file.size || 100;
+
+ this.matrix = {
+ sx: 0,
+ sy: 0,
+ sw: 0,
+ sh: 0,
+ dx: 0,
+ dy: 0,
+ dw: 0,
+ dh: 0,
+ resize: 0, // min, max OR preview
+ deg: 0,
+ quality: 1, // jpeg quality
+ filter: 0
+ };
+ }
+
+
+ Image.prototype = {
+ image: true,
+ constructor: Image,
+
+ set: function (attrs){
+ api.extend(this.matrix, attrs);
+ return this;
+ },
+
+ crop: function (x, y, w, h){
+ if( w === undef ){
+ w = x;
+ h = y;
+ x = y = 0;
+ }
+ return this.set({ sx: x, sy: y, sw: w, sh: h || w });
+ },
+
+ resize: function (w, h, strategy){
+ if( /min|max|height|width/.test(h) ){
+ strategy = h;
+ h = w;
+ }
+
+ return this.set({ dw: w, dh: h || w, resize: strategy });
+ },
+
+ preview: function (w, h){
+ return this.resize(w, h || w, 'preview');
+ },
+
+ rotate: function (deg){
+ return this.set({ deg: deg });
+ },
+
+ filter: function (filter){
+ return this.set({ filter: filter });
+ },
+
+ overlay: function (images){
+ return this.set({ overlay: images });
+ },
+
+ clone: function (){
+ return new Image(this);
+ },
+
+ _load: function (image, fn){
+ var self = this;
+
+ if( /img|video/i.test(image.nodeName) ){
+ fn.call(self, null, image);
+ }
+ else {
+ api.readAsImage(image, function (evt){
+ fn.call(self, evt.type != 'load', evt.result);
+ });
+ }
+ },
+
+ _apply: function (image, fn){
+ var
+ canvas = getCanvas()
+ , m = this.getMatrix(image)
+ , ctx = canvas.getContext('2d')
+ , width = image.videoWidth || image.width
+ , height = image.videoHeight || image.height
+ , deg = m.deg
+ , dw = m.dw
+ , dh = m.dh
+ , w = width
+ , h = height
+ , filter = m.filter
+ , copy // canvas copy
+ , buffer = image
+ , overlay = m.overlay
+ , queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
+ , renderImageToCanvas = api.renderImageToCanvas
+ ;
+
+ // Normalize angle
+ deg = deg - Math.floor(deg/360)*360;
+
+ // For `renderImageToCanvas`
+ image._type = this.file.type;
+
+ while(m.multipass && min(w/dw, h/dh) > 2 ){
+ w = (w/2 + 0.5)|0;
+ h = (h/2 + 0.5)|0;
+
+ copy = getCanvas();
+ copy.width = w;
+ copy.height = h;
+
+ if( buffer !== image ){
+ renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
+ buffer = copy;
+ }
+ else {
+ buffer = copy;
+ renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
+ m.sx = m.sy = m.sw = m.sh = 0;
+ }
+ }
+
+
+ canvas.width = (deg % 180) ? dh : dw;
+ canvas.height = (deg % 180) ? dw : dh;
+
+ canvas.type = m.type;
+ canvas.quality = m.quality;
+
+ ctx.rotate(deg * Math.PI / 180);
+ renderImageToCanvas(ctx.canvas, buffer
+ , m.sx, m.sy
+ , m.sw || buffer.width
+ , m.sh || buffer.height
+ , (deg == 180 || deg == 270 ? -dw : 0)
+ , (deg == 90 || deg == 180 ? -dh : 0)
+ , dw, dh
+ );
+
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+
+ dw = canvas.width;
+ dh = canvas.height;
+
+ // Apply overlay
+ overlay && api.each([].concat(overlay), function (over){
+ queue.inc();
+ // preload
+ var img = new window.Image, fn = function (){
+ var
+ x = over.x|0
+ , y = over.y|0
+ , w = over.w || img.width
+ , h = over.h || img.height
+ , rel = over.rel
+ ;
+
+ // center | right | left
+ x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
+
+ // center | bottom | top
+ y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
+
+ api.event.off(img, 'error load abort', fn);
+
+ try {
+ ctx.globalAlpha = over.opacity || 1;
+ ctx.drawImage(img, x, y, w, h);
+ }
+ catch (er){}
+
+ queue.next();
+ };
+
+ api.event.on(img, 'error load abort', fn);
+ img.src = over.src;
+
+ if( img.complete ){
+ fn();
+ }
+ });
+
+ if( filter ){
+ queue.inc();
+ Image.applyFilter(canvas, filter, queue.next);
+ }
+
+ queue.check();
+ },
+
+ getMatrix: function (image){
+ var
+ m = api.extend({}, this.matrix)
+ , sw = m.sw = m.sw || image.videoWidth || image.naturalWidth || image.width
+ , sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
+ , dw = m.dw = m.dw || sw
+ , dh = m.dh = m.dh || sh
+ , sf = sw/sh, df = dw/dh
+ , strategy = m.resize
+ ;
+
+ if( strategy == 'preview' ){
+ if( dw != sw || dh != sh ){
+ // Make preview
+ var w, h;
+
+ if( df >= sf ){
+ w = sw;
+ h = w / df;
+ } else {
+ h = sh;
+ w = h * df;
+ }
+
+ if( w != sw || h != sh ){
+ m.sx = ~~((sw - w)/2);
+ m.sy = ~~((sh - h)/2);
+ sw = w;
+ sh = h;
+ }
+ }
+ }
+ else if( strategy == 'height' ){
+ dw = dh * sf;
+ }
+ else if( strategy == 'width' ){
+ dh = dw / sf;
+ }
+ else if( strategy ){
+ if( !(sw > dw || sh > dh) ){
+ dw = sw;
+ dh = sh;
+ }
+ else if( strategy == 'min' ){
+ dw = round(sf < df ? min(sw, dw) : dh*sf);
+ dh = round(sf < df ? dw/sf : min(sh, dh));
+ }
+ else {
+ dw = round(sf >= df ? min(sw, dw) : dh*sf);
+ dh = round(sf >= df ? dw/sf : min(sh, dh));
+ }
+ }
+
+ m.sw = sw;
+ m.sh = sh;
+ m.dw = dw;
+ m.dh = dh;
+ m.multipass = api.multiPassResize;
+ return m;
+ },
+
+ _trans: function (fn){
+ this._load(this.file, function (err, image){
+ if( err ){
+ fn(err);
+ }
+ else {
+ try {
+ this._apply(image, fn);
+ } catch (err){
+ api.log('[err] FileAPI.Image.fn._apply:', err);
+ fn(err);
+ }
+ }
+ });
+ },
+
+
+ get: function (fn){
+ if( api.support.transform ){
+ var _this = this, matrix = _this.matrix;
+
+ if( matrix.deg == 'auto' ){
+ api.getInfo(_this.file, function (err, info){
+ // rotate by exif orientation
+ matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
+ _this._trans(fn);
+ });
+ }
+ else {
+ _this._trans(fn);
+ }
+ }
+ else {
+ fn('not_support_transform');
+ }
+
+ return this;
+ },
+
+
+ toData: function (fn){
+ return this.get(fn);
+ }
+
+ };
+
+
+ Image.exifOrientation = exifOrientation;
+
+
+ Image.transform = function (file, transform, autoOrientation, fn){
+ function _transform(err, img){
+ // img -- info object
+ var
+ images = {}
+ , queue = api.queue(function (err){
+ fn(err, images);
+ })
+ ;
+
+ if( !err ){
+ api.each(transform, function (params, name){
+ if( !queue.isFail() ){
+ var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
+
+ if( isFn ){
+ params(img, ImgTrans);
+ }
+ else if( params.width ){
+ ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
+ }
+ else {
+ if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
+ ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
+ }
+ }
+
+ if( params.crop ){
+ var crop = params.crop;
+ ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
+ }
+
+ if( params.rotate === undef && autoOrientation ){
+ params.rotate = 'auto';
+ }
+
+ ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
+
+ if( !isFn ){
+ ImgTrans.set({
+ deg: params.rotate
+ , overlay: params.overlay
+ , filter: params.filter
+ , quality: params.quality || 1
+ });
+ }
+
+ queue.inc();
+ ImgTrans.toData(function (err, image){
+ if( err ){
+ queue.fail();
+ }
+ else {
+ images[name] = image;
+ queue.next();
+ }
+ });
+ }
+ });
+ }
+ else {
+ queue.fail();
+ }
+ }
+
+
+ // @todo: Оло-ло, нужно рефакторить это место
+ if( file.width ){
+ _transform(false, file);
+ } else {
+ api.getInfo(file, _transform);
+ }
+ };
+
+
+ // @const
+ api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
+ api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
+ Image[x+'_'+y] = i*3 + j;
+ Image[y+'_'+x] = i*3 + j;
+ });
+ });
+
+
+ /**
+ * Trabsform element to canvas
+ *
+ * @param {Image|HTMLVideoElement} el
+ * @returns {Canvas}
+ */
+ Image.toCanvas = function(el){
+ var canvas = document.createElement('canvas');
+ canvas.width = el.videoWidth || el.width;
+ canvas.height = el.videoHeight || el.height;
+ canvas.getContext('2d').drawImage(el, 0, 0);
+ return canvas;
+ };
+
+
+ /**
+ * Create image from DataURL
+ * @param {String} dataURL
+ * @param {Object} size
+ * @param {Function} callback
+ */
+ Image.fromDataURL = function (dataURL, size, callback){
+ var img = api.newImage(dataURL);
+ api.extend(img, size);
+ callback(img);
+ };
+
+
+ /**
+ * Apply filter (caman.js)
+ *
+ * @param {Canvas|Image} canvas
+ * @param {String|Function} filter
+ * @param {Function} doneFn
+ */
+ Image.applyFilter = function (canvas, filter, doneFn){
+ if( typeof filter == 'function' ){
+ filter(canvas, doneFn);
+ }
+ else if( window.Caman ){
+ // http://camanjs.com/guides/
+ window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
+ if( typeof filter == 'string' ){
+ this[filter]();
+ }
+ else {
+ api.each(filter, function (val, method){
+ this[method](val);
+ }, this);
+ }
+ this.render(doneFn);
+ });
+ }
+ };
+
+
+ /**
+ * For load-image-ios.js
+ */
+ api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
+ try {
+ return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
+ } catch (ex) {
+ api.log('renderImageToCanvas failed');
+ throw ex;
+ }
+ };
+
+
+ // @export
+ api.support.canvas = api.support.transform = support;
+ api.Image = Image;
+})(FileAPI, document);
+
+/*
+ * JavaScript Load Image iOS scaling fixes 1.0.3
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * iOS image scaling fixes based on
+ * https://github.com/stomita/ios-imagefile-megapixel
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/*jslint nomen: true, bitwise: true */
+/*global FileAPI, window, document */
+
+(function (factory) {
+ 'use strict';
+ factory(FileAPI);
+}(function (loadImage) {
+ 'use strict';
+
+ // Only apply fixes on the iOS platform:
+ if (!window.navigator || !window.navigator.platform ||
+ !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
+ return;
+ }
+
+ var originalRenderMethod = loadImage.renderImageToCanvas;
+
+ // Detects subsampling in JPEG images:
+ loadImage.detectSubsampling = function (img) {
+ var canvas,
+ context;
+ if (img.width * img.height > 1024 * 1024) { // only consider megapixel images
+ canvas = document.createElement('canvas');
+ canvas.width = canvas.height = 1;
+ context = canvas.getContext('2d');
+ context.drawImage(img, -img.width + 1, 0);
+ // subsampled image becomes half smaller in rendering size.
+ // check alpha channel value to confirm image is covering edge pixel or not.
+ // if alpha value is 0 image is not covering, hence subsampled.
+ return context.getImageData(0, 0, 1, 1).data[3] === 0;
+ }
+ return false;
+ };
+
+ // Detects vertical squash in JPEG images:
+ loadImage.detectVerticalSquash = function (img, subsampled) {
+ var naturalHeight = img.naturalHeight || img.height,
+ canvas = document.createElement('canvas'),
+ context = canvas.getContext('2d'),
+ data,
+ sy,
+ ey,
+ py,
+ alpha;
+ if (subsampled) {
+ naturalHeight /= 2;
+ }
+ canvas.width = 1;
+ canvas.height = naturalHeight;
+ context.drawImage(img, 0, 0);
+ data = context.getImageData(0, 0, 1, naturalHeight).data;
+ // search image edge pixel position in case it is squashed vertically:
+ sy = 0;
+ ey = naturalHeight;
+ py = naturalHeight;
+ while (py > sy) {
+ alpha = data[(py - 1) * 4 + 3];
+ if (alpha === 0) {
+ ey = py;
+ } else {
+ sy = py;
+ }
+ py = (ey + sy) >> 1;
+ }
+ return (py / naturalHeight) || 1;
+ };
+
+ // Renders image to canvas while working around iOS image scaling bugs:
+ // https://github.com/blueimp/JavaScript-Load-Image/issues/13
+ loadImage.renderImageToCanvas = function (
+ canvas,
+ img,
+ sourceX,
+ sourceY,
+ sourceWidth,
+ sourceHeight,
+ destX,
+ destY,
+ destWidth,
+ destHeight
+ ) {
+ if (img._type === 'image/jpeg') {
+ var context = canvas.getContext('2d'),
+ tmpCanvas = document.createElement('canvas'),
+ tileSize = 1024,
+ tmpContext = tmpCanvas.getContext('2d'),
+ subsampled,
+ vertSquashRatio,
+ tileX,
+ tileY;
+ tmpCanvas.width = tileSize;
+ tmpCanvas.height = tileSize;
+ context.save();
+ subsampled = loadImage.detectSubsampling(img);
+ if (subsampled) {
+ sourceX /= 2;
+ sourceY /= 2;
+ sourceWidth /= 2;
+ sourceHeight /= 2;
+ }
+ vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
+ if (subsampled || vertSquashRatio !== 1) {
+ sourceY *= vertSquashRatio;
+ destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
+ destHeight = Math.ceil(
+ tileSize * destHeight / sourceHeight / vertSquashRatio
+ );
+ destY = 0;
+ tileY = 0;
+ while (tileY < sourceHeight) {
+ destX = 0;
+ tileX = 0;
+ while (tileX < sourceWidth) {
+ tmpContext.clearRect(0, 0, tileSize, tileSize);
+ tmpContext.drawImage(
+ img,
+ sourceX,
+ sourceY,
+ sourceWidth,
+ sourceHeight,
+ -tileX,
+ -tileY,
+ sourceWidth,
+ sourceHeight
+ );
+ context.drawImage(
+ tmpCanvas,
+ 0,
+ 0,
+ tileSize,
+ tileSize,
+ destX,
+ destY,
+ destWidth,
+ destHeight
+ );
+ tileX += tileSize;
+ destX += destWidth;
+ }
+ tileY += tileSize;
+ destY += destHeight;
+ }
+ context.restore();
+ return canvas;
+ }
+ }
+ return originalRenderMethod(
+ canvas,
+ img,
+ sourceX,
+ sourceY,
+ sourceWidth,
+ sourceHeight,
+ destX,
+ destY,
+ destWidth,
+ destHeight
+ );
+ };
+
+}));
+
+/*global window, FileAPI */
+
+(function (api, window){
+ "use strict";
+
+ var
+ document = window.document
+ , FormData = window.FormData
+ , Form = function (){ this.items = []; }
+ , encodeURIComponent = window.encodeURIComponent
+ ;
+
+
+ Form.prototype = {
+
+ append: function (name, blob, file, type){
+ this.items.push({
+ name: name
+ , blob: blob && blob.blob || (blob == void 0 ? '' : blob)
+ , file: blob && (file || blob.name)
+ , type: blob && (type || blob.type)
+ });
+ },
+
+ each: function (fn){
+ var i = 0, n = this.items.length;
+ for( ; i < n; i++ ){
+ fn.call(this, this.items[i]);
+ }
+ },
+
+ toData: function (fn, options){
+ // allow chunked transfer if we have only one file to send
+ // flag is used below and in XHR._send
+ options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
+
+ if( !api.support.html5 ){
+ api.log('FileAPI.Form.toHtmlData');
+ this.toHtmlData(fn);
+ }
+ else if( !api.formData || this.multipart || !FormData ){
+ api.log('FileAPI.Form.toMultipartData');
+ this.toMultipartData(fn);
+ }
+ else if( options._chunked ){
+ api.log('FileAPI.Form.toPlainData');
+ this.toPlainData(fn);
+ }
+ else {
+ api.log('FileAPI.Form.toFormData');
+ this.toFormData(fn);
+ }
+ },
+
+ _to: function (data, complete, next, arg){
+ var queue = api.queue(function (){
+ complete(data);
+ });
+
+ this.each(function (file){
+ try{
+ next(file, data, queue, arg);
+ }
+ catch( err ){
+ api.log('FileAPI.Form._to: ' + err.message);
+ complete(err);
+ }
+ });
+
+ queue.check();
+ },
+
+
+ toHtmlData: function (fn){
+ this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
+ var blob = file.blob, hidden;
+
+ if( file.file ){
+ api.reset(blob, true);
+ // set new name
+ blob.name = file.name;
+ blob.disabled = false;
+ data.appendChild(blob);
+ }
+ else {
+ hidden = document.createElement('input');
+ hidden.name = file.name;
+ hidden.type = 'hidden';
+ hidden.value = blob;
+ data.appendChild(hidden);
+ }
+ });
+ },
+
+ toPlainData: function (fn){
+ this._to({}, fn, function (file, data, queue){
+ if( file.file ){
+ data.type = file.file;
+ }
+
+ if( file.blob.toBlob ){
+ // canvas
+ queue.inc();
+ _convertFile(file, function (file, blob){
+ data.name = file.name;
+ data.file = blob;
+ data.size = blob.length;
+ data.type = file.type;
+ queue.next();
+ });
+ }
+ else if( file.file ){
+ // file
+ data.name = file.blob.name;
+ data.file = file.blob;
+ data.size = file.blob.size;
+ data.type = file.type;
+ }
+ else {
+ // additional data
+ if( !data.params ){
+ data.params = [];
+ }
+ data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
+ }
+
+ data.start = -1;
+ data.end = data.file && data.file.FileAPIReadPosition || -1;
+ data.retry = 0;
+ });
+ },
+
+ toFormData: function (fn){
+ this._to(new FormData, fn, function (file, data, queue){
+ if( file.blob && file.blob.toBlob ){
+ queue.inc();
+ _convertFile(file, function (file, blob){
+ data.append(file.name, blob, file.file);
+ queue.next();
+ });
+ }
+ else if( file.file ){
+ data.append(file.name, file.blob, file.file);
+ }
+ else {
+ data.append(file.name, file.blob);
+ }
+
+ if( file.file ){
+ data.append('_'+file.name, file.file);
+ }
+ });
+ },
+
+
+ toMultipartData: function (fn){
+ this._to([], fn, function (file, data, queue, boundary){
+ queue.inc();
+ _convertFile(file, function (file, blob){
+ data.push(
+ '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
+ + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
+ + '\r\n'
+ + '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
+ + '\r\n')
+ );
+ queue.next();
+ }, true);
+ }, api.expando);
+ }
+ };
+
+
+ function _convertFile(file, fn, useBinaryString){
+ var blob = file.blob, filename = file.file;
+
+ if( filename ){
+ if( !blob.toDataURL ){
+ // The Blob is not an image.
+ api.readAsBinaryString(blob, function (evt){
+ if( evt.type == 'load' ){
+ fn(file, evt.result);
+ }
+ });
+ return;
+ }
+
+ var
+ mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
+ , type = mime[file.type] ? file.type : 'image/png'
+ , ext = mime[type] || '.png'
+ , quality = blob.quality || 1
+ ;
+
+ if( !filename.match(new RegExp(ext+'$', 'i')) ){
+ // Does not change the current extension, but add a new one.
+ filename += ext.replace('?', '');
+ }
+
+ file.file = filename;
+ file.type = type;
+
+ if( !useBinaryString && blob.toBlob ){
+ blob.toBlob(function (blob){
+ fn(file, blob);
+ }, type, quality);
+ }
+ else {
+ fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
+ }
+ }
+ else {
+ fn(file, blob);
+ }
+ }
+
+
+ // @export
+ api.Form = Form;
+})(FileAPI, window);
+
+/*global window, FileAPI, Uint8Array */
+
+(function (window, api){
+ "use strict";
+
+ var
+ noop = function (){}
+ , document = window.document
+
+ , XHR = function (options){
+ this.uid = api.uid();
+ this.xhr = {
+ abort: noop
+ , getResponseHeader: noop
+ , getAllResponseHeaders: noop
+ };
+ this.options = options;
+ },
+
+ _xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
+ ;
+
+
+ XHR.prototype = {
+ status: 0,
+ statusText: '',
+ constructor: XHR,
+
+ getResponseHeader: function (name){
+ return this.xhr.getResponseHeader(name);
+ },
+
+ getAllResponseHeaders: function (){
+ return this.xhr.getAllResponseHeaders() || {};
+ },
+
+ end: function (status, statusText){
+ var _this = this, options = _this.options;
+
+ _this.end =
+ _this.abort = noop;
+ _this.status = status;
+
+ if( statusText ){
+ _this.statusText = statusText;
+ }
+
+ api.log('xhr.end:', status, statusText);
+ options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
+
+ if( _this.xhr && _this.xhr.node ){
+ setTimeout(function (){
+ var node = _this.xhr.node;
+ try { node.parentNode.removeChild(node); } catch (e){}
+ try { delete window[_this.uid]; } catch (e){}
+ window[_this.uid] = _this.xhr.node = null;
+ }, 9);
+ }
+ },
+
+ abort: function (){
+ this.end(0, 'abort');
+
+ if( this.xhr ){
+ this.xhr.aborted = true;
+ this.xhr.abort();
+ }
+ },
+
+ send: function (FormData){
+ var _this = this, options = this.options;
+
+ FormData.toData(function (data){
+ if( data instanceof Error ){
+ _this.end(0, data.message);
+ }
+ else{
+ // Start uploading
+ options.upload(options, _this);
+ _this._send.call(_this, options, data);
+ }
+ }, options);
+ },
+
+ _send: function (options, data){
+ var _this = this, xhr, uid = _this.uid, onLoadFnName = _this.uid + "Load", url = options.url;
+
+ api.log('XHR._send:', data);
+
+ if( !options.cache ){
+ // No cache
+ url += (~url.indexOf('?') ? '&' : '?') + api.uid();
+ }
+
+ if( data.nodeName ){
+ var jsonp = options.jsonp;
+
+ // prepare callback in GET
+ url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
+
+ // legacy
+ options.upload(options, _this);
+
+ var
+ onPostMessage = function (evt){
+ if( ~url.indexOf(evt.origin) ){
+ try {
+ var result = api.parseJSON(evt.data);
+ if( result.id == uid ){
+ complete(result.status, result.statusText, result.response);
+ }
+ } catch( err ){
+ complete(0, err.message);
+ }
+ }
+ },
+
+ // jsonp-callack
+ complete = window[uid] = function (status, statusText, response){
+ _this.readyState = 4;
+ _this.responseText = response;
+ _this.end(status, statusText);
+
+ api.event.off(window, 'message', onPostMessage);
+ window[uid] = xhr = transport = window[onLoadFnName] = null;
+ }
+ ;
+
+ _this.xhr.abort = function (){
+ try {
+ if( transport.stop ){ transport.stop(); }
+ else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
+ else { transport.contentWindow.document.execCommand('Stop'); }
+ }
+ catch (er) {}
+ complete(0, "abort");
+ };
+
+ api.event.on(window, 'message', onPostMessage);
+
+ window[onLoadFnName] = function (){
+ try {
+ var
+ win = transport.contentWindow
+ , doc = win.document
+ , result = win.result || api.parseJSON(doc.body.innerHTML)
+ ;
+ complete(result.status, result.statusText, result.response);
+ } catch (e){
+ api.log('[transport.onload]', e);
+ }
+ };
+
+ xhr = document.createElement('div');
+ xhr.innerHTML = '
'
+ ;
+
+ // get form-data & transport
+ var
+ form = xhr.getElementsByTagName('form')[0]
+ , transport = xhr.getElementsByTagName('iframe')[0]
+ ;
+
+ form.appendChild(data);
+
+ api.log(form.parentNode.innerHTML);
+
+ // append to DOM
+ document.body.appendChild(xhr);
+
+ // keep a reference to node-transport
+ _this.xhr.node = xhr;
+
+ // send
+ _this.readyState = 2; // loaded
+ try {
+ form.submit();
+ } catch (err) {
+ api.log('iframe.error: ' + err);
+ }
+ form = null;
+ }
+ else {
+ // Clean url
+ url = url.replace(/([a-z]+)=(\?)&?/i, '');
+
+ // html5
+ if (this.xhr && this.xhr.aborted) {
+ api.log("Error: already aborted");
+ return;
+ }
+ xhr = _this.xhr = api.getXHR();
+
+ if (data.params) {
+ url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
+ }
+
+ xhr.open(options.uploadMethod || 'POST', url, true);
+
+ if (typeof options.uploadCredentials === 'boolean') {
+ xhr.withCredentials = options.uploadCredentials ? 'true' : null;
+ } else if( api.withCredentials ){
+ xhr.withCredentials = "true";
+ }
+
+ if( !options.headers || !options.headers['X-Requested-With'] ){
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ }
+
+ api.each(options.headers, function (val, key){
+ xhr.setRequestHeader(key, val);
+ });
+
+
+ if ( options._chunked ) {
+ // chunked upload
+ if( xhr.upload ){
+ xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
+ if (!data.retry) {
+ // show progress only for correct chunk uploads
+ options.progress({
+ type: evt.type
+ , total: data.size
+ , loaded: data.start + evt.loaded
+ , totalSize: data.size
+ }, _this, options);
+ }
+ }, 100), false);
+ }
+
+ xhr.onreadystatechange = function (){
+ var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
+
+ _this.status = xhr.status;
+ _this.statusText = xhr.statusText;
+ _this.readyState = xhr.readyState;
+
+ if( xhr.readyState == 4 ){
+ for( var k in _xhrResponsePostfix ){
+ _this['response'+k] = xhr['response'+k];
+ }
+ xhr.onreadystatechange = null;
+
+ if (!xhr.status || xhr.status - 201 > 0) {
+ api.log("Error: " + xhr.status);
+ // some kind of error
+ // 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
+ // up - server error
+ if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
+ // let's try again the same chunk
+ // only applicable for recoverable error codes 500 && 416
+ var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
+
+ // inform about recoverable problems
+ options.pause(data.file, options);
+
+ // smart restart if server reports about the last known byte
+ api.log("X-Last-Known-Byte: " + lkb);
+ if (lkb) {
+ data.end = lkb;
+ } else {
+ data.end = data.start - 1;
+ if (416 == xhr.status) {
+ data.end = data.end - options.chunkSize;
+ }
+ }
+
+ setTimeout(function () {
+ _this._send(options, data);
+ }, delay);
+ } else {
+ // no mo retries
+ _this.end(xhr.status);
+ }
+ } else {
+ // success
+ data.retry = 0;
+
+ if (data.end == data.size - 1) {
+ // finished
+ _this.end(xhr.status);
+ } else {
+ // next chunk
+
+ // shift position if server reports about the last known byte
+ api.log("X-Last-Known-Byte: " + lkb);
+ if (lkb) {
+ data.end = lkb;
+ }
+ data.file.FileAPIReadPosition = data.end;
+
+ setTimeout(function () {
+ _this._send(options, data);
+ }, 0);
+ }
+ }
+
+ xhr = null;
+ }
+ };
+
+ data.start = data.end + 1;
+ data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
+
+ // Retrieve a slice of file
+ var
+ file = data.file
+ , slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
+ ;
+
+ if( data.size && !slice.size ){
+ setTimeout(function (){
+ _this.end(-1);
+ });
+ } else {
+ xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
+ xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
+ xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
+
+ xhr.send(slice);
+ }
+
+ file = slice = null;
+ } else {
+ // single piece upload
+ if( xhr.upload ){
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
+ xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
+ options.progress(evt, _this, options);
+ }, 100), false);
+ }
+
+ xhr.onreadystatechange = function (){
+ _this.status = xhr.status;
+ _this.statusText = xhr.statusText;
+ _this.readyState = xhr.readyState;
+
+ if( xhr.readyState == 4 ){
+ for( var k in _xhrResponsePostfix ){
+ _this['response'+k] = xhr['response'+k];
+ }
+ xhr.onreadystatechange = null;
+
+ if (!xhr.status || xhr.status > 201) {
+ api.log("Error: " + xhr.status);
+ if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
+ options.retry = (options.retry || 0) + 1;
+ var delay = api.networkDownRetryTimeout;
+
+ // inform about recoverable problems
+ options.pause(options.file, options);
+
+ setTimeout(function () {
+ _this._send(options, data);
+ }, delay);
+ } else {
+ //success
+ _this.end(xhr.status);
+ }
+ } else {
+ //success
+ _this.end(xhr.status);
+ }
+
+ xhr = null;
+ }
+ };
+
+ if( api.isArray(data) ){
+ // multipart
+ xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
+ var rawData = data.join('') +'--_'+ api.expando +'--';
+
+ /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
+ if( xhr.sendAsBinary ){
+ xhr.sendAsBinary(rawData);
+ }
+ else {
+ var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
+ xhr.send(new Uint8Array(bytes).buffer);
+
+ }
+ } else {
+ // FormData
+ xhr.send(data);
+ }
+ }
+ }
+ }
+ };
+
+
+ // @export
+ api.XHR = XHR;
+})(window, FileAPI);
+
+/**
+ * @class FileAPI.Camera
+ * @author RubaXa
+ * @support Chrome 21+, FF 18+, Opera 12+
+ */
+
+/*global window, FileAPI, jQuery */
+/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
+(function (window, api){
+ "use strict";
+
+ var
+ URL = window.URL || window.webkitURL,
+
+ document = window.document,
+ navigator = window.navigator,
+
+ getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
+
+ html5 = !!getMedia
+ ;
+
+
+ // Support "media"
+ api.support.media = html5;
+
+
+ var Camera = function (video){
+ this.video = video;
+ };
+
+
+ Camera.prototype = {
+ isActive: function (){
+ return !!this._active;
+ },
+
+
+ /**
+ * Start camera streaming
+ * @param {Function} callback
+ */
+ start: function (callback){
+ var
+ _this = this
+ , video = _this.video
+ , _successId
+ , _failId
+ , _complete = function (err){
+ _this._active = !err;
+ clearTimeout(_failId);
+ clearTimeout(_successId);
+// api.event.off(video, 'loadedmetadata', _complete);
+ callback && callback(err, _this);
+ }
+ ;
+
+ getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
+ // Success
+ _this.stream = stream;
+
+// api.event.on(video, 'loadedmetadata', function (){
+// _complete(null);
+// });
+
+ // Set camera stream
+ try {
+ video.src = URL.createObjectURL(stream);
+ } catch (err) {
+ video.srcObject = stream;
+ }
+
+ // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
+ // See crbug.com/110938.
+ _successId = setInterval(function (){
+ if( _detectVideoSignal(video) ){
+ _complete(null);
+ }
+ }, 1000);
+
+ _failId = setTimeout(function (){
+ _complete('timeout');
+ }, 5000);
+
+ // Go-go-go!
+ video.play();
+ }, _complete/*error*/);
+ },
+
+
+ /**
+ * Stop camera streaming
+ */
+ stop: function (){
+ try {
+ this._active = false;
+ this.video.pause();
+
+ try {
+ this.stream.stop();
+ } catch (err) {
+ api.each(this.stream.getTracks(), function (track) {
+ track.stop();
+ });
+ }
+
+ this.stream = null;
+ } catch( err ){
+ api.log('[FileAPI.Camera] stop:', err);
+ }
+ },
+
+
+ /**
+ * Create screenshot
+ * @return {FileAPI.Camera.Shot}
+ */
+ shot: function (){
+ return new Shot(this.video);
+ }
+ };
+
+
+ /**
+ * Get camera element from container
+ *
+ * @static
+ * @param {HTMLElement} el
+ * @return {Camera}
+ */
+ Camera.get = function (el){
+ return new Camera(el.firstChild);
+ };
+
+
+ /**
+ * Publish camera element into container
+ *
+ * @static
+ * @param {HTMLElement} el
+ * @param {Object} options
+ * @param {Function} [callback]
+ */
+ Camera.publish = function (el, options, callback){
+ if( typeof options == 'function' ){
+ callback = options;
+ options = {};
+ }
+
+ // Dimensions of "camera"
+ options = api.extend({}, {
+ width: '100%'
+ , height: '100%'
+ , start: true
+ }, options);
+
+
+ if( el.jquery ){
+ // Extract first element, from jQuery collection
+ el = el[0];
+ }
+
+
+ var doneFn = function (err){
+ if( err ){
+ callback(err);
+ }
+ else {
+ // Get camera
+ var cam = Camera.get(el);
+ if( options.start ){
+ cam.start(callback);
+ }
+ else {
+ callback(null, cam);
+ }
+ }
+ };
+
+
+ el.style.width = _px(options.width);
+ el.style.height = _px(options.height);
+
+
+ if( api.html5 && html5 && !api.insecureChrome ){
+ // Create video element
+ var video = document.createElement('video');
+
+ // Set dimensions
+ video.style.width = _px(options.width);
+ video.style.height = _px(options.height);
+
+ // Clean container
+ if( window.jQuery ){
+ jQuery(el).empty();
+ } else {
+ el.innerHTML = '';
+ }
+
+ // Add "camera" to container
+ el.appendChild(video);
+
+ // end
+ doneFn();
+ }
+ else {
+ Camera.fallback(el, options, doneFn);
+ }
+ };
+
+
+ Camera.fallback = function (el, options, callback){
+ callback('not_support_camera');
+ };
+
+ Camera.checkAlreadyCaptured = (function () {
+ var mediaDevices = navigator.mediaDevices,
+ MediaStreamTrack = window.MediaStreamTrack,
+ navigatorEnumerateDevices = navigator.enumerateDevices,
+ enumerateDevices;
+
+ if (mediaDevices && mediaDevices.enumerateDevices) {
+ enumerateDevices = function (callback) {
+ mediaDevices.enumerateDevices().then(callback);
+ };
+ } else if (MediaStreamTrack && MediaStreamTrack.getSources) {
+ enumerateDevices = MediaStreamTrack.getSources.bind(MediaStreamTrack);
+ } else if (navigatorEnumerateDevices) {
+ enumerateDevices = navigatorEnumerateDevices.bind(navigator);
+ } else {
+ enumerateDevices = function (fn) {
+ fn([]);
+ };
+ }
+
+ return function (callback) {
+ enumerateDevices(function (devices) {
+ var deviceExists = devices.some(function (device) {
+ return (device.kind === 'videoinput' || device.kind === 'video') && device.label;
+ });
+
+ callback(deviceExists);
+ });
+ };
+
+ })();
+
+
+ /**
+ * @class FileAPI.Camera.Shot
+ */
+ var Shot = function (video){
+ var canvas = video.nodeName ? api.Image.toCanvas(video) : video;
+ var shot = api.Image(canvas);
+ shot.type = 'image/png';
+ shot.width = canvas.width;
+ shot.height = canvas.height;
+ shot.size = canvas.width * canvas.height * 4;
+ return shot;
+ };
+
+
+ /**
+ * Add "px" postfix, if value is a number
+ *
+ * @private
+ * @param {*} val
+ * @return {String}
+ */
+ function _px(val){
+ return val >= 0 ? val + 'px' : val;
+ }
+
+
+ /**
+ * @private
+ * @param {HTMLVideoElement} video
+ * @return {Boolean}
+ */
+ function _detectVideoSignal(video){
+ var canvas = document.createElement('canvas'), ctx, res = false;
+ try {
+ ctx = canvas.getContext('2d');
+ ctx.drawImage(video, 0, 0, 1, 1);
+ res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
+ }
+ catch( err ){
+ api.log('[FileAPI.Camera] detectVideoSignal:', err);
+ }
+ return res;
+ }
+
+
+ // @export
+ Camera.Shot = Shot;
+ api.Camera = Camera;
+})(window, FileAPI);
+
+/**
+ * FileAPI fallback to Flash
+ *
+ * @flash-developer "Vladimir Demidov"
+ */
+
+/*global window, FileAPI */
+(function (window, jQuery, api) {
+ "use strict";
+
+ var _each = api.each,
+ _cameraQueue = [];
+
+ if (api.support.flash && (api.media && (!api.support.media || !api.html5 || api.insecureChrome))) {
+ (function () {
+ function _wrap(fn) {
+ var id = fn.wid = api.uid();
+ api.Flash._fn[id] = fn;
+ return 'FileAPI.Flash._fn.' + id;
+ }
+
+
+ function _unwrap(fn) {
+ try {
+ api.Flash._fn[fn.wid] = null;
+ delete api.Flash._fn[fn.wid];
+ } catch (e) {
+ }
+ }
+
+ var flash = api.Flash;
+ api.extend(api.Flash, {
+
+ patchCamera: function () {
+ api.Camera.fallback = function (el, options, callback) {
+ var camId = api.uid();
+ api.log('FlashAPI.Camera.publish: ' + camId);
+ flash.publish(el, camId, api.extend(options, {
+ camera: true,
+ onEvent: _wrap(function _(evt) {
+ if (evt.type === 'camera') {
+ _unwrap(_);
+
+ if (evt.error) {
+ api.log('FlashAPI.Camera.publish.error: ' + evt.error);
+ callback(evt.error);
+ } else {
+ api.log('FlashAPI.Camera.publish.success: ' + camId);
+ callback(null);
+ }
+ }
+ })
+ }));
+ };
+ // Run
+ _each(_cameraQueue, function (args) {
+ api.Camera.fallback.apply(api.Camera, args);
+ });
+ _cameraQueue = [];
+
+
+ // FileAPI.Camera:proto
+ api.extend(api.Camera.prototype, {
+ _id: function () {
+ return this.video.id;
+ },
+
+ start: function (callback) {
+ var _this = this;
+ flash.cmd(this._id(), 'camera.on', {
+ callback: _wrap(function _(evt) {
+ _unwrap(_);
+
+ if (evt.error) {
+ api.log('FlashAPI.camera.on.error: ' + evt.error);
+ callback(evt.error, _this);
+ } else {
+ api.log('FlashAPI.camera.on.success: ' + _this._id());
+ _this._active = true;
+ callback(null, _this);
+ }
+ })
+ });
+ },
+
+ stop: function () {
+ this._active = false;
+ flash.cmd(this._id(), 'camera.off');
+ },
+
+ shot: function () {
+ api.log('FlashAPI.Camera.shot:', this._id());
+
+ var shot = api.Flash.cmd(this._id(), 'shot', {});
+ shot.type = 'image/png';
+ shot.flashId = this._id();
+ shot.isShot = true;
+
+ return new api.Camera.Shot(shot);
+ }
+ });
+ }
+ });
+
+ api.Camera.fallback = function () {
+ _cameraQueue.push(arguments);
+ };
+
+ }());
+ }
+}(window, window.jQuery, FileAPI));
+if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
diff --git a/dist/FileAPI.html5.min.js b/dist/FileAPI.html5.min.js
new file mode 100644
index 00000000..16b8c50f
--- /dev/null
+++ b/dist/FileAPI.html5.min.js
@@ -0,0 +1,2 @@
+/*! FileAPI 2.1.1 - BSD | git://github.com/mailru/FileAPI.git */
+!function(a){"use strict";var b=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,c=a.Blob&&function(){try{return Boolean(new Blob)}catch(a){return!1}}(),d=c&&a.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(a){return!1}}(),e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,f=(c||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var b,f,g,h,i,j;for(b=a.split(",")[0].indexOf("base64")>=0?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]),f=new ArrayBuffer(b.length),g=new Uint8Array(f),h=0;h0,I=a.dataURLtoBlob,J=/img/i,K=/canvas/i,L=/img|canvas/i,M=/input/i,N=/^data:[^,]+,/,O={}.toString,P=a.Math,Q=function(b){return b=new a.Number(P.pow(1024,b)),b.from=function(a){return P.round(a*this)},b},R={},S=[],T="abort progress error load loadend",U="status statusText readyState response responseXML responseText responseBody".split(" "),V="currentTarget",W="preventDefault",X=function(a){return a&&"length"in a},Y=function(a,b,c){if(a)if(X(a))for(var d=0,e=a.length;d=c&&!d&&f.end()},isFail:function(){return d},fail:function(){!d&&a(d=!0)},end:function(){e||(e=!0,a())}};return f},each:Y,afor:function(a,b){var c=0,d=a.length;X(a)&&d--?function e(){b(d!=c&&e,a[c],c++)}():b(!1)},extend:Z,isFile:function(a){return"[object File]"===O.call(a)},isBlob:function(a){return this.isFile(a)||"[object Blob]"===O.call(a)},isCanvas:function(a){return a&&K.test(a.nodeName)},getFilesFilter:function(a){return a="string"==typeof a?a:a.getAttribute&&a.getAttribute("accept")||"",a?new RegExp("("+a.replace(/\./g,"\\.").replace(/,/g,"|")+")$","i"):/./},readAsDataURL:function(a,b){da.isCanvas(a)?c(a,b,"load",da.toDataURL(a)):e(a,b,"DataURL")},readAsBinaryString:function(a,b){d("BinaryString")?e(a,b,"BinaryString"):e(a,function(a){if("load"==a.type)try{a.result=da.toBinaryString(a.result)}catch(b){a.type="error",a.message=b.toString()}b(a)},"DataURL")},readAsArrayBuffer:function(a,b){e(a,b,"ArrayBuffer")},readAsText:function(a,b,c){c||(c=b,b="utf-8"),e(a,c,"Text",b)},toDataURL:function(a,b){return"string"==typeof a?a:a.toDataURL?a.toDataURL(b||"image/png"):void 0},toBinaryString:function(b){return a.atob(da.toDataURL(b).replace(N,""))},readAsImage:function(a,d,e){if(da.isBlob(a))if(x){var f=x.createObjectURL(a);f===b?c(a,d,"error"):da.readAsImage(f,d,e)}else da.readAsDataURL(a,function(b){"load"==b.type?da.readAsImage(b.result,d,e):(e||"error"==b.type)&&c(a,d,b,null,{loaded:b.loaded,total:b.total})});else if(da.isCanvas(a))c(a,d,"load",a);else if(J.test(a.nodeName))if(a.complete)c(a,d,"load",a);else{var g="error abort load";aa(a,g,function b(e){"load"==e.type&&x&&x.revokeObjectURL(a.src),_(a,g,b),c(a,d,e,a)})}else if(a.iframe)c(a,d,{type:"error"});else{var h=da.newImage(a.dataURL||a);da.readAsImage(h,d,e)}},checkFileObj:function(a){var b={},c=da.accept;return"object"==typeof a?b=a:b.name=(a+"").split(/\\|\//g).pop(),null==b.type&&(b.type=b.name.split(".").pop()),Y(c,function(a,c){a=new RegExp(a.replace(/\s/g,"|"),"i"),(a.test(b.type)||da.ext2mime[b.type])&&(b.type=da.ext2mime[b.type]||c.split("/")[0]+"/"+b.type)}),b},getDropFiles:function(a,b){var c,d=[],e=[],j=l(a),k=j.files,m=j.items,n=X(m)&&m[0]&&h(m[0]),o=da.queue(function(){b(d,e)});if(n)if(H&&k){var p,q,r=k.length;for(c=new Array(r);r--;){p=k[r];try{q=h(m[r])}catch(a){da.log("[err] getDropFiles: ",a),q=null}g(q)&&(q.isDirectory||q.isFile&&p.name==p.name.normalize("NFC"))?c[r]=q:c[r]=p}}else c=m;else c=k;Y(c||[],function(a){o.inc();try{n&&g(a)?i(a,function(a,b,c){a?da.log("[err] getDropFiles:",a):d.push.apply(d,b),e.push.apply(e,c),o.next()}):f(a,function(b,c){b?d.push(a):a.error=c,e.push(a),o.next()})}catch(a){o.next(),da.log("[err] getDropFiles: ",a)}}),o.check()},getFiles:function(a,b,c){var d=[];return c?(da.filterFiles(da.getFiles(a),b,c),null):(a.jquery&&(a.each(function(){d=d.concat(da.getFiles(this))}),a=d,d=[]),"string"==typeof b&&(b=da.getFilesFilter(b)),a.originalEvent?a=ba(a.originalEvent):a.srcElement&&(a=ba(a)),a.dataTransfer?a=a.dataTransfer:a.target&&(a=a.target),a.files?(d=a.files,E||(d[0].blob=a,d[0].iframe=!0)):!E&&k(a)?da.trim(a.value)&&(d=[da.checkFileObj(a.value)],d[0].blob=a,d[0].iframe=!0):X(a)&&(d=a),da.filter(d,function(a){return!b||b.test(a.name)}))},getTotalSize:function(a){for(var b=0,c=a&&a.length;c--;)b+=a[c].size;return b},getInfo:function(a,b){var c={},d=S.concat();da.isBlob(a)?function e(){var f=d.shift();f?f.test(a.type)?f(a,function(a,d){a?b(a):(Z(c,d),e())}):e():b(!1,c)}():b("not_support_info",c)},addInfoReader:function(a,b){b.test=function(b){return a.test(b)},S.push(b)},filter:function(a,b){for(var c,d=[],e=0,f=a.length;e>2,k=(3&g)<<4|h>>4;isNaN(h)?e=f=64:(e=(15&h)<<2|i>>6,f=isNaN(i)?64:63&i),c+=b.charAt(j)+b.charAt(k)+b.charAt(e)+b.charAt(f)}return c}};da.addInfoReader(/^image/,function(a,b){if(!a.__dimensions){var c=a.__dimensions=da.defer();da.readAsImage(a,function(a){var b=a.target;c.resolve("load"!=a.type&&"error",{width:b.width,height:b.height}),b.src=da.EMPTY_PNG,b=null})}a.__dimensions.then(b)}),da.event.dnd=function(a,b,c){var d,e;c||(c=b,b=da.F),A?($(a,"dragenter dragleave dragover",b.ff=b.ff||function(a){for(var c=l(a).types,f=c&&c.length,g=!1;f--;)if(~c[f].indexOf("File")){a[W](),e!==a.type&&(e=a.type,"dragleave"!=e&&b.call(a[V],!0,a),g=!0);break}g&&(clearTimeout(d),d=setTimeout(function(){b.call(a[V],"dragleave"!=e,a)},50))}),$(a,"drop",c.ff=c.ff||function(a){a[W](),e=0,da.getDropFiles(a,function(b,d){c.call(a[V],b,d,a)}),b.call(a[V],!1,a)})):da.log("Drag'n'Drop -- not supported")},da.event.dnd.off=function(a,b,c){_(a,"dragenter dragleave dragover",b.ff),_(a,"drop",c.ff)},D&&!D.fn.dnd&&(D.fn.dnd=function(a,b){return this.each(function(){da.event.dnd(this,a,b)})},D.fn.offdnd=function(a,b){return this.each(function(){da.event.dnd.off(this,a,b)})}),a.FileAPI=Z(da,a.FileAPI),da.log("FileAPI: "+da.version),da.log("protocol: "+a.location.protocol),da.log("doctype: ["+s.name+"] "+s.publicId+" "+s.systemId),Y(r.getElementsByTagName("meta"),function(a){/x-ua-compatible/i.test(a.getAttribute("http-equiv"))&&da.log("meta.http-equiv: "+a.getAttribute("content"))});try{n=!!console.log,o=!!console.log.apply}catch(a){}da.flashUrl||(da.flashUrl=da.staticPath+"FileAPI.flash.swf"),da.flashImageUrl||(da.flashImageUrl=da.staticPath+"FileAPI.flash.image.swf"),da.flashWebcamUrl||(da.flashWebcamUrl=da.staticPath+"FileAPI.flash.camera.swf")}(window,void 0),function(a,b,c){"use strict";function d(b){if(b instanceof d){var c=new d(b.file);return a.extend(c.matrix,b.matrix),c}if(!(this instanceof d))return new d(b);this.file=b,this.size=b.size||100,this.matrix={sx:0,sy:0,sw:0,sh:0,dx:0,dy:0,dw:0,dh:0,resize:0,deg:0,quality:1,filter:0}}var e=Math.min,f=Math.round,g=function(){return b.createElement("canvas")},h=!1,i={8:270,3:180,6:90,7:270,4:180,5:90};try{h=g().toDataURL("image/png").indexOf("data:image/png")>-1}catch(a){}d.prototype={image:!0,constructor:d,set:function(b){return a.extend(this.matrix,b),this},crop:function(a,b,d,e){return d===c&&(d=a,e=b,a=b=0),this.set({sx:a,sy:b,sw:d,sh:e||d})},resize:function(a,b,c){return/min|max|height|width/.test(b)&&(c=b,b=a),this.set({dw:a,dh:b||a,resize:c})},preview:function(a,b){return this.resize(a,b||a,"preview")},rotate:function(a){return this.set({deg:a})},filter:function(a){return this.set({filter:a})},overlay:function(a){return this.set({overlay:a})},clone:function(){return new d(this)},_load:function(b,c){var d=this;/img|video/i.test(b.nodeName)?c.call(d,null,b):a.readAsImage(b,function(a){c.call(d,"load"!=a.type,a.result)})},_apply:function(b,c){var f,h=g(),i=this.getMatrix(b),j=h.getContext("2d"),k=b.videoWidth||b.width,l=b.videoHeight||b.height,m=i.deg,n=i.dw,o=i.dh,p=k,q=l,r=i.filter,s=b,t=i.overlay,u=a.queue(function(){b.src=a.EMPTY_PNG,c(!1,h)}),v=a.renderImageToCanvas;for(m-=360*Math.floor(m/360),b._type=this.file.type;i.multipass&&e(p/n,q/o)>2;)p=p/2+.5|0,q=q/2+.5|0,f=g(),f.width=p,f.height=q,s!==b?(v(f,s,0,0,s.width,s.height,0,0,p,q),s=f):(s=f,v(s,b,i.sx,i.sy,i.sw,i.sh,0,0,p,q),i.sx=i.sy=i.sw=i.sh=0);h.width=m%180?o:n,h.height=m%180?n:o,h.type=i.type,h.quality=i.quality,j.rotate(m*Math.PI/180),v(j.canvas,s,i.sx,i.sy,i.sw||s.width,i.sh||s.height,180==m||270==m?-n:0,90==m||180==m?-o:0,n,o),n=h.width,o=h.height,t&&a.each([].concat(t),function(b){u.inc();var c=new window.Image,d=function(){var e=0|b.x,f=0|b.y,g=b.w||c.width,h=b.h||c.height,i=b.rel;e=1==i||4==i||7==i?(n-g+e)/2:2==i||5==i||8==i?n-(g+e):e,f=3==i||4==i||5==i?(o-h+f)/2:i>=6?o-(h+f):f,a.event.off(c,"error load abort",d);try{j.globalAlpha=b.opacity||1,j.drawImage(c,e,f,g,h)}catch(a){}u.next()};a.event.on(c,"error load abort",d),c.src=b.src,c.complete&&d()}),r&&(u.inc(),d.applyFilter(h,r,u.next)),u.check()},getMatrix:function(b){var c=a.extend({},this.matrix),d=c.sw=c.sw||b.videoWidth||b.naturalWidth||b.width,g=c.sh=c.sh||b.videoHeight||b.naturalHeight||b.height,h=c.dw=c.dw||d,i=c.dh=c.dh||g,j=d/g,k=h/i,l=c.resize;if("preview"==l){if(h!=d||i!=g){var m,n;k>=j?(m=d,n=m/k):(n=g,m=n*k),m==d&&n==g||(c.sx=~~((d-m)/2),c.sy=~~((g-n)/2),d=m,g=n)}}else"height"==l?h=i*j:"width"==l?i=h/j:l&&(d>h||g>i?"min"==l?(h=f(j=k?e(d,h):i*j),i=f(j>=k?h/j:e(g,i))):(h=d,i=g));return c.sw=d,c.sh=g,c.dw=h,c.dh=i,c.multipass=a.multiPassResize,c},_trans:function(b){this._load(this.file,function(c,d){if(c)b(c);else try{this._apply(d,b)}catch(c){a.log("[err] FileAPI.Image.fn._apply:",c),b(c)}})},get:function(b){if(a.support.transform){var c=this,d=c.matrix;"auto"==d.deg?a.getInfo(c.file,function(a,e){d.deg=i[e&&e.exif&&e.exif.Orientation]||0,c._trans(b)}):c._trans(b)}else b("not_support_transform");return this},toData:function(a){return this.get(a)}},d.exifOrientation=i,d.transform=function(b,e,f,g){function h(h,i){var j={},k=a.queue(function(a){g(a,j)});h?k.fail():a.each(e,function(a,e){if(!k.isFail()){var g=new d(i.nodeType?i:b),h="function"==typeof a;if(h?a(i,g):a.width?g[a.preview?"preview":"resize"](a.width,a.height,a.strategy):a.maxWidth&&(i.width>a.maxWidth||i.height>a.maxHeight)&&g.resize(a.maxWidth,a.maxHeight,"max"),a.crop){var l=a.crop;g.crop(0|l.x,0|l.y,l.w||l.width,l.h||l.height)}a.rotate===c&&f&&(a.rotate="auto"),g.set({type:g.matrix.type||a.type||b.type||"image/png"}),h||g.set({deg:a.rotate,overlay:a.overlay,filter:a.filter,quality:a.quality||1}),k.inc(),g.toData(function(a,b){a?k.fail():(j[e]=b,k.next())})}})}b.width?h(!1,b):a.getInfo(b,h)},a.each(["TOP","CENTER","BOTTOM"],function(b,c){a.each(["LEFT","CENTER","RIGHT"],function(a,e){d[b+"_"+a]=3*c+e,d[a+"_"+b]=3*c+e})}),d.toCanvas=function(a){var c=b.createElement("canvas");return c.width=a.videoWidth||a.width,c.height=a.videoHeight||a.height,c.getContext("2d").drawImage(a,0,0),c},d.fromDataURL=function(b,c,d){var e=a.newImage(b);a.extend(e,c),d(e)},d.applyFilter=function(b,c,e){"function"==typeof c?c(b,e):window.Caman&&window.Caman("IMG"==b.tagName?d.toCanvas(b):b,function(){"string"==typeof c?this[c]():a.each(c,function(a,b){this[b](a)},this),this.render(e)})},a.renderImageToCanvas=function(b,c,d,e,f,g,h,i,j,k){try{return b.getContext("2d").drawImage(c,d,e,f,g,h,i,j,k)}catch(b){throw a.log("renderImageToCanvas failed"),b}},a.support.canvas=a.support.transform=h,a.Image=d}(FileAPI,document),function(a){"use strict";a(FileAPI)}(function(a){"use strict";if(window.navigator&&window.navigator.platform&&/iP(hone|od|ad)/.test(window.navigator.platform)){var b=a.renderImageToCanvas;a.detectSubsampling=function(a){var b,c;return a.width*a.height>1048576&&(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,1-a.width,0),0===c.getImageData(0,0,1,1).data[3])},a.detectVerticalSquash=function(a,b){var c,d,e,f,g,h=a.naturalHeight||a.height,i=document.createElement("canvas"),j=i.getContext("2d");for(b&&(h/=2),i.width=1,i.height=h,j.drawImage(a,0,0),c=j.getImageData(0,0,1,h).data,d=0,e=h,f=h;f>d;)g=c[4*(f-1)+3],0===g?e=f:d=f,f=e+d>>1;return f/h||1},a.renderImageToCanvas=function(c,d,e,f,g,h,i,j,k,l){if("image/jpeg"===d._type){var m,n,o,p,q=c.getContext("2d"),r=document.createElement("canvas"),s=1024,t=r.getContext("2d");if(r.width=s,r.height=s,q.save(),m=a.detectSubsampling(d),m&&(e/=2,f/=2,g/=2,h/=2),n=a.detectVerticalSquash(d,m),m||1!==n){for(f*=n,k=Math.ceil(s*k/g),l=Math.ceil(s*l/h/n),j=0,p=0;p0&&1==a.filter(this.items,function(a){return a.file}).length,a.support.html5?a.formData&&!this.multipart&&e?c._chunked?(a.log("FileAPI.Form.toPlainData"),this.toPlainData(b)):(a.log("FileAPI.Form.toFormData"),this.toFormData(b)):(a.log("FileAPI.Form.toMultipartData"),this.toMultipartData(b)):(a.log("FileAPI.Form.toHtmlData"),this.toHtmlData(b))},_to:function(b,c,d,e){var f=a.queue(function(){c(b)});this.each(function(g){try{d(g,b,f,e)}catch(b){a.log("FileAPI.Form._to: "+b.message),c(b)}}),f.check()},toHtmlData:function(b){this._to(d.createDocumentFragment(),b,function(b,c){var e,f=b.blob;b.file?(a.reset(f,!0),f.name=b.name,f.disabled=!1,c.appendChild(f)):(e=d.createElement("input"),e.name=b.name,e.type="hidden",e.value=f,c.appendChild(e))})},toPlainData:function(a){this._to({},a,function(a,b,d){a.file&&(b.type=a.file),a.blob.toBlob?(d.inc(),c(a,function(a,c){b.name=a.name,b.file=c,b.size=c.length,b.type=a.type,d.next()})):a.file?(b.name=a.blob.name,b.file=a.blob,b.size=a.blob.size,b.type=a.type):(b.params||(b.params=[]),b.params.push(g(a.name)+"="+g(a.blob))),b.start=-1,b.end=b.file&&b.file.FileAPIReadPosition||-1,b.retry=0})},toFormData:function(a){this._to(new e,a,function(a,b,d){a.blob&&a.blob.toBlob?(d.inc(),c(a,function(a,c){b.append(a.name,c,a.file),d.next()})):a.file?b.append(a.name,a.blob,a.file):b.append(a.name,a.blob),a.file&&b.append("_"+a.name,a.file)})},toMultipartData:function(b){this._to([],b,function(a,b,d,e){d.inc(),c(a,function(a,c){b.push("--_"+e+'\r\nContent-Disposition: form-data; name="'+a.name+'"'+(a.file?'; filename="'+g(a.file)+'"':"")+(a.file?"\r\nContent-Type: "+(a.type||"application/octet-stream"):"")+"\r\n\r\n"+(a.file?c:g(c))+"\r\n"),d.next()},!0)},a.expando)}},a.Form=f}(FileAPI,window),function(a,b){"use strict";var c=function(){},d=a.document,e=function(a){this.uid=b.uid(),this.xhr={abort:c,getResponseHeader:c,getAllResponseHeaders:c},this.options=a},f={"":1,XML:1,Text:1,Body:1};e.prototype={status:0,statusText:"",constructor:e,getResponseHeader:function(a){return this.xhr.getResponseHeader(a)},getAllResponseHeaders:function(){return this.xhr.getAllResponseHeaders()||{}},end:function(d,e){var f=this,g=f.options;f.end=f.abort=c,f.status=d,e&&(f.statusText=e),b.log("xhr.end:",d,e),g.complete(200!=d&&201!=d&&(f.statusText||"unknown"),f),f.xhr&&f.xhr.node&&setTimeout(function(){var b=f.xhr.node;try{b.parentNode.removeChild(b)}catch(a){}try{delete a[f.uid]}catch(a){}a[f.uid]=f.xhr.node=null},9)},abort:function(){this.end(0,"abort"),this.xhr&&(this.xhr.aborted=!0,this.xhr.abort())},send:function(a){var b=this,c=this.options;a.toData(function(a){a instanceof Error?b.end(0,a.message):(c.upload(c,b),b._send.call(b,c,a))},c)},_send:function(c,e){var g,h=this,i=h.uid,j=h.uid+"Load",k=c.url;if(b.log("XHR._send:",e),c.cache||(k+=(~k.indexOf("?")?"&":"?")+b.uid()),e.nodeName){var l=c.jsonp;k=k.replace(/([a-z]+)=(\?)/i,"$1="+i),c.upload(c,h);var m=function(a){if(~k.indexOf(a.origin))try{var c=b.parseJSON(a.data);c.id==i&&n(c.status,c.statusText,c.response)}catch(a){n(0,a.message)}},n=a[i]=function(c,d,e){h.readyState=4,h.responseText=e,h.end(c,d),b.event.off(a,"message",m),a[i]=g=p=a[j]=null};h.xhr.abort=function(){try{p.stop?p.stop():p.contentWindow.stop?p.contentWindow.stop():p.contentWindow.document.execCommand("Stop")}catch(a){}n(0,"abort")},b.event.on(a,"message",m),a[j]=function(){try{var a=p.contentWindow,c=a.document,d=a.result||b.parseJSON(c.body.innerHTML);n(d.status,d.statusText,d.response)}catch(a){b.log("[transport.onload]",a)}},g=d.createElement("div"),g.innerHTML='";var o=g.getElementsByTagName("form")[0],p=g.getElementsByTagName("iframe")[0];o.appendChild(e),b.log(o.parentNode.innerHTML),d.body.appendChild(g),h.xhr.node=g,h.readyState=2;try{o.submit()}catch(a){b.log("iframe.error: "+a)}o=null}else{if(k=k.replace(/([a-z]+)=(\?)&?/i,""),this.xhr&&this.xhr.aborted)return void b.log("Error: already aborted");if(g=h.xhr=b.getXHR(),e.params&&(k+=(k.indexOf("?")<0?"?":"&")+e.params.join("&")),g.open(c.uploadMethod||"POST",k,!0),"boolean"==typeof c.uploadCredentials?g.withCredentials=c.uploadCredentials?"true":null:b.withCredentials&&(g.withCredentials="true"),c.headers&&c.headers["X-Requested-With"]||g.setRequestHeader("X-Requested-With","XMLHttpRequest"),b.each(c.headers,function(a,b){g.setRequestHeader(b,a)}),c._chunked){g.upload&&g.upload.addEventListener("progress",b.throttle(function(a){e.retry||c.progress({type:a.type,total:e.size,loaded:e.start+a.loaded,totalSize:e.size},h,c)},100),!1),g.onreadystatechange=function(){var a=parseInt(g.getResponseHeader("X-Last-Known-Byte"),10);if(h.status=g.status,h.statusText=g.statusText,h.readyState=g.readyState,4==g.readyState){for(var d in f)h["response"+d]=g["response"+d];if(g.onreadystatechange=null,!g.status||g.status-201>0)if(b.log("Error: "+g.status),(!g.status&&!g.aborted||500==g.status||416==g.status)&&++e.retry<=c.chunkUploadRetry){var i=g.status?0:b.chunkNetworkDownRetryTimeout;c.pause(e.file,c),b.log("X-Last-Known-Byte: "+a),a?e.end=a:(e.end=e.start-1,416==g.status&&(e.end=e.end-c.chunkSize)),setTimeout(function(){h._send(c,e)},i)}else h.end(g.status);else e.retry=0,e.end==e.size-1?h.end(g.status):(b.log("X-Last-Known-Byte: "+a),a&&(e.end=a),e.file.FileAPIReadPosition=e.end,setTimeout(function(){h._send(c,e)},0));g=null}},e.start=e.end+1,e.end=Math.max(Math.min(e.start+c.chunkSize,e.size)-1,e.start);var q=e.file,r=(q.slice||q.mozSlice||q.webkitSlice).call(q,e.start,e.end+1);e.size&&!r.size?setTimeout(function(){h.end(-1)}):(g.setRequestHeader("Content-Range","bytes "+e.start+"-"+e.end+"/"+e.size),g.setRequestHeader("Content-Disposition","attachment; filename="+encodeURIComponent(e.name)),g.setRequestHeader("Content-Type",e.type||"application/octet-stream"),g.send(r)),q=r=null}else if(g.upload&&g.upload.addEventListener("progress",b.throttle(function(a){c.progress(a,h,c)},100),!1),g.onreadystatechange=function(){if(h.status=g.status,h.statusText=g.statusText,h.readyState=g.readyState,4==g.readyState){for(var a in f)h["response"+a]=g["response"+a];if(g.onreadystatechange=null,!g.status||g.status>201)if(b.log("Error: "+g.status),(!g.status&&!g.aborted||500==g.status)&&(c.retry||0)=0?a+"px":a}function d(a){var c,d=f.createElement("canvas"),e=!1;try{c=d.getContext("2d"),c.drawImage(a,0,0,1,1),e=255!=c.getImageData(0,0,1,1).data[4]}catch(a){b.log("[FileAPI.Camera] detectVideoSignal:",a)}return e}var e=a.URL||a.webkitURL,f=a.document,g=a.navigator,h=g.getUserMedia||g.webkitGetUserMedia||g.mozGetUserMedia||g.msGetUserMedia,i=!!h;b.support.media=i;var j=function(a){this.video=a};j.prototype={isActive:function(){return!!this._active},start:function(a){var b,c,f=this,i=f.video,j=function(d){f._active=!d,clearTimeout(c),clearTimeout(b),a&&a(d,f)};h.call(g,{video:!0},function(a){f.stream=a;try{i.src=e.createObjectURL(a)}catch(b){i.srcObject=a}b=setInterval(function(){d(i)&&j(null)},1e3),c=setTimeout(function(){j("timeout")},5e3),i.play()},j)},stop:function(){try{this._active=!1,this.video.pause();try{this.stream.stop()}catch(a){b.each(this.stream.getTracks(),function(a){a.stop()})}this.stream=null}catch(a){b.log("[FileAPI.Camera] stop:",a)}},shot:function(){return new k(this.video)}},j.get=function(a){return new j(a.firstChild)},j.publish=function(d,e,g){"function"==typeof e&&(g=e,e={}),e=b.extend({},{width:"100%",height:"100%",start:!0},e),d.jquery&&(d=d[0]);var h=function(a){if(a)g(a);else{var b=j.get(d);e.start?b.start(g):g(null,b)}};if(d.style.width=c(e.width),d.style.height=c(e.height),b.html5&&i&&!b.insecureChrome){var k=f.createElement("video");k.style.width=c(e.width),k.style.height=c(e.height),a.jQuery?jQuery(d).empty():d.innerHTML="",d.appendChild(k),h()}else j.fallback(d,e,h)},j.fallback=function(a,b,c){c("not_support_camera")},j.checkAlreadyCaptured=function(){var b,c=g.mediaDevices,d=a.MediaStreamTrack,e=g.enumerateDevices;return b=c&&c.enumerateDevices?function(a){c.enumerateDevices().then(a)}:d&&d.getSources?d.getSources.bind(d):e?e.bind(g):function(a){a([])},function(a){b(function(b){var c=b.some(function(a){return("videoinput"===a.kind||"video"===a.kind)&&a.label});a(c)})}}();var k=function(a){var c=a.nodeName?b.Image.toCanvas(a):a,d=b.Image(c);return d.type="image/png",d.width=c.width,d.height=c.height,d.size=c.width*c.height*4,d};j.Shot=k,b.Camera=j}(window,FileAPI),function(a,b,c){"use strict";var d=c.each,e=[];!c.support.flash||!c.media||c.support.media&&c.html5&&!c.insecureChrome||function(){function a(a){var b=a.wid=c.uid();return c.Flash._fn[b]=a,"FileAPI.Flash._fn."+b}function b(a){try{c.Flash._fn[a.wid]=null,delete c.Flash._fn[a.wid]}catch(a){}}var f=c.Flash;c.extend(c.Flash,{patchCamera:function(){c.Camera.fallback=function(d,e,g){var h=c.uid();c.log("FlashAPI.Camera.publish: "+h),f.publish(d,h,c.extend(e,{camera:!0,onEvent:a(function a(d){"camera"===d.type&&(b(a),d.error?(c.log("FlashAPI.Camera.publish.error: "+d.error),g(d.error)):(c.log("FlashAPI.Camera.publish.success: "+h),g(null)))})}))},d(e,function(a){c.Camera.fallback.apply(c.Camera,a)}),e=[],c.extend(c.Camera.prototype,{_id:function(){return this.video.id},start:function(d){var e=this;f.cmd(this._id(),"camera.on",{callback:a(function a(f){b(a),f.error?(c.log("FlashAPI.camera.on.error: "+f.error),d(f.error,e)):(c.log("FlashAPI.camera.on.success: "+e._id()),e._active=!0,d(null,e))})})},stop:function(){this._active=!1,f.cmd(this._id(),"camera.off")},shot:function(){c.log("FlashAPI.Camera.shot:",this._id());var a=c.Flash.cmd(this._id(),"shot",{});return a.type="image/png",a.flashId=this._id(),a.isShot=!0,new c.Camera.Shot(a)}})}}),c.Camera.fallback=function(){e.push(arguments)}}()}(window,window.jQuery,FileAPI),"function"==typeof define&&define.amd&&define("FileAPI",[],function(){return FileAPI});
\ No newline at end of file
diff --git a/dist/FileAPI.js b/dist/FileAPI.js
new file mode 100644
index 00000000..210de9d9
--- /dev/null
+++ b/dist/FileAPI.js
@@ -0,0 +1,4406 @@
+/*! FileAPI 2.1.1 - BSD | git://github.com/mailru/FileAPI.git
+ * 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.
+ */
+
+/*
+ * JavaScript Canvas to Blob 2.0.5
+ * https://github.com/blueimp/JavaScript-Canvas-to-Blob
+ *
+ * Copyright 2012, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ *
+ * Based on stackoverflow user Stoive's code snippet:
+ * http://stackoverflow.com/q/4998908
+ */
+
+/*jslint nomen: true, regexp: true */
+/*global window, atob, Blob, ArrayBuffer, Uint8Array */
+
+(function (window) {
+ 'use strict';
+ var CanvasPrototype = window.HTMLCanvasElement &&
+ window.HTMLCanvasElement.prototype,
+ hasBlobConstructor = window.Blob && (function () {
+ try {
+ return Boolean(new Blob());
+ } catch (e) {
+ return false;
+ }
+ }()),
+ hasArrayBufferViewSupport = hasBlobConstructor && window.Uint8Array &&
+ (function () {
+ try {
+ return new Blob([new Uint8Array(100)]).size === 100;
+ } catch (e) {
+ return false;
+ }
+ }()),
+ BlobBuilder = window.BlobBuilder || window.WebKitBlobBuilder ||
+ window.MozBlobBuilder || window.MSBlobBuilder,
+ dataURLtoBlob = (hasBlobConstructor || BlobBuilder) && window.atob &&
+ window.ArrayBuffer && window.Uint8Array && function (dataURI) {
+ var byteString,
+ arrayBuffer,
+ intArray,
+ i,
+ mimeString,
+ bb;
+ if (dataURI.split(',')[0].indexOf('base64') >= 0) {
+ // Convert base64 to raw binary data held in a string:
+ byteString = atob(dataURI.split(',')[1]);
+ } else {
+ // Convert base64/URLEncoded data component to raw binary data:
+ byteString = decodeURIComponent(dataURI.split(',')[1]);
+ }
+ // Write the bytes of the string to an ArrayBuffer:
+ arrayBuffer = new ArrayBuffer(byteString.length);
+ intArray = new Uint8Array(arrayBuffer);
+ for (i = 0; i < byteString.length; i += 1) {
+ intArray[i] = byteString.charCodeAt(i);
+ }
+ // Separate out the mime component:
+ mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
+ // Write the ArrayBuffer (or ArrayBufferView) to a blob:
+ if (hasBlobConstructor) {
+ return new Blob(
+ [hasArrayBufferViewSupport ? intArray : arrayBuffer],
+ {type: mimeString}
+ );
+ }
+ bb = new BlobBuilder();
+ bb.append(arrayBuffer);
+ return bb.getBlob(mimeString);
+ };
+ if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
+ if (CanvasPrototype.mozGetAsFile) {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
+ callback(dataURLtoBlob(this.toDataURL(type, quality)));
+ } else {
+ callback(this.mozGetAsFile('blob', type));
+ }
+ };
+ } else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ callback(dataURLtoBlob(this.toDataURL(type, quality)));
+ };
+ }
+ }
+ window.dataURLtoBlob = dataURLtoBlob;
+})(window);
+
+/*jslint evil: true */
+/*global window, URL, webkitURL, ActiveXObject */
+
+(function (window, undef){
+ 'use strict';
+
+ var
+ gid = 1,
+ noop = function (){},
+
+ document = window.document,
+ doctype = document.doctype || {},
+ userAgent = window.navigator.userAgent,
+ safari = /safari\//i.test(userAgent) && !/chrome\//i.test(userAgent),
+ iemobile = /iemobile\//i.test(userAgent),
+ insecureChrome = !safari && /chrome\//i.test(userAgent) && window.location.protocol === 'http:',
+
+ // https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
+ apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
+
+ Blob = window.Blob,
+ File = window.File,
+ FileReader = window.FileReader,
+ FormData = window.FormData,
+
+
+ XMLHttpRequest = window.XMLHttpRequest,
+ jQuery = window.jQuery,
+
+ html5 = !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
+ && !(safari && /windows/i.test(userAgent) && !iemobile), // BugFix: https://github.com/mailru/FileAPI/issues/25
+
+ cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
+
+ chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
+
+ normalize = ('' + ''.normalize).indexOf('[native code]') > 0,
+
+ // https://github.com/blueimp/JavaScript-Canvas-to-Blob
+ dataURLtoBlob = window.dataURLtoBlob,
+
+
+ _rimg = /img/i,
+ _rcanvas = /canvas/i,
+ _rimgcanvas = /img|canvas/i,
+ _rinput = /input/i,
+ _rdata = /^data:[^,]+,/,
+
+ _toString = {}.toString,
+ _supportConsoleLog,
+ _supportConsoleLogApply,
+
+
+ Math = window.Math,
+
+ _SIZE_CONST = function (pow){
+ pow = new window.Number(Math.pow(1024, pow));
+ pow.from = function (sz){ return Math.round(sz * this); };
+ return pow;
+ },
+
+ _elEvents = {}, // element event listeners
+ _infoReader = [], // list of file info processors
+
+ _readerEvents = 'abort progress error load loadend',
+ _xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
+
+ currentTarget = 'currentTarget', // for minimize
+ preventDefault = 'preventDefault', // and this too
+
+ _isArray = function (ar) {
+ return ar && ('length' in ar);
+ },
+
+ /**
+ * Iterate over a object or array
+ */
+ _each = function (obj, fn, ctx){
+ if( obj ){
+ if( _isArray(obj) ){
+ for( var i = 0, n = obj.length; i < n; i++ ){
+ if( i in obj ){
+ fn.call(ctx, obj[i], i, obj);
+ }
+ }
+ }
+ else {
+ for( var key in obj ){
+ if( obj.hasOwnProperty(key) ){
+ fn.call(ctx, obj[key], key, obj);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Merge the contents of two or more objects together into the first object
+ */
+ _extend = function (dst){
+ var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
+ for( ; i < args.length; i++ ){
+ _each(args[i], _ext);
+ }
+ return dst;
+ },
+
+ /**
+ * Add event listener
+ */
+ _on = function (el, type, fn){
+ if( el ){
+ var uid = api.uid(el);
+
+ if( !_elEvents[uid] ){
+ _elEvents[uid] = {};
+ }
+
+ var isFileReader = (FileReader && el) && (el instanceof FileReader);
+ _each(type.split(/\s+/), function (type){
+ if( jQuery && !isFileReader){
+ jQuery.event.add(el, type, fn);
+ } else {
+ if( !_elEvents[uid][type] ){
+ _elEvents[uid][type] = [];
+ }
+
+ _elEvents[uid][type].push(fn);
+
+ if( el.addEventListener ){ el.addEventListener(type, fn, false); }
+ else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
+ else { el['on'+type] = fn; }
+ }
+ });
+ }
+ },
+
+
+ /**
+ * Remove event listener
+ */
+ _off = function (el, type, fn){
+ if( el ){
+ var uid = api.uid(el), events = _elEvents[uid] || {};
+
+ var isFileReader = (FileReader && el) && (el instanceof FileReader);
+ _each(type.split(/\s+/), function (type){
+ if( jQuery && !isFileReader){
+ jQuery.event.remove(el, type, fn);
+ }
+ else {
+ var fns = events[type] || [], i = fns.length;
+
+ while( i-- ){
+ if( fns[i] === fn ){
+ fns.splice(i, 1);
+ break;
+ }
+ }
+
+ if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
+ else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
+ else { el['on'+type] = null; }
+ }
+ });
+ }
+ },
+
+
+ _one = function(el, type, fn){
+ _on(el, type, function _(evt){
+ _off(el, type, _);
+ fn(evt);
+ });
+ },
+
+
+ _fixEvent = function (evt){
+ if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
+ if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
+ return evt;
+ },
+
+
+ _supportInputAttr = function (attr){
+ var input = document.createElement('input');
+ input.setAttribute('type', "file");
+ return attr in input;
+ },
+
+
+ /**
+ * FileAPI (core object)
+ */
+ api = {
+ version: '2.1.1',
+
+ cors: false,
+ html5: true,
+ media: false,
+ formData: true,
+ multiPassResize: true,
+ insecureChrome: insecureChrome,
+
+ debug: false,
+ pingUrl: false,
+ multiFlash: false,
+ flashAbortTimeout: 0,
+ withCredentials: true,
+
+ staticPath: './dist/',
+
+ flashUrl: 0, // @default: './FileAPI.flash.swf'
+ flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
+
+ postNameConcat: function (name, idx){
+ return name + (idx != null ? '['+ idx +']' : '');
+ },
+
+ ext2mime: {
+ jpg: 'image/jpeg'
+ , tif: 'image/tiff'
+ , txt: 'text/plain'
+ },
+
+ // Fallback for flash
+ accept: {
+ 'image/*': 'art bm bmp dwg dxf cbr cbz fif fpx gif ico iefs jfif jpe jpeg jpg jps jut mcf nap nif pbm pcx pgm pict pm png pnm qif qtif ras rast rf rp svf tga tif tiff xbm xbm xpm xwd'
+ , 'audio/*': 'm4a flac aac rm mpa wav wma ogg mp3 mp2 m3u mod amf dmf dsm far gdm imf it m15 med okt s3m stm sfx ult uni xm sid ac3 dts cue aif aiff wpl ape mac mpc mpp shn wv nsf spc gym adplug adx dsp adp ymf ast afc hps xs'
+ , 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl'
+ },
+
+ uploadRetry : 0,
+ networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
+
+ chunkSize : 0,
+ chunkUploadRetry : 0,
+ chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
+
+ KB: _SIZE_CONST(1),
+ MB: _SIZE_CONST(2),
+ GB: _SIZE_CONST(3),
+ TB: _SIZE_CONST(4),
+
+ EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
+
+ expando: 'fileapi' + (new Date).getTime(),
+
+ uid: function (obj){
+ return obj
+ ? (obj[api.expando] = obj[api.expando] || api.uid())
+ : (++gid, api.expando + gid)
+ ;
+ },
+
+ log: function (){
+ if( api.debug && _supportConsoleLog ){
+ if( _supportConsoleLogApply ){
+ console.log.apply(console, arguments);
+ }
+ else {
+ console.log([].join.call(arguments, ' '));
+ }
+ }
+ },
+
+ /**
+ * Create new image
+ *
+ * @param {String} [src]
+ * @param {Function} [fn] 1. error -- boolean, 2. img -- Image element
+ * @returns {HTMLElement}
+ */
+ newImage: function (src, fn){
+ var img = document.createElement('img');
+ if( fn ){
+ api.event.one(img, 'error load', function (evt){
+ fn(evt.type == 'error', img);
+ img = null;
+ });
+ }
+ img.src = src;
+ return img;
+ },
+
+ /**
+ * Get XHR
+ * @returns {XMLHttpRequest}
+ */
+ getXHR: function (){
+ var xhr;
+
+ if( XMLHttpRequest ){
+ xhr = new XMLHttpRequest;
+ }
+ else if( window.ActiveXObject ){
+ try {
+ xhr = new ActiveXObject('MSXML2.XMLHttp.3.0');
+ } catch (e) {
+ xhr = new ActiveXObject('Microsoft.XMLHTTP');
+ }
+ }
+
+ return xhr;
+ },
+
+ isArray: _isArray,
+
+ support: {
+ dnd: cors && ('ondrop' in document.createElement('div')),
+ cors: cors,
+ html5: html5,
+ chunked: chunked,
+ dataURI: true,
+ accept: _supportInputAttr('accept'),
+ multiple: _supportInputAttr('multiple')
+ },
+
+ event: {
+ on: _on
+ , off: _off
+ , one: _one
+ , fix: _fixEvent
+ },
+
+
+ throttle: function(fn, delay) {
+ var id, args;
+
+ return function _throttle(){
+ args = arguments;
+
+ if( !id ){
+ fn.apply(window, args);
+ id = setTimeout(function (){
+ id = 0;
+ fn.apply(window, args);
+ }, delay);
+ }
+ };
+ },
+
+
+ F: function (){},
+
+
+ parseJSON: function (str){
+ var json;
+ if( window.JSON && JSON.parse ){
+ json = JSON.parse(str);
+ }
+ else {
+ json = (new Function('return ('+str.replace(/([\r\n])/g, '\\$1')+');'))();
+ }
+ return json;
+ },
+
+
+ trim: function (str){
+ str = String(str);
+ return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
+ },
+
+ /**
+ * Simple Defer
+ * @return {Object}
+ */
+ defer: function (){
+ var
+ list = []
+ , result
+ , error
+ , defer = {
+ resolve: function (err, res){
+ defer.resolve = noop;
+ error = err || false;
+ result = res;
+
+ while( res = list.shift() ){
+ res(error, result);
+ }
+ },
+
+ then: function (fn){
+ if( error !== undef ){
+ fn(error, result);
+ } else {
+ list.push(fn);
+ }
+ }
+ };
+
+ return defer;
+ },
+
+ queue: function (fn){
+ var
+ _idx = 0
+ , _length = 0
+ , _fail = false
+ , _end = false
+ , queue = {
+ inc: function (){
+ _length++;
+ },
+
+ next: function (){
+ _idx++;
+ setTimeout(queue.check, 0);
+ },
+
+ check: function (){
+ (_idx >= _length) && !_fail && queue.end();
+ },
+
+ isFail: function (){
+ return _fail;
+ },
+
+ fail: function (){
+ !_fail && fn(_fail = true);
+ },
+
+ end: function (){
+ if( !_end ){
+ _end = true;
+ fn();
+ }
+ }
+ }
+ ;
+ return queue;
+ },
+
+
+ /**
+ * For each object
+ *
+ * @param {Object|Array} obj
+ * @param {Function} fn
+ * @param {*} [ctx]
+ */
+ each: _each,
+
+
+ /**
+ * Async for
+ * @param {Array} array
+ * @param {Function} callback
+ */
+ afor: function (array, callback){
+ var i = 0, n = array.length;
+
+ if( _isArray(array) && n-- ){
+ (function _next(){
+ callback(n != i && _next, array[i], i++);
+ })();
+ }
+ else {
+ callback(false);
+ }
+ },
+
+
+ /**
+ * Merge the contents of two or more objects together into the first object
+ *
+ * @param {Object} dst
+ * @return {Object}
+ */
+ extend: _extend,
+
+
+ /**
+ * Is file?
+ * @param {File} file
+ * @return {Boolean}
+ */
+ isFile: function (file){
+ return _toString.call(file) === '[object File]';
+ },
+
+
+ /**
+ * Is blob?
+ * @param {Blob} blob
+ * @returns {Boolean}
+ */
+ isBlob: function (blob) {
+ return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
+ },
+
+
+ /**
+ * Is canvas element
+ *
+ * @param {HTMLElement} el
+ * @return {Boolean}
+ */
+ isCanvas: function (el){
+ return el && _rcanvas.test(el.nodeName);
+ },
+
+
+ getFilesFilter: function (filter){
+ filter = typeof filter == 'string' ? filter : (filter.getAttribute && filter.getAttribute('accept') || '');
+ return filter ? new RegExp('('+ filter.replace(/\./g, '\\.').replace(/,/g, '|') +')$', 'i') : /./;
+ },
+
+
+
+ /**
+ * Read as DataURL
+ *
+ * @param {File|Element} file
+ * @param {Function} fn
+ */
+ readAsDataURL: function (file, fn){
+ if( api.isCanvas(file) ){
+ _emit(file, fn, 'load', api.toDataURL(file));
+ }
+ else {
+ _readAs(file, fn, 'DataURL');
+ }
+ },
+
+
+ /**
+ * Read as Binary string
+ *
+ * @param {File} file
+ * @param {Function} fn
+ */
+ readAsBinaryString: function (file, fn){
+ if( _hasSupportReadAs('BinaryString') ){
+ _readAs(file, fn, 'BinaryString');
+ } else {
+ // Hello IE10!
+ _readAs(file, function (evt){
+ if( evt.type == 'load' ){
+ try {
+ // dataURL -> binaryString
+ evt.result = api.toBinaryString(evt.result);
+ } catch (e){
+ evt.type = 'error';
+ evt.message = e.toString();
+ }
+ }
+ fn(evt);
+ }, 'DataURL');
+ }
+ },
+
+
+ /**
+ * Read as ArrayBuffer
+ *
+ * @param {File} file
+ * @param {Function} fn
+ */
+ readAsArrayBuffer: function(file, fn){
+ _readAs(file, fn, 'ArrayBuffer');
+ },
+
+
+ /**
+ * Read as text
+ *
+ * @param {File} file
+ * @param {String} encoding
+ * @param {Function} [fn]
+ */
+ readAsText: function(file, encoding, fn){
+ if( !fn ){
+ fn = encoding;
+ encoding = 'utf-8';
+ }
+
+ _readAs(file, fn, 'Text', encoding);
+ },
+
+
+ /**
+ * Convert image or canvas to DataURL
+ *
+ * @param {Element} el Image or Canvas element
+ * @param {String} [type] mime-type
+ * @return {String}
+ */
+ toDataURL: function (el, type){
+ if( typeof el == 'string' ){
+ return el;
+ }
+ else if( el.toDataURL ){
+ return el.toDataURL(type || 'image/png');
+ }
+ },
+
+
+ /**
+ * Canvert string, image or canvas to binary string
+ *
+ * @param {String|Element} val
+ * @return {String}
+ */
+ toBinaryString: function (val){
+ return window.atob(api.toDataURL(val).replace(_rdata, ''));
+ },
+
+
+ /**
+ * Read file or DataURL as ImageElement
+ *
+ * @param {File|String} file
+ * @param {Function} fn
+ * @param {Boolean} [progress]
+ */
+ readAsImage: function (file, fn, progress){
+ if( api.isBlob(file) ){
+ if( apiURL ){
+ /** @namespace apiURL.createObjectURL */
+ var data = apiURL.createObjectURL(file);
+ if( data === undef ){
+ _emit(file, fn, 'error');
+ }
+ else {
+ api.readAsImage(data, fn, progress);
+ }
+ }
+ else {
+ api.readAsDataURL(file, function (evt){
+ if( evt.type == 'load' ){
+ api.readAsImage(evt.result, fn, progress);
+ }
+ else if( progress || evt.type == 'error' ){
+ _emit(file, fn, evt, null, { loaded: evt.loaded, total: evt.total });
+ }
+ });
+ }
+ }
+ else if( api.isCanvas(file) ){
+ _emit(file, fn, 'load', file);
+ }
+ else if( _rimg.test(file.nodeName) ){
+ if( file.complete ){
+ _emit(file, fn, 'load', file);
+ }
+ else {
+ var events = 'error abort load';
+ _one(file, events, function _fn(evt){
+ if( evt.type == 'load' && apiURL ){
+ /** @namespace apiURL.revokeObjectURL */
+ apiURL.revokeObjectURL(file.src);
+ }
+
+ _off(file, events, _fn);
+ _emit(file, fn, evt, file);
+ });
+ }
+ }
+ else if( file.iframe ){
+ _emit(file, fn, { type: 'error' });
+ }
+ else {
+ // Created image
+ var img = api.newImage(file.dataURL || file);
+ api.readAsImage(img, fn, progress);
+ }
+ },
+
+
+ /**
+ * Make file by name
+ *
+ * @param {String} name
+ * @return {Array}
+ */
+ checkFileObj: function (name){
+ var file = {}, accept = api.accept;
+
+ if( typeof name == 'object' ){
+ file = name;
+ }
+ else {
+ file.name = (name + '').split(/\\|\//g).pop();
+ }
+
+ if( file.type == null ){
+ file.type = file.name.split('.').pop();
+ }
+
+ _each(accept, function (ext, type){
+ ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
+ if( ext.test(file.type) || api.ext2mime[file.type] ){
+ file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
+ }
+ });
+
+ return file;
+ },
+
+
+ /**
+ * Get drop files
+ *
+ * @param {Event} evt
+ * @param {Function} callback
+ */
+ getDropFiles: function (evt, callback){
+ var
+ files = []
+ , all = []
+ , items
+ , dataTransfer = _getDataTransfer(evt)
+ , transFiles = dataTransfer.files
+ , transItems = dataTransfer.items
+ , entrySupport = _isArray(transItems) && transItems[0] && _getAsEntry(transItems[0])
+ , queue = api.queue(function (){ callback(files, all); })
+ ;
+
+ if( entrySupport ){
+ if( normalize && transFiles ){
+ var
+ i = transFiles.length
+ , file
+ , entry
+ ;
+
+ items = new Array(i);
+ while( i-- ){
+ file = transFiles[i];
+
+ try {
+ entry = _getAsEntry(transItems[i]);
+ }
+ catch( err ){
+ api.log('[err] getDropFiles: ', err);
+ entry = null;
+ }
+
+ if( _isEntry(entry) ){
+ // OSX filesystems use Unicode Normalization Form D (NFD),
+ // and entry.file(…) can't read the files with the same names
+ if( entry.isDirectory || (entry.isFile && file.name == file.name.normalize('NFC')) ){
+ items[i] = entry;
+ }
+ else {
+ items[i] = file;
+ }
+ }
+ else {
+ items[i] = file;
+ }
+ }
+ }
+ else {
+ items = transItems;
+ }
+ }
+ else {
+ items = transFiles;
+ }
+
+ _each(items || [], function (item){
+ queue.inc();
+
+ try {
+ if( entrySupport && _isEntry(item) ){
+ _readEntryAsFiles(item, function (err, entryFiles, allEntries){
+ if( err ){
+ api.log('[err] getDropFiles:', err);
+ } else {
+ files.push.apply(files, entryFiles);
+ }
+ all.push.apply(all, allEntries);
+
+ queue.next();
+ });
+ }
+ else {
+ _isRegularFile(item, function (yes, err){
+ if( yes ){
+ files.push(item);
+ }
+ else {
+ item.error = err;
+ }
+ all.push(item);
+
+ queue.next();
+ });
+ }
+ }
+ catch( err ){
+ queue.next();
+ api.log('[err] getDropFiles: ', err);
+ }
+ });
+
+ queue.check();
+ },
+
+
+ /**
+ * Get file list
+ *
+ * @param {HTMLInputElement|Event} input
+ * @param {String|Function} [filter]
+ * @param {Function} [callback]
+ * @return {Array|Null}
+ */
+ getFiles: function (input, filter, callback){
+ var files = [];
+
+ if( callback ){
+ api.filterFiles(api.getFiles(input), filter, callback);
+ return null;
+ }
+
+ if( input.jquery ){
+ // jQuery object
+ input.each(function (){
+ files = files.concat(api.getFiles(this));
+ });
+ input = files;
+ files = [];
+ }
+
+ if( typeof filter == 'string' ){
+ filter = api.getFilesFilter(filter);
+ }
+
+ if( input.originalEvent ){
+ // jQuery event
+ input = _fixEvent(input.originalEvent);
+ }
+ else if( input.srcElement ){
+ // IE Event
+ input = _fixEvent(input);
+ }
+
+
+ if( input.dataTransfer ){
+ // Drag'n'Drop
+ input = input.dataTransfer;
+ }
+ else if( input.target ){
+ // Event
+ input = input.target;
+ }
+
+ if( input.files ){
+ // Input[type="file"]
+ files = input.files;
+
+ if( !html5 ){
+ // Partial support for file api
+ files[0].blob = input;
+ files[0].iframe = true;
+ }
+ }
+ else if( !html5 && isInputFile(input) ){
+ if( api.trim(input.value) ){
+ files = [api.checkFileObj(input.value)];
+ files[0].blob = input;
+ files[0].iframe = true;
+ }
+ }
+ else if( _isArray(input) ){
+ files = input;
+ }
+
+ return api.filter(files, function (file){ return !filter || filter.test(file.name); });
+ },
+
+
+ /**
+ * Get total file size
+ * @param {Array} files
+ * @return {Number}
+ */
+ getTotalSize: function (files){
+ var size = 0, i = files && files.length;
+ while( i-- ){
+ size += files[i].size;
+ }
+ return size;
+ },
+
+
+ /**
+ * Get image information
+ *
+ * @param {File} file
+ * @param {Function} fn
+ */
+ getInfo: function (file, fn){
+ var info = {}, readers = _infoReader.concat();
+
+ if( api.isBlob(file) ){
+ (function _next(){
+ var reader = readers.shift();
+ if( reader ){
+ if( reader.test(file.type) ){
+ reader(file, function (err, res){
+ if( err ){
+ fn(err);
+ }
+ else {
+ _extend(info, res);
+ _next();
+ }
+ });
+ }
+ else {
+ _next();
+ }
+ }
+ else {
+ fn(false, info);
+ }
+ })();
+ }
+ else {
+ fn('not_support_info', info);
+ }
+ },
+
+
+ /**
+ * Add information reader
+ *
+ * @param {RegExp} mime
+ * @param {Function} fn
+ */
+ addInfoReader: function (mime, fn){
+ fn.test = function (type){ return mime.test(type); };
+ _infoReader.push(fn);
+ },
+
+
+ /**
+ * Filter of array
+ *
+ * @param {Array} input
+ * @param {Function} fn
+ * @return {Array}
+ */
+ filter: function (input, fn){
+ var result = [], i = 0, n = input.length, val;
+
+ for( ; i < n; i++ ){
+ if( i in input ){
+ val = input[i];
+ if( fn.call(val, val, i, input) ){
+ result.push(val);
+ }
+ }
+ }
+
+ return result;
+ },
+
+
+ /**
+ * Filter files
+ *
+ * @param {Array} files
+ * @param {Function} eachFn
+ * @param {Function} resultFn
+ */
+ filterFiles: function (files, eachFn, resultFn){
+ if( files.length ){
+ // HTML5 or Flash
+ var queue = files.concat(), file, result = [], deleted = [];
+
+ (function _next(){
+ if( queue.length ){
+ file = queue.shift();
+ api.getInfo(file, function (err, info){
+ (eachFn(file, err ? false : info) ? result : deleted).push(file);
+ _next();
+ });
+ }
+ else {
+ resultFn(result, deleted);
+ }
+ })();
+ }
+ else {
+ resultFn([], files);
+ }
+ },
+
+
+ upload: function (options){
+ options = _extend({
+ jsonp: 'callback'
+ , prepare: api.F
+ , beforeupload: api.F
+ , upload: api.F
+ , fileupload: api.F
+ , fileprogress: api.F
+ , filecomplete: api.F
+ , progress: api.F
+ , complete: api.F
+ , pause: api.F
+ , imageOriginal: true
+ , chunkSize: api.chunkSize
+ , chunkUploadRetry: api.chunkUploadRetry
+ , uploadRetry: api.uploadRetry
+ }, options);
+
+
+ if( options.imageAutoOrientation && !options.imageTransform ){
+ options.imageTransform = { rotate: 'auto' };
+ }
+
+
+ var
+ proxyXHR = new api.XHR(options)
+ , dataArray = this._getFilesDataArray(options.files)
+ , _this = this
+ , _total = 0
+ , _loaded = 0
+ , _nextFile
+ , _complete = false
+ ;
+
+
+ // calc total size
+ _each(dataArray, function (data){
+ _total += data.size;
+ });
+
+ // Array of files
+ proxyXHR.files = [];
+ _each(dataArray, function (data){
+ proxyXHR.files.push(data.file);
+ });
+
+ // Set upload status props
+ proxyXHR.total = _total;
+ proxyXHR.loaded = 0;
+ proxyXHR.filesLeft = dataArray.length;
+
+ // emit "beforeupload" event
+ options.beforeupload(proxyXHR, options);
+
+ // Upload by file
+ _nextFile = function (){
+ var
+ data = dataArray.shift()
+ , _file = data && data.file
+ , _fileLoaded = false
+ , _fileOptions = _simpleClone(options)
+ ;
+
+ proxyXHR.filesLeft = dataArray.length;
+
+ if( _file && _file.name === api.expando ){
+ _file = null;
+ api.log('[warn] FileAPI.upload() — called without files');
+ }
+
+ if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
+ // Mark active job
+ _complete = false;
+
+ // Set current upload file
+ proxyXHR.currentFile = _file;
+
+ // Prepare file options
+ if (_file && options.prepare(_file, _fileOptions) === false) {
+ _nextFile.call(_this);
+ return;
+ }
+ _fileOptions.file = _file;
+
+ _this._getFormData(_fileOptions, data, function (form){
+ if( !_loaded ){
+ // emit "upload" event
+ options.upload(proxyXHR, options);
+ }
+
+ var xhr = new api.XHR(_extend({}, _fileOptions, {
+
+ upload: _file ? function (){
+ // emit "fileupload" event
+ options.fileupload(_file, xhr, _fileOptions);
+ } : noop,
+
+ progress: _file ? function (evt){
+ if( !_fileLoaded ){
+ // For ignore the double calls.
+ _fileLoaded = (evt.loaded === evt.total);
+
+ // emit "fileprogress" event
+ options.fileprogress({
+ type: 'progress'
+ , total: data.total = evt.total
+ , loaded: data.loaded = evt.loaded
+ }, _file, xhr, _fileOptions);
+
+ // emit "progress" event
+ options.progress({
+ type: 'progress'
+ , total: _total
+ , loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total)) || 0
+ }, _file, xhr, _fileOptions);
+ }
+ } : noop,
+
+ complete: function (err){
+ _each(_xhrPropsExport, function (name){
+ proxyXHR[name] = xhr[name];
+ });
+
+ if( _file ){
+ data.total = (data.total || data.size);
+ data.loaded = data.total;
+
+ if( !err ) {
+ // emulate 100% "progress"
+ this.progress(data);
+
+ // fixed throttle event
+ _fileLoaded = true;
+
+ // bytes loaded
+ _loaded += data.size; // data.size != data.total, it's desirable fix this
+ proxyXHR.loaded = _loaded;
+ }
+
+ // emit "filecomplete" event
+ options.filecomplete(err, xhr, _file, _fileOptions);
+ }
+
+ // upload next file
+ setTimeout(function () {_nextFile.call(_this);}, 0);
+ }
+ })); // xhr
+
+
+ // ...
+ proxyXHR.abort = function (current){
+ if (!current) { dataArray.length = 0; }
+ this.current = current;
+ xhr.abort();
+ };
+
+ // Start upload
+ xhr.send(form);
+ });
+ }
+ else {
+ var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
+ options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
+ // Mark done state
+ _complete = true;
+ }
+ };
+
+
+ // Next tick
+ setTimeout(_nextFile, 0);
+
+
+ // Append more files to the existing request
+ // first - add them to the queue head/tail
+ proxyXHR.append = function (files, first) {
+ files = api._getFilesDataArray([].concat(files));
+
+ _each(files, function (data) {
+ _total += data.size;
+ proxyXHR.files.push(data.file);
+ if (first) {
+ dataArray.unshift(data);
+ } else {
+ dataArray.push(data);
+ }
+ });
+
+ proxyXHR.statusText = "";
+
+ if( _complete ){
+ _nextFile.call(_this);
+ }
+ };
+
+
+ // Removes file from queue by file reference and returns it
+ proxyXHR.remove = function (file) {
+ var i = dataArray.length, _file;
+ while( i-- ){
+ if( dataArray[i].file == file ){
+ _file = dataArray.splice(i, 1);
+ _total -= _file.size;
+ }
+ }
+ return _file;
+ };
+
+ return proxyXHR;
+ },
+
+
+ _getFilesDataArray: function (data){
+ var files = [], oFiles = {};
+
+ if( isInputFile(data) ){
+ var tmp = api.getFiles(data);
+ oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
+ }
+ else if( _isArray(data) && isInputFile(data[0]) ){
+ _each(data, function (input){
+ oFiles[input.name || 'file'] = api.getFiles(input);
+ });
+ }
+ else {
+ oFiles = data;
+ }
+
+ _each(oFiles, function add(file, name){
+ if( _isArray(file) ){
+ _each(file, function (file){
+ add(file, name);
+ });
+ }
+ else if( file && (file.name || file.image) ){
+ files.push({
+ name: name
+ , file: file
+ , size: file.size
+ , total: file.size
+ , loaded: 0
+ });
+ }
+ });
+
+ if( !files.length ){
+ // Create fake `file` object
+ files.push({ file: { name: api.expando } });
+ }
+
+ return files;
+ },
+
+
+ _getFormData: function (options, data, fn){
+ var
+ file = data.file
+ , name = data.name
+ , filename = file.name
+ , filetype = file.type
+ , trans = api.support.transform && options.imageTransform
+ , Form = new api.Form
+ , queue = api.queue(function (){ fn(Form); })
+ , isOrignTrans = trans && _isOriginTransform(trans)
+ , postNameConcat = api.postNameConcat
+ ;
+
+ // Append data
+ _each(options.data, function add(val, name){
+ if( typeof val == 'object' ){
+ _each(val, function (v, i){
+ add(v, postNameConcat(name, i));
+ });
+ }
+ else {
+ Form.append(name, val);
+ }
+ });
+
+ (function _addFile(file/**Object*/){
+ if( file.image ){ // This is a FileAPI.Image
+ queue.inc();
+
+ file.toData(function (err, image){
+ // @todo: требует рефакторинга и обработки ошибки
+ if (file.file) {
+ image.type = file.file.type;
+ image.quality = file.matrix.quality;
+ filename = file.file && file.file.name;
+ }
+
+ filename = filename || (new Date).getTime()+'.png';
+
+ _addFile(image);
+ queue.next();
+ });
+ }
+ else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
+ queue.inc();
+
+ if( isOrignTrans ){
+ // Convert to array for transform function
+ trans = [trans];
+ }
+
+ api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
+ if( isOrignTrans && !err ){
+ if( !dataURLtoBlob && !api.flashEngine ){
+ // Canvas.toBlob or Flash not supported, use multipart
+ Form.multipart = true;
+ }
+
+ Form.append(name, images[0], filename, trans[0].type || filetype);
+ }
+ else {
+ var addOrigin = 0;
+
+ if( !err ){
+ _each(images, function (image, idx){
+ if( !dataURLtoBlob && !api.flashEngine ){
+ Form.multipart = true;
+ }
+
+ if( !trans[idx].postName ){
+ addOrigin = 1;
+ }
+
+ Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
+ });
+ }
+
+ if( err || options.imageOriginal ){
+ Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
+ }
+ }
+
+ queue.next();
+ });
+ }
+ else if( filename !== api.expando ){
+ Form.append(name, file, filename);
+ }
+ })(file);
+
+ queue.check();
+ },
+
+
+ reset: function (inp, notRemove){
+ var parent, clone;
+
+ if( jQuery ){
+ clone = jQuery(inp).clone(true).insertBefore(inp).val('')[0];
+ if( !notRemove ){
+ jQuery(inp).remove();
+ }
+ } else {
+ parent = inp.parentNode;
+ clone = parent.insertBefore(inp.cloneNode(true), inp);
+ clone.value = '';
+
+ if( !notRemove ){
+ parent.removeChild(inp);
+ }
+
+ _each(_elEvents[api.uid(inp)], function (fns, type){
+ _each(fns, function (fn){
+ _off(inp, type, fn);
+ _on(clone, type, fn);
+ });
+ });
+ }
+
+ return clone;
+ },
+
+
+ /**
+ * Load remote file
+ *
+ * @param {String} url
+ * @param {Function} fn
+ * @return {XMLHttpRequest}
+ */
+ load: function (url, fn){
+ var xhr = api.getXHR();
+ if( xhr ){
+ xhr.open('GET', url, true);
+
+ if( xhr.overrideMimeType ){
+ xhr.overrideMimeType('text/plain; charset=x-user-defined');
+ }
+
+ _on(xhr, 'progress', function (/**Event*/evt){
+ /** @namespace evt.lengthComputable */
+ if( evt.lengthComputable ){
+ fn({ type: evt.type, loaded: evt.loaded, total: evt.total }, xhr);
+ }
+ });
+
+ xhr.onreadystatechange = function(){
+ if( xhr.readyState == 4 ){
+ xhr.onreadystatechange = null;
+ if( xhr.status == 200 ){
+ url = url.split('/');
+ /** @namespace xhr.responseBody */
+ var file = {
+ name: url[url.length-1]
+ , size: xhr.getResponseHeader('Content-Length')
+ , type: xhr.getResponseHeader('Content-Type')
+ };
+ file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
+ fn({ type: 'load', result: file }, xhr);
+ }
+ else {
+ fn({ type: 'error' }, xhr);
+ }
+ }
+ };
+ xhr.send(null);
+ } else {
+ fn({ type: 'error' });
+ }
+
+ return xhr;
+ },
+
+ encode64: function (str){
+ var b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=', outStr = '', i = 0;
+
+ if( typeof str !== 'string' ){
+ str = String(str);
+ }
+
+ while( i < str.length ){
+ //all three "& 0xff" added below are there to fix a known bug
+ //with bytes returned by xhr.responseText
+ var
+ byte1 = str.charCodeAt(i++) & 0xff
+ , byte2 = str.charCodeAt(i++) & 0xff
+ , byte3 = str.charCodeAt(i++) & 0xff
+ , enc1 = byte1 >> 2
+ , enc2 = ((byte1 & 3) << 4) | (byte2 >> 4)
+ , enc3, enc4
+ ;
+
+ if( isNaN(byte2) ){
+ enc3 = enc4 = 64;
+ } else {
+ enc3 = ((byte2 & 15) << 2) | (byte3 >> 6);
+ enc4 = isNaN(byte3) ? 64 : byte3 & 63;
+ }
+
+ outStr += b64.charAt(enc1) + b64.charAt(enc2) + b64.charAt(enc3) + b64.charAt(enc4);
+ }
+
+ return outStr;
+ }
+
+ } // api
+ ;
+
+
+ function _emit(target, fn, name, res, ext){
+ var evt = {
+ type: name.type || name
+ , target: target
+ , result: res
+ };
+ _extend(evt, ext);
+ fn(evt);
+ }
+
+
+ function _hasSupportReadAs(method){
+ return FileReader && !!FileReader.prototype['readAs' + method];
+ }
+
+
+ function _readAs(file, fn, method, encoding){
+ if( api.isBlob(file) && _hasSupportReadAs(method) ){
+ var Reader = new FileReader;
+
+ // Add event listener
+ _on(Reader, _readerEvents, function _fn(evt){
+ var type = evt.type;
+ if( type == 'progress' ){
+ _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
+ }
+ else if( type == 'loadend' ){
+ _off(Reader, _readerEvents, _fn);
+ Reader = null;
+ }
+ else {
+ _emit(file, fn, evt, evt.target.result);
+ }
+ });
+
+
+ try {
+ // ReadAs ...
+ if( encoding ){
+ Reader['readAs' + method](file, encoding);
+ }
+ else {
+ Reader['readAs' + method](file);
+ }
+ }
+ catch (err){
+ _emit(file, fn, 'error', undef, { error: err.toString() });
+ }
+ }
+ else {
+ _emit(file, fn, 'error', undef, { error: 'filreader_not_support_' + method });
+ }
+ }
+
+
+ function _isRegularFile(file, callback){
+ // http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
+ if( !file.type && (safari || ((file.size % 4096) === 0 && (file.size <= 102400))) ){
+ if( FileReader ){
+ try {
+ var reader = new FileReader();
+
+ _one(reader, _readerEvents, function (evt){
+ var isFile = evt.type != 'error';
+ if( isFile ){
+ if ( reader.readyState == null || reader.readyState === reader.LOADING ) {
+ reader.abort();
+ }
+ callback(isFile);
+ }
+ else {
+ callback(false, reader.error);
+ }
+ });
+
+ reader.readAsDataURL(file);
+ } catch( err ){
+ callback(false, err);
+ }
+ }
+ else {
+ callback(null, new Error('FileReader is not supported'));
+ }
+ }
+ else {
+ callback(true);
+ }
+ }
+
+
+ function _isEntry(item){
+ return item && (item.isFile || item.isDirectory);
+ }
+
+
+ function _getAsEntry(item){
+ var entry;
+ if( item.getAsEntry ){ entry = item.getAsEntry(); }
+ else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
+ return entry;
+ }
+
+
+ function _readEntryAsFiles(entry, callback){
+ if( !entry ){
+ // error
+ var err = new Error('invalid entry');
+ entry = new Object(entry);
+ entry.error = err;
+ callback(err.message, [], [entry]);
+ }
+ else if( entry.isFile ){
+ // Read as file
+ entry.file(function (file){
+ // success
+ file.fullPath = entry.fullPath;
+ callback(false, [file], [file]);
+ }, function (err){
+ // error
+ entry.error = err;
+ callback('FileError.code: ' + err.code, [], [entry]);
+ });
+ }
+ else if( entry.isDirectory ){
+ var
+ reader = entry.createReader()
+ , firstAttempt = true
+ , files = []
+ , all = [entry]
+ ;
+
+ var onerror = function (err){
+ // error
+ entry.error = err;
+ callback('DirectoryError.code: ' + err.code, files, all);
+ };
+ var ondone = function ondone(entries){
+ if( firstAttempt ){
+ firstAttempt = false;
+ if( !entries.length ){
+ entry.error = new Error('directory is empty');
+ }
+ }
+
+ // success
+ if( entries.length ){
+ api.afor(entries, function (next, entry){
+ _readEntryAsFiles(entry, function (err, entryFiles, allEntries){
+ if( !err ){
+ files = files.concat(entryFiles);
+ }
+ all = all.concat(allEntries);
+
+ if( next ){
+ next();
+ }
+ else {
+ reader.readEntries(ondone, onerror);
+ }
+ });
+ });
+ }
+ else {
+ callback(false, files, all);
+ }
+ };
+
+ reader.readEntries(ondone, onerror);
+ }
+ else {
+ _readEntryAsFiles(_getAsEntry(entry), callback);
+ }
+ }
+
+
+ function _simpleClone(obj){
+ var copy = {};
+ _each(obj, function (val, key){
+ if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
+ val = _extend({}, val);
+ }
+ copy[key] = val;
+ });
+ return copy;
+ }
+
+
+ function isInputFile(el){
+ return _rinput.test(el && el.tagName);
+ }
+
+
+ function _getDataTransfer(evt){
+ return (evt.originalEvent || evt || '').dataTransfer || {};
+ }
+
+
+ function _isOriginTransform(trans){
+ var key;
+ for( key in trans ){
+ if( trans.hasOwnProperty(key) ){
+ if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+
+ // Add default image info reader
+ api.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
+ if( !file.__dimensions ){
+ var defer = file.__dimensions = api.defer();
+
+ api.readAsImage(file, function (evt){
+ var img = evt.target;
+ defer.resolve(evt.type == 'load' ? false : 'error', {
+ width: img.width
+ , height: img.height
+ });
+ img.src = api.EMPTY_PNG;
+ img = null;
+ });
+ }
+
+ file.__dimensions.then(callback);
+ });
+
+
+ /**
+ * Drag'n'Drop special event
+ *
+ * @param {HTMLElement} el
+ * @param {Function} onHover
+ * @param {Function} onDrop
+ */
+ api.event.dnd = function (el, onHover, onDrop){
+ var _id, _type;
+
+ if( !onDrop ){
+ onDrop = onHover;
+ onHover = api.F;
+ }
+
+ if( FileReader ){
+ // Hover
+ _on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
+ var
+ types = _getDataTransfer(evt).types
+ , i = types && types.length
+ , debounceTrigger = false
+ ;
+
+ while( i-- ){
+ if( ~types[i].indexOf('File') ){
+ evt[preventDefault]();
+
+ if( _type !== evt.type ){
+ _type = evt.type; // Store current type of event
+
+ if( _type != 'dragleave' ){
+ onHover.call(evt[currentTarget], true, evt);
+ }
+
+ debounceTrigger = true;
+ }
+
+ break; // exit from "while"
+ }
+ }
+
+ if( debounceTrigger ){
+ clearTimeout(_id);
+ _id = setTimeout(function (){
+ onHover.call(evt[currentTarget], _type != 'dragleave', evt);
+ }, 50);
+ }
+ });
+
+
+ // Drop
+ _on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
+ evt[preventDefault]();
+
+ _type = 0;
+
+ api.getDropFiles(evt, function (files, all){
+ onDrop.call(evt[currentTarget], files, all, evt);
+ });
+
+ onHover.call(evt[currentTarget], false, evt);
+ });
+ }
+ else {
+ api.log("Drag'n'Drop -- not supported");
+ }
+ };
+
+
+ /**
+ * Remove drag'n'drop
+ * @param {HTMLElement} el
+ * @param {Function} onHover
+ * @param {Function} onDrop
+ */
+ api.event.dnd.off = function (el, onHover, onDrop){
+ _off(el, 'dragenter dragleave dragover', onHover.ff);
+ _off(el, 'drop', onDrop.ff);
+ };
+
+
+ // Support jQuery
+ if( jQuery && !jQuery.fn.dnd ){
+ jQuery.fn.dnd = function (onHover, onDrop){
+ return this.each(function (){
+ api.event.dnd(this, onHover, onDrop);
+ });
+ };
+
+ jQuery.fn.offdnd = function (onHover, onDrop){
+ return this.each(function (){
+ api.event.dnd.off(this, onHover, onDrop);
+ });
+ };
+ }
+
+ // @export
+ window.FileAPI = _extend(api, window.FileAPI);
+
+
+ // Debug info
+ api.log('FileAPI: ' + api.version);
+ api.log('protocol: ' + window.location.protocol);
+ api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
+
+
+ // @detect 'x-ua-compatible'
+ _each(document.getElementsByTagName('meta'), function (meta){
+ if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
+ api.log('meta.http-equiv: ' + meta.getAttribute('content'));
+ }
+ });
+
+
+ // Configuration
+ try {
+ _supportConsoleLog = !!console.log;
+ _supportConsoleLogApply = !!console.log.apply;
+ }
+ catch (err) {}
+
+ if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
+ if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
+ if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
+})(window, void 0);
+
+/*global window, FileAPI, document */
+
+(function (api, document, undef) {
+ 'use strict';
+
+ var
+ min = Math.min,
+ round = Math.round,
+ getCanvas = function () { return document.createElement('canvas'); },
+ support = false,
+ exifOrientation = {
+ 8: 270
+ , 3: 180
+ , 6: 90
+ , 7: 270
+ , 4: 180
+ , 5: 90
+ }
+ ;
+
+ try {
+ support = getCanvas().toDataURL('image/png').indexOf('data:image/png') > -1;
+ }
+ catch (e){}
+
+
+ function Image(file){
+ if( file instanceof Image ){
+ var img = new Image(file.file);
+ api.extend(img.matrix, file.matrix);
+ return img;
+ }
+ else if( !(this instanceof Image) ){
+ return new Image(file);
+ }
+
+ this.file = file;
+ this.size = file.size || 100;
+
+ this.matrix = {
+ sx: 0,
+ sy: 0,
+ sw: 0,
+ sh: 0,
+ dx: 0,
+ dy: 0,
+ dw: 0,
+ dh: 0,
+ resize: 0, // min, max OR preview
+ deg: 0,
+ quality: 1, // jpeg quality
+ filter: 0
+ };
+ }
+
+
+ Image.prototype = {
+ image: true,
+ constructor: Image,
+
+ set: function (attrs){
+ api.extend(this.matrix, attrs);
+ return this;
+ },
+
+ crop: function (x, y, w, h){
+ if( w === undef ){
+ w = x;
+ h = y;
+ x = y = 0;
+ }
+ return this.set({ sx: x, sy: y, sw: w, sh: h || w });
+ },
+
+ resize: function (w, h, strategy){
+ if( /min|max|height|width/.test(h) ){
+ strategy = h;
+ h = w;
+ }
+
+ return this.set({ dw: w, dh: h || w, resize: strategy });
+ },
+
+ preview: function (w, h){
+ return this.resize(w, h || w, 'preview');
+ },
+
+ rotate: function (deg){
+ return this.set({ deg: deg });
+ },
+
+ filter: function (filter){
+ return this.set({ filter: filter });
+ },
+
+ overlay: function (images){
+ return this.set({ overlay: images });
+ },
+
+ clone: function (){
+ return new Image(this);
+ },
+
+ _load: function (image, fn){
+ var self = this;
+
+ if( /img|video/i.test(image.nodeName) ){
+ fn.call(self, null, image);
+ }
+ else {
+ api.readAsImage(image, function (evt){
+ fn.call(self, evt.type != 'load', evt.result);
+ });
+ }
+ },
+
+ _apply: function (image, fn){
+ var
+ canvas = getCanvas()
+ , m = this.getMatrix(image)
+ , ctx = canvas.getContext('2d')
+ , width = image.videoWidth || image.width
+ , height = image.videoHeight || image.height
+ , deg = m.deg
+ , dw = m.dw
+ , dh = m.dh
+ , w = width
+ , h = height
+ , filter = m.filter
+ , copy // canvas copy
+ , buffer = image
+ , overlay = m.overlay
+ , queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
+ , renderImageToCanvas = api.renderImageToCanvas
+ ;
+
+ // Normalize angle
+ deg = deg - Math.floor(deg/360)*360;
+
+ // For `renderImageToCanvas`
+ image._type = this.file.type;
+
+ while(m.multipass && min(w/dw, h/dh) > 2 ){
+ w = (w/2 + 0.5)|0;
+ h = (h/2 + 0.5)|0;
+
+ copy = getCanvas();
+ copy.width = w;
+ copy.height = h;
+
+ if( buffer !== image ){
+ renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
+ buffer = copy;
+ }
+ else {
+ buffer = copy;
+ renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
+ m.sx = m.sy = m.sw = m.sh = 0;
+ }
+ }
+
+
+ canvas.width = (deg % 180) ? dh : dw;
+ canvas.height = (deg % 180) ? dw : dh;
+
+ canvas.type = m.type;
+ canvas.quality = m.quality;
+
+ ctx.rotate(deg * Math.PI / 180);
+ renderImageToCanvas(ctx.canvas, buffer
+ , m.sx, m.sy
+ , m.sw || buffer.width
+ , m.sh || buffer.height
+ , (deg == 180 || deg == 270 ? -dw : 0)
+ , (deg == 90 || deg == 180 ? -dh : 0)
+ , dw, dh
+ );
+
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
+
+ dw = canvas.width;
+ dh = canvas.height;
+
+ // Apply overlay
+ overlay && api.each([].concat(overlay), function (over){
+ queue.inc();
+ // preload
+ var img = new window.Image, fn = function (){
+ var
+ x = over.x|0
+ , y = over.y|0
+ , w = over.w || img.width
+ , h = over.h || img.height
+ , rel = over.rel
+ ;
+
+ // center | right | left
+ x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
+
+ // center | bottom | top
+ y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
+
+ api.event.off(img, 'error load abort', fn);
+
+ try {
+ ctx.globalAlpha = over.opacity || 1;
+ ctx.drawImage(img, x, y, w, h);
+ }
+ catch (er){}
+
+ queue.next();
+ };
+
+ api.event.on(img, 'error load abort', fn);
+ img.src = over.src;
+
+ if( img.complete ){
+ fn();
+ }
+ });
+
+ if( filter ){
+ queue.inc();
+ Image.applyFilter(canvas, filter, queue.next);
+ }
+
+ queue.check();
+ },
+
+ getMatrix: function (image){
+ var
+ m = api.extend({}, this.matrix)
+ , sw = m.sw = m.sw || image.videoWidth || image.naturalWidth || image.width
+ , sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
+ , dw = m.dw = m.dw || sw
+ , dh = m.dh = m.dh || sh
+ , sf = sw/sh, df = dw/dh
+ , strategy = m.resize
+ ;
+
+ if( strategy == 'preview' ){
+ if( dw != sw || dh != sh ){
+ // Make preview
+ var w, h;
+
+ if( df >= sf ){
+ w = sw;
+ h = w / df;
+ } else {
+ h = sh;
+ w = h * df;
+ }
+
+ if( w != sw || h != sh ){
+ m.sx = ~~((sw - w)/2);
+ m.sy = ~~((sh - h)/2);
+ sw = w;
+ sh = h;
+ }
+ }
+ }
+ else if( strategy == 'height' ){
+ dw = dh * sf;
+ }
+ else if( strategy == 'width' ){
+ dh = dw / sf;
+ }
+ else if( strategy ){
+ if( !(sw > dw || sh > dh) ){
+ dw = sw;
+ dh = sh;
+ }
+ else if( strategy == 'min' ){
+ dw = round(sf < df ? min(sw, dw) : dh*sf);
+ dh = round(sf < df ? dw/sf : min(sh, dh));
+ }
+ else {
+ dw = round(sf >= df ? min(sw, dw) : dh*sf);
+ dh = round(sf >= df ? dw/sf : min(sh, dh));
+ }
+ }
+
+ m.sw = sw;
+ m.sh = sh;
+ m.dw = dw;
+ m.dh = dh;
+ m.multipass = api.multiPassResize;
+ return m;
+ },
+
+ _trans: function (fn){
+ this._load(this.file, function (err, image){
+ if( err ){
+ fn(err);
+ }
+ else {
+ try {
+ this._apply(image, fn);
+ } catch (err){
+ api.log('[err] FileAPI.Image.fn._apply:', err);
+ fn(err);
+ }
+ }
+ });
+ },
+
+
+ get: function (fn){
+ if( api.support.transform ){
+ var _this = this, matrix = _this.matrix;
+
+ if( matrix.deg == 'auto' ){
+ api.getInfo(_this.file, function (err, info){
+ // rotate by exif orientation
+ matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
+ _this._trans(fn);
+ });
+ }
+ else {
+ _this._trans(fn);
+ }
+ }
+ else {
+ fn('not_support_transform');
+ }
+
+ return this;
+ },
+
+
+ toData: function (fn){
+ return this.get(fn);
+ }
+
+ };
+
+
+ Image.exifOrientation = exifOrientation;
+
+
+ Image.transform = function (file, transform, autoOrientation, fn){
+ function _transform(err, img){
+ // img -- info object
+ var
+ images = {}
+ , queue = api.queue(function (err){
+ fn(err, images);
+ })
+ ;
+
+ if( !err ){
+ api.each(transform, function (params, name){
+ if( !queue.isFail() ){
+ var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
+
+ if( isFn ){
+ params(img, ImgTrans);
+ }
+ else if( params.width ){
+ ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
+ }
+ else {
+ if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
+ ImgTrans.resize(params.maxWidth, params.maxHeight, 'max');
+ }
+ }
+
+ if( params.crop ){
+ var crop = params.crop;
+ ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
+ }
+
+ if( params.rotate === undef && autoOrientation ){
+ params.rotate = 'auto';
+ }
+
+ ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
+
+ if( !isFn ){
+ ImgTrans.set({
+ deg: params.rotate
+ , overlay: params.overlay
+ , filter: params.filter
+ , quality: params.quality || 1
+ });
+ }
+
+ queue.inc();
+ ImgTrans.toData(function (err, image){
+ if( err ){
+ queue.fail();
+ }
+ else {
+ images[name] = image;
+ queue.next();
+ }
+ });
+ }
+ });
+ }
+ else {
+ queue.fail();
+ }
+ }
+
+
+ // @todo: Оло-ло, нужно рефакторить это место
+ if( file.width ){
+ _transform(false, file);
+ } else {
+ api.getInfo(file, _transform);
+ }
+ };
+
+
+ // @const
+ api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
+ api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
+ Image[x+'_'+y] = i*3 + j;
+ Image[y+'_'+x] = i*3 + j;
+ });
+ });
+
+
+ /**
+ * Trabsform element to canvas
+ *
+ * @param {Image|HTMLVideoElement} el
+ * @returns {Canvas}
+ */
+ Image.toCanvas = function(el){
+ var canvas = document.createElement('canvas');
+ canvas.width = el.videoWidth || el.width;
+ canvas.height = el.videoHeight || el.height;
+ canvas.getContext('2d').drawImage(el, 0, 0);
+ return canvas;
+ };
+
+
+ /**
+ * Create image from DataURL
+ * @param {String} dataURL
+ * @param {Object} size
+ * @param {Function} callback
+ */
+ Image.fromDataURL = function (dataURL, size, callback){
+ var img = api.newImage(dataURL);
+ api.extend(img, size);
+ callback(img);
+ };
+
+
+ /**
+ * Apply filter (caman.js)
+ *
+ * @param {Canvas|Image} canvas
+ * @param {String|Function} filter
+ * @param {Function} doneFn
+ */
+ Image.applyFilter = function (canvas, filter, doneFn){
+ if( typeof filter == 'function' ){
+ filter(canvas, doneFn);
+ }
+ else if( window.Caman ){
+ // http://camanjs.com/guides/
+ window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
+ if( typeof filter == 'string' ){
+ this[filter]();
+ }
+ else {
+ api.each(filter, function (val, method){
+ this[method](val);
+ }, this);
+ }
+ this.render(doneFn);
+ });
+ }
+ };
+
+
+ /**
+ * For load-image-ios.js
+ */
+ api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
+ try {
+ return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
+ } catch (ex) {
+ api.log('renderImageToCanvas failed');
+ throw ex;
+ }
+ };
+
+
+ // @export
+ api.support.canvas = api.support.transform = support;
+ api.Image = Image;
+})(FileAPI, document);
+
+/*
+ * JavaScript Load Image iOS scaling fixes 1.0.3
+ * https://github.com/blueimp/JavaScript-Load-Image
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * iOS image scaling fixes based on
+ * https://github.com/stomita/ios-imagefile-megapixel
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/*jslint nomen: true, bitwise: true */
+/*global FileAPI, window, document */
+
+(function (factory) {
+ 'use strict';
+ factory(FileAPI);
+}(function (loadImage) {
+ 'use strict';
+
+ // Only apply fixes on the iOS platform:
+ if (!window.navigator || !window.navigator.platform ||
+ !(/iP(hone|od|ad)/).test(window.navigator.platform)) {
+ return;
+ }
+
+ var originalRenderMethod = loadImage.renderImageToCanvas;
+
+ // Detects subsampling in JPEG images:
+ loadImage.detectSubsampling = function (img) {
+ var canvas,
+ context;
+ if (img.width * img.height > 1024 * 1024) { // only consider megapixel images
+ canvas = document.createElement('canvas');
+ canvas.width = canvas.height = 1;
+ context = canvas.getContext('2d');
+ context.drawImage(img, -img.width + 1, 0);
+ // subsampled image becomes half smaller in rendering size.
+ // check alpha channel value to confirm image is covering edge pixel or not.
+ // if alpha value is 0 image is not covering, hence subsampled.
+ return context.getImageData(0, 0, 1, 1).data[3] === 0;
+ }
+ return false;
+ };
+
+ // Detects vertical squash in JPEG images:
+ loadImage.detectVerticalSquash = function (img, subsampled) {
+ var naturalHeight = img.naturalHeight || img.height,
+ canvas = document.createElement('canvas'),
+ context = canvas.getContext('2d'),
+ data,
+ sy,
+ ey,
+ py,
+ alpha;
+ if (subsampled) {
+ naturalHeight /= 2;
+ }
+ canvas.width = 1;
+ canvas.height = naturalHeight;
+ context.drawImage(img, 0, 0);
+ data = context.getImageData(0, 0, 1, naturalHeight).data;
+ // search image edge pixel position in case it is squashed vertically:
+ sy = 0;
+ ey = naturalHeight;
+ py = naturalHeight;
+ while (py > sy) {
+ alpha = data[(py - 1) * 4 + 3];
+ if (alpha === 0) {
+ ey = py;
+ } else {
+ sy = py;
+ }
+ py = (ey + sy) >> 1;
+ }
+ return (py / naturalHeight) || 1;
+ };
+
+ // Renders image to canvas while working around iOS image scaling bugs:
+ // https://github.com/blueimp/JavaScript-Load-Image/issues/13
+ loadImage.renderImageToCanvas = function (
+ canvas,
+ img,
+ sourceX,
+ sourceY,
+ sourceWidth,
+ sourceHeight,
+ destX,
+ destY,
+ destWidth,
+ destHeight
+ ) {
+ if (img._type === 'image/jpeg') {
+ var context = canvas.getContext('2d'),
+ tmpCanvas = document.createElement('canvas'),
+ tileSize = 1024,
+ tmpContext = tmpCanvas.getContext('2d'),
+ subsampled,
+ vertSquashRatio,
+ tileX,
+ tileY;
+ tmpCanvas.width = tileSize;
+ tmpCanvas.height = tileSize;
+ context.save();
+ subsampled = loadImage.detectSubsampling(img);
+ if (subsampled) {
+ sourceX /= 2;
+ sourceY /= 2;
+ sourceWidth /= 2;
+ sourceHeight /= 2;
+ }
+ vertSquashRatio = loadImage.detectVerticalSquash(img, subsampled);
+ if (subsampled || vertSquashRatio !== 1) {
+ sourceY *= vertSquashRatio;
+ destWidth = Math.ceil(tileSize * destWidth / sourceWidth);
+ destHeight = Math.ceil(
+ tileSize * destHeight / sourceHeight / vertSquashRatio
+ );
+ destY = 0;
+ tileY = 0;
+ while (tileY < sourceHeight) {
+ destX = 0;
+ tileX = 0;
+ while (tileX < sourceWidth) {
+ tmpContext.clearRect(0, 0, tileSize, tileSize);
+ tmpContext.drawImage(
+ img,
+ sourceX,
+ sourceY,
+ sourceWidth,
+ sourceHeight,
+ -tileX,
+ -tileY,
+ sourceWidth,
+ sourceHeight
+ );
+ context.drawImage(
+ tmpCanvas,
+ 0,
+ 0,
+ tileSize,
+ tileSize,
+ destX,
+ destY,
+ destWidth,
+ destHeight
+ );
+ tileX += tileSize;
+ destX += destWidth;
+ }
+ tileY += tileSize;
+ destY += destHeight;
+ }
+ context.restore();
+ return canvas;
+ }
+ }
+ return originalRenderMethod(
+ canvas,
+ img,
+ sourceX,
+ sourceY,
+ sourceWidth,
+ sourceHeight,
+ destX,
+ destY,
+ destWidth,
+ destHeight
+ );
+ };
+
+}));
+
+/*global window, FileAPI */
+
+(function (api, window){
+ "use strict";
+
+ var
+ document = window.document
+ , FormData = window.FormData
+ , Form = function (){ this.items = []; }
+ , encodeURIComponent = window.encodeURIComponent
+ ;
+
+
+ Form.prototype = {
+
+ append: function (name, blob, file, type){
+ this.items.push({
+ name: name
+ , blob: blob && blob.blob || (blob == void 0 ? '' : blob)
+ , file: blob && (file || blob.name)
+ , type: blob && (type || blob.type)
+ });
+ },
+
+ each: function (fn){
+ var i = 0, n = this.items.length;
+ for( ; i < n; i++ ){
+ fn.call(this, this.items[i]);
+ }
+ },
+
+ toData: function (fn, options){
+ // allow chunked transfer if we have only one file to send
+ // flag is used below and in XHR._send
+ options._chunked = api.support.chunked && options.chunkSize > 0 && api.filter(this.items, function (item){ return item.file; }).length == 1;
+
+ if( !api.support.html5 ){
+ api.log('FileAPI.Form.toHtmlData');
+ this.toHtmlData(fn);
+ }
+ else if( !api.formData || this.multipart || !FormData ){
+ api.log('FileAPI.Form.toMultipartData');
+ this.toMultipartData(fn);
+ }
+ else if( options._chunked ){
+ api.log('FileAPI.Form.toPlainData');
+ this.toPlainData(fn);
+ }
+ else {
+ api.log('FileAPI.Form.toFormData');
+ this.toFormData(fn);
+ }
+ },
+
+ _to: function (data, complete, next, arg){
+ var queue = api.queue(function (){
+ complete(data);
+ });
+
+ this.each(function (file){
+ try{
+ next(file, data, queue, arg);
+ }
+ catch( err ){
+ api.log('FileAPI.Form._to: ' + err.message);
+ complete(err);
+ }
+ });
+
+ queue.check();
+ },
+
+
+ toHtmlData: function (fn){
+ this._to(document.createDocumentFragment(), fn, function (file, data/**DocumentFragment*/){
+ var blob = file.blob, hidden;
+
+ if( file.file ){
+ api.reset(blob, true);
+ // set new name
+ blob.name = file.name;
+ blob.disabled = false;
+ data.appendChild(blob);
+ }
+ else {
+ hidden = document.createElement('input');
+ hidden.name = file.name;
+ hidden.type = 'hidden';
+ hidden.value = blob;
+ data.appendChild(hidden);
+ }
+ });
+ },
+
+ toPlainData: function (fn){
+ this._to({}, fn, function (file, data, queue){
+ if( file.file ){
+ data.type = file.file;
+ }
+
+ if( file.blob.toBlob ){
+ // canvas
+ queue.inc();
+ _convertFile(file, function (file, blob){
+ data.name = file.name;
+ data.file = blob;
+ data.size = blob.length;
+ data.type = file.type;
+ queue.next();
+ });
+ }
+ else if( file.file ){
+ // file
+ data.name = file.blob.name;
+ data.file = file.blob;
+ data.size = file.blob.size;
+ data.type = file.type;
+ }
+ else {
+ // additional data
+ if( !data.params ){
+ data.params = [];
+ }
+ data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
+ }
+
+ data.start = -1;
+ data.end = data.file && data.file.FileAPIReadPosition || -1;
+ data.retry = 0;
+ });
+ },
+
+ toFormData: function (fn){
+ this._to(new FormData, fn, function (file, data, queue){
+ if( file.blob && file.blob.toBlob ){
+ queue.inc();
+ _convertFile(file, function (file, blob){
+ data.append(file.name, blob, file.file);
+ queue.next();
+ });
+ }
+ else if( file.file ){
+ data.append(file.name, file.blob, file.file);
+ }
+ else {
+ data.append(file.name, file.blob);
+ }
+
+ if( file.file ){
+ data.append('_'+file.name, file.file);
+ }
+ });
+ },
+
+
+ toMultipartData: function (fn){
+ this._to([], fn, function (file, data, queue, boundary){
+ queue.inc();
+ _convertFile(file, function (file, blob){
+ data.push(
+ '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
+ + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
+ + '\r\n'
+ + '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
+ + '\r\n')
+ );
+ queue.next();
+ }, true);
+ }, api.expando);
+ }
+ };
+
+
+ function _convertFile(file, fn, useBinaryString){
+ var blob = file.blob, filename = file.file;
+
+ if( filename ){
+ if( !blob.toDataURL ){
+ // The Blob is not an image.
+ api.readAsBinaryString(blob, function (evt){
+ if( evt.type == 'load' ){
+ fn(file, evt.result);
+ }
+ });
+ return;
+ }
+
+ var
+ mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
+ , type = mime[file.type] ? file.type : 'image/png'
+ , ext = mime[type] || '.png'
+ , quality = blob.quality || 1
+ ;
+
+ if( !filename.match(new RegExp(ext+'$', 'i')) ){
+ // Does not change the current extension, but add a new one.
+ filename += ext.replace('?', '');
+ }
+
+ file.file = filename;
+ file.type = type;
+
+ if( !useBinaryString && blob.toBlob ){
+ blob.toBlob(function (blob){
+ fn(file, blob);
+ }, type, quality);
+ }
+ else {
+ fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
+ }
+ }
+ else {
+ fn(file, blob);
+ }
+ }
+
+
+ // @export
+ api.Form = Form;
+})(FileAPI, window);
+
+/*global window, FileAPI, Uint8Array */
+
+(function (window, api){
+ "use strict";
+
+ var
+ noop = function (){}
+ , document = window.document
+
+ , XHR = function (options){
+ this.uid = api.uid();
+ this.xhr = {
+ abort: noop
+ , getResponseHeader: noop
+ , getAllResponseHeaders: noop
+ };
+ this.options = options;
+ },
+
+ _xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
+ ;
+
+
+ XHR.prototype = {
+ status: 0,
+ statusText: '',
+ constructor: XHR,
+
+ getResponseHeader: function (name){
+ return this.xhr.getResponseHeader(name);
+ },
+
+ getAllResponseHeaders: function (){
+ return this.xhr.getAllResponseHeaders() || {};
+ },
+
+ end: function (status, statusText){
+ var _this = this, options = _this.options;
+
+ _this.end =
+ _this.abort = noop;
+ _this.status = status;
+
+ if( statusText ){
+ _this.statusText = statusText;
+ }
+
+ api.log('xhr.end:', status, statusText);
+ options.complete(status == 200 || status == 201 ? false : _this.statusText || 'unknown', _this);
+
+ if( _this.xhr && _this.xhr.node ){
+ setTimeout(function (){
+ var node = _this.xhr.node;
+ try { node.parentNode.removeChild(node); } catch (e){}
+ try { delete window[_this.uid]; } catch (e){}
+ window[_this.uid] = _this.xhr.node = null;
+ }, 9);
+ }
+ },
+
+ abort: function (){
+ this.end(0, 'abort');
+
+ if( this.xhr ){
+ this.xhr.aborted = true;
+ this.xhr.abort();
+ }
+ },
+
+ send: function (FormData){
+ var _this = this, options = this.options;
+
+ FormData.toData(function (data){
+ if( data instanceof Error ){
+ _this.end(0, data.message);
+ }
+ else{
+ // Start uploading
+ options.upload(options, _this);
+ _this._send.call(_this, options, data);
+ }
+ }, options);
+ },
+
+ _send: function (options, data){
+ var _this = this, xhr, uid = _this.uid, onLoadFnName = _this.uid + "Load", url = options.url;
+
+ api.log('XHR._send:', data);
+
+ if( !options.cache ){
+ // No cache
+ url += (~url.indexOf('?') ? '&' : '?') + api.uid();
+ }
+
+ if( data.nodeName ){
+ var jsonp = options.jsonp;
+
+ // prepare callback in GET
+ url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
+
+ // legacy
+ options.upload(options, _this);
+
+ var
+ onPostMessage = function (evt){
+ if( ~url.indexOf(evt.origin) ){
+ try {
+ var result = api.parseJSON(evt.data);
+ if( result.id == uid ){
+ complete(result.status, result.statusText, result.response);
+ }
+ } catch( err ){
+ complete(0, err.message);
+ }
+ }
+ },
+
+ // jsonp-callack
+ complete = window[uid] = function (status, statusText, response){
+ _this.readyState = 4;
+ _this.responseText = response;
+ _this.end(status, statusText);
+
+ api.event.off(window, 'message', onPostMessage);
+ window[uid] = xhr = transport = window[onLoadFnName] = null;
+ }
+ ;
+
+ _this.xhr.abort = function (){
+ try {
+ if( transport.stop ){ transport.stop(); }
+ else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
+ else { transport.contentWindow.document.execCommand('Stop'); }
+ }
+ catch (er) {}
+ complete(0, "abort");
+ };
+
+ api.event.on(window, 'message', onPostMessage);
+
+ window[onLoadFnName] = function (){
+ try {
+ var
+ win = transport.contentWindow
+ , doc = win.document
+ , result = win.result || api.parseJSON(doc.body.innerHTML)
+ ;
+ complete(result.status, result.statusText, result.response);
+ } catch (e){
+ api.log('[transport.onload]', e);
+ }
+ };
+
+ xhr = document.createElement('div');
+ xhr.innerHTML = ''
+ ;
+
+ // get form-data & transport
+ var
+ form = xhr.getElementsByTagName('form')[0]
+ , transport = xhr.getElementsByTagName('iframe')[0]
+ ;
+
+ form.appendChild(data);
+
+ api.log(form.parentNode.innerHTML);
+
+ // append to DOM
+ document.body.appendChild(xhr);
+
+ // keep a reference to node-transport
+ _this.xhr.node = xhr;
+
+ // send
+ _this.readyState = 2; // loaded
+ try {
+ form.submit();
+ } catch (err) {
+ api.log('iframe.error: ' + err);
+ }
+ form = null;
+ }
+ else {
+ // Clean url
+ url = url.replace(/([a-z]+)=(\?)&?/i, '');
+
+ // html5
+ if (this.xhr && this.xhr.aborted) {
+ api.log("Error: already aborted");
+ return;
+ }
+ xhr = _this.xhr = api.getXHR();
+
+ if (data.params) {
+ url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
+ }
+
+ xhr.open(options.uploadMethod || 'POST', url, true);
+
+ if (typeof options.uploadCredentials === 'boolean') {
+ xhr.withCredentials = options.uploadCredentials ? 'true' : null;
+ } else if( api.withCredentials ){
+ xhr.withCredentials = "true";
+ }
+
+ if( !options.headers || !options.headers['X-Requested-With'] ){
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
+ }
+
+ api.each(options.headers, function (val, key){
+ xhr.setRequestHeader(key, val);
+ });
+
+
+ if ( options._chunked ) {
+ // chunked upload
+ if( xhr.upload ){
+ xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
+ if (!data.retry) {
+ // show progress only for correct chunk uploads
+ options.progress({
+ type: evt.type
+ , total: data.size
+ , loaded: data.start + evt.loaded
+ , totalSize: data.size
+ }, _this, options);
+ }
+ }, 100), false);
+ }
+
+ xhr.onreadystatechange = function (){
+ var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
+
+ _this.status = xhr.status;
+ _this.statusText = xhr.statusText;
+ _this.readyState = xhr.readyState;
+
+ if( xhr.readyState == 4 ){
+ for( var k in _xhrResponsePostfix ){
+ _this['response'+k] = xhr['response'+k];
+ }
+ xhr.onreadystatechange = null;
+
+ if (!xhr.status || xhr.status - 201 > 0) {
+ api.log("Error: " + xhr.status);
+ // some kind of error
+ // 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
+ // up - server error
+ if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
+ // let's try again the same chunk
+ // only applicable for recoverable error codes 500 && 416
+ var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
+
+ // inform about recoverable problems
+ options.pause(data.file, options);
+
+ // smart restart if server reports about the last known byte
+ api.log("X-Last-Known-Byte: " + lkb);
+ if (lkb) {
+ data.end = lkb;
+ } else {
+ data.end = data.start - 1;
+ if (416 == xhr.status) {
+ data.end = data.end - options.chunkSize;
+ }
+ }
+
+ setTimeout(function () {
+ _this._send(options, data);
+ }, delay);
+ } else {
+ // no mo retries
+ _this.end(xhr.status);
+ }
+ } else {
+ // success
+ data.retry = 0;
+
+ if (data.end == data.size - 1) {
+ // finished
+ _this.end(xhr.status);
+ } else {
+ // next chunk
+
+ // shift position if server reports about the last known byte
+ api.log("X-Last-Known-Byte: " + lkb);
+ if (lkb) {
+ data.end = lkb;
+ }
+ data.file.FileAPIReadPosition = data.end;
+
+ setTimeout(function () {
+ _this._send(options, data);
+ }, 0);
+ }
+ }
+
+ xhr = null;
+ }
+ };
+
+ data.start = data.end + 1;
+ data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
+
+ // Retrieve a slice of file
+ var
+ file = data.file
+ , slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
+ ;
+
+ if( data.size && !slice.size ){
+ setTimeout(function (){
+ _this.end(-1);
+ });
+ } else {
+ xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
+ xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
+ xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
+
+ xhr.send(slice);
+ }
+
+ file = slice = null;
+ } else {
+ // single piece upload
+ if( xhr.upload ){
+ // https://github.com/blueimp/jQuery-File-Upload/wiki/Fixing-Safari-hanging-on-very-high-speed-connections-%281Gbps%29
+ xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
+ options.progress(evt, _this, options);
+ }, 100), false);
+ }
+
+ xhr.onreadystatechange = function (){
+ _this.status = xhr.status;
+ _this.statusText = xhr.statusText;
+ _this.readyState = xhr.readyState;
+
+ if( xhr.readyState == 4 ){
+ for( var k in _xhrResponsePostfix ){
+ _this['response'+k] = xhr['response'+k];
+ }
+ xhr.onreadystatechange = null;
+
+ if (!xhr.status || xhr.status > 201) {
+ api.log("Error: " + xhr.status);
+ if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
+ options.retry = (options.retry || 0) + 1;
+ var delay = api.networkDownRetryTimeout;
+
+ // inform about recoverable problems
+ options.pause(options.file, options);
+
+ setTimeout(function () {
+ _this._send(options, data);
+ }, delay);
+ } else {
+ //success
+ _this.end(xhr.status);
+ }
+ } else {
+ //success
+ _this.end(xhr.status);
+ }
+
+ xhr = null;
+ }
+ };
+
+ if( api.isArray(data) ){
+ // multipart
+ xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
+ var rawData = data.join('') +'--_'+ api.expando +'--';
+
+ /** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
+ if( xhr.sendAsBinary ){
+ xhr.sendAsBinary(rawData);
+ }
+ else {
+ var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
+ xhr.send(new Uint8Array(bytes).buffer);
+
+ }
+ } else {
+ // FormData
+ xhr.send(data);
+ }
+ }
+ }
+ }
+ };
+
+
+ // @export
+ api.XHR = XHR;
+})(window, FileAPI);
+
+/**
+ * @class FileAPI.Camera
+ * @author RubaXa
+ * @support Chrome 21+, FF 18+, Opera 12+
+ */
+
+/*global window, FileAPI, jQuery */
+/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
+(function (window, api){
+ "use strict";
+
+ var
+ URL = window.URL || window.webkitURL,
+
+ document = window.document,
+ navigator = window.navigator,
+
+ getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
+
+ html5 = !!getMedia
+ ;
+
+
+ // Support "media"
+ api.support.media = html5;
+
+
+ var Camera = function (video){
+ this.video = video;
+ };
+
+
+ Camera.prototype = {
+ isActive: function (){
+ return !!this._active;
+ },
+
+
+ /**
+ * Start camera streaming
+ * @param {Function} callback
+ */
+ start: function (callback){
+ var
+ _this = this
+ , video = _this.video
+ , _successId
+ , _failId
+ , _complete = function (err){
+ _this._active = !err;
+ clearTimeout(_failId);
+ clearTimeout(_successId);
+// api.event.off(video, 'loadedmetadata', _complete);
+ callback && callback(err, _this);
+ }
+ ;
+
+ getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
+ // Success
+ _this.stream = stream;
+
+// api.event.on(video, 'loadedmetadata', function (){
+// _complete(null);
+// });
+
+ // Set camera stream
+ try {
+ video.src = URL.createObjectURL(stream);
+ } catch (err) {
+ video.srcObject = stream;
+ }
+
+ // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
+ // See crbug.com/110938.
+ _successId = setInterval(function (){
+ if( _detectVideoSignal(video) ){
+ _complete(null);
+ }
+ }, 1000);
+
+ _failId = setTimeout(function (){
+ _complete('timeout');
+ }, 5000);
+
+ // Go-go-go!
+ video.play();
+ }, _complete/*error*/);
+ },
+
+
+ /**
+ * Stop camera streaming
+ */
+ stop: function (){
+ try {
+ this._active = false;
+ this.video.pause();
+
+ try {
+ this.stream.stop();
+ } catch (err) {
+ api.each(this.stream.getTracks(), function (track) {
+ track.stop();
+ });
+ }
+
+ this.stream = null;
+ } catch( err ){
+ api.log('[FileAPI.Camera] stop:', err);
+ }
+ },
+
+
+ /**
+ * Create screenshot
+ * @return {FileAPI.Camera.Shot}
+ */
+ shot: function (){
+ return new Shot(this.video);
+ }
+ };
+
+
+ /**
+ * Get camera element from container
+ *
+ * @static
+ * @param {HTMLElement} el
+ * @return {Camera}
+ */
+ Camera.get = function (el){
+ return new Camera(el.firstChild);
+ };
+
+
+ /**
+ * Publish camera element into container
+ *
+ * @static
+ * @param {HTMLElement} el
+ * @param {Object} options
+ * @param {Function} [callback]
+ */
+ Camera.publish = function (el, options, callback){
+ if( typeof options == 'function' ){
+ callback = options;
+ options = {};
+ }
+
+ // Dimensions of "camera"
+ options = api.extend({}, {
+ width: '100%'
+ , height: '100%'
+ , start: true
+ }, options);
+
+
+ if( el.jquery ){
+ // Extract first element, from jQuery collection
+ el = el[0];
+ }
+
+
+ var doneFn = function (err){
+ if( err ){
+ callback(err);
+ }
+ else {
+ // Get camera
+ var cam = Camera.get(el);
+ if( options.start ){
+ cam.start(callback);
+ }
+ else {
+ callback(null, cam);
+ }
+ }
+ };
+
+
+ el.style.width = _px(options.width);
+ el.style.height = _px(options.height);
+
+
+ if( api.html5 && html5 && !api.insecureChrome ){
+ // Create video element
+ var video = document.createElement('video');
+
+ // Set dimensions
+ video.style.width = _px(options.width);
+ video.style.height = _px(options.height);
+
+ // Clean container
+ if( window.jQuery ){
+ jQuery(el).empty();
+ } else {
+ el.innerHTML = '';
+ }
+
+ // Add "camera" to container
+ el.appendChild(video);
+
+ // end
+ doneFn();
+ }
+ else {
+ Camera.fallback(el, options, doneFn);
+ }
+ };
+
+
+ Camera.fallback = function (el, options, callback){
+ callback('not_support_camera');
+ };
+
+ Camera.checkAlreadyCaptured = (function () {
+ var mediaDevices = navigator.mediaDevices,
+ MediaStreamTrack = window.MediaStreamTrack,
+ navigatorEnumerateDevices = navigator.enumerateDevices,
+ enumerateDevices;
+
+ if (mediaDevices && mediaDevices.enumerateDevices) {
+ enumerateDevices = function (callback) {
+ mediaDevices.enumerateDevices().then(callback);
+ };
+ } else if (MediaStreamTrack && MediaStreamTrack.getSources) {
+ enumerateDevices = MediaStreamTrack.getSources.bind(MediaStreamTrack);
+ } else if (navigatorEnumerateDevices) {
+ enumerateDevices = navigatorEnumerateDevices.bind(navigator);
+ } else {
+ enumerateDevices = function (fn) {
+ fn([]);
+ };
+ }
+
+ return function (callback) {
+ enumerateDevices(function (devices) {
+ var deviceExists = devices.some(function (device) {
+ return (device.kind === 'videoinput' || device.kind === 'video') && device.label;
+ });
+
+ callback(deviceExists);
+ });
+ };
+
+ })();
+
+
+ /**
+ * @class FileAPI.Camera.Shot
+ */
+ var Shot = function (video){
+ var canvas = video.nodeName ? api.Image.toCanvas(video) : video;
+ var shot = api.Image(canvas);
+ shot.type = 'image/png';
+ shot.width = canvas.width;
+ shot.height = canvas.height;
+ shot.size = canvas.width * canvas.height * 4;
+ return shot;
+ };
+
+
+ /**
+ * Add "px" postfix, if value is a number
+ *
+ * @private
+ * @param {*} val
+ * @return {String}
+ */
+ function _px(val){
+ return val >= 0 ? val + 'px' : val;
+ }
+
+
+ /**
+ * @private
+ * @param {HTMLVideoElement} video
+ * @return {Boolean}
+ */
+ function _detectVideoSignal(video){
+ var canvas = document.createElement('canvas'), ctx, res = false;
+ try {
+ ctx = canvas.getContext('2d');
+ ctx.drawImage(video, 0, 0, 1, 1);
+ res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
+ }
+ catch( err ){
+ api.log('[FileAPI.Camera] detectVideoSignal:', err);
+ }
+ return res;
+ }
+
+
+ // @export
+ Camera.Shot = Shot;
+ api.Camera = Camera;
+})(window, FileAPI);
+
+/**
+ * FileAPI fallback to Flash
+ *
+ * @flash-developer "Vladimir Demidov"
+ */
+
+/*global window, ActiveXObject, FileAPI */
+(function (window, jQuery, api) {
+ "use strict";
+
+ var
+ document = window.document
+ , location = window.location
+ , navigator = window.navigator
+ , _each = api.each
+ ;
+
+
+ api.support.flash = (function (){
+ var mime = navigator.mimeTypes, has = false;
+
+ if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
+ has = navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
+ }
+ else {
+ try {
+ has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
+ }
+ catch(er){
+ api.log('Flash -- does not supported.');
+ }
+ }
+
+ if( has && /^file:/i.test(location) ){
+ api.log('[warn] Flash does not work on `file:` protocol.');
+ }
+
+ return has;
+ })();
+
+
+ api.support.flash
+ && (0
+ || !api.html5 || !api.support.html5
+ || (api.cors && !api.support.cors)
+ || (api.media && !api.support.media)
+ || api.insecureChrome
+ )
+ && (function (){
+ var
+ _attr = api.uid()
+ , _retry = 0
+ , _files = {}
+ , _rhttp = /^https?:/i
+
+ , flash = {
+ _fn: {},
+
+
+ /**
+ * Initialization & preload flash object
+ */
+ init: function (){
+ var child = document.body && document.body.firstChild;
+
+ if( child ){
+ do {
+ if( child.nodeType == 1 ){
+ api.log('FlashAPI.state: awaiting');
+
+ var dummy = document.createElement('div');
+
+ dummy.id = '_' + _attr;
+
+ _css(dummy, {
+ top: 1
+ , right: 1
+ , width: 5
+ , height: 5
+ , position: 'absolute'
+ , zIndex: 2147483647+'' // set max zIndex
+ });
+
+ child.parentNode.insertBefore(dummy, child);
+ flash.publish(dummy, _attr);
+
+ return;
+ }
+ }
+ while( child = child.nextSibling );
+ }
+
+ if( _retry < 10 ){
+ setTimeout(flash.init, ++_retry*50);
+ }
+ },
+
+
+ /**
+ * Publish flash-object
+ *
+ * @param {HTMLElement} el
+ * @param {String} id
+ * @param {Object} [opts]
+ */
+ publish: function (el, id, opts){
+ opts = opts || {};
+ el.innerHTML = _makeFlashHTML({
+ id: id
+ , src: _getUrl(api.flashUrl, 'r=' + api.version)
+// , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
+ , wmode: opts.camera ? '' : 'transparent'
+ , flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
+ + '&flashId='+ id
+ + '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
+ + (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
+ + '&timeout='+api.flashAbortTimeout
+ + (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
+ + '&debug='+(api.debug?"1":"")
+ }, opts);
+ },
+
+
+ ready: function (){
+ api.log('FlashAPI.state: ready');
+
+ flash.ready = api.F;
+ flash.isReady = true;
+ flash.patch();
+ flash.patchCamera && flash.patchCamera();
+ api.event.on(document, 'mouseover', flash.mouseover);
+ api.event.on(document, 'click', function (evt){
+ if( flash.mouseover(evt) ){
+ evt.preventDefault
+ ? evt.preventDefault()
+ : (evt.returnValue = true)
+ ;
+ }
+ });
+ },
+
+
+ getEl: function (){
+ return document.getElementById('_'+_attr);
+ },
+
+
+ getWrapper: function (node){
+ do {
+ if( /js-fileapi-wrapper/.test(node.className) ){
+ return node;
+ }
+ }
+ while( (node = node.parentNode) && (node !== document.body) );
+ },
+
+
+ mouseover: function (evt){
+ var target = api.event.fix(evt).target;
+
+ if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
+ var
+ state = target.getAttribute(_attr)
+ , wrapper = flash.getWrapper(target)
+ ;
+
+ if( api.multiFlash ){
+ // check state:
+ // p — published
+ // i — initialization
+ // r — ready
+ if( state == 'i' || state == 'r' ){
+ // publish fail
+ return false;
+ }
+ else if( state != 'p' ){
+ // set "init" state
+ target.setAttribute(_attr, 'i');
+
+ var dummy = document.createElement('div');
+
+ if( !wrapper ){
+ api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
+ return;
+ }
+
+ _css(dummy, {
+ top: 0
+ , left: 0
+ , width: target.offsetWidth
+ , height: target.offsetHeight
+ , zIndex: 2147483647+'' // set max zIndex
+ , position: 'absolute'
+ });
+
+ wrapper.appendChild(dummy);
+ flash.publish(dummy, api.uid());
+
+ // set "publish" state
+ target.setAttribute(_attr, 'p');
+ }
+
+ return true;
+ }
+ else if( wrapper ){
+ // Use one flash element
+ var box = _getDimensions(wrapper);
+
+ _css(flash.getEl(), box);
+
+ // Set current input
+ flash.curInp = target;
+ }
+ }
+ else if( !/object|embed/i.test(target.nodeName) ){
+ _css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
+ }
+ },
+
+ onEvent: function (evt){
+ var type = evt.type;
+
+ if( type == 'ready' ){
+ try {
+ // set "ready" state
+ flash.getInput(evt.flashId).setAttribute(_attr, 'r');
+ } catch (e){
+ }
+
+ flash.ready();
+ setTimeout(function (){ flash.mouseenter(evt); }, 50);
+ return true;
+ }
+ else if( type === 'ping' ){
+ api.log('(flash -> js).ping:', [evt.status, evt.savedStatus], evt.error);
+ }
+ else if( type === 'log' ){
+ api.log('(flash -> js).log:', evt.target);
+ }
+ else if( type in flash ){
+ setTimeout(function (){
+ api.log('FlashAPI.event.'+evt.type+':', evt);
+ flash[type](evt);
+ }, 1);
+ }
+ },
+
+
+ mouseenter: function (evt){
+ var node = flash.getInput(evt.flashId);
+
+ if( node ){
+ // Set multiple mode
+ flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
+
+
+ // Set files filter
+ var accept = [], exts = {};
+
+ _each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
+ api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
+ exts[ext] = 1;
+ });
+ });
+
+ _each(exts, function (i, ext){
+ accept.push( ext );
+ });
+
+ flash.cmd(evt, 'accept', accept.length ? accept.join(',')+','+accept.join(',').toUpperCase() : '*');
+ }
+ },
+
+
+ get: function (id){
+ return document[id] || window[id] || document.embeds[id];
+ },
+
+
+ getInput: function (id){
+ if( api.multiFlash ){
+ try {
+ var node = flash.getWrapper(flash.get(id));
+ if( node ){
+ return node.getElementsByTagName('input')[0];
+ }
+ } catch (e){
+ api.log('[err] Can not find "input" by flashId:', id, e);
+ }
+ } else {
+ return flash.curInp;
+ }
+ },
+
+
+ select: function (evt){
+ var
+ inp = flash.getInput(evt.flashId)
+ , uid = api.uid(inp)
+ , files = evt.target.files
+ , event
+ ;
+
+ _each(files, function (file){
+ api.checkFileObj(file);
+ });
+
+ _files[uid] = files;
+
+ if( document.createEvent ){
+ event = document.createEvent('Event');
+ event.files = files;
+ event.initEvent('change', true, true);
+ inp.dispatchEvent(event);
+ }
+ else if( jQuery ){
+ jQuery(inp).trigger({ type: 'change', files: files });
+ }
+ else {
+ event = document.createEventObject();
+ event.files = files;
+ inp.fireEvent('onchange', event);
+ }
+ },
+
+
+ cmd: function (id, name, data, last){
+ try {
+ api.log('(js -> flash).'+name+':', data);
+ return flash.get(id.flashId || id).cmd(name, data);
+ } catch (err){
+ api.log('(js -> flash).onError:', err.toString());
+ if( !last ){
+ // try again
+ setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
+ }
+ }
+ },
+
+
+ patch: function (){
+ api.flashEngine = true;
+
+ // FileAPI
+ _inherit(api, {
+ getFiles: function (input, filter, callback){
+ if( callback ){
+ api.filterFiles(api.getFiles(input), filter, callback);
+ return null;
+ }
+
+ var files = api.isArray(input) ? input : _files[api.uid(input.target || input.srcElement || input)];
+
+
+ if( !files ){
+ // Файлов нету, вызываем родительский метод
+ return this.parent.apply(this, arguments);
+ }
+
+
+ if( filter ){
+ filter = api.getFilesFilter(filter);
+ files = api.filter(files, function (file){ return filter.test(file.name); });
+ }
+
+ return files;
+ },
+
+
+ getInfo: function (file, fn){
+ if( _isHtmlFile(file) ){
+ this.parent.apply(this, arguments);
+ }
+ else if( file.isShot ){
+ fn(null, file.info = {
+ width: file.width,
+ height: file.height
+ });
+ }
+ else {
+ if( !file.__info ){
+ var defer = file.__info = api.defer();
+
+ flash.cmd(file, 'getFileInfo', {
+ id: file.id
+ , callback: _wrap(function _(err, info){
+ _unwrap(_);
+ defer.resolve(err, file.info = info);
+ })
+ });
+ }
+
+ file.__info.then(fn);
+ }
+ }
+ });
+
+
+ // FileAPI.Image
+ api.support.transform = true;
+ api.Image && _inherit(api.Image.prototype, {
+ get: function (fn, scaleMode){
+ this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
+ return this.parent(fn);
+ },
+
+ _load: function (file, fn){
+ api.log('FlashAPI.Image._load:', file);
+
+ if( _isHtmlFile(file) ){
+ this.parent.apply(this, arguments);
+ }
+ else {
+ var _this = this;
+ api.getInfo(file, function (err){
+ fn.call(_this, err, file);
+ });
+ }
+ },
+
+ _apply: function (file, fn){
+ api.log('FlashAPI.Image._apply:', file);
+
+ if( _isHtmlFile(file) ){
+ this.parent.apply(this, arguments);
+ }
+ else {
+ var m = this.getMatrix(file.info), doneFn = fn;
+
+ flash.cmd(file, 'imageTransform', {
+ id: file.id
+ , matrix: m
+ , callback: _wrap(function _(err, base64){
+ api.log('FlashAPI.Image._apply.callback:', err);
+ _unwrap(_);
+
+ if( err ){
+ doneFn(err);
+ }
+ else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
+ _makeFlashImage({
+ width: (m.deg % 180) ? m.dh : m.dw
+ , height: (m.deg % 180) ? m.dw : m.dh
+ , scale: m.scaleMode
+ }, base64, doneFn);
+ }
+ else {
+ if( m.filter ){
+ doneFn = function (err, img){
+ if( err ){
+ fn(err);
+ }
+ else {
+ api.Image.applyFilter(img, m.filter, function (){
+ fn(err, this.canvas);
+ });
+ }
+ };
+ }
+
+ api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
+ }
+ })
+ });
+ }
+ },
+
+ toData: function (fn){
+ var
+ file = this.file
+ , info = file.info
+ , matrix = this.getMatrix(info)
+ ;
+ api.log('FlashAPI.Image.toData');
+
+ if( _isHtmlFile(file) ){
+ this.parent.apply(this, arguments);
+ }
+ else {
+ if( matrix.deg == 'auto' ){
+ matrix.deg = api.Image.exifOrientation[info && info.exif && info.exif.Orientation] || 0;
+ }
+
+ fn.call(this, !file.info, {
+ id: file.id
+ , flashId: file.flashId
+ , name: file.name
+ , type: file.type
+ , matrix: matrix
+ });
+ }
+ }
+ });
+
+
+ api.Image && _inherit(api.Image, {
+ fromDataURL: function (dataURL, size, callback){
+ if( !api.support.dataURI || dataURL.length > 3e4 ){
+ _makeFlashImage(
+ api.extend({ scale: 'exactFit' }, size)
+ , dataURL.replace(/^data:[^,]+,/, '')
+ , function (err, el){ callback(el); }
+ );
+ }
+ else {
+ this.parent(dataURL, size, callback);
+ }
+ }
+ });
+
+ // FileAPI.Form
+ _inherit(api.Form.prototype, {
+ toData: function (fn){
+ var items = this.items, i = items.length;
+
+ for( ; i--; ){
+ if( items[i].file && _isHtmlFile(items[i].blob) ){
+ return this.parent.apply(this, arguments);
+ }
+ }
+
+ api.log('FlashAPI.Form.toData');
+ fn(items);
+ }
+ });
+
+
+ // FileAPI.XHR
+ _inherit(api.XHR.prototype, {
+ _send: function (options, formData){
+ if(
+ formData.nodeName
+ || formData.append && api.support.html5
+ || api.isArray(formData) && (typeof formData[0] === 'string')
+ ){
+ // HTML5, Multipart or IFrame
+ return this.parent.apply(this, arguments);
+ }
+
+
+ var
+ data = {}
+ , files = {}
+ , _this = this
+ , flashId
+ , fileId
+ ;
+
+ _each(formData, function (item){
+ if( item.file ){
+ files[item.name] = item = _getFileDescr(item.blob);
+ fileId = item.id;
+ flashId = item.flashId;
+ }
+ else {
+ data[item.name] = item.blob;
+ }
+ });
+
+ if( !fileId ){
+ flashId = _attr;
+ }
+
+ if( !flashId ){
+ api.log('[err] FlashAPI._send: flashId -- undefined');
+ return this.parent.apply(this, arguments);
+ }
+ else {
+ api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
+ }
+
+ _this.xhr = {
+ headers: {},
+ abort: function (){ flash.cmd(flashId, 'abort', { id: fileId }); },
+ getResponseHeader: function (name){ return this.headers[name]; },
+ getAllResponseHeaders: function (){ return this.headers; }
+ };
+
+ var queue = api.queue(function (){
+ flash.cmd(flashId, 'upload', {
+ url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
+ , data: data
+ , files: fileId ? files : null
+ , headers: options.headers || {}
+ , callback: _wrap(function upload(evt){
+ var type = evt.type, result = evt.result;
+
+ api.log('FlashAPI.upload.'+type);
+
+ if( type == 'progress' ){
+ evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
+ evt.lengthComputable = true;
+ options.progress(evt);
+ }
+ else if( type == 'complete' ){
+ _unwrap(upload);
+
+ if( typeof result == 'string' ){
+ _this.responseText = result.replace(/%22/g, "\"").replace(/%5c/g, "\\").replace(/%26/g, "&").replace(/%25/g, "%");
+ }
+
+ _this.end(evt.status || 200);
+ }
+ else if( type == 'abort' || type == 'error' ){
+ _this.end(evt.status || 0, evt.message);
+ _unwrap(upload);
+ }
+ })
+ });
+ });
+
+
+ // #2174: FileReference.load() call while FileReference.upload() or vice versa
+ _each(files, function (file){
+ queue.inc();
+ api.getInfo(file, queue.next);
+ });
+
+ queue.check();
+ }
+ });
+ }
+ }
+ ;
+
+
+ function _makeFlashHTML(opts){
+ return (''
+ + ' '
+ + ' '
+ + ' '
+ + ' '
+ + ' '
+ + ' '
+ + ' '
+ + ' '
+ + ' ').replace(/#(\w+)#/ig, function (a, name){ return opts[name]; })
+ ;
+ }
+
+
+ function _css(el, css){
+ if( el && el.style ){
+ var key, val;
+ for( key in css ){
+ val = css[key];
+ if( typeof val == 'number' ){
+ val += 'px';
+ }
+ try { el.style[key] = val; } catch (e) {}
+ }
+ }
+ }
+
+
+ function _inherit(obj, methods){
+ _each(methods, function (fn, name){
+ var prev = obj[name];
+ obj[name] = function (){
+ this.parent = prev;
+ return fn.apply(this, arguments);
+ };
+ });
+ }
+
+ function _isHtmlFile(file){
+ return file && !file.flashId;
+ }
+
+ function _wrap(fn){
+ var id = fn.wid = api.uid();
+ flash._fn[id] = fn;
+ return 'FileAPI.Flash._fn.'+id;
+ }
+
+
+ function _unwrap(fn){
+ try {
+ flash._fn[fn.wid] = null;
+ delete flash._fn[fn.wid];
+ }
+ catch(e){}
+ }
+
+
+ function _getUrl(url, params){
+ if( !_rhttp.test(url) ){
+ if( /^\.\//.test(url) || '/' != url.charAt(0) ){
+ var path = location.pathname;
+ path = path.substr(0, path.lastIndexOf('/'));
+ url = (path +'/'+ url).replace('/./', '/');
+ }
+
+ if( '//' != url.substr(0, 2) ){
+ url = '//' + location.host + url;
+ }
+
+ if( !_rhttp.test(url) ){
+ url = location.protocol + url;
+ }
+ }
+
+ if( params ){
+ url += (/\?/.test(url) ? '&' : '?') + params;
+ }
+
+ return url;
+ }
+
+
+ function _makeFlashImage(opts, base64, fn){
+ var
+ key
+ , flashId = api.uid()
+ , el = document.createElement('div')
+ , attempts = 10
+ ;
+
+ for( key in opts ){
+ el.setAttribute(key, opts[key]);
+ el[key] = opts[key];
+ }
+
+ _css(el, opts);
+
+ opts.width = '100%';
+ opts.height = '100%';
+
+ el.innerHTML = _makeFlashHTML(api.extend({
+ id: flashId
+ , src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
+ , wmode: 'opaque'
+ , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
+ _unwrap(_);
+ if( --attempts > 0 ){
+ _setImage();
+ }
+ return true;
+ })
+ }, opts));
+
+ function _setImage(){
+ try {
+ // Get flash-object by id
+ var img = flash.get(flashId);
+ img.setImage(base64);
+ } catch (e){
+ api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
+ }
+ }
+
+ fn(false, el);
+ el = null;
+ }
+
+
+ function _getFileDescr(file){
+ return {
+ id: file.id
+ , name: file.name
+ , matrix: file.matrix
+ , flashId: file.flashId
+ };
+ }
+
+
+ function _getDimensions(el){
+ var
+ box = el.getBoundingClientRect()
+ , body = document.body
+ , docEl = (el && el.ownerDocument).documentElement
+ ;
+
+ return {
+ top: box.top + (window.pageYOffset || docEl.scrollTop) - (docEl.clientTop || body.clientTop || 0)
+ , left: box.left + (window.pageXOffset || docEl.scrollLeft) - (docEl.clientLeft || body.clientLeft || 0)
+ , width: box.right - box.left
+ , height: box.bottom - box.top
+ };
+ }
+
+ // @export
+ api.Flash = flash;
+
+
+ // Check dataURI support
+ api.newImage('data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', function (err, img){
+ api.support.dataURI = !(img.width != 1 || img.height != 1);
+ flash.init();
+ });
+ })();
+})(window, window.jQuery, FileAPI);
+
+/**
+ * FileAPI fallback to Flash
+ *
+ * @flash-developer "Vladimir Demidov"
+ */
+
+/*global window, FileAPI */
+(function (window, jQuery, api) {
+ "use strict";
+
+ var _each = api.each,
+ _cameraQueue = [];
+
+ if (api.support.flash && (api.media && (!api.support.media || !api.html5 || api.insecureChrome))) {
+ (function () {
+ function _wrap(fn) {
+ var id = fn.wid = api.uid();
+ api.Flash._fn[id] = fn;
+ return 'FileAPI.Flash._fn.' + id;
+ }
+
+
+ function _unwrap(fn) {
+ try {
+ api.Flash._fn[fn.wid] = null;
+ delete api.Flash._fn[fn.wid];
+ } catch (e) {
+ }
+ }
+
+ var flash = api.Flash;
+ api.extend(api.Flash, {
+
+ patchCamera: function () {
+ api.Camera.fallback = function (el, options, callback) {
+ var camId = api.uid();
+ api.log('FlashAPI.Camera.publish: ' + camId);
+ flash.publish(el, camId, api.extend(options, {
+ camera: true,
+ onEvent: _wrap(function _(evt) {
+ if (evt.type === 'camera') {
+ _unwrap(_);
+
+ if (evt.error) {
+ api.log('FlashAPI.Camera.publish.error: ' + evt.error);
+ callback(evt.error);
+ } else {
+ api.log('FlashAPI.Camera.publish.success: ' + camId);
+ callback(null);
+ }
+ }
+ })
+ }));
+ };
+ // Run
+ _each(_cameraQueue, function (args) {
+ api.Camera.fallback.apply(api.Camera, args);
+ });
+ _cameraQueue = [];
+
+
+ // FileAPI.Camera:proto
+ api.extend(api.Camera.prototype, {
+ _id: function () {
+ return this.video.id;
+ },
+
+ start: function (callback) {
+ var _this = this;
+ flash.cmd(this._id(), 'camera.on', {
+ callback: _wrap(function _(evt) {
+ _unwrap(_);
+
+ if (evt.error) {
+ api.log('FlashAPI.camera.on.error: ' + evt.error);
+ callback(evt.error, _this);
+ } else {
+ api.log('FlashAPI.camera.on.success: ' + _this._id());
+ _this._active = true;
+ callback(null, _this);
+ }
+ })
+ });
+ },
+
+ stop: function () {
+ this._active = false;
+ flash.cmd(this._id(), 'camera.off');
+ },
+
+ shot: function () {
+ api.log('FlashAPI.Camera.shot:', this._id());
+
+ var shot = api.Flash.cmd(this._id(), 'shot', {});
+ shot.type = 'image/png';
+ shot.flashId = this._id();
+ shot.isShot = true;
+
+ return new api.Camera.Shot(shot);
+ }
+ });
+ }
+ });
+
+ api.Camera.fallback = function () {
+ _cameraQueue.push(arguments);
+ };
+
+ }());
+ }
+}(window, window.jQuery, FileAPI));
+if( typeof define === "function" && define.amd ){ define("FileAPI", [], function (){ return FileAPI; }); }
diff --git a/dist/FileAPI.min.js b/dist/FileAPI.min.js
new file mode 100644
index 00000000..d42d7acb
--- /dev/null
+++ b/dist/FileAPI.min.js
@@ -0,0 +1,2 @@
+/*! FileAPI 2.1.1 - BSD | git://github.com/mailru/FileAPI.git */
+!function(a){"use strict";var b=a.HTMLCanvasElement&&a.HTMLCanvasElement.prototype,c=a.Blob&&function(){try{return Boolean(new Blob)}catch(a){return!1}}(),d=c&&a.Uint8Array&&function(){try{return 100===new Blob([new Uint8Array(100)]).size}catch(a){return!1}}(),e=a.BlobBuilder||a.WebKitBlobBuilder||a.MozBlobBuilder||a.MSBlobBuilder,f=(c||e)&&a.atob&&a.ArrayBuffer&&a.Uint8Array&&function(a){var b,f,g,h,i,j;for(b=a.split(",")[0].indexOf("base64")>=0?atob(a.split(",")[1]):decodeURIComponent(a.split(",")[1]),f=new ArrayBuffer(b.length),g=new Uint8Array(f),h=0;h0,I=a.dataURLtoBlob,J=/img/i,K=/canvas/i,L=/img|canvas/i,M=/input/i,N=/^data:[^,]+,/,O={}.toString,P=a.Math,Q=function(b){return b=new a.Number(P.pow(1024,b)),b.from=function(a){return P.round(a*this)},b},R={},S=[],T="abort progress error load loadend",U="status statusText readyState response responseXML responseText responseBody".split(" "),V="currentTarget",W="preventDefault",X=function(a){return a&&"length"in a},Y=function(a,b,c){if(a)if(X(a))for(var d=0,e=a.length;d=c&&!d&&f.end()},isFail:function(){return d},fail:function(){!d&&a(d=!0)},end:function(){e||(e=!0,a())}};return f},each:Y,afor:function(a,b){var c=0,d=a.length;X(a)&&d--?function e(){b(d!=c&&e,a[c],c++)}():b(!1)},extend:Z,isFile:function(a){return"[object File]"===O.call(a)},isBlob:function(a){return this.isFile(a)||"[object Blob]"===O.call(a)},isCanvas:function(a){return a&&K.test(a.nodeName)},getFilesFilter:function(a){return a="string"==typeof a?a:a.getAttribute&&a.getAttribute("accept")||"",a?new RegExp("("+a.replace(/\./g,"\\.").replace(/,/g,"|")+")$","i"):/./},readAsDataURL:function(a,b){da.isCanvas(a)?c(a,b,"load",da.toDataURL(a)):e(a,b,"DataURL")},readAsBinaryString:function(a,b){d("BinaryString")?e(a,b,"BinaryString"):e(a,function(a){if("load"==a.type)try{a.result=da.toBinaryString(a.result)}catch(b){a.type="error",a.message=b.toString()}b(a)},"DataURL")},readAsArrayBuffer:function(a,b){e(a,b,"ArrayBuffer")},readAsText:function(a,b,c){c||(c=b,b="utf-8"),e(a,c,"Text",b)},toDataURL:function(a,b){return"string"==typeof a?a:a.toDataURL?a.toDataURL(b||"image/png"):void 0},toBinaryString:function(b){return a.atob(da.toDataURL(b).replace(N,""))},readAsImage:function(a,d,e){if(da.isBlob(a))if(x){var f=x.createObjectURL(a);f===b?c(a,d,"error"):da.readAsImage(f,d,e)}else da.readAsDataURL(a,function(b){"load"==b.type?da.readAsImage(b.result,d,e):(e||"error"==b.type)&&c(a,d,b,null,{loaded:b.loaded,total:b.total})});else if(da.isCanvas(a))c(a,d,"load",a);else if(J.test(a.nodeName))if(a.complete)c(a,d,"load",a);else{var g="error abort load";aa(a,g,function b(e){"load"==e.type&&x&&x.revokeObjectURL(a.src),_(a,g,b),c(a,d,e,a)})}else if(a.iframe)c(a,d,{type:"error"});else{var h=da.newImage(a.dataURL||a);da.readAsImage(h,d,e)}},checkFileObj:function(a){var b={},c=da.accept;return"object"==typeof a?b=a:b.name=(a+"").split(/\\|\//g).pop(),null==b.type&&(b.type=b.name.split(".").pop()),Y(c,function(a,c){a=new RegExp(a.replace(/\s/g,"|"),"i"),(a.test(b.type)||da.ext2mime[b.type])&&(b.type=da.ext2mime[b.type]||c.split("/")[0]+"/"+b.type)}),b},getDropFiles:function(a,b){var c,d=[],e=[],j=l(a),k=j.files,m=j.items,n=X(m)&&m[0]&&h(m[0]),o=da.queue(function(){b(d,e)});if(n)if(H&&k){var p,q,r=k.length;for(c=new Array(r);r--;){p=k[r];try{q=h(m[r])}catch(a){da.log("[err] getDropFiles: ",a),q=null}g(q)&&(q.isDirectory||q.isFile&&p.name==p.name.normalize("NFC"))?c[r]=q:c[r]=p}}else c=m;else c=k;Y(c||[],function(a){o.inc();try{n&&g(a)?i(a,function(a,b,c){a?da.log("[err] getDropFiles:",a):d.push.apply(d,b),e.push.apply(e,c),o.next()}):f(a,function(b,c){b?d.push(a):a.error=c,e.push(a),o.next()})}catch(a){o.next(),da.log("[err] getDropFiles: ",a)}}),o.check()},getFiles:function(a,b,c){var d=[];return c?(da.filterFiles(da.getFiles(a),b,c),null):(a.jquery&&(a.each(function(){d=d.concat(da.getFiles(this))}),a=d,d=[]),"string"==typeof b&&(b=da.getFilesFilter(b)),a.originalEvent?a=ba(a.originalEvent):a.srcElement&&(a=ba(a)),a.dataTransfer?a=a.dataTransfer:a.target&&(a=a.target),a.files?(d=a.files,E||(d[0].blob=a,d[0].iframe=!0)):!E&&k(a)?da.trim(a.value)&&(d=[da.checkFileObj(a.value)],d[0].blob=a,d[0].iframe=!0):X(a)&&(d=a),da.filter(d,function(a){return!b||b.test(a.name)}))},getTotalSize:function(a){for(var b=0,c=a&&a.length;c--;)b+=a[c].size;return b},getInfo:function(a,b){var c={},d=S.concat();da.isBlob(a)?function e(){var f=d.shift();f?f.test(a.type)?f(a,function(a,d){a?b(a):(Z(c,d),e())}):e():b(!1,c)}():b("not_support_info",c)},addInfoReader:function(a,b){b.test=function(b){return a.test(b)},S.push(b)},filter:function(a,b){for(var c,d=[],e=0,f=a.length;e>2,k=(3&g)<<4|h>>4;isNaN(h)?e=f=64:(e=(15&h)<<2|i>>6,f=isNaN(i)?64:63&i),c+=b.charAt(j)+b.charAt(k)+b.charAt(e)+b.charAt(f)}return c}};da.addInfoReader(/^image/,function(a,b){if(!a.__dimensions){var c=a.__dimensions=da.defer();da.readAsImage(a,function(a){var b=a.target;c.resolve("load"!=a.type&&"error",{width:b.width,height:b.height}),b.src=da.EMPTY_PNG,b=null})}a.__dimensions.then(b)}),da.event.dnd=function(a,b,c){var d,e;c||(c=b,b=da.F),A?($(a,"dragenter dragleave dragover",b.ff=b.ff||function(a){for(var c=l(a).types,f=c&&c.length,g=!1;f--;)if(~c[f].indexOf("File")){a[W](),e!==a.type&&(e=a.type,"dragleave"!=e&&b.call(a[V],!0,a),g=!0);break}g&&(clearTimeout(d),d=setTimeout(function(){b.call(a[V],"dragleave"!=e,a)},50))}),$(a,"drop",c.ff=c.ff||function(a){a[W](),e=0,da.getDropFiles(a,function(b,d){c.call(a[V],b,d,a)}),b.call(a[V],!1,a)})):da.log("Drag'n'Drop -- not supported")},da.event.dnd.off=function(a,b,c){_(a,"dragenter dragleave dragover",b.ff),_(a,"drop",c.ff)},D&&!D.fn.dnd&&(D.fn.dnd=function(a,b){return this.each(function(){da.event.dnd(this,a,b)})},D.fn.offdnd=function(a,b){return this.each(function(){da.event.dnd.off(this,a,b)})}),a.FileAPI=Z(da,a.FileAPI),da.log("FileAPI: "+da.version),da.log("protocol: "+a.location.protocol),da.log("doctype: ["+s.name+"] "+s.publicId+" "+s.systemId),Y(r.getElementsByTagName("meta"),function(a){/x-ua-compatible/i.test(a.getAttribute("http-equiv"))&&da.log("meta.http-equiv: "+a.getAttribute("content"))});try{n=!!console.log,o=!!console.log.apply}catch(a){}da.flashUrl||(da.flashUrl=da.staticPath+"FileAPI.flash.swf"),da.flashImageUrl||(da.flashImageUrl=da.staticPath+"FileAPI.flash.image.swf"),da.flashWebcamUrl||(da.flashWebcamUrl=da.staticPath+"FileAPI.flash.camera.swf")}(window,void 0),function(a,b,c){"use strict";function d(b){if(b instanceof d){var c=new d(b.file);return a.extend(c.matrix,b.matrix),c}if(!(this instanceof d))return new d(b);this.file=b,this.size=b.size||100,this.matrix={sx:0,sy:0,sw:0,sh:0,dx:0,dy:0,dw:0,dh:0,resize:0,deg:0,quality:1,filter:0}}var e=Math.min,f=Math.round,g=function(){return b.createElement("canvas")},h=!1,i={8:270,3:180,6:90,7:270,4:180,5:90};try{h=g().toDataURL("image/png").indexOf("data:image/png")>-1}catch(a){}d.prototype={image:!0,constructor:d,set:function(b){return a.extend(this.matrix,b),this},crop:function(a,b,d,e){return d===c&&(d=a,e=b,a=b=0),this.set({sx:a,sy:b,sw:d,sh:e||d})},resize:function(a,b,c){return/min|max|height|width/.test(b)&&(c=b,b=a),this.set({dw:a,dh:b||a,resize:c})},preview:function(a,b){return this.resize(a,b||a,"preview")},rotate:function(a){return this.set({deg:a})},filter:function(a){return this.set({filter:a})},overlay:function(a){return this.set({overlay:a})},clone:function(){return new d(this)},_load:function(b,c){var d=this;/img|video/i.test(b.nodeName)?c.call(d,null,b):a.readAsImage(b,function(a){c.call(d,"load"!=a.type,a.result)})},_apply:function(b,c){var f,h=g(),i=this.getMatrix(b),j=h.getContext("2d"),k=b.videoWidth||b.width,l=b.videoHeight||b.height,m=i.deg,n=i.dw,o=i.dh,p=k,q=l,r=i.filter,s=b,t=i.overlay,u=a.queue(function(){b.src=a.EMPTY_PNG,c(!1,h)}),v=a.renderImageToCanvas;for(m-=360*Math.floor(m/360),b._type=this.file.type;i.multipass&&e(p/n,q/o)>2;)p=p/2+.5|0,q=q/2+.5|0,f=g(),f.width=p,f.height=q,s!==b?(v(f,s,0,0,s.width,s.height,0,0,p,q),s=f):(s=f,v(s,b,i.sx,i.sy,i.sw,i.sh,0,0,p,q),i.sx=i.sy=i.sw=i.sh=0);h.width=m%180?o:n,h.height=m%180?n:o,h.type=i.type,h.quality=i.quality,j.rotate(m*Math.PI/180),v(j.canvas,s,i.sx,i.sy,i.sw||s.width,i.sh||s.height,180==m||270==m?-n:0,90==m||180==m?-o:0,n,o),n=h.width,o=h.height,t&&a.each([].concat(t),function(b){u.inc();var c=new window.Image,d=function(){var e=0|b.x,f=0|b.y,g=b.w||c.width,h=b.h||c.height,i=b.rel;e=1==i||4==i||7==i?(n-g+e)/2:2==i||5==i||8==i?n-(g+e):e,f=3==i||4==i||5==i?(o-h+f)/2:i>=6?o-(h+f):f,a.event.off(c,"error load abort",d);try{j.globalAlpha=b.opacity||1,j.drawImage(c,e,f,g,h)}catch(a){}u.next()};a.event.on(c,"error load abort",d),c.src=b.src,c.complete&&d()}),r&&(u.inc(),d.applyFilter(h,r,u.next)),u.check()},getMatrix:function(b){var c=a.extend({},this.matrix),d=c.sw=c.sw||b.videoWidth||b.naturalWidth||b.width,g=c.sh=c.sh||b.videoHeight||b.naturalHeight||b.height,h=c.dw=c.dw||d,i=c.dh=c.dh||g,j=d/g,k=h/i,l=c.resize;if("preview"==l){if(h!=d||i!=g){var m,n;k>=j?(m=d,n=m/k):(n=g,m=n*k),m==d&&n==g||(c.sx=~~((d-m)/2),c.sy=~~((g-n)/2),d=m,g=n)}}else"height"==l?h=i*j:"width"==l?i=h/j:l&&(d>h||g>i?"min"==l?(h=f(j=k?e(d,h):i*j),i=f(j>=k?h/j:e(g,i))):(h=d,i=g));return c.sw=d,c.sh=g,c.dw=h,c.dh=i,c.multipass=a.multiPassResize,c},_trans:function(b){this._load(this.file,function(c,d){if(c)b(c);else try{this._apply(d,b)}catch(c){a.log("[err] FileAPI.Image.fn._apply:",c),b(c)}})},get:function(b){if(a.support.transform){var c=this,d=c.matrix;"auto"==d.deg?a.getInfo(c.file,function(a,e){d.deg=i[e&&e.exif&&e.exif.Orientation]||0,c._trans(b)}):c._trans(b)}else b("not_support_transform");return this},toData:function(a){return this.get(a)}},d.exifOrientation=i,d.transform=function(b,e,f,g){function h(h,i){var j={},k=a.queue(function(a){g(a,j)});h?k.fail():a.each(e,function(a,e){if(!k.isFail()){var g=new d(i.nodeType?i:b),h="function"==typeof a;if(h?a(i,g):a.width?g[a.preview?"preview":"resize"](a.width,a.height,a.strategy):a.maxWidth&&(i.width>a.maxWidth||i.height>a.maxHeight)&&g.resize(a.maxWidth,a.maxHeight,"max"),a.crop){var l=a.crop;g.crop(0|l.x,0|l.y,l.w||l.width,l.h||l.height)}a.rotate===c&&f&&(a.rotate="auto"),g.set({type:g.matrix.type||a.type||b.type||"image/png"}),h||g.set({deg:a.rotate,overlay:a.overlay,filter:a.filter,quality:a.quality||1}),k.inc(),g.toData(function(a,b){a?k.fail():(j[e]=b,k.next())})}})}b.width?h(!1,b):a.getInfo(b,h)},a.each(["TOP","CENTER","BOTTOM"],function(b,c){a.each(["LEFT","CENTER","RIGHT"],function(a,e){d[b+"_"+a]=3*c+e,d[a+"_"+b]=3*c+e})}),d.toCanvas=function(a){var c=b.createElement("canvas");return c.width=a.videoWidth||a.width,c.height=a.videoHeight||a.height,c.getContext("2d").drawImage(a,0,0),c},d.fromDataURL=function(b,c,d){var e=a.newImage(b);a.extend(e,c),d(e)},d.applyFilter=function(b,c,e){"function"==typeof c?c(b,e):window.Caman&&window.Caman("IMG"==b.tagName?d.toCanvas(b):b,function(){"string"==typeof c?this[c]():a.each(c,function(a,b){this[b](a)},this),this.render(e)})},a.renderImageToCanvas=function(b,c,d,e,f,g,h,i,j,k){try{return b.getContext("2d").drawImage(c,d,e,f,g,h,i,j,k)}catch(b){throw a.log("renderImageToCanvas failed"),b}},a.support.canvas=a.support.transform=h,a.Image=d}(FileAPI,document),function(a){"use strict";a(FileAPI)}(function(a){"use strict";if(window.navigator&&window.navigator.platform&&/iP(hone|od|ad)/.test(window.navigator.platform)){var b=a.renderImageToCanvas;a.detectSubsampling=function(a){var b,c;return a.width*a.height>1048576&&(b=document.createElement("canvas"),b.width=b.height=1,c=b.getContext("2d"),c.drawImage(a,1-a.width,0),0===c.getImageData(0,0,1,1).data[3])},a.detectVerticalSquash=function(a,b){var c,d,e,f,g,h=a.naturalHeight||a.height,i=document.createElement("canvas"),j=i.getContext("2d");for(b&&(h/=2),i.width=1,i.height=h,j.drawImage(a,0,0),c=j.getImageData(0,0,1,h).data,d=0,e=h,f=h;f>d;)g=c[4*(f-1)+3],0===g?e=f:d=f,f=e+d>>1;return f/h||1},a.renderImageToCanvas=function(c,d,e,f,g,h,i,j,k,l){if("image/jpeg"===d._type){var m,n,o,p,q=c.getContext("2d"),r=document.createElement("canvas"),s=1024,t=r.getContext("2d");if(r.width=s,r.height=s,q.save(),m=a.detectSubsampling(d),m&&(e/=2,f/=2,g/=2,h/=2),n=a.detectVerticalSquash(d,m),m||1!==n){for(f*=n,k=Math.ceil(s*k/g),l=Math.ceil(s*l/h/n),j=0,p=0;p0&&1==a.filter(this.items,function(a){return a.file}).length,a.support.html5?a.formData&&!this.multipart&&e?c._chunked?(a.log("FileAPI.Form.toPlainData"),this.toPlainData(b)):(a.log("FileAPI.Form.toFormData"),this.toFormData(b)):(a.log("FileAPI.Form.toMultipartData"),this.toMultipartData(b)):(a.log("FileAPI.Form.toHtmlData"),this.toHtmlData(b))},_to:function(b,c,d,e){var f=a.queue(function(){c(b)});this.each(function(g){try{d(g,b,f,e)}catch(b){a.log("FileAPI.Form._to: "+b.message),c(b)}}),f.check()},toHtmlData:function(b){this._to(d.createDocumentFragment(),b,function(b,c){var e,f=b.blob;b.file?(a.reset(f,!0),f.name=b.name,f.disabled=!1,c.appendChild(f)):(e=d.createElement("input"),e.name=b.name,e.type="hidden",e.value=f,c.appendChild(e))})},toPlainData:function(a){this._to({},a,function(a,b,d){a.file&&(b.type=a.file),a.blob.toBlob?(d.inc(),c(a,function(a,c){b.name=a.name,b.file=c,b.size=c.length,b.type=a.type,d.next()})):a.file?(b.name=a.blob.name,b.file=a.blob,b.size=a.blob.size,b.type=a.type):(b.params||(b.params=[]),b.params.push(g(a.name)+"="+g(a.blob))),b.start=-1,b.end=b.file&&b.file.FileAPIReadPosition||-1,b.retry=0})},toFormData:function(a){this._to(new e,a,function(a,b,d){a.blob&&a.blob.toBlob?(d.inc(),c(a,function(a,c){b.append(a.name,c,a.file),d.next()})):a.file?b.append(a.name,a.blob,a.file):b.append(a.name,a.blob),a.file&&b.append("_"+a.name,a.file)})},toMultipartData:function(b){this._to([],b,function(a,b,d,e){d.inc(),c(a,function(a,c){b.push("--_"+e+'\r\nContent-Disposition: form-data; name="'+a.name+'"'+(a.file?'; filename="'+g(a.file)+'"':"")+(a.file?"\r\nContent-Type: "+(a.type||"application/octet-stream"):"")+"\r\n\r\n"+(a.file?c:g(c))+"\r\n"),d.next()},!0)},a.expando)}},a.Form=f}(FileAPI,window),function(a,b){"use strict";var c=function(){},d=a.document,e=function(a){this.uid=b.uid(),this.xhr={abort:c,getResponseHeader:c,getAllResponseHeaders:c},this.options=a},f={"":1,XML:1,Text:1,Body:1};e.prototype={status:0,statusText:"",constructor:e,getResponseHeader:function(a){return this.xhr.getResponseHeader(a)},getAllResponseHeaders:function(){return this.xhr.getAllResponseHeaders()||{}},end:function(d,e){var f=this,g=f.options;f.end=f.abort=c,f.status=d,e&&(f.statusText=e),b.log("xhr.end:",d,e),g.complete(200!=d&&201!=d&&(f.statusText||"unknown"),f),f.xhr&&f.xhr.node&&setTimeout(function(){var b=f.xhr.node;try{b.parentNode.removeChild(b)}catch(a){}try{delete a[f.uid]}catch(a){}a[f.uid]=f.xhr.node=null},9)},abort:function(){this.end(0,"abort"),this.xhr&&(this.xhr.aborted=!0,this.xhr.abort())},send:function(a){var b=this,c=this.options;a.toData(function(a){a instanceof Error?b.end(0,a.message):(c.upload(c,b),b._send.call(b,c,a))},c)},_send:function(c,e){var g,h=this,i=h.uid,j=h.uid+"Load",k=c.url;if(b.log("XHR._send:",e),c.cache||(k+=(~k.indexOf("?")?"&":"?")+b.uid()),e.nodeName){var l=c.jsonp;k=k.replace(/([a-z]+)=(\?)/i,"$1="+i),c.upload(c,h);var m=function(a){if(~k.indexOf(a.origin))try{var c=b.parseJSON(a.data);c.id==i&&n(c.status,c.statusText,c.response)}catch(a){n(0,a.message)}},n=a[i]=function(c,d,e){h.readyState=4,h.responseText=e,h.end(c,d),b.event.off(a,"message",m),a[i]=g=p=a[j]=null};h.xhr.abort=function(){try{p.stop?p.stop():p.contentWindow.stop?p.contentWindow.stop():p.contentWindow.document.execCommand("Stop")}catch(a){}n(0,"abort")},b.event.on(a,"message",m),a[j]=function(){try{var a=p.contentWindow,c=a.document,d=a.result||b.parseJSON(c.body.innerHTML);n(d.status,d.statusText,d.response)}catch(a){b.log("[transport.onload]",a)}},g=d.createElement("div"),g.innerHTML='";var o=g.getElementsByTagName("form")[0],p=g.getElementsByTagName("iframe")[0];o.appendChild(e),b.log(o.parentNode.innerHTML),d.body.appendChild(g),h.xhr.node=g,h.readyState=2;try{o.submit()}catch(a){b.log("iframe.error: "+a)}o=null}else{if(k=k.replace(/([a-z]+)=(\?)&?/i,""),this.xhr&&this.xhr.aborted)return void b.log("Error: already aborted");if(g=h.xhr=b.getXHR(),e.params&&(k+=(k.indexOf("?")<0?"?":"&")+e.params.join("&")),g.open(c.uploadMethod||"POST",k,!0),"boolean"==typeof c.uploadCredentials?g.withCredentials=c.uploadCredentials?"true":null:b.withCredentials&&(g.withCredentials="true"),c.headers&&c.headers["X-Requested-With"]||g.setRequestHeader("X-Requested-With","XMLHttpRequest"),b.each(c.headers,function(a,b){g.setRequestHeader(b,a)}),c._chunked){g.upload&&g.upload.addEventListener("progress",b.throttle(function(a){e.retry||c.progress({type:a.type,total:e.size,loaded:e.start+a.loaded,totalSize:e.size},h,c)},100),!1),g.onreadystatechange=function(){var a=parseInt(g.getResponseHeader("X-Last-Known-Byte"),10);if(h.status=g.status,h.statusText=g.statusText,h.readyState=g.readyState,4==g.readyState){for(var d in f)h["response"+d]=g["response"+d];if(g.onreadystatechange=null,!g.status||g.status-201>0)if(b.log("Error: "+g.status),(!g.status&&!g.aborted||500==g.status||416==g.status)&&++e.retry<=c.chunkUploadRetry){var i=g.status?0:b.chunkNetworkDownRetryTimeout;c.pause(e.file,c),b.log("X-Last-Known-Byte: "+a),a?e.end=a:(e.end=e.start-1,416==g.status&&(e.end=e.end-c.chunkSize)),setTimeout(function(){h._send(c,e)},i)}else h.end(g.status);else e.retry=0,e.end==e.size-1?h.end(g.status):(b.log("X-Last-Known-Byte: "+a),a&&(e.end=a),e.file.FileAPIReadPosition=e.end,setTimeout(function(){h._send(c,e)},0));g=null}},e.start=e.end+1,e.end=Math.max(Math.min(e.start+c.chunkSize,e.size)-1,e.start);var q=e.file,r=(q.slice||q.mozSlice||q.webkitSlice).call(q,e.start,e.end+1);e.size&&!r.size?setTimeout(function(){h.end(-1)}):(g.setRequestHeader("Content-Range","bytes "+e.start+"-"+e.end+"/"+e.size),g.setRequestHeader("Content-Disposition","attachment; filename="+encodeURIComponent(e.name)),g.setRequestHeader("Content-Type",e.type||"application/octet-stream"),g.send(r)),q=r=null}else if(g.upload&&g.upload.addEventListener("progress",b.throttle(function(a){c.progress(a,h,c)},100),!1),g.onreadystatechange=function(){if(h.status=g.status,h.statusText=g.statusText,h.readyState=g.readyState,4==g.readyState){for(var a in f)h["response"+a]=g["response"+a];if(g.onreadystatechange=null,!g.status||g.status>201)if(b.log("Error: "+g.status),(!g.status&&!g.aborted||500==g.status)&&(c.retry||0)=0?a+"px":a}function d(a){var c,d=f.createElement("canvas"),e=!1;try{c=d.getContext("2d"),c.drawImage(a,0,0,1,1),e=255!=c.getImageData(0,0,1,1).data[4]}catch(a){b.log("[FileAPI.Camera] detectVideoSignal:",a)}return e}var e=a.URL||a.webkitURL,f=a.document,g=a.navigator,h=g.getUserMedia||g.webkitGetUserMedia||g.mozGetUserMedia||g.msGetUserMedia,i=!!h;b.support.media=i;var j=function(a){this.video=a};j.prototype={isActive:function(){return!!this._active},start:function(a){var b,c,f=this,i=f.video,j=function(d){f._active=!d,clearTimeout(c),clearTimeout(b),a&&a(d,f)};h.call(g,{video:!0},function(a){f.stream=a;try{i.src=e.createObjectURL(a)}catch(b){i.srcObject=a}b=setInterval(function(){d(i)&&j(null)},1e3),c=setTimeout(function(){j("timeout")},5e3),i.play()},j)},stop:function(){try{this._active=!1,this.video.pause();try{this.stream.stop()}catch(a){b.each(this.stream.getTracks(),function(a){a.stop()})}this.stream=null}catch(a){b.log("[FileAPI.Camera] stop:",a)}},shot:function(){return new k(this.video)}},j.get=function(a){return new j(a.firstChild)},j.publish=function(d,e,g){"function"==typeof e&&(g=e,e={}),e=b.extend({},{width:"100%",height:"100%",start:!0},e),d.jquery&&(d=d[0]);var h=function(a){if(a)g(a);else{var b=j.get(d);e.start?b.start(g):g(null,b)}};if(d.style.width=c(e.width),d.style.height=c(e.height),b.html5&&i&&!b.insecureChrome){var k=f.createElement("video");k.style.width=c(e.width),k.style.height=c(e.height),a.jQuery?jQuery(d).empty():d.innerHTML="",d.appendChild(k),h()}else j.fallback(d,e,h)},j.fallback=function(a,b,c){c("not_support_camera")},j.checkAlreadyCaptured=function(){var b,c=g.mediaDevices,d=a.MediaStreamTrack,e=g.enumerateDevices;return b=c&&c.enumerateDevices?function(a){c.enumerateDevices().then(a)}:d&&d.getSources?d.getSources.bind(d):e?e.bind(g):function(a){a([])},function(a){b(function(b){var c=b.some(function(a){return("videoinput"===a.kind||"video"===a.kind)&&a.label});a(c)})}}();var k=function(a){var c=a.nodeName?b.Image.toCanvas(a):a,d=b.Image(c);return d.type="image/png",d.width=c.width,d.height=c.height,d.size=c.width*c.height*4,d};j.Shot=k,b.Camera=j}(window,FileAPI),function(a,b,c){"use strict";var d=a.document,e=a.location,f=a.navigator,g=c.each;c.support.flash=function(){var b=f.mimeTypes,d=!1;if(f.plugins&&"object"==typeof f.plugins["Shockwave Flash"])d=f.plugins["Shockwave Flash"].description&&!(b&&b["application/x-shockwave-flash"]&&!b["application/x-shockwave-flash"].enabledPlugin);else try{d=!(!a.ActiveXObject||!new ActiveXObject("ShockwaveFlash.ShockwaveFlash"))}catch(a){c.log("Flash -- does not supported.")}return d&&/^file:/i.test(e)&&c.log("[warn] Flash does not work on `file:` protocol."),d}(),c.support.flash&&(!c.html5||!c.support.html5||c.cors&&!c.support.cors||c.media&&!c.support.media||c.insecureChrome)&&function(){function h(a){return(' ').replace(/#(\w+)#/gi,function(b,c){return a[c]})}function i(a,b){if(a&&a.style){var c,d;for(c in b){d=b[c],"number"==typeof d&&(d+="px");try{a.style[c]=d}catch(a){}}}}function j(a,b){g(b,function(b,c){var d=a[c];a[c]=function(){return this.parent=d,b.apply(this,arguments)}})}function k(a){return a&&!a.flashId}function l(a){var b=a.wid=c.uid();return v._fn[b]=a,"FileAPI.Flash._fn."+b}function m(a){try{v._fn[a.wid]=null,delete v._fn[a.wid]}catch(a){}}function n(a,b){if(!u.test(a)){if(/^\.\//.test(a)||"/"!=a.charAt(0)){var c=e.pathname;c=c.substr(0,c.lastIndexOf("/")),a=(c+"/"+a).replace("/./","/")}"//"!=a.substr(0,2)&&(a="//"+e.host+a),u.test(a)||(a=e.protocol+a)}return b&&(a+=(/\?/.test(a)?"&":"?")+b),a}function o(a,b,e){function f(){try{v.get(j).setImage(b)}catch(a){c.log('[err] FlashAPI.Preview.setImage -- can not set "base64":',a)}}var g,j=c.uid(),k=d.createElement("div"),o=10;for(g in a)k.setAttribute(g,a[g]),k[g]=a[g];i(k,a),a.width="100%",a.height="100%",k.innerHTML=h(c.extend({id:j,src:n(c.flashImageUrl,"r="+c.uid()),wmode:"opaque",flashvars:"scale="+a.scale+"&callback="+l(function a(){return m(a),--o>0&&f(),!0})},a)),e(!1,k),k=null}function p(a){return{id:a.id,name:a.name,matrix:a.matrix,flashId:a.flashId}}function q(b){var c=b.getBoundingClientRect(),e=d.body,f=(b&&b.ownerDocument).documentElement;return{top:c.top+(a.pageYOffset||f.scrollTop)-(f.clientTop||e.clientTop||0),left:c.left+(a.pageXOffset||f.scrollLeft)-(f.clientLeft||e.clientLeft||0),width:c.right-c.left,height:c.bottom-c.top}}var r=c.uid(),s=0,t={},u=/^https?:/i,v={_fn:{},init:function(){var a=d.body&&d.body.firstChild;if(a)do{if(1==a.nodeType){c.log("FlashAPI.state: awaiting");var b=d.createElement("div");return b.id="_"+r,i(b,{top:1,right:1,width:5,height:5,position:"absolute",zIndex:"2147483647"}),a.parentNode.insertBefore(b,a),void v.publish(b,r)}}while(a=a.nextSibling);s<10&&setTimeout(v.init,50*++s)},publish:function(a,b,d){d=d||{},a.innerHTML=h({id:b,src:n(c.flashUrl,"r="+c.version),wmode:d.camera?"":"transparent",flashvars:"callback="+(d.onEvent||"FileAPI.Flash.onEvent")+"&flashId="+b+"&storeKey="+f.userAgent.match(/\d/gi).join("")+"_"+c.version+(v.isReady||(c.pingUrl?"&ping="+c.pingUrl:""))+"&timeout="+c.flashAbortTimeout+(d.camera?"&useCamera="+n(c.flashWebcamUrl):"")+"&debug="+(c.debug?"1":"")},d)},ready:function(){c.log("FlashAPI.state: ready"),v.ready=c.F,v.isReady=!0,v.patch(),v.patchCamera&&v.patchCamera(),c.event.on(d,"mouseover",v.mouseover),c.event.on(d,"click",function(a){v.mouseover(a)&&(a.preventDefault?a.preventDefault():a.returnValue=!0)})},getEl:function(){return d.getElementById("_"+r)},getWrapper:function(a){do{if(/js-fileapi-wrapper/.test(a.className))return a}while((a=a.parentNode)&&a!==d.body)},mouseover:function(a){var b=c.event.fix(a).target;if(/input/i.test(b.nodeName)&&"file"==b.type&&!b.disabled){var e=b.getAttribute(r),f=v.getWrapper(b);if(c.multiFlash){if("i"==e||"r"==e)return!1;if("p"!=e){b.setAttribute(r,"i");var g=d.createElement("div");if(!f)return void c.log("[err] FlashAPI.mouseover: js-fileapi-wrapper not found");i(g,{top:0,left:0,width:b.offsetWidth,height:b.offsetHeight,zIndex:"2147483647",position:"absolute"}),f.appendChild(g),v.publish(g,c.uid()),b.setAttribute(r,"p")}return!0}if(f){var h=q(f);i(v.getEl(),h),v.curInp=b}}else/object|embed/i.test(b.nodeName)||i(v.getEl(),{top:1,left:1,width:5,height:5})},onEvent:function(a){var b=a.type;if("ready"==b){try{v.getInput(a.flashId).setAttribute(r,"r")}catch(a){}return v.ready(),setTimeout(function(){v.mouseenter(a)},50),!0}"ping"===b?c.log("(flash -> js).ping:",[a.status,a.savedStatus],a.error):"log"===b?c.log("(flash -> js).log:",a.target):b in v&&setTimeout(function(){c.log("FlashAPI.event."+a.type+":",a),v[b](a)},1)},mouseenter:function(a){var b=v.getInput(a.flashId);if(b){v.cmd(a,"multiple",null!=b.getAttribute("multiple"));var d=[],e={};g((b.getAttribute("accept")||"").split(/,\s*/),function(a){c.accept[a]&&g(c.accept[a].split(" "),function(a){e[a]=1})}),g(e,function(a,b){d.push(b)}),v.cmd(a,"accept",d.length?d.join(",")+","+d.join(",").toUpperCase():"*")}},get:function(b){return d[b]||a[b]||d.embeds[b]},getInput:function(a){if(!c.multiFlash)return v.curInp;try{var b=v.getWrapper(v.get(a));if(b)return b.getElementsByTagName("input")[0]}catch(b){c.log('[err] Can not find "input" by flashId:',a,b)}},select:function(a){var e,f=v.getInput(a.flashId),h=c.uid(f),i=a.target.files;g(i,function(a){c.checkFileObj(a)}),t[h]=i,d.createEvent?(e=d.createEvent("Event"),e.files=i,e.initEvent("change",!0,!0),f.dispatchEvent(e)):b?b(f).trigger({type:"change",files:i}):(e=d.createEventObject(),e.files=i,f.fireEvent("onchange",e))},cmd:function(a,b,d,e){try{return c.log("(js -> flash)."+b+":",d),v.get(a.flashId||a).cmd(b,d)}catch(f){c.log("(js -> flash).onError:",f.toString()),e||setTimeout(function(){v.cmd(a,b,d,!0)},50)}},patch:function(){c.flashEngine=!0,j(c,{getFiles:function(a,b,d){if(d)return c.filterFiles(c.getFiles(a),b,d),null;var e=c.isArray(a)?a:t[c.uid(a.target||a.srcElement||a)];return e?(b&&(b=c.getFilesFilter(b),e=c.filter(e,function(a){return b.test(a.name)})),e):this.parent.apply(this,arguments)},getInfo:function(a,b){if(k(a))this.parent.apply(this,arguments);else if(a.isShot)b(null,a.info={width:a.width,height:a.height});else{if(!a.__info){var d=a.__info=c.defer();v.cmd(a,"getFileInfo",{id:a.id,callback:l(function b(c,e){m(b),d.resolve(c,a.info=e)})})}a.__info.then(b)}}}),c.support.transform=!0,c.Image&&j(c.Image.prototype,{get:function(a,b){return this.set({scaleMode:b||"noScale"}),this.parent(a)},_load:function(a,b){if(c.log("FlashAPI.Image._load:",a),k(a))this.parent.apply(this,arguments);else{var d=this;c.getInfo(a,function(c){b.call(d,c,a)})}},_apply:function(a,b){if(c.log("FlashAPI.Image._apply:",a),k(a))this.parent.apply(this,arguments);else{var d=this.getMatrix(a.info),e=b;v.cmd(a,"imageTransform",{id:a.id,matrix:d,callback:l(function f(g,h){c.log("FlashAPI.Image._apply.callback:",g),m(f),g?e(g):c.support.html5||c.support.dataURI&&!(h.length>3e4)?(d.filter&&(e=function(a,e){a?b(a):c.Image.applyFilter(e,d.filter,function(){b(a,this.canvas)})}),c.newImage("data:"+a.type+";base64,"+h,e)):o({width:d.deg%180?d.dh:d.dw,height:d.deg%180?d.dw:d.dh,scale:d.scaleMode},h,e)})})}},toData:function(a){var b=this.file,d=b.info,e=this.getMatrix(d);c.log("FlashAPI.Image.toData"),k(b)?this.parent.apply(this,arguments):("auto"==e.deg&&(e.deg=c.Image.exifOrientation[d&&d.exif&&d.exif.Orientation]||0),a.call(this,!b.info,{id:b.id,flashId:b.flashId,name:b.name,type:b.type,matrix:e}))}}),c.Image&&j(c.Image,{fromDataURL:function(a,b,d){!c.support.dataURI||a.length>3e4?o(c.extend({scale:"exactFit"},b),a.replace(/^data:[^,]+,/,""),function(a,b){d(b)}):this.parent(a,b,d)}}),j(c.Form.prototype,{toData:function(a){for(var b=this.items,d=b.length;d--;)if(b[d].file&&k(b[d].blob))return this.parent.apply(this,arguments);c.log("FlashAPI.Form.toData"),a(b)}}),j(c.XHR.prototype,{_send:function(a,b){if(b.nodeName||b.append&&c.support.html5||c.isArray(b)&&"string"==typeof b[0])return this.parent.apply(this,arguments);var d,e,f={},h={},i=this;if(g(b,function(a){a.file?(h[a.name]=a=p(a.blob),e=a.id,d=a.flashId):f[a.name]=a.blob}),e||(d=r),!d)return c.log("[err] FlashAPI._send: flashId -- undefined"),this.parent.apply(this,arguments);c.log("FlashAPI.XHR._send: "+d+" -> "+e),i.xhr={headers:{},abort:function(){v.cmd(d,"abort",{id:e})},getResponseHeader:function(a){return this.headers[a]},getAllResponseHeaders:function(){return this.headers}};var j=c.queue(function(){v.cmd(d,"upload",{url:n(a.url.replace(/([a-z]+)=(\?)&?/i,"")),data:f,files:e?h:null,headers:a.headers||{},callback:l(function b(d){var e=d.type,f=d.result;c.log("FlashAPI.upload."+e),"progress"==e?(d.loaded=Math.min(d.loaded,d.total),d.lengthComputable=!0,a.progress(d)):"complete"==e?(m(b),"string"==typeof f&&(i.responseText=f.replace(/%22/g,'"').replace(/%5c/g,"\\").replace(/%26/g,"&").replace(/%25/g,"%")),i.end(d.status||200)):"abort"!=e&&"error"!=e||(i.end(d.status||0,d.message),m(b))})})});g(h,function(a){j.inc(),c.getInfo(a,j.next)}),j.check()}})}};c.Flash=v,c.newImage("data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==",function(a,b){c.support.dataURI=!(1!=b.width||1!=b.height),v.init()})}()}(window,window.jQuery,FileAPI),function(a,b,c){"use strict";var d=c.each,e=[];!c.support.flash||!c.media||c.support.media&&c.html5&&!c.insecureChrome||function(){function a(a){var b=a.wid=c.uid();return c.Flash._fn[b]=a,"FileAPI.Flash._fn."+b}function b(a){try{c.Flash._fn[a.wid]=null,delete c.Flash._fn[a.wid]}catch(a){}}var f=c.Flash;c.extend(c.Flash,{patchCamera:function(){c.Camera.fallback=function(d,e,g){var h=c.uid();c.log("FlashAPI.Camera.publish: "+h),f.publish(d,h,c.extend(e,{camera:!0,onEvent:a(function a(d){"camera"===d.type&&(b(a),d.error?(c.log("FlashAPI.Camera.publish.error: "+d.error),g(d.error)):(c.log("FlashAPI.Camera.publish.success: "+h),g(null)))})}))},d(e,function(a){c.Camera.fallback.apply(c.Camera,a)}),e=[],c.extend(c.Camera.prototype,{_id:function(){return this.video.id},start:function(d){var e=this;f.cmd(this._id(),"camera.on",{callback:a(function a(f){b(a),f.error?(c.log("FlashAPI.camera.on.error: "+f.error),d(f.error,e)):(c.log("FlashAPI.camera.on.success: "+e._id()),e._active=!0,d(null,e))})})},stop:function(){this._active=!1,f.cmd(this._id(),"camera.off")},shot:function(){c.log("FlashAPI.Camera.shot:",this._id());var a=c.Flash.cmd(this._id(),"shot",{});return a.type="image/png",a.flashId=this._id(),a.isShot=!0,new c.Camera.Shot(a)}})}}),c.Camera.fallback=function(){e.push(arguments)}}()}(window,window.jQuery,FileAPI),"function"==typeof define&&define.amd&&define("FileAPI",[],function(){return FileAPI});
\ No newline at end of file
diff --git a/dist/jquery.fileapi.min.js b/dist/jquery.fileapi.min.js
new file mode 100644
index 00000000..ec7369ec
--- /dev/null
+++ b/dist/jquery.fileapi.min.js
@@ -0,0 +1,2 @@
+/*! fileapi 2.0.1 - BSD | git://github.com/mailru/FileAPI.git */
+(function(e,t){"use strict";var n=e.noop,i=!e.fn.prop,a=i?"attr":"prop",r="data-fileapi",o="data-fileapi-id",s=function(i,a){if(this.$el=i=e(i).on("change.fileapi",e.proxy(this,"_onSelect")),this.el=i[0],this._options={},this.options=a=e.extend({url:0,data:{},accept:0,multiple:!1,paramName:0,dataType:"json",duplicate:!1,chunkSize:0,chunkUploadRetry:3,maxSize:0,maxFiles:0,imageSize:0,sortFn:0,filterFn:0,autoUpload:!1,lang:{B:"bytes",KB:"KB",MB:"MB",GB:"GB",TB:"TB"},sizeFormat:"0.00",imageTransform:0,elements:{ctrl:{upload:'[data-fileapi="ctrl.upload"]',reset:'[data-fileapi="ctrl.reset"]',abort:'[data-fileapi="ctrl.abort"]'},empty:{show:'[data-fileapi="empty.show"]',hide:'[data-fileapi="empty.hide"]'},emptyQueue:{show:'[data-fileapi="emptyQueue.show"]',hide:'[data-fileapi="emptyQueue.hide"]'},active:{show:'[data-fileapi="active.show"]',hide:'[data-fileapi="active.hide"]'},size:'[data-fileapi="size"]',name:'[data-fileapi="name"]',progress:'[data-fileapi="progress"]',file:{tpl:'[data-fileapi="file.tpl"]',progress:'[data-fileapi="file.progress"]',active:{show:'[data-fileapi="active.show"]',hide:'[data-fileapi="active.hide"]'},preview:{el:0,get:0,width:0,height:0,processing:0}},dnd:{el:'[data-fileapi="dnd"]',hover:"dnd_hover"}},onDrop:n,onDropHover:n,onSelect:n,onUpload:n,onProgress:n,onComplete:n,onFileUpload:n,onFileProgress:n,onFileComplete:n},a),!a.url){var o=this.$el.attr("action")||this.$el.find("form").attr("action");o?a.url=o:this._throw("url — is not defined")}this.$files=this.elem("list"),this.itemTplFn=e.fn.fileapi.tpl(e("
").append(this.elem("file.tpl")).html()),t.each(a,function(e,t){this._setOption(t,e)},this),this.$el.on("reset.fileapi",e.proxy(this,"_onReset")).on("submit.fileapi",e.proxy(this,"_onSubmit")).on("upload.fileapi progress.fileapi complete.fileapi",e.proxy(this,"_onUploadEvent")).on("fileupload.fileapi fileprogress.fileapi filecomplete.fileapi",e.proxy(this,"_onFileUploadEvent")).on("click","["+r+"]",e.proxy(this,"_onActionClick"));var s=a.elements.ctrl;s&&(s.reset&&this.$el.on("click.fileapi",s.reset,e.proxy(this,"_onReset")),s.upload&&this.$el.on("click.fileapi",s.upload,e.proxy(this,"_onSubmit"))),this.elem("dnd.el",!0).dnd(e.proxy(this,"_onDropHover"),e.proxy(this,"_onDrop")),this.$progress=this.elem("progress"),this._crop={},this._rotate={},this.files=[],this.uploaded=[],this.clear()};s.prototype={constructor:s,_throw:function(e){throw"jquery.fileapi: "+e},_getFiles:function(e,n){var i=this.options,a=i.maxSize,r=i.filterFn,o=t.getFiles(e),s={all:o,files:[],other:[],duplicate:i.duplicate?[]:this._extractDuplicateFiles(o)},l=i.imageSize,u=this;l||r?t.filterFiles(o,function(e,t){var n=!0;return t&&l&&(n=(!l.minWidth||t.width>=l.minWidth)&&(!l.minHeight||t.height>=l.minHeight)&&(!l.maxWidth||t.height<=l.maxWidth)&&(!l.maxHeight||t.height<=l.maxHeight)),n&&(!a||a>=e.size)&&(!r||r(e,t))},function(e,t){s.files=e,s.other=t,n.call(u,s)}):(t.each(o,function(e){s[!a||a>=e.size?"files":"other"].push(e)}),n.call(u,s))},_extractDuplicateFiles:function(e){for(var t,n=[],i=e.length,a=this.files;i--;)for(t=a.length;t--;)if(this._fileCompare(e[i],a[t])){n.push(e.splice(i,1));break}return n},_fileCompare:function(e,t){return e.size==t.size&&e.name==t.name},_getFormatedSize:function(e){var n=this.options,i="B";return e>=t.TB?e/=t[i="TB"]:e>=t.GB?e/=t[i="GB"]:e>=t.MB?e/=t[i="MB"]:e>=t.KB&&(e/=t[i="KB"]),n.sizeFormat.replace(/^\d+([^\d]+)(\d*)/,function(t,a,r){return e=e.toFixed(r.length),(e+"").replace(".",a)+" "+n.lang[i]})},_onSelect:function(t){this._getFiles(t,e.proxy(function(e){e.all.length&&this.emit("select",e)!==!1&&this.add(e.files)},this))},_onActionClick:function(n){var i=n.currentTarget,a=e.attr(i,r),s=e(i).closest("["+o+"]",this.$el),l=s.attr(o),u=!0;"remove"==a?(s.remove(),this.queue=t.filter(this.queue,function(e){return t.uid(e)!=l}),this.files=t.filter(this.files,function(e){return t.uid(e)!=l}),this._redraw()):/^rotate/.test(a)?this.rotate(l,/ccw/.test(a)?"-=90":"+=90"):u=!1,u&&n.preventDefault()},_onSubmit:function(e){this.upload(),e.preventDefault()},_onReset:function(e){this.clear(),e.preventDefault()},_onDrop:function(e){this._getFiles(e,function(e){this.emit("drop",e)!==!1&&this.add(e.files)})},_onDropHover:function(t,n){if(this.emit("dropHover",{state:t,event:n})!==!1){var i=this.option("elements.dnd.hover");i&&e(n.currentTarget).toggleClass(i,t)}},_getUploadEvent:function(t){var n=this.xhr,i={xhr:n,file:n.currentFile,files:n.files,widget:this};return e.extend(i,t)},_emitUploadEvent:function(e){var t=this._getUploadEvent();this.emit(e+"upload",t)},_emitProgressEvent:function(e,t){var n=this._getUploadEvent(t);this.emit(e+"progress",n)},_emitCompleteEvent:function(t,n){var i=this.xhr,a=this._getUploadEvent({error:n,status:i.status,statusText:i.statusText,result:i.responseText});"json"==this.options.dataType&&(a.result=e.parseJSON(a.result)),this.emit(t+"complete",a)},_onUploadEvent:function(e,t){var n=this,i=this.$progress,a=e.type;if("progress"==a)i.stop().animate({width:100*(t.loaded/t.total)+"%"},300);else if("upload"==a)i.width(0);else{var r=function(){i.dequeue(),n.clear()};this.xhr=null,this.active=!1,i.length?i.queue(r):r()}},_onFileUploadPrepare:function(n,i){var a=t.uid(n),r=this._rotate[a],o=this._crop[a];if(r||o){var s=i.imageTransform=i.imageTransform||{};e.isEmptyObject(s)||parseInt(s.maxWidth||s.minWidth||s.width,10)>0||s.type||s.quality?(s.crop=o,s.rotate=r):t.each(s,function(e){e.crop=o,e.rotate=r})}},_onFileUploadEvent:function(e,n){var i=this,a=e.type.substr(4),o=t.uid(n.file),s=this.fileElem(o),l=this._$fileprogress;if(this.__fileId!==o&&(this.__fileId=o,this._$fileprogress=l=s.find(this.option("elements.file.progress"))),"progress"==a)l.stop().animate({width:100*(n.loaded/n.total)+"%"},300);else if("upload"==a||"complete"==a){var u=function(){var e="elements.file."+a;"upload"==a&&(s.find("["+r+'="remove"]').hide(),l.width(0)),l.dequeue(),s.find(i.option(e+".show")).show(),s.find(i.option(e+".hide")).hide()};l.length?l.queue(u):u(),"complete"==a&&(this.uploaded.push(n.file),delete this._rotate[o])}},_redraw:function(){var n=!!this.active,i=this.files,r=!i.length&&!n,s=!this.queue.length&&!n,l=[],u=0,c=this.$files,f=c.children().length,d=this.option("elements.file.preview");t.each(i,function(n,i){var a=t.uid(n);if(l.push(n.name),u+=n.size,c.length&&!this.fileElem(a).length){var r=this.itemTplFn({$idx:f+i,uid:n.uid,name:n.name,type:n.type,size:n.size,sizeText:this._getFormatedSize(n.size)});c.append(e(r).attr(o,a)),d.el&&this._makeFilePreview(a,n,d)}},this),this.elem("name").text(l.join(", ")),this.elem("size").text(this._getFormatedSize(u)),this.__empty!==r&&(this.__empty=r,this.elem("empty.show").toggle(r),this.elem("empty.hide").toggle(!r)),this.__emptyQueue!==s&&(this.__emptyQueue=r,this.elem("emptyQueue.show").toggle(s),this.elem("emptyQueue.hide").toggle(!s)),this.__active!==n&&(this.__active=n,this.elem("active.show").toggle(n),this.elem("active.hide").toggle(!n),this.$(".js-fileapi-wrapper,:file")[n?"attr":"removeAttr"]("aria-disabled",n)[a]("disabled",n)),this.elem("ctrl.upload").add(this.elem("ctrl.reset"))[r||n?"attr":"removeAttr"]("aria-disabled",r||n)[a]("disabled",r||n)},_makeFilePreview:function(e,n,i,a){var r=this,o=a?r.$(i.el):r.fileElem(e).find(i.el);if(/^image/.test(n.type)){t.log("_makeFilePreview:",e,n);var s=t.Image(n),l=function(){s.get(function(t,a){r._crop[e]||(t?(i.get&&i.get(o,n),r.emit("filePreviewError",{error:t,file:n})):o.html(a))})};i.width&&s.preview(i.width,i.height),i.rotate&&s.rotate(i.rotate),i.processing?i.processing(n,s,l):l()}else i.get&&i.get(o,n)},emit:function(t,n){var i,a=this.options,r=e.Event(t);return r.widget=this,t=e.camelCase("on-"+t.replace(/(file)(upload)/,"$1-$2")),e.isFunction(a[t])&&(i=a[t].call(this.el,r,n)),i!==!1&&this.$el.triggerHandler(r,n)},add:function(e){if(e.length){var n=this.options,i=n.sortFn,a=n.elements.preview;i&&e.sort(i),a&&a.el&&t.each(e,function(e){this._makeFilePreview(t.uid(e),e,a,!0)},this),this.xhr&&this.xhr.append(e),this.queue=this.queue.concat(e),this.files=this.files.concat(e),this.options.autoUpload?this.upload():this._redraw()}},$:function(t,n){return"string"==typeof t&&(t=/^#/.test(t)?t:(n?e(n):this.$el).find(t)),e(t)},elem:function(t,n){var i=this.option("elements."+t);return void 0===i&&n&&(i=this.option("elements."+t.substr(0,t.lastIndexOf(".")))),this.$("string"!=e.type(i)&&e.isEmptyObject(i)?[]:i)},fileElem:function(e){return this.$("["+o+'="'+e+'"]')},option:function(n,i){if(void 0!==i&&e.isPlainObject(i))return t.each(i,function(e,t){this.option(n+"."+t,e)},this),this;var a,r,o=this.options,s=o[n],l=0;if(-1!=n.indexOf("."))for(s=o,n=n.split("."),a=n.length;a>l;l++){if(r=n[l],void 0!==i&&1===a-l){s[r]=i;break}void 0===s[r]&&(s[r]={}),s=s[r]}else void 0!==i&&(o[n]=i);return void 0!==i&&(this._setOption(n,i,this._options[n]),this._options[n]=i),void 0!==i?i:s},_setOption:function(e,t){switch(e){case"accept":case"multiple":case"paramName":"paramName"==e&&(e="name"),t&&this.$(":file")[a](e,t)}},serialize:function(){var t,n={};return this.$el.find(":input").each(function(i,a){(i=a.name)&&!a.disabled&&(a.checked||/select|textarea|input/i.test(a.nodeName)&&/checkbox|radio/i.test(a.type))&&(t=e(a).val(),void 0!==n[i]?(n[i].push||(n[i]=[n[i]]),n[i].push(t)):n[i]=t)}),n},upload:function(){if(!this.active){this.active=!0;var n=this.$el,i=this.options,a={},r={url:i.url,data:e.extend({},this.serialize(),i.data),files:a,chunkSize:0|i.chunkSize,chunkUploadRetry:0|i.chunkUploadRetry,prepare:e.proxy(this,"_onFileUploadPrepare"),imageTransform:i.imageTransform};a[n.find(":file").attr("name")||"files[]"]=this.queue,t.each(["upload","progress","complete"],function(t){r[t]=e.proxy(this,e.camelCase("_emit-"+t+"Event"),""),r["file"+t]=e.proxy(this,e.camelCase("_emit-"+t+"Event"),"file")},this),this.xhr=t.upload(r),this._redraw()}},crop:function(n,i){var a=t.uid(n),r=this.options,o=r.multiple?this.option("elements.file.preview"):r.elements.preview,s=(r.multiple?this.fileElem(a):this.$el).find(o&&o.el);s.length&&t.getInfo(n,e.proxy(function(a,r){if(!a){s.find("div>div").length||s.html(e("").css(o).css("overflow","hidden")),this.__cropFile!==n&&(this.__cropFile=n,t.Image(n).get(function(t,n){s.find(">div>div").html(e(n).width("100%").height("100%"))},"exactFit"));var l=o.width/i.w,u=o.height/i.h;s.find(">div>div").css({width:Math.round(l*r.width),height:Math.round(u*r.height),marginLeft:-Math.round(l*i.x),marginTop:-Math.round(u*i.y)})}},this)),this._crop[a]=i},rotate:function(e,n){var i="object"==typeof e?t.uid(e):e,a=this.options,r=a.multiple?this.option("elements.file.preview"):a.elements.preview,o=(a.multiple?this.fileElem(i):this.$el).find(r&&r.el),s=this._rotate;/([+-])=/.test(n)?n=s[i]=(s[i]||0)+("+"==RegExp.$1?1:-1)*n.substr(2):s[i]=n,o.css({"-webkit-transform":"rotate("+n+"deg)","-moz-transform":"rotate("+n+"deg)",transform:"rotate("+n+"deg)"})},clear:function(){this.queue=[],this._redraw()},widget:function(){return this},destroy:function(){this.$el.off(".fileapi").removeData("fileapi")}},e.fn.fileapi=function(t,n){var i=this.data("fileapi");if(i){if("widget"===t)return i;if("string"==typeof t){var a,r=i[t];return e.isFunction(r)?a=r.call(i,n,arguments[2]):void 0===r&&(a=this.option(t,n)),void 0===a?this:a}}else this.data("fileapi",new s(this,t));return this},e.fn.fileapi.version="0.1.0",e.fn.fileapi.tpl=function(e){var t=0,n="__b+='";return e.replace(/(?:<|<)%([-=])?([\s\S]+?)%(?:>|>)|$/g,function(i,a,r,o){return n+=e.slice(t,o).replace(/[\r\n"']/g,function(e){return"\\"+e}),r&&(n+=a?"'+\n((__x=("+r+"))==null?'':"+("-"==a?"__esc(__x)":"__x")+")\n+'":"';\n"+r+"\n__b+='"),t=o+i.length,i}),Function("ctx","var __x,__b='',__esc=function(val){return typeof val=='string'?val.replace(/
+
+
+
+
+
+
+ FileAPI :: CamanJS :: example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select photo
+
+
+
+
+
+
WebCam
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/demo.html b/examples/demo.html
new file mode 100644
index 00000000..93570b8c
--- /dev/null
+++ b/examples/demo.html
@@ -0,0 +1,555 @@
+
+
+
+
+
+
+
+ FileAPI :: Demo :: example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Увы, ваш браузер не поддерживает html5 и flash,
+ поэтому смотреть тут нечего, а iframe не даёт всей красоты :]
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/thumbnails.html b/examples/thumbnails.html
new file mode 100644
index 00000000..dd4e1e95
--- /dev/null
+++ b/examples/thumbnails.html
@@ -0,0 +1,173 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FileAPI :: Thumbnails :: example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Thumbnails
+
+
+
+ Select photo (min 480x320)
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/toolkit.css b/examples/toolkit.css
new file mode 100644
index 00000000..8cba3b1f
--- /dev/null
+++ b/examples/toolkit.css
@@ -0,0 +1,76 @@
+.body {
+ font-size: 14px;
+}
+
+.btn, .body {
+ font-family: "Raleway","Helvetica Neue",Helvetica,Arial,sans-serif;
+}
+
+
+.btn {
+ padding: 10px 50px;
+ color: #fff;
+ cursor: pointer;
+ display: inline-block;
+ text-decoration: none;
+ font-size: 24px;
+ border-radius: 4px;
+ background-color: #80BD95;
+ box-shadow: 0 3px 0 0 #72A884;
+ text-shadow: 0 -2px 0 rgba(0,0,0,.2);
+}
+
+
+.loader {
+ width: 30px;
+ height: 30px;
+ margin: 50px 0 50px -15px;
+ border: 8px solid #000;
+ border-right-color: transparent;
+ display: inline-block;
+ border-radius: 50%;
+ box-shadow: 0 0 25px 2px #eee;
+ border-right: 0 none;
+ -webkit-animation: spin 1s linear infinite;
+ -moz-animation: spin 1s linear infinite;
+ -ms-animation: spin 1s linear infinite;
+ -o-animation: spin 1s linear infinite;
+ animation: spin 1s linear infinite;
+}
+
+.js-fileapi-wrapper {
+ display: inline-block;
+ *zoom: 1;
+ *display: inline;
+}
+ .js-fileapi-wrapper input {
+ width: 0;
+ height: 0;
+ opacity: 0;
+ overflow: 0;
+ position: absolute;
+ }
+
+@-webkit-keyframes spin {
+ from { -webkit-transform: rotate(0deg); opacity: 0.4; }
+ 50% { -webkit-transform: rotate(180deg); opacity: 1; }
+ to { -webkit-transform: rotate(360deg); opacity: 0.4; }
+}
+
+@-moz-keyframes spin {
+ from { -moz-transform: rotate(0deg); opacity: 0.4; }
+ 50% { -moz-transform: rotate(180deg); opacity: 1; }
+ to { -moz-transform: rotate(360deg); opacity: 0.4; }
+}
+
+@-ms-keyframes spin {
+ from { -ms-transform: rotate(0deg); opacity: 0.4; }
+ 50% { -ms-transform: rotate(180deg); opacity: 1; }
+ to { -ms-transform: rotate(360deg); opacity: 0.4; }
+}
+
+@keyframes spin {
+ from { transform: rotate(0deg); opacity: 0.2; }
+ 50% { transform: rotate(180deg); opacity: 1; }
+ to { transform: rotate(360deg); opacity: 0.2; }
+}
diff --git a/examples/userpic.html b/examples/userpic.html
new file mode 100644
index 00000000..e72a55d4
--- /dev/null
+++ b/examples/userpic.html
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FileAPI :: UserPic :: example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/watermark.html b/examples/watermark.html
new file mode 100644
index 00000000..4403d000
--- /dev/null
+++ b/examples/watermark.html
@@ -0,0 +1,262 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FileAPI :: Watermark :: example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/webcam.html b/examples/webcam.html
new file mode 100644
index 00000000..0a995eee
--- /dev/null
+++ b/examples/webcam.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ FileAPI :: WebCam :: example
+
+
+
+
+
+
+
+
+
+
+
+
+
+
WebCam
+
+
+
+
shot
+
start
+
stop
+
+
Shots
+
+
+
Server
+
+
+
+
+
+
+
diff --git a/flash/.actionScriptProperties b/flash/.actionScriptProperties
deleted file mode 100644
index 5c1915b8..00000000
--- a/flash/.actionScriptProperties
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/flash/.gitignore b/flash/.gitignore
new file mode 100644
index 00000000..197c6a96
--- /dev/null
+++ b/flash/.gitignore
@@ -0,0 +1,19 @@
+#.gitignore
+
+core/bin-debug
+core/bin-release
+
+core/.actionScriptProperties
+core/.project
+
+camera/bin-debug
+camera/bin-release
+
+camera/.actionScriptProperties
+camera/.project
+
+image/bin-debug
+image/bin-release
+
+image/.actionScriptProperties
+image/.project
diff --git a/flash/.project b/flash/.project
deleted file mode 100644
index 84ab7bd3..00000000
--- a/flash/.project
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
- FlashFileAPI
-
-
-
-
-
- com.adobe.flexbuilder.project.flexbuilder
-
-
-
-
-
- com.adobe.flexbuilder.project.actionscriptnature
-
-
diff --git a/flash/camera/.settings/org.eclipse.core.resources.prefs b/flash/camera/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000..efb1c598
--- /dev/null
+++ b/flash/camera/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Thu May 02 20:14:08 MSD 2013
+eclipse.preferences.version=1
+encoding/=utf-8
diff --git a/flash/html-template/history/history.css b/flash/camera/html-template/history/history.css
similarity index 100%
rename from flash/html-template/history/history.css
rename to flash/camera/html-template/history/history.css
diff --git a/flash/camera/html-template/history/history.js b/flash/camera/html-template/history/history.js
new file mode 100644
index 00000000..00a8bfee
--- /dev/null
+++ b/flash/camera/html-template/history/history.js
@@ -0,0 +1,678 @@
+BrowserHistoryUtils = {
+ addEvent: function(elm, evType, fn, useCapture) {
+ useCapture = useCapture || false;
+ if (elm.addEventListener) {
+ elm.addEventListener(evType, fn, useCapture);
+ return true;
+ }
+ else if (elm.attachEvent) {
+ var r = elm.attachEvent('on' + evType, fn);
+ return r;
+ }
+ else {
+ elm['on' + evType] = fn;
+ }
+ }
+}
+
+BrowserHistory = (function() {
+ // type of browser
+ var browser = {
+ ie: false,
+ ie8: false,
+ firefox: false,
+ safari: false,
+ opera: false,
+ version: -1
+ };
+
+ // Default app state URL to use when no fragment ID present
+ var defaultHash = '';
+
+ // Last-known app state URL
+ var currentHref = document.location.href;
+
+ // Initial URL (used only by IE)
+ var initialHref = document.location.href;
+
+ // Initial URL (used only by IE)
+ var initialHash = document.location.hash;
+
+ // History frame source URL prefix (used only by IE)
+ var historyFrameSourcePrefix = 'history/historyFrame.html?';
+
+ // History maintenance (used only by Safari)
+ var currentHistoryLength = -1;
+
+ // Flag to denote the existence of onhashchange
+ var browserHasHashChange = false;
+
+ var historyHash = [];
+
+ var initialState = createState(initialHref, initialHref + '#' + initialHash, initialHash);
+
+ var backStack = [];
+ var forwardStack = [];
+
+ var currentObjectId = null;
+
+ //UserAgent detection
+ var useragent = navigator.userAgent.toLowerCase();
+
+ if (useragent.indexOf("opera") != -1) {
+ browser.opera = true;
+ } else if (useragent.indexOf("msie") != -1) {
+ browser.ie = true;
+ browser.version = parseFloat(useragent.substring(useragent.indexOf('msie') + 4));
+ if (browser.version == 8)
+ {
+ browser.ie = false;
+ browser.ie8 = true;
+ }
+ } else if (useragent.indexOf("safari") != -1) {
+ browser.safari = true;
+ browser.version = parseFloat(useragent.substring(useragent.indexOf('safari') + 7));
+ } else if (useragent.indexOf("gecko") != -1) {
+ browser.firefox = true;
+ }
+
+ if (browser.ie == true && browser.version == 7) {
+ window["_ie_firstload"] = false;
+ }
+
+ function hashChangeHandler()
+ {
+ currentHref = document.location.href;
+ var flexAppUrl = getHash();
+ //ADR: to fix multiple
+ if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) {
+ var pl = getPlayers();
+ for (var i = 0; i < pl.length; i++) {
+ pl[i].browserURLChange(flexAppUrl);
+ }
+ } else {
+ getPlayer().browserURLChange(flexAppUrl);
+ }
+ }
+
+ // Accessor functions for obtaining specific elements of the page.
+ function getHistoryFrame()
+ {
+ return document.getElementById('ie_historyFrame');
+ }
+
+ function getFormElement()
+ {
+ return document.getElementById('safari_formDiv');
+ }
+
+ function getRememberElement()
+ {
+ return document.getElementById("safari_remember_field");
+ }
+
+ // Get the Flash player object for performing ExternalInterface callbacks.
+ // Updated for changes to SWFObject2.
+ function getPlayer(id) {
+ var i;
+
+ if (id && document.getElementById(id)) {
+ var r = document.getElementById(id);
+ if (typeof r.SetVariable != "undefined") {
+ return r;
+ }
+ else {
+ var o = r.getElementsByTagName("object");
+ var e = r.getElementsByTagName("embed");
+ for (i = 0; i < o.length; i++) {
+ if (typeof o[i].browserURLChange != "undefined")
+ return o[i];
+ }
+ for (i = 0; i < e.length; i++) {
+ if (typeof e[i].browserURLChange != "undefined")
+ return e[i];
+ }
+ }
+ }
+ else {
+ var o = document.getElementsByTagName("object");
+ var e = document.getElementsByTagName("embed");
+ for (i = 0; i < e.length; i++) {
+ if (typeof e[i].browserURLChange != "undefined")
+ {
+ return e[i];
+ }
+ }
+ for (i = 0; i < o.length; i++) {
+ if (typeof o[i].browserURLChange != "undefined")
+ {
+ return o[i];
+ }
+ }
+ }
+ return undefined;
+ }
+
+ function getPlayers() {
+ var i;
+ var players = [];
+ if (players.length == 0) {
+ var tmp = document.getElementsByTagName('object');
+ for (i = 0; i < tmp.length; i++)
+ {
+ if (typeof tmp[i].browserURLChange != "undefined")
+ players.push(tmp[i]);
+ }
+ }
+ if (players.length == 0 || players[0].object == null) {
+ var tmp = document.getElementsByTagName('embed');
+ for (i = 0; i < tmp.length; i++)
+ {
+ if (typeof tmp[i].browserURLChange != "undefined")
+ players.push(tmp[i]);
+ }
+ }
+ return players;
+ }
+
+ function getIframeHash() {
+ var doc = getHistoryFrame().contentWindow.document;
+ var hash = String(doc.location.search);
+ if (hash.length == 1 && hash.charAt(0) == "?") {
+ hash = "";
+ }
+ else if (hash.length >= 2 && hash.charAt(0) == "?") {
+ hash = hash.substring(1);
+ }
+ return hash;
+ }
+
+ /* Get the current location hash excluding the '#' symbol. */
+ function getHash() {
+ // It would be nice if we could use document.location.hash here,
+ // but it's faulty sometimes.
+ var idx = document.location.href.indexOf('#');
+ return (idx >= 0) ? document.location.href.substr(idx+1) : '';
+ }
+
+ /* Get the current location hash excluding the '#' symbol. */
+ function setHash(hash) {
+ // It would be nice if we could use document.location.hash here,
+ // but it's faulty sometimes.
+ if (hash == '') hash = '#'
+ document.location.hash = hash;
+ }
+
+ function createState(baseUrl, newUrl, flexAppUrl) {
+ return { 'baseUrl': baseUrl, 'newUrl': newUrl, 'flexAppUrl': flexAppUrl, 'title': null };
+ }
+
+ /* Add a history entry to the browser.
+ * baseUrl: the portion of the location prior to the '#'
+ * newUrl: the entire new URL, including '#' and following fragment
+ * flexAppUrl: the portion of the location following the '#' only
+ */
+ function addHistoryEntry(baseUrl, newUrl, flexAppUrl) {
+
+ //delete all the history entries
+ forwardStack = [];
+
+ if (browser.ie) {
+ //Check to see if we are being asked to do a navigate for the first
+ //history entry, and if so ignore, because it's coming from the creation
+ //of the history iframe
+ if (flexAppUrl == defaultHash && document.location.href == initialHref && window['_ie_firstload']) {
+ currentHref = initialHref;
+ return;
+ }
+ if ((!flexAppUrl || flexAppUrl == defaultHash) && window['_ie_firstload']) {
+ newUrl = baseUrl + '#' + defaultHash;
+ flexAppUrl = defaultHash;
+ } else {
+ // for IE, tell the history frame to go somewhere without a '#'
+ // in order to get this entry into the browser history.
+ getHistoryFrame().src = historyFrameSourcePrefix + flexAppUrl;
+ }
+ setHash(flexAppUrl);
+ } else {
+
+ //ADR
+ if (backStack.length == 0 && initialState.flexAppUrl == flexAppUrl) {
+ initialState = createState(baseUrl, newUrl, flexAppUrl);
+ } else if(backStack.length > 0 && backStack[backStack.length - 1].flexAppUrl == flexAppUrl) {
+ backStack[backStack.length - 1] = createState(baseUrl, newUrl, flexAppUrl);
+ }
+
+ if (browser.safari && !browserHasHashChange) {
+ // for Safari, submit a form whose action points to the desired URL
+ if (browser.version <= 419.3) {
+ var file = window.location.pathname.toString();
+ file = file.substring(file.lastIndexOf("/")+1);
+ getFormElement().innerHTML = '';
+ //get the current elements and add them to the form
+ var qs = window.location.search.substring(1);
+ var qs_arr = qs.split("&");
+ for (var i = 0; i < qs_arr.length; i++) {
+ var tmp = qs_arr[i].split("=");
+ var elem = document.createElement("input");
+ elem.type = "hidden";
+ elem.name = tmp[0];
+ elem.value = tmp[1];
+ document.forms.historyForm.appendChild(elem);
+ }
+ document.forms.historyForm.submit();
+ } else {
+ top.location.hash = flexAppUrl;
+ }
+ // We also have to maintain the history by hand for Safari
+ historyHash[history.length] = flexAppUrl;
+ _storeStates();
+ } else {
+ // Otherwise, just tell the browser to go there
+ setHash(flexAppUrl);
+ }
+ }
+ backStack.push(createState(baseUrl, newUrl, flexAppUrl));
+ }
+
+ function _storeStates() {
+ if (browser.safari) {
+ getRememberElement().value = historyHash.join(",");
+ }
+ }
+
+ function handleBackButton() {
+ //The "current" page is always at the top of the history stack.
+ var current = backStack.pop();
+ if (!current) { return; }
+ var last = backStack[backStack.length - 1];
+ if (!last && backStack.length == 0){
+ last = initialState;
+ }
+ forwardStack.push(current);
+ }
+
+ function handleForwardButton() {
+ //summary: private method. Do not call this directly.
+
+ var last = forwardStack.pop();
+ if (!last) { return; }
+ backStack.push(last);
+ }
+
+ function handleArbitraryUrl() {
+ //delete all the history entries
+ forwardStack = [];
+ }
+
+ /* Called periodically to poll to see if we need to detect navigation that has occurred */
+ function checkForUrlChange() {
+
+ if (browser.ie) {
+ if (currentHref != document.location.href && currentHref + '#' != document.location.href) {
+ //This occurs when the user has navigated to a specific URL
+ //within the app, and didn't use browser back/forward
+ //IE seems to have a bug where it stops updating the URL it
+ //shows the end-user at this point, but programatically it
+ //appears to be correct. Do a full app reload to get around
+ //this issue.
+ if (browser.version < 7) {
+ currentHref = document.location.href;
+ document.location.reload();
+ } else {
+ if (getHash() != getIframeHash()) {
+ // this.iframe.src = this.blankURL + hash;
+ var sourceToSet = historyFrameSourcePrefix + getHash();
+ getHistoryFrame().src = sourceToSet;
+ currentHref = document.location.href;
+ }
+ }
+ }
+ }
+
+ if (browser.safari && !browserHasHashChange) {
+ // For Safari, we have to check to see if history.length changed.
+ if (currentHistoryLength >= 0 && history.length != currentHistoryLength) {
+ //alert("did change: " + history.length + ", " + historyHash.length + "|" + historyHash[history.length] + "|>" + historyHash.join("|"));
+ var flexAppUrl = getHash();
+ if (browser.version < 528.16 /* Anything earlier than Safari 4.0 */)
+ {
+ // If it did change and we're running Safari 3.x or earlier,
+ // then we have to look the old state up in our hand-maintained
+ // array since document.location.hash won't have changed,
+ // then call back into BrowserManager.
+ currentHistoryLength = history.length;
+ flexAppUrl = historyHash[currentHistoryLength];
+ }
+
+ //ADR: to fix multiple
+ if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) {
+ var pl = getPlayers();
+ for (var i = 0; i < pl.length; i++) {
+ pl[i].browserURLChange(flexAppUrl);
+ }
+ } else {
+ getPlayer().browserURLChange(flexAppUrl);
+ }
+ _storeStates();
+ }
+ }
+ if (browser.firefox && !browserHasHashChange) {
+ if (currentHref != document.location.href) {
+ var bsl = backStack.length;
+
+ var urlActions = {
+ back: false,
+ forward: false,
+ set: false
+ }
+
+ if ((window.location.hash == initialHash || window.location.href == initialHref) && (bsl == 1)) {
+ urlActions.back = true;
+ // FIXME: could this ever be a forward button?
+ // we can't clear it because we still need to check for forwards. Ugg.
+ // clearInterval(this.locationTimer);
+ handleBackButton();
+ }
+
+ // first check to see if we could have gone forward. We always halt on
+ // a no-hash item.
+ if (forwardStack.length > 0) {
+ if (forwardStack[forwardStack.length-1].flexAppUrl == getHash()) {
+ urlActions.forward = true;
+ handleForwardButton();
+ }
+ }
+
+ // ok, that didn't work, try someplace back in the history stack
+ if ((bsl >= 2) && (backStack[bsl - 2])) {
+ if (backStack[bsl - 2].flexAppUrl == getHash()) {
+ urlActions.back = true;
+ handleBackButton();
+ }
+ }
+
+ if (!urlActions.back && !urlActions.forward) {
+ var foundInStacks = {
+ back: -1,
+ forward: -1
+ }
+
+ for (var i = 0; i < backStack.length; i++) {
+ if (backStack[i].flexAppUrl == getHash() && i != (bsl - 2)) {
+ arbitraryUrl = true;
+ foundInStacks.back = i;
+ }
+ }
+ for (var i = 0; i < forwardStack.length; i++) {
+ if (forwardStack[i].flexAppUrl == getHash() && i != (bsl - 2)) {
+ arbitraryUrl = true;
+ foundInStacks.forward = i;
+ }
+ }
+ handleArbitraryUrl();
+ }
+
+ // Firefox changed; do a callback into BrowserManager to tell it.
+ currentHref = document.location.href;
+ var flexAppUrl = getHash();
+ //ADR: to fix multiple
+ if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) {
+ var pl = getPlayers();
+ for (var i = 0; i < pl.length; i++) {
+ pl[i].browserURLChange(flexAppUrl);
+ }
+ } else {
+ getPlayer().browserURLChange(flexAppUrl);
+ }
+ }
+ }
+ }
+
+ var _initialize = function () {
+
+ browserHasHashChange = ("onhashchange" in document.body);
+
+ if (browser.ie)
+ {
+ var scripts = document.getElementsByTagName('script');
+ for (var i = 0, s; s = scripts[i]; i++) {
+ if (s.src.indexOf("history.js") > -1) {
+ var iframe_location = (new String(s.src)).replace("history.js", "historyFrame.html");
+ }
+ }
+ historyFrameSourcePrefix = iframe_location + "?";
+ var src = historyFrameSourcePrefix;
+
+ var iframe = document.createElement("iframe");
+ iframe.id = 'ie_historyFrame';
+ iframe.name = 'ie_historyFrame';
+ iframe.src = 'javascript:false;';
+
+ try {
+ document.body.appendChild(iframe);
+ } catch(e) {
+ setTimeout(function() {
+ document.body.appendChild(iframe);
+ }, 0);
+ }
+ }
+
+ if (browser.safari && !browserHasHashChange)
+ {
+ var rememberDiv = document.createElement("div");
+ rememberDiv.id = 'safari_rememberDiv';
+ document.body.appendChild(rememberDiv);
+ rememberDiv.innerHTML = ' ';
+
+ var formDiv = document.createElement("div");
+ formDiv.id = 'safari_formDiv';
+ document.body.appendChild(formDiv);
+
+ var reloader_content = document.createElement('div');
+ reloader_content.id = 'safarireloader';
+ var scripts = document.getElementsByTagName('script');
+ for (var i = 0, s; s = scripts[i]; i++) {
+ if (s.src.indexOf("history.js") > -1) {
+ html = (new String(s.src)).replace(".js", ".html");
+ }
+ }
+ reloader_content.innerHTML = '';
+ document.body.appendChild(reloader_content);
+ reloader_content.style.position = 'absolute';
+ reloader_content.style.left = reloader_content.style.top = '-9999px';
+ iframe = reloader_content.getElementsByTagName('iframe')[0];
+
+ if (document.getElementById("safari_remember_field").value != "" ) {
+ historyHash = document.getElementById("safari_remember_field").value.split(",");
+ }
+ }
+
+ if (browserHasHashChange)
+ document.body.onhashchange = hashChangeHandler;
+ }
+
+ return {
+ historyHash: historyHash,
+ backStack: function() { return backStack; },
+ forwardStack: function() { return forwardStack },
+ getPlayer: getPlayer,
+ initialize: function(src) {
+ _initialize(src);
+ },
+ setURL: function(url) {
+ document.location.href = url;
+ },
+ getURL: function() {
+ return document.location.href;
+ },
+ getTitle: function() {
+ return document.title;
+ },
+ setTitle: function(title) {
+ try {
+ backStack[backStack.length - 1].title = title;
+ } catch(e) { }
+ //if on safari, set the title to be the empty string.
+ if (browser.safari) {
+ if (title == "") {
+ try {
+ var tmp = window.location.href.toString();
+ title = tmp.substring((tmp.lastIndexOf("/")+1), tmp.lastIndexOf("#"));
+ } catch(e) {
+ title = "";
+ }
+ }
+ }
+ document.title = title;
+ },
+ setDefaultURL: function(def)
+ {
+ defaultHash = def;
+ def = getHash();
+ //trailing ? is important else an extra frame gets added to the history
+ //when navigating back to the first page. Alternatively could check
+ //in history frame navigation to compare # and ?.
+ if (browser.ie)
+ {
+ window['_ie_firstload'] = true;
+ var sourceToSet = historyFrameSourcePrefix + def;
+ var func = function() {
+ getHistoryFrame().src = sourceToSet;
+ window.location.replace("#" + def);
+ setInterval(checkForUrlChange, 50);
+ }
+ try {
+ func();
+ } catch(e) {
+ window.setTimeout(function() { func(); }, 0);
+ }
+ }
+
+ if (browser.safari)
+ {
+ currentHistoryLength = history.length;
+ if (historyHash.length == 0) {
+ historyHash[currentHistoryLength] = def;
+ var newloc = "#" + def;
+ window.location.replace(newloc);
+ } else {
+ //alert(historyHash[historyHash.length-1]);
+ }
+ setInterval(checkForUrlChange, 50);
+ }
+
+
+ if (browser.firefox || browser.opera)
+ {
+ var reg = new RegExp("#" + def + "$");
+ if (window.location.toString().match(reg)) {
+ } else {
+ var newloc ="#" + def;
+ window.location.replace(newloc);
+ }
+ setInterval(checkForUrlChange, 50);
+ }
+
+ },
+
+ /* Set the current browser URL; called from inside BrowserManager to propagate
+ * the application state out to the container.
+ */
+ setBrowserURL: function(flexAppUrl, objectId) {
+ if (browser.ie && typeof objectId != "undefined") {
+ currentObjectId = objectId;
+ }
+ //fromIframe = fromIframe || false;
+ //fromFlex = fromFlex || false;
+ //alert("setBrowserURL: " + flexAppUrl);
+ //flexAppUrl = (flexAppUrl == "") ? defaultHash : flexAppUrl ;
+
+ var pos = document.location.href.indexOf('#');
+ var baseUrl = pos != -1 ? document.location.href.substr(0, pos) : document.location.href;
+ var newUrl = baseUrl + '#' + flexAppUrl;
+
+ if (document.location.href != newUrl && document.location.href + '#' != newUrl) {
+ currentHref = newUrl;
+ addHistoryEntry(baseUrl, newUrl, flexAppUrl);
+ currentHistoryLength = history.length;
+ }
+ },
+
+ browserURLChange: function(flexAppUrl) {
+ var objectId = null;
+ if (browser.ie && currentObjectId != null) {
+ objectId = currentObjectId;
+ }
+
+ if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) {
+ var pl = getPlayers();
+ for (var i = 0; i < pl.length; i++) {
+ try {
+ pl[i].browserURLChange(flexAppUrl);
+ } catch(e) { }
+ }
+ } else {
+ try {
+ getPlayer(objectId).browserURLChange(flexAppUrl);
+ } catch(e) { }
+ }
+
+ currentObjectId = null;
+ },
+ getUserAgent: function() {
+ return navigator.userAgent;
+ },
+ getPlatform: function() {
+ return navigator.platform;
+ }
+
+ }
+
+})();
+
+// Initialization
+
+// Automated unit testing and other diagnostics
+
+function setURL(url)
+{
+ document.location.href = url;
+}
+
+function backButton()
+{
+ history.back();
+}
+
+function forwardButton()
+{
+ history.forward();
+}
+
+function goForwardOrBackInHistory(step)
+{
+ history.go(step);
+}
+
+//BrowserHistoryUtils.addEvent(window, "load", function() { BrowserHistory.initialize(); });
+(function(i) {
+ var u =navigator.userAgent;var e=/*@cc_on!@*/false;
+ var st = setTimeout;
+ if(/webkit/i.test(u)){
+ st(function(){
+ var dr=document.readyState;
+ if(dr=="loaded"||dr=="complete"){i()}
+ else{st(arguments.callee,10);}},10);
+ } else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){
+ document.addEventListener("DOMContentLoaded",i,false);
+ } else if(e){
+ (function(){
+ var t=document.createElement('doc:rdy');
+ try{t.doScroll('left');
+ i();t=null;
+ }catch(e){st(arguments.callee,0);}})();
+ } else{
+ window.onload=i;
+ }
+})( function() {BrowserHistory.initialize();} );
diff --git a/flash/html-template/history/historyFrame.html b/flash/camera/html-template/history/historyFrame.html
similarity index 100%
rename from flash/html-template/history/historyFrame.html
rename to flash/camera/html-template/history/historyFrame.html
diff --git a/flash/camera/html-template/index.template.html b/flash/camera/html-template/index.template.html
new file mode 100644
index 00000000..d327dade
--- /dev/null
+++ b/flash/camera/html-template/index.template.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+ ${title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To view this page ensure that Adobe Flash Player version
+ ${version_major}.${version_minor}.${version_revision} or greater is installed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Either scripts and active content are not permitted to run or Adobe Flash Player version
+ ${version_major}.${version_minor}.${version_revision} or greater is not installed.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/flash/html-template/playerProductInstall.swf b/flash/camera/html-template/playerProductInstall.swf
similarity index 100%
rename from flash/html-template/playerProductInstall.swf
rename to flash/camera/html-template/playerProductInstall.swf
diff --git a/flash/html-template/swfobject.js b/flash/camera/html-template/swfobject.js
similarity index 100%
rename from flash/html-template/swfobject.js
rename to flash/camera/html-template/swfobject.js
diff --git a/flash/camera/src/FileAPI_flash_camera.as b/flash/camera/src/FileAPI_flash_camera.as
new file mode 100644
index 00000000..736ab4f2
--- /dev/null
+++ b/flash/camera/src/FileAPI_flash_camera.as
@@ -0,0 +1,133 @@
+package
+{
+ import flash.display.BitmapData;
+ import flash.display.Sprite;
+ import flash.display.StageAlign;
+ import flash.display.StageScaleMode;
+ import flash.events.Event;
+ import flash.events.StatusEvent;
+ import flash.media.Camera;
+ import flash.media.Video;
+ import flash.utils.setTimeout;
+
+ public class FileAPI_flash_camera extends Sprite
+ {
+ private var video:Video;
+ private var camera:Camera;
+
+ public function FileAPI_flash_camera()
+ {
+ if (stage)
+ init();
+ else
+ addEventListener(Event.ADDED_TO_STAGE, init);
+
+ }
+
+ public function init(e:Event = null):void
+ {
+ if(e)
+ removeEventListener(Event.ADDED_TO_STAGE, init);
+
+ stage.scaleMode = StageScaleMode.SHOW_ALL;
+ stage.align = StageAlign.TOP_LEFT;
+
+ // init camera
+ camera = Camera.getCamera();
+ if (camera != null) {
+ camera.addEventListener(StatusEvent.STATUS, onCameraStatus);
+ // we need to show settings dialog, so we attach camera to a video
+ video = new Video();
+ video.attachCamera(camera);
+ if (!camera.muted) {
+ onCameraStatus(new StatusEvent(StatusEvent.STATUS, false, false, 'Camera.Unmuted'));
+ }
+ else {
+ setTimeout(function ():void {
+ if (securityPanelIsClosed()) {
+ onCameraStatus(new StatusEvent(StatusEvent.STATUS, false, false, 'Camera.Muted'));
+ }
+ }, 1000);
+ }
+
+ } else {
+ // callback with error
+ }
+ }
+
+ public function toggleCamera(on:Boolean):void {
+ trace('toggleCamera',on);
+ if (on) {
+ if (video != null) {
+ // turn current video off
+ toggleCamera(false);
+ }
+ trace('stage width',stage.stageWidth,'stage w',stage.width,'camera width',camera.width);
+ var w:Number = stage.stageWidth;
+ var h:Number = stage.stageHeight; //stage.stageWidth * camera.height / camera.width;
+ camera.setMode(w, h, camera.fps);
+ video = new Video(w, h);
+ video.attachCamera(camera);
+
+ video.addEventListener(Event.ADDED_TO_STAGE, function(event:Event):void {
+ trace('video addedToStage');
+ // now the camera is visible
+ dispatchEvent(new Event('Camera.On'));
+ });
+ addChildAt(video,0);
+ } else {
+ if(video)
+ removeChild(video);
+ video = null;
+ }
+ }
+
+ public function shot():BitmapData {
+ if (video) {
+ trace('click!', video.width, stage.stageWidth);
+ try{
+ var bm:BitmapData = new BitmapData(video.width,video.height);
+ bm.draw(video);
+ return bm;
+ } catch(error:Error) {
+ trace('error',error.toString() );
+ return null;
+ }
+ }
+ return null;
+ }
+
+ private function onCameraStatus(event:StatusEvent):void {
+ trace('status event:',event.toString() );
+ video = null; // turn off video
+ // redispatch
+ dispatchEvent(event.clone());
+ }
+
+ /**
+ * This code checks one time if the security panel is closed.
+ * When you open the security panel, you should run this test
+ * repeatedly with a timer (every 500ms seems to work well).
+ * If the security panel is closed, you can then clean up your timers
+ */
+ private function securityPanelIsClosed():Boolean
+ {
+ // Why not just wait for an event from the SettingsPanel to know that it's closed? Because there isn't one.
+ // See http://bugs.adobe.com/jira/browse/FP-41
+ var closed:Boolean = true;
+ var hack:BitmapData = new BitmapData(1,1);
+ try
+ {
+ // Trying to capture the stage triggers a Security error when the settings dialog box is open.
+ hack.draw(stage);
+ }
+ catch (error:Error)
+ {
+ closed = false;
+ }
+ hack.dispose();
+ hack = null;
+ return (closed);
+ }
+ }
+}
diff --git a/flash/.settings/org.eclipse.core.resources.prefs b/flash/core/.settings/org.eclipse.core.resources.prefs
similarity index 100%
rename from flash/.settings/org.eclipse.core.resources.prefs
rename to flash/core/.settings/org.eclipse.core.resources.prefs
diff --git a/flash/core/html-template/history/history.css b/flash/core/html-template/history/history.css
new file mode 100644
index 00000000..dbc47c61
--- /dev/null
+++ b/flash/core/html-template/history/history.css
@@ -0,0 +1,6 @@
+/* This CSS stylesheet defines styles used by required elements in a flex application page that supports browser history */
+
+#ie_historyFrame { width: 0px; height: 0px; display:none }
+#firefox_anchorDiv { width: 0px; height: 0px; display:none }
+#safari_formDiv { width: 0px; height: 0px; display:none }
+#safari_rememberDiv { width: 0px; height: 0px; display:none }
diff --git a/flash/core/html-template/history/history.js b/flash/core/html-template/history/history.js
new file mode 100644
index 00000000..00a8bfee
--- /dev/null
+++ b/flash/core/html-template/history/history.js
@@ -0,0 +1,678 @@
+BrowserHistoryUtils = {
+ addEvent: function(elm, evType, fn, useCapture) {
+ useCapture = useCapture || false;
+ if (elm.addEventListener) {
+ elm.addEventListener(evType, fn, useCapture);
+ return true;
+ }
+ else if (elm.attachEvent) {
+ var r = elm.attachEvent('on' + evType, fn);
+ return r;
+ }
+ else {
+ elm['on' + evType] = fn;
+ }
+ }
+}
+
+BrowserHistory = (function() {
+ // type of browser
+ var browser = {
+ ie: false,
+ ie8: false,
+ firefox: false,
+ safari: false,
+ opera: false,
+ version: -1
+ };
+
+ // Default app state URL to use when no fragment ID present
+ var defaultHash = '';
+
+ // Last-known app state URL
+ var currentHref = document.location.href;
+
+ // Initial URL (used only by IE)
+ var initialHref = document.location.href;
+
+ // Initial URL (used only by IE)
+ var initialHash = document.location.hash;
+
+ // History frame source URL prefix (used only by IE)
+ var historyFrameSourcePrefix = 'history/historyFrame.html?';
+
+ // History maintenance (used only by Safari)
+ var currentHistoryLength = -1;
+
+ // Flag to denote the existence of onhashchange
+ var browserHasHashChange = false;
+
+ var historyHash = [];
+
+ var initialState = createState(initialHref, initialHref + '#' + initialHash, initialHash);
+
+ var backStack = [];
+ var forwardStack = [];
+
+ var currentObjectId = null;
+
+ //UserAgent detection
+ var useragent = navigator.userAgent.toLowerCase();
+
+ if (useragent.indexOf("opera") != -1) {
+ browser.opera = true;
+ } else if (useragent.indexOf("msie") != -1) {
+ browser.ie = true;
+ browser.version = parseFloat(useragent.substring(useragent.indexOf('msie') + 4));
+ if (browser.version == 8)
+ {
+ browser.ie = false;
+ browser.ie8 = true;
+ }
+ } else if (useragent.indexOf("safari") != -1) {
+ browser.safari = true;
+ browser.version = parseFloat(useragent.substring(useragent.indexOf('safari') + 7));
+ } else if (useragent.indexOf("gecko") != -1) {
+ browser.firefox = true;
+ }
+
+ if (browser.ie == true && browser.version == 7) {
+ window["_ie_firstload"] = false;
+ }
+
+ function hashChangeHandler()
+ {
+ currentHref = document.location.href;
+ var flexAppUrl = getHash();
+ //ADR: to fix multiple
+ if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) {
+ var pl = getPlayers();
+ for (var i = 0; i < pl.length; i++) {
+ pl[i].browserURLChange(flexAppUrl);
+ }
+ } else {
+ getPlayer().browserURLChange(flexAppUrl);
+ }
+ }
+
+ // Accessor functions for obtaining specific elements of the page.
+ function getHistoryFrame()
+ {
+ return document.getElementById('ie_historyFrame');
+ }
+
+ function getFormElement()
+ {
+ return document.getElementById('safari_formDiv');
+ }
+
+ function getRememberElement()
+ {
+ return document.getElementById("safari_remember_field");
+ }
+
+ // Get the Flash player object for performing ExternalInterface callbacks.
+ // Updated for changes to SWFObject2.
+ function getPlayer(id) {
+ var i;
+
+ if (id && document.getElementById(id)) {
+ var r = document.getElementById(id);
+ if (typeof r.SetVariable != "undefined") {
+ return r;
+ }
+ else {
+ var o = r.getElementsByTagName("object");
+ var e = r.getElementsByTagName("embed");
+ for (i = 0; i < o.length; i++) {
+ if (typeof o[i].browserURLChange != "undefined")
+ return o[i];
+ }
+ for (i = 0; i < e.length; i++) {
+ if (typeof e[i].browserURLChange != "undefined")
+ return e[i];
+ }
+ }
+ }
+ else {
+ var o = document.getElementsByTagName("object");
+ var e = document.getElementsByTagName("embed");
+ for (i = 0; i < e.length; i++) {
+ if (typeof e[i].browserURLChange != "undefined")
+ {
+ return e[i];
+ }
+ }
+ for (i = 0; i < o.length; i++) {
+ if (typeof o[i].browserURLChange != "undefined")
+ {
+ return o[i];
+ }
+ }
+ }
+ return undefined;
+ }
+
+ function getPlayers() {
+ var i;
+ var players = [];
+ if (players.length == 0) {
+ var tmp = document.getElementsByTagName('object');
+ for (i = 0; i < tmp.length; i++)
+ {
+ if (typeof tmp[i].browserURLChange != "undefined")
+ players.push(tmp[i]);
+ }
+ }
+ if (players.length == 0 || players[0].object == null) {
+ var tmp = document.getElementsByTagName('embed');
+ for (i = 0; i < tmp.length; i++)
+ {
+ if (typeof tmp[i].browserURLChange != "undefined")
+ players.push(tmp[i]);
+ }
+ }
+ return players;
+ }
+
+ function getIframeHash() {
+ var doc = getHistoryFrame().contentWindow.document;
+ var hash = String(doc.location.search);
+ if (hash.length == 1 && hash.charAt(0) == "?") {
+ hash = "";
+ }
+ else if (hash.length >= 2 && hash.charAt(0) == "?") {
+ hash = hash.substring(1);
+ }
+ return hash;
+ }
+
+ /* Get the current location hash excluding the '#' symbol. */
+ function getHash() {
+ // It would be nice if we could use document.location.hash here,
+ // but it's faulty sometimes.
+ var idx = document.location.href.indexOf('#');
+ return (idx >= 0) ? document.location.href.substr(idx+1) : '';
+ }
+
+ /* Get the current location hash excluding the '#' symbol. */
+ function setHash(hash) {
+ // It would be nice if we could use document.location.hash here,
+ // but it's faulty sometimes.
+ if (hash == '') hash = '#'
+ document.location.hash = hash;
+ }
+
+ function createState(baseUrl, newUrl, flexAppUrl) {
+ return { 'baseUrl': baseUrl, 'newUrl': newUrl, 'flexAppUrl': flexAppUrl, 'title': null };
+ }
+
+ /* Add a history entry to the browser.
+ * baseUrl: the portion of the location prior to the '#'
+ * newUrl: the entire new URL, including '#' and following fragment
+ * flexAppUrl: the portion of the location following the '#' only
+ */
+ function addHistoryEntry(baseUrl, newUrl, flexAppUrl) {
+
+ //delete all the history entries
+ forwardStack = [];
+
+ if (browser.ie) {
+ //Check to see if we are being asked to do a navigate for the first
+ //history entry, and if so ignore, because it's coming from the creation
+ //of the history iframe
+ if (flexAppUrl == defaultHash && document.location.href == initialHref && window['_ie_firstload']) {
+ currentHref = initialHref;
+ return;
+ }
+ if ((!flexAppUrl || flexAppUrl == defaultHash) && window['_ie_firstload']) {
+ newUrl = baseUrl + '#' + defaultHash;
+ flexAppUrl = defaultHash;
+ } else {
+ // for IE, tell the history frame to go somewhere without a '#'
+ // in order to get this entry into the browser history.
+ getHistoryFrame().src = historyFrameSourcePrefix + flexAppUrl;
+ }
+ setHash(flexAppUrl);
+ } else {
+
+ //ADR
+ if (backStack.length == 0 && initialState.flexAppUrl == flexAppUrl) {
+ initialState = createState(baseUrl, newUrl, flexAppUrl);
+ } else if(backStack.length > 0 && backStack[backStack.length - 1].flexAppUrl == flexAppUrl) {
+ backStack[backStack.length - 1] = createState(baseUrl, newUrl, flexAppUrl);
+ }
+
+ if (browser.safari && !browserHasHashChange) {
+ // for Safari, submit a form whose action points to the desired URL
+ if (browser.version <= 419.3) {
+ var file = window.location.pathname.toString();
+ file = file.substring(file.lastIndexOf("/")+1);
+ getFormElement().innerHTML = '';
+ //get the current elements and add them to the form
+ var qs = window.location.search.substring(1);
+ var qs_arr = qs.split("&");
+ for (var i = 0; i < qs_arr.length; i++) {
+ var tmp = qs_arr[i].split("=");
+ var elem = document.createElement("input");
+ elem.type = "hidden";
+ elem.name = tmp[0];
+ elem.value = tmp[1];
+ document.forms.historyForm.appendChild(elem);
+ }
+ document.forms.historyForm.submit();
+ } else {
+ top.location.hash = flexAppUrl;
+ }
+ // We also have to maintain the history by hand for Safari
+ historyHash[history.length] = flexAppUrl;
+ _storeStates();
+ } else {
+ // Otherwise, just tell the browser to go there
+ setHash(flexAppUrl);
+ }
+ }
+ backStack.push(createState(baseUrl, newUrl, flexAppUrl));
+ }
+
+ function _storeStates() {
+ if (browser.safari) {
+ getRememberElement().value = historyHash.join(",");
+ }
+ }
+
+ function handleBackButton() {
+ //The "current" page is always at the top of the history stack.
+ var current = backStack.pop();
+ if (!current) { return; }
+ var last = backStack[backStack.length - 1];
+ if (!last && backStack.length == 0){
+ last = initialState;
+ }
+ forwardStack.push(current);
+ }
+
+ function handleForwardButton() {
+ //summary: private method. Do not call this directly.
+
+ var last = forwardStack.pop();
+ if (!last) { return; }
+ backStack.push(last);
+ }
+
+ function handleArbitraryUrl() {
+ //delete all the history entries
+ forwardStack = [];
+ }
+
+ /* Called periodically to poll to see if we need to detect navigation that has occurred */
+ function checkForUrlChange() {
+
+ if (browser.ie) {
+ if (currentHref != document.location.href && currentHref + '#' != document.location.href) {
+ //This occurs when the user has navigated to a specific URL
+ //within the app, and didn't use browser back/forward
+ //IE seems to have a bug where it stops updating the URL it
+ //shows the end-user at this point, but programatically it
+ //appears to be correct. Do a full app reload to get around
+ //this issue.
+ if (browser.version < 7) {
+ currentHref = document.location.href;
+ document.location.reload();
+ } else {
+ if (getHash() != getIframeHash()) {
+ // this.iframe.src = this.blankURL + hash;
+ var sourceToSet = historyFrameSourcePrefix + getHash();
+ getHistoryFrame().src = sourceToSet;
+ currentHref = document.location.href;
+ }
+ }
+ }
+ }
+
+ if (browser.safari && !browserHasHashChange) {
+ // For Safari, we have to check to see if history.length changed.
+ if (currentHistoryLength >= 0 && history.length != currentHistoryLength) {
+ //alert("did change: " + history.length + ", " + historyHash.length + "|" + historyHash[history.length] + "|>" + historyHash.join("|"));
+ var flexAppUrl = getHash();
+ if (browser.version < 528.16 /* Anything earlier than Safari 4.0 */)
+ {
+ // If it did change and we're running Safari 3.x or earlier,
+ // then we have to look the old state up in our hand-maintained
+ // array since document.location.hash won't have changed,
+ // then call back into BrowserManager.
+ currentHistoryLength = history.length;
+ flexAppUrl = historyHash[currentHistoryLength];
+ }
+
+ //ADR: to fix multiple
+ if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) {
+ var pl = getPlayers();
+ for (var i = 0; i < pl.length; i++) {
+ pl[i].browserURLChange(flexAppUrl);
+ }
+ } else {
+ getPlayer().browserURLChange(flexAppUrl);
+ }
+ _storeStates();
+ }
+ }
+ if (browser.firefox && !browserHasHashChange) {
+ if (currentHref != document.location.href) {
+ var bsl = backStack.length;
+
+ var urlActions = {
+ back: false,
+ forward: false,
+ set: false
+ }
+
+ if ((window.location.hash == initialHash || window.location.href == initialHref) && (bsl == 1)) {
+ urlActions.back = true;
+ // FIXME: could this ever be a forward button?
+ // we can't clear it because we still need to check for forwards. Ugg.
+ // clearInterval(this.locationTimer);
+ handleBackButton();
+ }
+
+ // first check to see if we could have gone forward. We always halt on
+ // a no-hash item.
+ if (forwardStack.length > 0) {
+ if (forwardStack[forwardStack.length-1].flexAppUrl == getHash()) {
+ urlActions.forward = true;
+ handleForwardButton();
+ }
+ }
+
+ // ok, that didn't work, try someplace back in the history stack
+ if ((bsl >= 2) && (backStack[bsl - 2])) {
+ if (backStack[bsl - 2].flexAppUrl == getHash()) {
+ urlActions.back = true;
+ handleBackButton();
+ }
+ }
+
+ if (!urlActions.back && !urlActions.forward) {
+ var foundInStacks = {
+ back: -1,
+ forward: -1
+ }
+
+ for (var i = 0; i < backStack.length; i++) {
+ if (backStack[i].flexAppUrl == getHash() && i != (bsl - 2)) {
+ arbitraryUrl = true;
+ foundInStacks.back = i;
+ }
+ }
+ for (var i = 0; i < forwardStack.length; i++) {
+ if (forwardStack[i].flexAppUrl == getHash() && i != (bsl - 2)) {
+ arbitraryUrl = true;
+ foundInStacks.forward = i;
+ }
+ }
+ handleArbitraryUrl();
+ }
+
+ // Firefox changed; do a callback into BrowserManager to tell it.
+ currentHref = document.location.href;
+ var flexAppUrl = getHash();
+ //ADR: to fix multiple
+ if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) {
+ var pl = getPlayers();
+ for (var i = 0; i < pl.length; i++) {
+ pl[i].browserURLChange(flexAppUrl);
+ }
+ } else {
+ getPlayer().browserURLChange(flexAppUrl);
+ }
+ }
+ }
+ }
+
+ var _initialize = function () {
+
+ browserHasHashChange = ("onhashchange" in document.body);
+
+ if (browser.ie)
+ {
+ var scripts = document.getElementsByTagName('script');
+ for (var i = 0, s; s = scripts[i]; i++) {
+ if (s.src.indexOf("history.js") > -1) {
+ var iframe_location = (new String(s.src)).replace("history.js", "historyFrame.html");
+ }
+ }
+ historyFrameSourcePrefix = iframe_location + "?";
+ var src = historyFrameSourcePrefix;
+
+ var iframe = document.createElement("iframe");
+ iframe.id = 'ie_historyFrame';
+ iframe.name = 'ie_historyFrame';
+ iframe.src = 'javascript:false;';
+
+ try {
+ document.body.appendChild(iframe);
+ } catch(e) {
+ setTimeout(function() {
+ document.body.appendChild(iframe);
+ }, 0);
+ }
+ }
+
+ if (browser.safari && !browserHasHashChange)
+ {
+ var rememberDiv = document.createElement("div");
+ rememberDiv.id = 'safari_rememberDiv';
+ document.body.appendChild(rememberDiv);
+ rememberDiv.innerHTML = ' ';
+
+ var formDiv = document.createElement("div");
+ formDiv.id = 'safari_formDiv';
+ document.body.appendChild(formDiv);
+
+ var reloader_content = document.createElement('div');
+ reloader_content.id = 'safarireloader';
+ var scripts = document.getElementsByTagName('script');
+ for (var i = 0, s; s = scripts[i]; i++) {
+ if (s.src.indexOf("history.js") > -1) {
+ html = (new String(s.src)).replace(".js", ".html");
+ }
+ }
+ reloader_content.innerHTML = '';
+ document.body.appendChild(reloader_content);
+ reloader_content.style.position = 'absolute';
+ reloader_content.style.left = reloader_content.style.top = '-9999px';
+ iframe = reloader_content.getElementsByTagName('iframe')[0];
+
+ if (document.getElementById("safari_remember_field").value != "" ) {
+ historyHash = document.getElementById("safari_remember_field").value.split(",");
+ }
+ }
+
+ if (browserHasHashChange)
+ document.body.onhashchange = hashChangeHandler;
+ }
+
+ return {
+ historyHash: historyHash,
+ backStack: function() { return backStack; },
+ forwardStack: function() { return forwardStack },
+ getPlayer: getPlayer,
+ initialize: function(src) {
+ _initialize(src);
+ },
+ setURL: function(url) {
+ document.location.href = url;
+ },
+ getURL: function() {
+ return document.location.href;
+ },
+ getTitle: function() {
+ return document.title;
+ },
+ setTitle: function(title) {
+ try {
+ backStack[backStack.length - 1].title = title;
+ } catch(e) { }
+ //if on safari, set the title to be the empty string.
+ if (browser.safari) {
+ if (title == "") {
+ try {
+ var tmp = window.location.href.toString();
+ title = tmp.substring((tmp.lastIndexOf("/")+1), tmp.lastIndexOf("#"));
+ } catch(e) {
+ title = "";
+ }
+ }
+ }
+ document.title = title;
+ },
+ setDefaultURL: function(def)
+ {
+ defaultHash = def;
+ def = getHash();
+ //trailing ? is important else an extra frame gets added to the history
+ //when navigating back to the first page. Alternatively could check
+ //in history frame navigation to compare # and ?.
+ if (browser.ie)
+ {
+ window['_ie_firstload'] = true;
+ var sourceToSet = historyFrameSourcePrefix + def;
+ var func = function() {
+ getHistoryFrame().src = sourceToSet;
+ window.location.replace("#" + def);
+ setInterval(checkForUrlChange, 50);
+ }
+ try {
+ func();
+ } catch(e) {
+ window.setTimeout(function() { func(); }, 0);
+ }
+ }
+
+ if (browser.safari)
+ {
+ currentHistoryLength = history.length;
+ if (historyHash.length == 0) {
+ historyHash[currentHistoryLength] = def;
+ var newloc = "#" + def;
+ window.location.replace(newloc);
+ } else {
+ //alert(historyHash[historyHash.length-1]);
+ }
+ setInterval(checkForUrlChange, 50);
+ }
+
+
+ if (browser.firefox || browser.opera)
+ {
+ var reg = new RegExp("#" + def + "$");
+ if (window.location.toString().match(reg)) {
+ } else {
+ var newloc ="#" + def;
+ window.location.replace(newloc);
+ }
+ setInterval(checkForUrlChange, 50);
+ }
+
+ },
+
+ /* Set the current browser URL; called from inside BrowserManager to propagate
+ * the application state out to the container.
+ */
+ setBrowserURL: function(flexAppUrl, objectId) {
+ if (browser.ie && typeof objectId != "undefined") {
+ currentObjectId = objectId;
+ }
+ //fromIframe = fromIframe || false;
+ //fromFlex = fromFlex || false;
+ //alert("setBrowserURL: " + flexAppUrl);
+ //flexAppUrl = (flexAppUrl == "") ? defaultHash : flexAppUrl ;
+
+ var pos = document.location.href.indexOf('#');
+ var baseUrl = pos != -1 ? document.location.href.substr(0, pos) : document.location.href;
+ var newUrl = baseUrl + '#' + flexAppUrl;
+
+ if (document.location.href != newUrl && document.location.href + '#' != newUrl) {
+ currentHref = newUrl;
+ addHistoryEntry(baseUrl, newUrl, flexAppUrl);
+ currentHistoryLength = history.length;
+ }
+ },
+
+ browserURLChange: function(flexAppUrl) {
+ var objectId = null;
+ if (browser.ie && currentObjectId != null) {
+ objectId = currentObjectId;
+ }
+
+ if (typeof BrowserHistory_multiple != "undefined" && BrowserHistory_multiple == true) {
+ var pl = getPlayers();
+ for (var i = 0; i < pl.length; i++) {
+ try {
+ pl[i].browserURLChange(flexAppUrl);
+ } catch(e) { }
+ }
+ } else {
+ try {
+ getPlayer(objectId).browserURLChange(flexAppUrl);
+ } catch(e) { }
+ }
+
+ currentObjectId = null;
+ },
+ getUserAgent: function() {
+ return navigator.userAgent;
+ },
+ getPlatform: function() {
+ return navigator.platform;
+ }
+
+ }
+
+})();
+
+// Initialization
+
+// Automated unit testing and other diagnostics
+
+function setURL(url)
+{
+ document.location.href = url;
+}
+
+function backButton()
+{
+ history.back();
+}
+
+function forwardButton()
+{
+ history.forward();
+}
+
+function goForwardOrBackInHistory(step)
+{
+ history.go(step);
+}
+
+//BrowserHistoryUtils.addEvent(window, "load", function() { BrowserHistory.initialize(); });
+(function(i) {
+ var u =navigator.userAgent;var e=/*@cc_on!@*/false;
+ var st = setTimeout;
+ if(/webkit/i.test(u)){
+ st(function(){
+ var dr=document.readyState;
+ if(dr=="loaded"||dr=="complete"){i()}
+ else{st(arguments.callee,10);}},10);
+ } else if((/mozilla/i.test(u)&&!/(compati)/.test(u)) || (/opera/i.test(u))){
+ document.addEventListener("DOMContentLoaded",i,false);
+ } else if(e){
+ (function(){
+ var t=document.createElement('doc:rdy');
+ try{t.doScroll('left');
+ i();t=null;
+ }catch(e){st(arguments.callee,0);}})();
+ } else{
+ window.onload=i;
+ }
+})( function() {BrowserHistory.initialize();} );
diff --git a/flash/core/html-template/history/historyFrame.html b/flash/core/html-template/history/historyFrame.html
new file mode 100644
index 00000000..07e3806f
--- /dev/null
+++ b/flash/core/html-template/history/historyFrame.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ Hidden frame for Browser History support.
+
+
diff --git a/flash/core/html-template/index.template.html b/flash/core/html-template/index.template.html
new file mode 100644
index 00000000..8d458fd7
--- /dev/null
+++ b/flash/core/html-template/index.template.html
@@ -0,0 +1,108 @@
+
+
+
+
+
+ ${title}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ To view this page ensure that Adobe Flash Player version
+ ${version_major}.${version_minor}.${version_revision} or greater is installed.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Either scripts and active content are not permitted to run or Adobe Flash Player version
+ ${version_major}.${version_minor}.${version_revision} or greater is not installed.
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/flash/core/html-template/playerProductInstall.swf b/flash/core/html-template/playerProductInstall.swf
new file mode 100644
index 00000000..bdc34378
Binary files /dev/null and b/flash/core/html-template/playerProductInstall.swf differ
diff --git a/flash/core/html-template/swfobject.js b/flash/core/html-template/swfobject.js
new file mode 100644
index 00000000..bf35c07c
--- /dev/null
+++ b/flash/core/html-template/swfobject.js
@@ -0,0 +1,777 @@
+/*! SWFObject v2.2
+ is released under the MIT License
+*/
+
+var swfobject = function() {
+
+ var UNDEF = "undefined",
+ OBJECT = "object",
+ SHOCKWAVE_FLASH = "Shockwave Flash",
+ SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
+ FLASH_MIME_TYPE = "application/x-shockwave-flash",
+ EXPRESS_INSTALL_ID = "SWFObjectExprInst",
+ ON_READY_STATE_CHANGE = "onreadystatechange",
+
+ win = window,
+ doc = document,
+ nav = navigator,
+
+ plugin = false,
+ domLoadFnArr = [main],
+ regObjArr = [],
+ objIdArr = [],
+ listenersArr = [],
+ storedAltContent,
+ storedAltContentId,
+ storedCallbackFn,
+ storedCallbackObj,
+ isDomLoaded = false,
+ isExpressInstallActive = false,
+ dynamicStylesheet,
+ dynamicStylesheetMedia,
+ autoHideShow = true,
+
+ /* Centralized function for browser feature detection
+ - User agent string detection is only used when no good alternative is possible
+ - Is executed directly for optimal performance
+ */
+ ua = function() {
+ var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
+ u = nav.userAgent.toLowerCase(),
+ p = nav.platform.toLowerCase(),
+ windows = p ? /win/.test(p) : /win/.test(u),
+ mac = p ? /mac/.test(p) : /mac/.test(u),
+ webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
+ ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html
+ playerVersion = [0,0,0],
+ d = null;
+ if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
+ d = nav.plugins[SHOCKWAVE_FLASH].description;
+ if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
+ plugin = true;
+ ie = false; // cascaded feature detection for Internet Explorer
+ d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
+ playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
+ playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
+ playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0;
+ }
+ }
+ else if (typeof win.ActiveXObject != UNDEF) {
+ try {
+ var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
+ if (a) { // a will return null when ActiveX is disabled
+ d = a.GetVariable("$version");
+ if (d) {
+ ie = true; // cascaded feature detection for Internet Explorer
+ d = d.split(" ")[1].split(",");
+ playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
+ }
+ }
+ }
+ catch(e) {}
+ }
+ return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac };
+ }(),
+
+ /* Cross-browser onDomLoad
+ - Will fire an event as soon as the DOM of a web page is loaded
+ - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/
+ - Regular onload serves as fallback
+ */
+ onDomLoad = function() {
+ if (!ua.w3) { return; }
+ if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically
+ callDomLoadFunctions();
+ }
+ if (!isDomLoaded) {
+ if (typeof doc.addEventListener != UNDEF) {
+ doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false);
+ }
+ if (ua.ie && ua.win) {
+ doc.attachEvent(ON_READY_STATE_CHANGE, function() {
+ if (doc.readyState == "complete") {
+ doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee);
+ callDomLoadFunctions();
+ }
+ });
+ if (win == top) { // if not inside an iframe
+ (function(){
+ if (isDomLoaded) { return; }
+ try {
+ doc.documentElement.doScroll("left");
+ }
+ catch(e) {
+ setTimeout(arguments.callee, 0);
+ return;
+ }
+ callDomLoadFunctions();
+ })();
+ }
+ }
+ if (ua.wk) {
+ (function(){
+ if (isDomLoaded) { return; }
+ if (!/loaded|complete/.test(doc.readyState)) {
+ setTimeout(arguments.callee, 0);
+ return;
+ }
+ callDomLoadFunctions();
+ })();
+ }
+ addLoadEvent(callDomLoadFunctions);
+ }
+ }();
+
+ function callDomLoadFunctions() {
+ if (isDomLoaded) { return; }
+ try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early
+ var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span"));
+ t.parentNode.removeChild(t);
+ }
+ catch (e) { return; }
+ isDomLoaded = true;
+ var dl = domLoadFnArr.length;
+ for (var i = 0; i < dl; i++) {
+ domLoadFnArr[i]();
+ }
+ }
+
+ function addDomLoadEvent(fn) {
+ if (isDomLoaded) {
+ fn();
+ }
+ else {
+ domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
+ }
+ }
+
+ /* Cross-browser onload
+ - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
+ - Will fire an event as soon as a web page including all of its assets are loaded
+ */
+ function addLoadEvent(fn) {
+ if (typeof win.addEventListener != UNDEF) {
+ win.addEventListener("load", fn, false);
+ }
+ else if (typeof doc.addEventListener != UNDEF) {
+ doc.addEventListener("load", fn, false);
+ }
+ else if (typeof win.attachEvent != UNDEF) {
+ addListener(win, "onload", fn);
+ }
+ else if (typeof win.onload == "function") {
+ var fnOld = win.onload;
+ win.onload = function() {
+ fnOld();
+ fn();
+ };
+ }
+ else {
+ win.onload = fn;
+ }
+ }
+
+ /* Main function
+ - Will preferably execute onDomLoad, otherwise onload (as a fallback)
+ */
+ function main() {
+ if (plugin) {
+ testPlayerVersion();
+ }
+ else {
+ matchVersions();
+ }
+ }
+
+ /* Detect the Flash Player version for non-Internet Explorer browsers
+ - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description:
+ a. Both release and build numbers can be detected
+ b. Avoid wrong descriptions by corrupt installers provided by Adobe
+ c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports
+ - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available
+ */
+ function testPlayerVersion() {
+ var b = doc.getElementsByTagName("body")[0];
+ var o = createElement(OBJECT);
+ o.setAttribute("type", FLASH_MIME_TYPE);
+ var t = b.appendChild(o);
+ if (t) {
+ var counter = 0;
+ (function(){
+ if (typeof t.GetVariable != UNDEF) {
+ var d = t.GetVariable("$version");
+ if (d) {
+ d = d.split(" ")[1].split(",");
+ ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
+ }
+ }
+ else if (counter < 10) {
+ counter++;
+ setTimeout(arguments.callee, 10);
+ return;
+ }
+ b.removeChild(o);
+ t = null;
+ matchVersions();
+ })();
+ }
+ else {
+ matchVersions();
+ }
+ }
+
+ /* Perform Flash Player and SWF version matching; static publishing only
+ */
+ function matchVersions() {
+ var rl = regObjArr.length;
+ if (rl > 0) {
+ for (var i = 0; i < rl; i++) { // for each registered object element
+ var id = regObjArr[i].id;
+ var cb = regObjArr[i].callbackFn;
+ var cbObj = {success:false, id:id};
+ if (ua.pv[0] > 0) {
+ var obj = getElementById(id);
+ if (obj) {
+ if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match!
+ setVisibility(id, true);
+ if (cb) {
+ cbObj.success = true;
+ cbObj.ref = getObjectById(id);
+ cb(cbObj);
+ }
+ }
+ else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported
+ var att = {};
+ att.data = regObjArr[i].expressInstall;
+ att.width = obj.getAttribute("width") || "0";
+ att.height = obj.getAttribute("height") || "0";
+ if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); }
+ if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); }
+ // parse HTML object param element's name-value pairs
+ var par = {};
+ var p = obj.getElementsByTagName("param");
+ var pl = p.length;
+ for (var j = 0; j < pl; j++) {
+ if (p[j].getAttribute("name").toLowerCase() != "movie") {
+ par[p[j].getAttribute("name")] = p[j].getAttribute("value");
+ }
+ }
+ showExpressInstall(att, par, id, cb);
+ }
+ else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display alternative content instead of SWF
+ displayAltContent(obj);
+ if (cb) { cb(cbObj); }
+ }
+ }
+ }
+ else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or alternative content)
+ setVisibility(id, true);
+ if (cb) {
+ var o = getObjectById(id); // test whether there is an HTML object element or not
+ if (o && typeof o.SetVariable != UNDEF) {
+ cbObj.success = true;
+ cbObj.ref = o;
+ }
+ cb(cbObj);
+ }
+ }
+ }
+ }
+ }
+
+ function getObjectById(objectIdStr) {
+ var r = null;
+ var o = getElementById(objectIdStr);
+ if (o && o.nodeName == "OBJECT") {
+ if (typeof o.SetVariable != UNDEF) {
+ r = o;
+ }
+ else {
+ var n = o.getElementsByTagName(OBJECT)[0];
+ if (n) {
+ r = n;
+ }
+ }
+ }
+ return r;
+ }
+
+ /* Requirements for Adobe Express Install
+ - only one instance can be active at a time
+ - fp 6.0.65 or higher
+ - Win/Mac OS only
+ - no Webkit engines older than version 312
+ */
+ function canExpressInstall() {
+ return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312);
+ }
+
+ /* Show the Adobe Express Install dialog
+ - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
+ */
+ function showExpressInstall(att, par, replaceElemIdStr, callbackFn) {
+ isExpressInstallActive = true;
+ storedCallbackFn = callbackFn || null;
+ storedCallbackObj = {success:false, id:replaceElemIdStr};
+ var obj = getElementById(replaceElemIdStr);
+ if (obj) {
+ if (obj.nodeName == "OBJECT") { // static publishing
+ storedAltContent = abstractAltContent(obj);
+ storedAltContentId = null;
+ }
+ else { // dynamic publishing
+ storedAltContent = obj;
+ storedAltContentId = replaceElemIdStr;
+ }
+ att.id = EXPRESS_INSTALL_ID;
+ if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; }
+ if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; }
+ doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
+ var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
+ fv = "MMredirectURL=" + encodeURI(window.location).toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title;
+ if (typeof par.flashvars != UNDEF) {
+ par.flashvars += "&" + fv;
+ }
+ else {
+ par.flashvars = fv;
+ }
+ // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it,
+ // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
+ if (ua.ie && ua.win && obj.readyState != 4) {
+ var newObj = createElement("div");
+ replaceElemIdStr += "SWFObjectNew";
+ newObj.setAttribute("id", replaceElemIdStr);
+ obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ obj.parentNode.removeChild(obj);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ createSWF(att, par, replaceElemIdStr);
+ }
+ }
+
+ /* Functions to abstract and display alternative content
+ */
+ function displayAltContent(obj) {
+ if (ua.ie && ua.win && obj.readyState != 4) {
+ // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it,
+ // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
+ var el = createElement("div");
+ obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content
+ el.parentNode.replaceChild(abstractAltContent(obj), el);
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ obj.parentNode.removeChild(obj);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ else {
+ obj.parentNode.replaceChild(abstractAltContent(obj), obj);
+ }
+ }
+
+ function abstractAltContent(obj) {
+ var ac = createElement("div");
+ if (ua.win && ua.ie) {
+ ac.innerHTML = obj.innerHTML;
+ }
+ else {
+ var nestedObj = obj.getElementsByTagName(OBJECT)[0];
+ if (nestedObj) {
+ var c = nestedObj.childNodes;
+ if (c) {
+ var cl = c.length;
+ for (var i = 0; i < cl; i++) {
+ if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) {
+ ac.appendChild(c[i].cloneNode(true));
+ }
+ }
+ }
+ }
+ }
+ return ac;
+ }
+
+ /* Cross-browser dynamic SWF creation
+ */
+ function createSWF(attObj, parObj, id) {
+ var r, el = getElementById(id);
+ if (ua.wk && ua.wk < 312) { return r; }
+ if (el) {
+ if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
+ attObj.id = id;
+ }
+ if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML
+ var att = "";
+ for (var i in attObj) {
+ if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries
+ if (i.toLowerCase() == "data") {
+ parObj.movie = attObj[i];
+ }
+ else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
+ att += ' class="' + attObj[i] + '"';
+ }
+ else if (i.toLowerCase() != "classid") {
+ att += ' ' + i + '="' + attObj[i] + '"';
+ }
+ }
+ }
+ var par = "";
+ for (var j in parObj) {
+ if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries
+ par += ' ';
+ }
+ }
+ el.outerHTML = '' + par + ' ';
+ objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only)
+ r = getElementById(attObj.id);
+ }
+ else { // well-behaving browsers
+ var o = createElement(OBJECT);
+ o.setAttribute("type", FLASH_MIME_TYPE);
+ for (var m in attObj) {
+ if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries
+ if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
+ o.setAttribute("class", attObj[m]);
+ }
+ else if (m.toLowerCase() != "classid") { // filter out IE specific attribute
+ o.setAttribute(m, attObj[m]);
+ }
+ }
+ }
+ for (var n in parObj) {
+ if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element
+ createObjParam(o, n, parObj[n]);
+ }
+ }
+ el.parentNode.replaceChild(o, el);
+ r = o;
+ }
+ }
+ return r;
+ }
+
+ function createObjParam(el, pName, pValue) {
+ var p = createElement("param");
+ p.setAttribute("name", pName);
+ p.setAttribute("value", pValue);
+ el.appendChild(p);
+ }
+
+ /* Cross-browser SWF removal
+ - Especially needed to safely and completely remove a SWF in Internet Explorer
+ */
+ function removeSWF(id) {
+ var obj = getElementById(id);
+ if (obj && obj.nodeName == "OBJECT") {
+ if (ua.ie && ua.win) {
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ removeObjectInIE(id);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ else {
+ obj.parentNode.removeChild(obj);
+ }
+ }
+ }
+
+ function removeObjectInIE(id) {
+ var obj = getElementById(id);
+ if (obj) {
+ for (var i in obj) {
+ if (typeof obj[i] == "function") {
+ obj[i] = null;
+ }
+ }
+ obj.parentNode.removeChild(obj);
+ }
+ }
+
+ /* Functions to optimize JavaScript compression
+ */
+ function getElementById(id) {
+ var el = null;
+ try {
+ el = doc.getElementById(id);
+ }
+ catch (e) {}
+ return el;
+ }
+
+ function createElement(el) {
+ return doc.createElement(el);
+ }
+
+ /* Updated attachEvent function for Internet Explorer
+ - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks
+ */
+ function addListener(target, eventType, fn) {
+ target.attachEvent(eventType, fn);
+ listenersArr[listenersArr.length] = [target, eventType, fn];
+ }
+
+ /* Flash Player and SWF content version matching
+ */
+ function hasPlayerVersion(rv) {
+ var pv = ua.pv, v = rv.split(".");
+ v[0] = parseInt(v[0], 10);
+ v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0"
+ v[2] = parseInt(v[2], 10) || 0;
+ return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
+ }
+
+ /* Cross-browser dynamic CSS creation
+ - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
+ */
+ function createCSS(sel, decl, media, newStyle) {
+ if (ua.ie && ua.mac) { return; }
+ var h = doc.getElementsByTagName("head")[0];
+ if (!h) { return; } // to also support badly authored HTML pages that lack a head element
+ var m = (media && typeof media == "string") ? media : "screen";
+ if (newStyle) {
+ dynamicStylesheet = null;
+ dynamicStylesheetMedia = null;
+ }
+ if (!dynamicStylesheet || dynamicStylesheetMedia != m) {
+ // create dynamic stylesheet + get a global reference to it
+ var s = createElement("style");
+ s.setAttribute("type", "text/css");
+ s.setAttribute("media", m);
+ dynamicStylesheet = h.appendChild(s);
+ if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
+ dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1];
+ }
+ dynamicStylesheetMedia = m;
+ }
+ // add style rule
+ if (ua.ie && ua.win) {
+ if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) {
+ dynamicStylesheet.addRule(sel, decl);
+ }
+ }
+ else {
+ if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) {
+ dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
+ }
+ }
+ }
+
+ function setVisibility(id, isVisible) {
+ if (!autoHideShow) { return; }
+ var v = isVisible ? "visible" : "hidden";
+ if (isDomLoaded && getElementById(id)) {
+ getElementById(id).style.visibility = v;
+ }
+ else {
+ createCSS("#" + id, "visibility:" + v);
+ }
+ }
+
+ /* Filter to avoid XSS attacks
+ */
+ function urlEncodeIfNecessary(s) {
+ var regex = /[\\\"<>\.;]/;
+ var hasBadChars = regex.exec(s) != null;
+ return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s;
+ }
+
+ /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only)
+ */
+ var cleanup = function() {
+ if (ua.ie && ua.win) {
+ window.attachEvent("onunload", function() {
+ // remove listeners to avoid memory leaks
+ var ll = listenersArr.length;
+ for (var i = 0; i < ll; i++) {
+ listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]);
+ }
+ // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect
+ var il = objIdArr.length;
+ for (var j = 0; j < il; j++) {
+ removeSWF(objIdArr[j]);
+ }
+ // cleanup library's main closures to avoid memory leaks
+ for (var k in ua) {
+ ua[k] = null;
+ }
+ ua = null;
+ for (var l in swfobject) {
+ swfobject[l] = null;
+ }
+ swfobject = null;
+ });
+ }
+ }();
+
+ return {
+ /* Public API
+ - Reference: http://code.google.com/p/swfobject/wiki/documentation
+ */
+ registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) {
+ if (ua.w3 && objectIdStr && swfVersionStr) {
+ var regObj = {};
+ regObj.id = objectIdStr;
+ regObj.swfVersion = swfVersionStr;
+ regObj.expressInstall = xiSwfUrlStr;
+ regObj.callbackFn = callbackFn;
+ regObjArr[regObjArr.length] = regObj;
+ setVisibility(objectIdStr, false);
+ }
+ else if (callbackFn) {
+ callbackFn({success:false, id:objectIdStr});
+ }
+ },
+
+ getObjectById: function(objectIdStr) {
+ if (ua.w3) {
+ return getObjectById(objectIdStr);
+ }
+ },
+
+ embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) {
+ var callbackObj = {success:false, id:replaceElemIdStr};
+ if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) {
+ setVisibility(replaceElemIdStr, false);
+ addDomLoadEvent(function() {
+ widthStr += ""; // auto-convert to string
+ heightStr += "";
+ var att = {};
+ if (attObj && typeof attObj === OBJECT) {
+ for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs
+ att[i] = attObj[i];
+ }
+ }
+ att.data = swfUrlStr;
+ att.width = widthStr;
+ att.height = heightStr;
+ var par = {};
+ if (parObj && typeof parObj === OBJECT) {
+ for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs
+ par[j] = parObj[j];
+ }
+ }
+ if (flashvarsObj && typeof flashvarsObj === OBJECT) {
+ for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs
+ if (typeof par.flashvars != UNDEF) {
+ par.flashvars += "&" + k + "=" + flashvarsObj[k];
+ }
+ else {
+ par.flashvars = k + "=" + flashvarsObj[k];
+ }
+ }
+ }
+ if (hasPlayerVersion(swfVersionStr)) { // create SWF
+ var obj = createSWF(att, par, replaceElemIdStr);
+ if (att.id == replaceElemIdStr) {
+ setVisibility(replaceElemIdStr, true);
+ }
+ callbackObj.success = true;
+ callbackObj.ref = obj;
+ }
+ else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install
+ att.data = xiSwfUrlStr;
+ showExpressInstall(att, par, replaceElemIdStr, callbackFn);
+ return;
+ }
+ else { // show alternative content
+ setVisibility(replaceElemIdStr, true);
+ }
+ if (callbackFn) { callbackFn(callbackObj); }
+ });
+ }
+ else if (callbackFn) { callbackFn(callbackObj); }
+ },
+
+ switchOffAutoHideShow: function() {
+ autoHideShow = false;
+ },
+
+ ua: ua,
+
+ getFlashPlayerVersion: function() {
+ return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
+ },
+
+ hasFlashPlayerVersion: hasPlayerVersion,
+
+ createSWF: function(attObj, parObj, replaceElemIdStr) {
+ if (ua.w3) {
+ return createSWF(attObj, parObj, replaceElemIdStr);
+ }
+ else {
+ return undefined;
+ }
+ },
+
+ showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) {
+ if (ua.w3 && canExpressInstall()) {
+ showExpressInstall(att, par, replaceElemIdStr, callbackFn);
+ }
+ },
+
+ removeSWF: function(objElemIdStr) {
+ if (ua.w3) {
+ removeSWF(objElemIdStr);
+ }
+ },
+
+ createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) {
+ if (ua.w3) {
+ createCSS(selStr, declStr, mediaStr, newStyleBoolean);
+ }
+ },
+
+ addDomLoadEvent: addDomLoadEvent,
+
+ addLoadEvent: addLoadEvent,
+
+ getQueryParamValue: function(param) {
+ var q = doc.location.search || doc.location.hash;
+ if (q) {
+ if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark
+ if (param == null) {
+ return urlEncodeIfNecessary(q);
+ }
+ var pairs = q.split("&");
+ for (var i = 0; i < pairs.length; i++) {
+ if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
+ return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1)));
+ }
+ }
+ }
+ return "";
+ },
+
+ // For internal usage only
+ expressInstallCallback: function() {
+ if (isExpressInstallActive) {
+ var obj = getElementById(EXPRESS_INSTALL_ID);
+ if (obj && storedAltContent) {
+ obj.parentNode.replaceChild(storedAltContent, obj);
+ if (storedAltContentId) {
+ setVisibility(storedAltContentId, true);
+ if (ua.ie && ua.win) { storedAltContent.style.display = "block"; }
+ }
+ if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); }
+ }
+ isExpressInstallActive = false;
+ }
+ }
+ };
+}();
diff --git a/flash/lib/EnginesLibrary.swc b/flash/core/lib/EnginesLibrary.swc
similarity index 100%
rename from flash/lib/EnginesLibrary.swc
rename to flash/core/lib/EnginesLibrary.swc
diff --git a/flash/lib/blooddy_crypto.swc b/flash/core/lib/blooddy_crypto.swc
similarity index 100%
rename from flash/lib/blooddy_crypto.swc
rename to flash/core/lib/blooddy_crypto.swc
diff --git a/flash/src/FlashFileAPI.as b/flash/core/src/FileAPI_flash.as
similarity index 66%
rename from flash/src/FlashFileAPI.as
rename to flash/core/src/FileAPI_flash.as
index 0d3ccc67..c869a068 100644
--- a/flash/src/FlashFileAPI.as
+++ b/flash/core/src/FileAPI_flash.as
@@ -5,20 +5,21 @@ package
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.Event;
+ import flash.events.UncaughtErrorEvent;
- import ru.mail.controller.AppController;
+ import ru.mail.controller.AppController;
/**
*
- * @author v.demidov
+ * @author v.demidov https://github.com/im-saxo
*
*/
- public class FlashFileAPI extends Sprite
+ public class FileAPI_flash extends Sprite
{
private var _controller:AppController;
private var _graphicContext:Sprite = new Sprite();
- public function FlashFileAPI()
+ public function FileAPI_flash()
{
if (stage) {
init();
@@ -35,7 +36,6 @@ package
*/
protected function init(event:Event = null):void
{
- trace ("{FlashFileAPI} - init");
removeEventListener(Event.ADDED_TO_STAGE, init);
// config stage
@@ -47,20 +47,10 @@ package
addChild(_graphicContext);
// initiate controller
- _controller = new AppController(_graphicContext, parseFlashVars());
- stage.addEventListener(Event.RESIZE, _controller.onStageResize)
- }
-
- /**
- * parse all flashvars into object
- */
- private function parseFlashVars():Object
- {
- var options:Object = new Object();
- for (var s:String in loaderInfo.parameters) {
- options[s] = loaderInfo.parameters[s];
- }
- return options;
+ _controller = new AppController(_graphicContext, loaderInfo.parameters);
+ // add some global listeners
+ stage.addEventListener(Event.RESIZE, _controller.onStageResize);
+ loaderInfo.uncaughtErrorEvents.addEventListener(UncaughtErrorEvent.UNCAUGHT_ERROR, _controller.onUncaughtError);
}
}
-}
\ No newline at end of file
+}
diff --git a/flash/src/net/inspirit/MultipartURLLoader.as b/flash/core/src/net/inspirit/MultipartURLLoader.as
similarity index 98%
rename from flash/src/net/inspirit/MultipartURLLoader.as
rename to flash/core/src/net/inspirit/MultipartURLLoader.as
index 3ea716f4..05216616 100644
--- a/flash/src/net/inspirit/MultipartURLLoader.as
+++ b/flash/core/src/net/inspirit/MultipartURLLoader.as
@@ -481,6 +481,7 @@
private function removeListener(): void
{
+ if (!_loader) return;
_loader.removeEventListener( Event.COMPLETE, onComplete );
_loader.removeEventListener( ProgressEvent.PROGRESS, onProgress );
_loader.removeEventListener( IOErrorEvent.IO_ERROR, onIOError );
@@ -562,16 +563,17 @@
}
}
+import flash.utils.ByteArray;
internal class FilePart
{
- public var fileContent:flash.utils.ByteArray;
+ public var fileContent:ByteArray;
public var fileName:String;
public var dataField:String;
public var contentType:String;
- public function FilePart(fileContent:flash.utils.ByteArray, fileName:String, dataField:String = 'Filedata', contentType:String = 'application/octet-stream')
+ public function FilePart(fileContent:ByteArray, fileName:String, dataField:String = 'Filedata', contentType:String = 'application/octet-stream')
{
this.fileContent = fileContent;
this.fileName = fileName;
diff --git a/flash/src/net/inspirit/events/MultipartURLLoaderEvent.as b/flash/core/src/net/inspirit/events/MultipartURLLoaderEvent.as
similarity index 100%
rename from flash/src/net/inspirit/events/MultipartURLLoaderEvent.as
rename to flash/core/src/net/inspirit/events/MultipartURLLoaderEvent.as
diff --git a/flash/src/ru/mail/commands/AbstractUploadFileCommand.as b/flash/core/src/ru/mail/commands/AbstractUploadFileCommand.as
similarity index 100%
rename from flash/src/ru/mail/commands/AbstractUploadFileCommand.as
rename to flash/core/src/ru/mail/commands/AbstractUploadFileCommand.as
diff --git a/flash/src/ru/mail/commands/DecodeBytesToBitmapCommand.as b/flash/core/src/ru/mail/commands/DecodeBytesToBitmapCommand.as
similarity index 100%
rename from flash/src/ru/mail/commands/DecodeBytesToBitmapCommand.as
rename to flash/core/src/ru/mail/commands/DecodeBytesToBitmapCommand.as
diff --git a/flash/src/ru/mail/commands/LoadFileCommand.as b/flash/core/src/ru/mail/commands/LoadFileCommand.as
similarity index 100%
rename from flash/src/ru/mail/commands/LoadFileCommand.as
rename to flash/core/src/ru/mail/commands/LoadFileCommand.as
diff --git a/flash/src/ru/mail/commands/ResizeFileCommand.as b/flash/core/src/ru/mail/commands/ResizeFileCommand.as
similarity index 69%
rename from flash/src/ru/mail/commands/ResizeFileCommand.as
rename to flash/core/src/ru/mail/commands/ResizeFileCommand.as
index c7b4fce6..0a9d6136 100644
--- a/flash/src/ru/mail/commands/ResizeFileCommand.as
+++ b/flash/core/src/ru/mail/commands/ResizeFileCommand.as
@@ -5,21 +5,25 @@ package ru.mail.commands
import flash.display.BitmapData;
import flash.events.EventDispatcher;
+ import flash.geom.ColorTransform;
import flash.geom.Matrix;
+ import flash.geom.Rectangle;
import flash.utils.ByteArray;
import ru.mail.data.vo.ErrorVO;
import ru.mail.data.vo.IFileVO;
import ru.mail.data.vo.ImageTransformVO;
+ import ru.mail.data.vo.OverlayVO;
import ru.mail.events.ImageTransformCompleteEvent;
+ import ru.mail.utils.LoggerJS;
/**
* Resize and rotate image using imageTransform object
*
* file must be loaded before transforming.
*
- * Only JPG or PNG images.
- * Performing transform on gif or bmp will result in returning the original image data.
+ * JPG and PNG will keep their extensions after transform.
+ * GIF and BMP will be encoded as PNG, so uploaded filename will be .png
*
* The possible solution is to transform them but save as PNG, but we also have to change the uploaded file extension to png.
*
@@ -43,17 +47,16 @@ package ru.mail.commands
{
if( !file.imageData ) {
complete(false, null, new ErrorVO("ResizeImageCommand - cannot resize file because it has not been succesfully loaded") );
+ return;
}
if (!needResize()) {
+ LoggerJS.log('ResizeImageCommand no need to resize');
complete(true, file.fileData);
+ return;
}
var fileType:String = file.fileType;
- if (fileType == "gif" || fileType == "bmp") {
- // TODO: scale but save jpg
- complete(true, file.fileData)
- }
checkTransform();
@@ -76,6 +79,9 @@ package ru.mail.commands
&& imageTransform.dw == imageTransform.sw
&& imageTransform.dh == imageTransform.sh
&& imageTransform.deg == 0
+ && imageTransform.type == ImageTransformVO.TYPE_PNG
+ && imageTransform.quality == 1
+ && imageTransform.overlay.length == 0
)
{
// transformed image equals original
@@ -102,6 +108,7 @@ package ru.mail.commands
{
try
{
+ LoggerJS.log('ResizeFileCommand tranform image');
var fullImageMap:BitmapData = file.imageData; //shortcut
var matrix:Matrix;
var currentImageMap:BitmapData;
@@ -122,6 +129,7 @@ package ru.mail.commands
else
{
currentImageMap = fullImageMap.clone();
+// currentImageMap = fullImageMap; // #199 #265 вернул обратно clone(), т.к. ниже есть currentImageMap.dispose()
}
// ==============
@@ -131,9 +139,7 @@ package ru.mail.commands
var angle:Number = imageTransform.deg*Math.PI/180;
var maxScale:Number = Math.max(scaleX, scaleY);
- if (maxScale < 0.5) {
-
- trace ("multi-step ");
+ if (imageTransform.multiPassResize && maxScale < 0.5) {
var curWidth:Number = currentImageMap.width;
var curHeight:Number = currentImageMap.height;
@@ -141,7 +147,6 @@ package ru.mail.commands
// multi-step
while(maxScale < 0.5)
{
- trace ("step ", maxScale);
// series if x2 scalings
// temp bitmapdata
@@ -189,13 +194,52 @@ package ru.mail.commands
// resize with bilinear interpolation
resizedImageMap.draw( currentImageMap, matrix, null, null, null, true );
- encodeImage(resizedImageMap);
+ applyOverlay(resizedImageMap);
}
- catch( e:Error ){
+ catch( e:Error ) {
complete( false, null, new ErrorVO(e.toString()) );
}
}
+ private function applyOverlay(imageMap:BitmapData):void {
+ try {
+ LoggerJS.log('ResizeFileCommand applyOverlay, overlays count: '+imageTransform.overlay.length);
+ // TODO:
+ for (var i:uint = 0; i < imageTransform.overlay.length; i++) {
+ var overlay:OverlayVO = imageTransform.overlay[i];
+ if (!overlay || !overlay.imageData) {
+ LoggerJS.log('ResizeFileCommand applyOverlay: no overlay image!');
+ continue;
+ }
+ // move:
+ // center | right | left
+ var x:Number = (overlay.rel == 1 || overlay.rel == 4 || overlay.rel == 7) ? (imageMap.width - overlay.w + overlay.x)/2
+ : (overlay.rel == 2 || overlay.rel == 5 || overlay.rel == 8 ? imageMap.width - (overlay.w + overlay.x)
+ : overlay.x);
+ // center | bottom | top
+ var y:Number = (overlay.rel == 3 || overlay.rel == 4 || overlay.rel == 5) ? (imageMap.height - overlay.h + overlay.y)/2
+ : (overlay.rel >= 6 ? imageMap.height - (overlay.h + overlay.y)
+ : overlay.y);
+ var matrix:Matrix = new Matrix();
+ matrix.identity();
+ matrix.translate(x,y);
+ // alpha:
+ var colorTransform:ColorTransform = new ColorTransform(1, 1, 1, overlay.opacity);
+ // clip:
+ // note adding translation x y, because clipRect is in the image's coordinates, not the overlay's.
+ var clipRect:Rectangle = new Rectangle(x/*+overlay.x*/, y/*+overlay.y*/, overlay.w, overlay.h);
+ // draw:
+ imageMap.draw(overlay.imageData, matrix, colorTransform, null, clipRect);
+ }
+
+ // success
+ encodeImage(imageMap);
+ }
+ catch( e:Error ) {
+ complete( false, null, new ErrorVO(e.toString()) );
+ }
+ }
+
/**
* Encode image using file type (JPG or PNG)
* @param imageMap
@@ -204,11 +248,12 @@ package ru.mail.commands
private function encodeImage(imageMap:BitmapData):void
{
try {
+ LoggerJS.log('ResizeFileCommand encode image, type '+imageTransform.type);
var resizedImageData:ByteArray;
// encode image
- if (file.fileType.toLowerCase() == 'jpg' || file.fileType.toLowerCase() == 'jpeg') {
- resizedImageData = JPEGEncoder.encode(imageMap, 90);
+ if ( imageTransform.type == ImageTransformVO.TYPE_JPEG ) {
+ resizedImageData = JPEGEncoder.encode(imageMap, uint(imageTransform.quality*100) );
}
else {
// use png encoder by default
diff --git a/flash/src/ru/mail/commands/UploadCommand.as b/flash/core/src/ru/mail/commands/UploadCommand.as
similarity index 81%
rename from flash/src/ru/mail/commands/UploadCommand.as
rename to flash/core/src/ru/mail/commands/UploadCommand.as
index 87d2e7cb..db522f7b 100644
--- a/flash/src/ru/mail/commands/UploadCommand.as
+++ b/flash/core/src/ru/mail/commands/UploadCommand.as
@@ -6,6 +6,7 @@ package ru.mail.commands
import ru.mail.data.IImageFactory;
import ru.mail.data.vo.ErrorVO;
+ import ru.mail.data.vo.FakeFileVO;
import ru.mail.data.vo.FileVO;
import ru.mail.data.vo.IFileVO;
import ru.mail.data.vo.ImageTransformVO;
@@ -70,7 +71,7 @@ package ru.mail.commands
{
filesPool[s] = INIT;
count++;
- if (!useMultiple && (files[s].matrix != null || count > 1)) {
+ if (!useMultiple && (files[s].matrix != null || files[s].overlay != null || count > 1)) {
// if there are several files or at least one with matrix, we cannot use fileReference
useMultiple = true;
}
@@ -112,23 +113,34 @@ package ru.mail.commands
// add to queue
filesPool[s] = INIT;
var fileName:String = file.fileNameModified;
- // get transformed image data
- var imageFactory:IImageFactory = file.imageFactory;
- (imageFactory as EventDispatcher).addEventListener(ImageTransformCompleteEvent.TYPE,function (event:ImageTransformCompleteEvent):void {
- event.currentTarget.removeEventListener(event.type, arguments.callee);
- trace("createImage imageTransform complete", event.isSuccess);
- if (event.isSuccess) {
- // upload transformed image
- filesPool[s] = new Object()
- filesPool[s][fileName] = event.data;
- }
- else {
- complete(false, null, event.error);
- }
- checkFilesPool();
- });
- imageFactory.createImage( trans? new ImageTransformVO(trans.sx, trans.sy, trans.sw, trans.sh, trans.dw, trans.dh, trans.deg) : null );
+ if(file is FakeFileVO) {
+ // upload with no filedata
+ filesPool[s] = new Object()
+ filesPool[s][fileName] = null;
+ checkFilesPool();
+ }
+ else {
+ // get transformed image data
+ var imageFactory:IImageFactory = file.imageFactory;
+ (imageFactory as EventDispatcher).addEventListener(ImageTransformCompleteEvent.TYPE,function (event:ImageTransformCompleteEvent):void {
+ event.currentTarget.removeEventListener(event.type, arguments.callee);
+ trace("createImage imageTransform complete", event.isSuccess);
+ if (event.isSuccess) {
+ // upload transformed image
+ filesPool[s] = new Object()
+ filesPool[s][fileName] = event.data;
+ }
+ else {
+ complete(false, null, event.error);
+ }
+ checkFilesPool();
+ });
+
+ imageFactory.createImage( trans
+ ? new ImageTransformVO(trans.sx, trans.sy, trans.sw, trans.sh, trans.dw, trans.dh, trans.deg, trans.type, trans.quality, (trans.overlay is Array)? trans.overlay : [trans.overlay], trans.multipass)
+ : null );
+ }
}
diff --git a/flash/src/ru/mail/commands/UploadFileCommand.as b/flash/core/src/ru/mail/commands/UploadFileCommand.as
similarity index 100%
rename from flash/src/ru/mail/commands/UploadFileCommand.as
rename to flash/core/src/ru/mail/commands/UploadFileCommand.as
diff --git a/flash/src/ru/mail/commands/UploadImageCommand.as b/flash/core/src/ru/mail/commands/UploadImageCommand.as
similarity index 96%
rename from flash/src/ru/mail/commands/UploadImageCommand.as
rename to flash/core/src/ru/mail/commands/UploadImageCommand.as
index 618eec35..b2851c60 100644
--- a/flash/src/ru/mail/commands/UploadImageCommand.as
+++ b/flash/core/src/ru/mail/commands/UploadImageCommand.as
@@ -98,8 +98,10 @@ package ru.mail.commands
{
for (var filename:String in _files[s]) {
trace ("s="+s+ ", filename = " + filename);
- _totalSize += _files[s][filename].length;
- _loader.addFile(_files[s][filename], filename, s);
+ if ( _files[s][filename] ) {
+ _totalSize += _files[s][filename].length;
+ _loader.addFile(_files[s][filename], filename, s);
+ }
}
}
}
diff --git a/flash/core/src/ru/mail/commands/graphicloader/IGraphicLoader.as b/flash/core/src/ru/mail/commands/graphicloader/IGraphicLoader.as
new file mode 100644
index 00000000..85573980
--- /dev/null
+++ b/flash/core/src/ru/mail/commands/graphicloader/IGraphicLoader.as
@@ -0,0 +1,14 @@
+package ru.mail.commands.graphicloader
+{
+ import flash.net.URLRequest;
+
+ /**
+ */
+ public interface IGraphicLoader
+ {
+ function loadGraphic( request:URLRequest ):void;
+
+ function cancel():void;
+ }
+
+}
\ No newline at end of file
diff --git a/flash/core/src/ru/mail/commands/graphicloader/SimpleGraphicLoader.as b/flash/core/src/ru/mail/commands/graphicloader/SimpleGraphicLoader.as
new file mode 100644
index 00000000..c5862a00
--- /dev/null
+++ b/flash/core/src/ru/mail/commands/graphicloader/SimpleGraphicLoader.as
@@ -0,0 +1,100 @@
+package ru.mail.commands.graphicloader
+{
+ import flash.display.Loader;
+ import flash.events.Event;
+ import flash.events.EventDispatcher;
+ import flash.events.IEventDispatcher;
+ import flash.events.IOErrorEvent;
+ import flash.events.ProgressEvent;
+ import flash.events.SecurityErrorEvent;
+ import flash.events.TimerEvent;
+ import flash.net.URLRequest;
+ import flash.utils.Timer;
+
+ import ru.mail.commands.graphicloader.events.GraphicLoaderCompleteEvent;
+ import ru.mail.commands.textloader.events.LoaderProgressEvent;
+ import ru.mail.data.vo.ErrorVO;
+ import ru.mail.utils.LoggerJS;
+
+
+ public class SimpleGraphicLoader extends EventDispatcher implements IGraphicLoader
+ {
+ private var _timeoutTimer:Timer = new Timer( _TIMEOUT, 1 ); // to keep off endless waiting
+ private var _loader:Loader = new Loader();
+
+ private var _TIMEOUT:uint = 60 * 1000; // timeout in millisecond: minutes * secs per min * millisecs per sec
+ private const _SUCCESS:Boolean = true;
+
+ public function SimpleGraphicLoader( timeOutInSeconds:uint = 60 )
+ {
+ _addURLLoaderListeners( _loader.contentLoaderInfo ) ;
+
+ _TIMEOUT = timeOutInSeconds * 1000;
+ _timeoutTimer = new Timer( _TIMEOUT, 1 ) ;
+ _timeoutTimer.addEventListener( TimerEvent.TIMER
+ , function( e:TimerEvent ):void{ _complete( _getContent() != null, new ErrorVO('SimpleGraphicLoader loadGraphic timeout') ); } ) ;
+ }
+
+ public function cancel():void
+ {
+ try
+ {
+ _loader.close();
+ }catch ( e:Error ) { }
+ }
+
+ /**
+ * method load data by request
+ */
+ public function loadGraphic( request:URLRequest ):void
+ {
+ //trace( "SimpleGraphicLoader.loadGraphic() ", request.url ) ;
+ LoggerJS.log('SimpleGraphicLoader loadGraphic '+request.url);
+ _timeoutTimer.start() ;
+ try
+ {
+ import flash.system.LoaderContext ;
+ _loader.load( request, new LoaderContext( true ) ) ;
+ }
+ catch( e:Error )
+ {
+ LoggerJS.log('SimpleGraphicLoader Error '+e.toString());
+ _complete( !_SUCCESS, new ErrorVO( e.toString() ) );
+ }
+ }
+
+ private function _complete( isSuccess:Boolean, error:ErrorVO = null ):void
+ { //trace( "SimpleGraphicLoader._complete() ", isSuccess ) ;
+ LoggerJS.log('SimpleGraphicLoader _complete, isSuccess = '+isSuccess+', error = '+(error?error.error:""));
+ _timeoutTimer.stop() ;
+
+ dispatchEvent( new GraphicLoaderCompleteEvent( isSuccess, _getContent(), error ) ) ;
+ }
+
+ private function _getContent():*
+ {
+ var content:* = null
+ try
+ {
+ content = _loader.content;
+ }
+ catch ( e:Error ) {
+ LoggerJS.log("SimpleGraphicLoader._getContent() Error "+ e.message);
+ }
+
+ return content;
+ }
+
+
+ private function _addURLLoaderListeners( dispatcher:IEventDispatcher ):void
+ {
+ dispatcher.addEventListener( Event.INIT, function( e:Event ):void{ _complete( _SUCCESS ); } );
+ dispatcher.addEventListener( SecurityErrorEvent.SECURITY_ERROR, function( e:Event ):void{ _complete( !_SUCCESS, new ErrorVO(e.toString()) ); } );
+ dispatcher.addEventListener( IOErrorEvent.IO_ERROR, function( e:Event ):void{ _complete( !_SUCCESS, new ErrorVO(e.toString()) ); } );
+ dispatcher.addEventListener( ProgressEvent.PROGRESS
+ , function( e:ProgressEvent ):void {
+ dispatchEvent( new LoaderProgressEvent( e.bytesLoaded, e.bytesTotal ) )
+ } ) ;
+ }
+ }
+}
\ No newline at end of file
diff --git a/flash/core/src/ru/mail/commands/graphicloader/events/GraphicLoaderCompleteEvent.as b/flash/core/src/ru/mail/commands/graphicloader/events/GraphicLoaderCompleteEvent.as
new file mode 100644
index 00000000..bd31416d
--- /dev/null
+++ b/flash/core/src/ru/mail/commands/graphicloader/events/GraphicLoaderCompleteEvent.as
@@ -0,0 +1,40 @@
+package ru.mail.commands.graphicloader.events
+{
+ import flash.events.Event;
+
+ import ru.mail.data.vo.ErrorVO;
+
+;
+
+ public class GraphicLoaderCompleteEvent extends Event
+ {
+ public static const TYPE:String = "GraphicLoaderCompleteEvent" ;
+
+ public function GraphicLoaderCompleteEvent( isSuccess:Boolean, content:*, error:ErrorVO = null )
+ {
+ super( TYPE ) ;
+ _isSuccess = isSuccess ;
+ _content = content ;
+ _error = error;
+ }
+
+ public function get isSuccess():Boolean
+ {
+ return _isSuccess ;
+ }
+
+ public function get content():*
+ {
+ return _content ;
+ }
+
+ public function get error():ErrorVO
+ {
+ return _error;
+ }
+
+ private var _isSuccess:Boolean ;
+ private var _content:* ;
+ private var _error:ErrorVO;
+ }
+}
\ No newline at end of file
diff --git a/flash/src/ru/mail/commands/textloader/ITextLoader.as b/flash/core/src/ru/mail/commands/textloader/ITextLoader.as
similarity index 100%
rename from flash/src/ru/mail/commands/textloader/ITextLoader.as
rename to flash/core/src/ru/mail/commands/textloader/ITextLoader.as
diff --git a/flash/src/ru/mail/commands/textloader/SimpleTextLoader.as b/flash/core/src/ru/mail/commands/textloader/SimpleTextLoader.as
similarity index 100%
rename from flash/src/ru/mail/commands/textloader/SimpleTextLoader.as
rename to flash/core/src/ru/mail/commands/textloader/SimpleTextLoader.as
diff --git a/flash/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as b/flash/core/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as
similarity index 100%
rename from flash/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as
rename to flash/core/src/ru/mail/commands/textloader/events/LoaderProgressEvent.as
diff --git a/flash/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as b/flash/core/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as
similarity index 100%
rename from flash/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as
rename to flash/core/src/ru/mail/commands/textloader/events/TextLoaderCompleteEvent.as
diff --git a/flash/src/ru/mail/communication/JSCallbackPresenter.as b/flash/core/src/ru/mail/communication/JSCallbackPresenter.as
similarity index 72%
rename from flash/src/ru/mail/communication/JSCallbackPresenter.as
rename to flash/core/src/ru/mail/communication/JSCallbackPresenter.as
index ea3bf0ac..3c45892d 100644
--- a/flash/src/ru/mail/communication/JSCallbackPresenter.as
+++ b/flash/core/src/ru/mail/communication/JSCallbackPresenter.as
@@ -27,6 +27,7 @@ package ru.mail.communication
ExternalInterface.addCallback("cmd", parseCmd);
}
catch (e:Error) {
+ LoggerJS.log('add js cmd callback error: '+e.toString() );
trace ("{JSCallbackPresenter} - unable to set callback, error:", e.message);
}
}
@@ -39,18 +40,31 @@ package ru.mail.communication
* @return
*
*/
- protected function parseCmd(command:String, data:Object):Boolean
+ protected function parseCmd(command:String, data:Object):Object
{
+ LoggerJS.log('parseCmd, command: '+command);
switch (command)
{
case "accept":
appController.setTypeFilter(data.toString());
break;
case "upload":
- /* cmd("upload", {id:12312, url:, name:UploadDataFieldName
- , data:URLVariables, headers:Object
- , imageOriginal: Boolean, // send original or transformed
- , imageTransform: Object, // transformObject
+ /* cmd("upload", {url:, data:URLVariables, headers:Object
+ ,files: {'filename[original]': {
+ id
+ , name
+ , matrix:{
+ sx , ...
+ ,dh
+ ,deg
+ , type:'image/png'
+ , quality: 1 // качество jpeg
+ ,overlay: [ // массив изображений, которые нужно разместить:
+ { x: 0, y: 0, opacity: .5, src: '...' }
+ , { x: 0, y: 0, w: 120, h: 30, opacity: 1, src: '...' }
+ ]
+ } }
+ , 'filename[XL]': { id, name, matrix:null } }
, callback:jsHandler}) */
// headers: { 'Content-Type': 'application/x-mru-upload' , 'Content-Disposition': '...' , ...}
appController.uploadFile(data.url
@@ -99,8 +113,17 @@ package ru.mail.communication
//});
appController.imageTransform(data.id, data.matrix, data.callback);
break;
+ // camera:
+ case "camera.on":
+ appController.cameraController.cameraOn(data.callback);
+ break;
+ case "camera.off":
+ appController.cameraController.cameraOff();
+ break;
+ case "shot":
+ return appController.cameraController.shot();
+ break;
default:
- trace ("JSCallbackPresenter - cannot parse command: "+command);
LoggerJS.log("cannot parse command: "+command);
break;
}
diff --git a/flash/src/ru/mail/communication/JSCaller.as b/flash/core/src/ru/mail/communication/JSCaller.as
similarity index 69%
rename from flash/src/ru/mail/communication/JSCaller.as
rename to flash/core/src/ru/mail/communication/JSCaller.as
index 90029c91..675105cc 100644
--- a/flash/src/ru/mail/communication/JSCaller.as
+++ b/flash/core/src/ru/mail/communication/JSCaller.as
@@ -1,6 +1,7 @@
package ru.mail.communication
{
import flash.external.ExternalInterface;
+ import flash.utils.ByteArray;
import ru.mail.data.vo.ErrorVO;
import ru.mail.data.vo.FileVO;
@@ -49,19 +50,24 @@ package ru.mail.communication
* @param data object with all nesessary data
*
*/
- public function callJS(_callback:String, data:Object, data2:Object = null):void
+ public function callJS(_callback:String, data:Object, data2:Object = null, withId:Boolean = false):void
{
try {
- // pass data to given callback
- if (data2) {
- ExternalInterface.call(_callback, data, data2);
- }
- else {
- ExternalInterface.call(_callback, data);
+ if (withId) {
+ if (!data) {
+ // new
+ data = {flashId:flashId};
+ }
+ else {
+ // add
+ data.flashId = flashId;
+ }
}
+
+ // pass data to given callback
+ _call(_callback, data, data2);
}
catch (e:Error) {
- trace ("callJS caused an exception", e);
}
}
@@ -75,13 +81,11 @@ package ru.mail.communication
{
var isReady:Boolean = false;
try {
- var r:* = ExternalInterface.call(callback, {type:"ready", flashId:flashId});
- trace( "JSCaller.notifyJSAboutAppReady() ", triesCount );
+ var r:* = _call(callback, {type:"ready", flashId:flashId});
isReady = ( r != null );
}
catch ( e:Error ) {
- trace ("notifyJSAboutAppReady error", e);
}
return isReady;
@@ -102,10 +106,9 @@ package ru.mail.communication
}
try {
- ExternalInterface.call(callback, { type:eventType, flashId:flashId });
+ _call(callback, { type:eventType, flashId:flashId });
}
catch (e:Error) {
- trace ("notifyJSMouseEvents error", e);
}
}
@@ -120,8 +123,6 @@ package ru.mail.communication
*/
public function notifyJSFilesEvents(eventType:String, filesVector:Vector. = null):void
{
- trace ("{JSCaller} - notifyJSFilesEvents, eventType", eventType)
-
var details:Object = new Object();
details.type = eventType;
@@ -155,10 +156,9 @@ package ru.mail.communication
try
{
- ExternalInterface.call(callback, details);
+ _call(callback, details);
}
catch (e:Error) {
- trace ("notifyJSFilesEvents error",e);
}
}
@@ -180,10 +180,23 @@ package ru.mail.communication
try
{
- ExternalInterface.call(callback, details);
+ _call(callback, details);
+ }
+ catch (e:Error) {
+ }
+ }
+
+ /**
+ * Notify JS about camera status event
+ * @param error: if null, then status if OK.
+ *
+ */
+ public function notifyCameraStatus(error:String):void
+ {
+ try {
+ _call(callback, { type:'camera', error:error, flashId:flashId });
}
catch (e:Error) {
- trace ("notifyJSErrors error",e);
}
}
@@ -198,11 +211,46 @@ package ru.mail.communication
{
try
{
- ExternalInterface.call(callback, {type:"error", message:errorVO.getError(), flashId:flashId});
+ _call(callback, {type:"error", message:errorVO.getError(), flashId:flashId});
}
catch (e:Error) {
- trace ("notifyJSErrors error",e);
+ }
+ }
+
+ private function clone(source:Object):* {
+ var myBA:ByteArray = new ByteArray();
+ myBA.writeObject(source);
+ myBA.position = 0;
+ return(myBA.readObject());
+ }
+
+ private function _escape(data:*):* {
+ if (typeof data === 'string') {
+ return data.replace(/\\/g, '\\\\');
+ } else if (typeof data === 'object') {
+ var ret:* = clone(data);
+ for (var i:String in data) {
+ ret[i] = _escape(data[i]);
+ }
+ return ret;
+ }
+ return data;
+ }
+
+ private function _call(callback:String, data:Object, data2:Object = null):* {
+ data = _escape(data);
+ if ( callback.match(/^FileAPI\.Flash\.(onEvent|_fn\.fileapi\d+)$/) ) {
+ if (data2) {
+ data2 = _escape(data2);
+ return ExternalInterface.call(callback, data, data2);
+ }
+ else {
+ return ExternalInterface.call(callback, data);
+ }
+ }
+ else {
+ return null;
}
}
}
-}
\ No newline at end of file
+}
diff --git a/flash/src/ru/mail/controller/AppController.as b/flash/core/src/ru/mail/controller/AppController.as
similarity index 84%
rename from flash/src/ru/mail/controller/AppController.as
rename to flash/core/src/ru/mail/controller/AppController.as
index acef6357..2c5e38d9 100644
--- a/flash/src/ru/mail/controller/AppController.as
+++ b/flash/core/src/ru/mail/controller/AppController.as
@@ -10,6 +10,7 @@ package ru.mail.controller
import flash.events.ProgressEvent;
import flash.events.TextEvent;
import flash.events.TimerEvent;
+ import flash.events.UncaughtErrorEvent;
import flash.net.FileFilter;
import flash.net.URLRequest;
import flash.system.Security;
@@ -66,6 +67,11 @@ package ru.mail.controller
private var readyTimer:Timer = new Timer(10);
private var readyTimerCount:int = 0;
+ /**
+ * Controller for managing camera swf
+ */
+ public var cameraController:CameraController;
+
/**
*
* @param graphicContext
@@ -89,6 +95,8 @@ package ru.mail.controller
// parse flashvars
_options = options;
JSCaller.callback = getJsFunctionName(options, JSCaller.callback);
+ // logger
+ LoggerJS.enable(!!options["debug"]);
// error store prefix
_model.storeKey = options["storeKey"];
LoggerJS.log("storeKey="+_model.storeKey);
@@ -98,10 +106,18 @@ package ru.mail.controller
if (options["flashId"]) {
JSCaller.flashId = options["flashId"];
}
+
+ // use camera
+ // options["useCamera"], if not false, contains url to camera swf
+ _model.useCamera = options["useCamera"];
+ if (_model.useCamera && _model.useCamera !== 'false') {
+ setupCamera();
+ }
+
// abort timeout
_model.timeout = options["timeout"];
LoggerJS.log("timeout="+_model.timeout);
-
+
setupChain();
configureListeners();
@@ -155,17 +171,19 @@ package ru.mail.controller
*/
private function setupChain():void
{
- // init factory
- var factory:EnginesFactory = EnginesFactory.getEnginesFactory();
- var engine:AbstractEngine = null;
-
- engine = factory.getFactory(SelectFilesEngine.TYPE);
- _chainRoot = engine;
- engine.addEventListener(CommandCompleteEvent.TYPE, onFilesSelected);
-
- engine = factory.getFactory(MouseListenerEngine.TYPE);
- _chainRoot.addEngine(engine);
- (engine as MouseListenerEngine).view = _view;
+ if (!_model.useCamera) {
+ // init factory
+ var factory:EnginesFactory = EnginesFactory.getEnginesFactory();
+ var engine:AbstractEngine = null;
+
+ engine = factory.getFactory(SelectFilesEngine.TYPE);
+ _chainRoot = engine;
+ engine.addEventListener(CommandCompleteEvent.TYPE, onFilesSelected);
+
+ engine = factory.getFactory(MouseListenerEngine.TYPE);
+ _chainRoot.addEngine(engine);
+ (engine as MouseListenerEngine).view = _view;
+ }
}
private function configureListeners():void
@@ -183,6 +201,11 @@ package ru.mail.controller
}
}
+ private function setupCamera():void
+ {
+ cameraController = new CameraController(_view);
+ }
+
/**
* Get JS function name from options
* @param defaultFunctionName
@@ -354,6 +377,7 @@ package ru.mail.controller
public function onStageResize(event:Event):void
{
+ // disabled. Now flash has very big size (oh, yeah!) but cropped by its html container
//resizeView (_view, (event.target as Stage).stageWidth, (event.target as Stage).stageHeight);
}
@@ -363,6 +387,22 @@ package ru.mail.controller
dispatchEvent(new MouseListenerEngineCommand(true));
}
+ /**
+ * Global Error Events Listener
+ * Listen for all uncaught events and log them.
+ *
+ * public: subscription is in the main application file.
+ *
+ * @param event
+ *
+ */
+ public function onUncaughtError(event:UncaughtErrorEvent):void
+ {
+ if (event && event.error) {
+ LoggerJS.log("Uncaught error: "+event.error);
+ }
+ }
+
//===================================================
//
// JS Callbacks
@@ -474,23 +514,24 @@ package ru.mail.controller
*/
public function getFileInfo(fileID:String, callback:String):void
{
- trace ("getFileInfo");
+ LoggerJS.log('getFileInfo, fileID: '+fileID+', callback: '+callback );
var file:BaseFileVO = _model.filesBuilder.getFileByID(fileID);
if (!file) {
- trace ("file with id "+ fileID +" doen't exist");
+ LoggerJS.log("getFileInfo, file with id "+ fileID +" doen't exist" );
return;
}
var imageFactory:IImageFactory = file.imageFactory;
(imageFactory as EventDispatcher).addEventListener(ImageTransformCompleteEvent.TYPE,function (event:ImageTransformCompleteEvent):void {
event.currentTarget.removeEventListener(event.type, arguments.callee);
- trace("getfileinfo complete", event.isSuccess);
+ LoggerJS.log("getFileInfo complete, success = "+ event.isSuccess );
if (event.isSuccess && (file as IFileVO).imageData)
{
// report file info
var info:Object = { "width":(file as IFileVO).imageData.width
, "height":(file as IFileVO).imageData.height
};
+ LoggerJS.log("getFileInfo imageFactory.readExif()");
var exif:Object = imageFactory.readExif();
if ( exif ) {
info["exif"] = exif;
@@ -538,9 +579,12 @@ package ru.mail.controller
}
});
- imageFactory.createImage( trans? new ImageTransformVO(trans.sx, trans.sy, trans.sw, trans.sh, trans.dw, trans.dh, trans.deg) : null );
+ imageFactory.createImage( trans
+ ? new ImageTransformVO(trans.sx, trans.sy, trans.sw, trans.sh, trans.dw, trans.dh, trans.deg, trans.type, trans.quality, (trans.overlay is Array)? trans.overlay : [trans.overlay], trans.multipass)
+ : null);
}
catch (e:Error){
+ LoggerJS.log('imageFactory createImage error: '+e.toString());
_jsCaller.notifyJSErrors(new ErrorVO(e.toString()));
}
}
@@ -554,7 +598,26 @@ package ru.mail.controller
* @param uploadPostData - URLVariables
* @param headers - custom request headers for URLLoader
* @param files - object with files uids and matrix objects
- * exapmle - files: {'filename[original]': { id, name, matrix:null }, 'filename[XL]': { id, name, matrix:{...} } }
+ * exapmle - files: {
+ * 'filename[original]': {
+ * id
+ * , name
+ * , matrix:null
+ * , 'filename[XL]': { id
+ * , name
+ * , matrix:{
+ * sx
+ * ...
+ * ,deg
+ * , type:'image/png'
+ * , quality: 1 // jpeg quality, от 0 до 1
+ * , overlay: [ // an array of images to place over the image:
+ * { x: 0, y: 0, opacity: .5, src: '...' // base64 or url }
+ * , { x: 0, y: 0, w: 120, h: 30, opacity: 1, src: '...' }
+ * ]
+ * }
+ * }
+ * }
* when there is only 1 file with null matrix, file will be uploaded using fileReference
* @param callback
*
@@ -567,19 +630,28 @@ package ru.mail.controller
{
trace ("upload file")
try {
- LoggerJS.log("call upload");
+ LoggerJS.log("call upload, files: "+files);
// get files
var file:BaseFileVO;
- for (var s:String in files)
- {
- file = _model.filesBuilder.getFileByID(files[s].id);
- if (!file) {
- trace ("file with id "+ files[s].id +" doen't exist");
- LoggerJS.log("upload: file with id "+ files[s].id + " doen't exist");
- return;
+ if (files) {
+ for (var s:String in files)
+ {
+ file = _model.filesBuilder.getFileByID(files[s].id);
+ if (!file) {
+ trace ("file with id "+ files[s].id +" doen't exist");
+ LoggerJS.log("upload: file with id "+ files[s].id + " doen't exist");
+ return;
+ }
+ files[s].file = file;
}
- files[s].file = file;
+ } else {
+ // https://github.com/mailru/FileAPI/issues/83
+ // upload request without files
+ // add fake file to avoid error in ImageFactory, but do not upload it
+ file = _model.filesBuilder.createFakeFileVO('dummy');
+ files = {'dummy':{'id': 'dummy', 'file':file, 'name':'', 'matrix':{} }};
+ LoggerJS.log("upload without files");
}
// launch command
@@ -623,6 +695,7 @@ package ru.mail.controller
uploadCommand.execute();
}
catch (err:Error) {
+ LoggerJS.log("upload error: "+err.toString());
_jsCaller.notifyJSErrors( new ErrorVO( err.toString() ) );
}
}
@@ -689,4 +762,4 @@ package ru.mail.controller
}
}
-}
\ No newline at end of file
+}
diff --git a/flash/core/src/ru/mail/controller/CameraController.as b/flash/core/src/ru/mail/controller/CameraController.as
new file mode 100644
index 00000000..aa34ae56
--- /dev/null
+++ b/flash/core/src/ru/mail/controller/CameraController.as
@@ -0,0 +1,124 @@
+package ru.mail.controller
+{
+ import flash.display.BitmapData;
+ import flash.display.Sprite;
+ import flash.events.Event;
+ import flash.events.StatusEvent;
+ import flash.net.URLRequest;
+
+ import ru.mail.commands.graphicloader.SimpleGraphicLoader;
+ import ru.mail.commands.graphicloader.events.GraphicLoaderCompleteEvent;
+ import ru.mail.communication.JSCaller;
+ import ru.mail.data.AttachmentsModel;
+ import ru.mail.data.vo.PhotoFileVO;
+ import ru.mail.utils.LoggerJS;
+
+ public class CameraController
+ {
+ private var _jsCaller:JSCaller;
+ private var _model:AttachmentsModel;
+ private var _view:Sprite;
+
+ private var _cameraSwf:*;
+
+ public function CameraController(view:Sprite)
+ {
+ LoggerJS.log('Camera Controller - init');
+
+ _view = view;
+ _jsCaller = JSCaller.jsCaller;
+ _model = AttachmentsModel.model;
+
+ loadCamera();
+ }
+
+ public function cameraOn(callback:String):void
+ {
+ LoggerJS.log('camera.on called');
+ try {
+ _cameraSwf.addEventListener('Camera.On', function(event:Event):void {
+ _jsCaller.callJS(callback, {error:null}, null, true);
+ });
+ _cameraSwf.toggleCamera(true);
+ } catch (e:Error) {
+ _jsCaller.callJS(callback, {error:e.toString()}, null, true);
+ }
+ }
+
+ public function cameraOff():void
+ {
+ LoggerJS.log('camera.off called');
+ _cameraSwf.toggleCamera(false);
+ }
+
+ public function shot():Object
+ {
+ LoggerJS.log('smile please!');
+ var bm:BitmapData = _cameraSwf.shot();
+ var result:Object = {};
+ if (bm == null) {
+ LoggerJS.log('shot image error');
+ result.error = 'create shot fail';
+ }
+ else {
+ LoggerJS.log('shot image w:'+bm.width+', h:'+bm.height);
+ var fileVO:PhotoFileVO = _model.filesBuilder.createPhotoFileVO(bm);
+ result.id = fileVO.fileID;
+ result.type = fileVO.fileType;
+ result.size = fileVO.fileSize;
+ result.width = fileVO.imageData.width;
+ result.height = fileVO.imageData.height;
+ }
+
+ return result;
+ }
+
+ // ============= init ================
+
+ private function loadCamera():void
+ {
+ var loader:SimpleGraphicLoader = new SimpleGraphicLoader(10);
+ loader.addEventListener(GraphicLoaderCompleteEvent.TYPE, function(evt:GraphicLoaderCompleteEvent):void {
+ if (evt.isSuccess) {
+ LoggerJS.log('load camera swf complete');
+ try {
+ _cameraSwf = evt.content;
+ _cameraSwf.addEventListener(StatusEvent.STATUS, onCameraStatus);
+ _view.addChild(_cameraSwf);
+ } catch (e:Error) {
+ LoggerJS.log('attach camera fail: '+e.toString());
+ initComplete(false, evt.error.getError());
+ }
+
+ }
+ else {
+ LoggerJS.log('load camera swf complete, _isSuccess='+evt.isSuccess+', error='+evt.error.getError());
+ // What a pity
+ initComplete(false, evt.error.getError());
+ }
+ });
+
+ loader.loadGraphic(new URLRequest(_model.useCamera || 'FileAPI.flash.camera.swf'));
+ }
+
+ private function onCameraStatus(event:StatusEvent):void
+ {
+ LoggerJS.log('onCameraStatus '+event.code);
+ if (event.code == 'Camera.Unmuted') {
+ // report ok
+ initComplete(true);
+ } else if (event.code == 'Camera.Muted') {
+ // report user denied
+ initComplete(false, 'user denied access to camera');
+ } else {
+ // report this strange thing
+ initComplete(false, 'unknown camera status: '+event.code);
+ }
+ }
+
+ private function initComplete(success:Boolean, error:String = null):void
+ {
+ _jsCaller.notifyCameraStatus(success? null : error);
+ }
+ }
+}
\ No newline at end of file
diff --git a/flash/src/ru/mail/data/AbstractImageFactory.as b/flash/core/src/ru/mail/data/AbstractImageFactory.as
similarity index 100%
rename from flash/src/ru/mail/data/AbstractImageFactory.as
rename to flash/core/src/ru/mail/data/AbstractImageFactory.as
diff --git a/flash/src/ru/mail/data/AttachmentsModel.as b/flash/core/src/ru/mail/data/AttachmentsModel.as
similarity index 98%
rename from flash/src/ru/mail/data/AttachmentsModel.as
rename to flash/core/src/ru/mail/data/AttachmentsModel.as
index 9c6a8a0f..462711a4 100644
--- a/flash/src/ru/mail/data/AttachmentsModel.as
+++ b/flash/core/src/ru/mail/data/AttachmentsModel.as
@@ -51,6 +51,8 @@ package ru.mail.data
return _filesBuilder;
}
+ public var useCamera:String = null;
+
/**
* if true, user can select multiple files
*/
diff --git a/flash/src/ru/mail/data/IImageFactory.as b/flash/core/src/ru/mail/data/IImageFactory.as
similarity index 100%
rename from flash/src/ru/mail/data/IImageFactory.as
rename to flash/core/src/ru/mail/data/IImageFactory.as
diff --git a/flash/src/ru/mail/data/ImageFactory.as b/flash/core/src/ru/mail/data/ImageFactory.as
similarity index 52%
rename from flash/src/ru/mail/data/ImageFactory.as
rename to flash/core/src/ru/mail/data/ImageFactory.as
index f1ecbe3c..8ca45f8e 100644
--- a/flash/src/ru/mail/data/ImageFactory.as
+++ b/flash/core/src/ru/mail/data/ImageFactory.as
@@ -1,21 +1,28 @@
package ru.mail.data
{
+ import by.blooddy.crypto.Base64;
+
import flash.display.BitmapData;
import flash.events.EventDispatcher;
import flash.geom.Point;
import flash.geom.Rectangle;
+ import flash.net.URLRequest;
import flash.utils.ByteArray;
import ru.mail.commands.DecodeBytesToBitmapCommand;
import ru.mail.commands.LoadFileCommand;
import ru.mail.commands.ResizeFileCommand;
+ import ru.mail.commands.graphicloader.SimpleGraphicLoader;
+ import ru.mail.commands.graphicloader.events.GraphicLoaderCompleteEvent;
import ru.mail.data.vo.ErrorVO;
import ru.mail.data.vo.FileVO;
import ru.mail.data.vo.IFileVO;
import ru.mail.data.vo.ImageTransformVO;
+ import ru.mail.data.vo.OverlayVO;
import ru.mail.events.DecodeBytesToBitmapCompleteEvent;
import ru.mail.events.ImageTransformCompleteEvent;
import ru.mail.utils.ExifReader2;
+ import ru.mail.utils.LoggerJS;
/**
* Produces images from file's source
@@ -29,50 +36,127 @@ package ru.mail.data
public class ImageFactory extends EventDispatcher implements IImageFactory
{
private var file:IFileVO;
-
+
private var loadCommand:LoadFileCommand;
-
+
+ private var isFileLoaded:Boolean = false;
+ private var isOverlayLoaded:Boolean = false;
+
public function ImageFactory(target:IFileVO)
{
if (!target) {
throw new Error("{ImageFactory} - init: target is null");
}
-
+
file = target;
}
-
+
public function createImage(imageTransform:ImageTransformVO):void {
+ isFileLoaded = false;
+ isOverlayLoaded = false;
+
if (!file.fileData) {
// first load file
if( file is FileVO) {
var loadFileCommand:LoadFileCommand = (file as FileVO).loadCommand? (file as FileVO).loadCommand as LoadFileCommand : new LoadFileCommand(file as FileVO);
-
+
loadFileCommand.addEventListener(ImageTransformCompleteEvent.TYPE, function(event:ImageTransformCompleteEvent):void{
event.currentTarget.removeEventListener(event.type, arguments.callee);
trace ("loadCommand complete", event.isSuccess);
+ LoggerJS.log('ImageFactory loadFile complete, success '+ event.isSuccess);
loadCommand = null;
if(event.isSuccess) {
- checkImageData(imageTransform);
+ onLoadImageAndOverlay(true, imageTransform);
}
else {
complete( false, event.data, event.error );
}
});
+ LoggerJS.log('ImageFactory loadFile');
if (!(file as FileVO).loadCommand)
loadFileCommand.execute();
(file as FileVO).loadCommand = loadFileCommand;
}
}
else {
- checkImageData(imageTransform);
+ onLoadImageAndOverlay(true, imageTransform);
+ }
+
+ // check overlay
+ if ( imageTransform && imageTransform.overlay ) {
+ // todo: wait for all overlay images to load and then proceed
+ var overlayLoadCounter:int = 0;
+ var overlay:OverlayVO;
+
+ for (var i:uint = 0; i < imageTransform.overlay.length; i++) {
+ overlay = imageTransform.overlay[i];
+ overlayLoadCounter++;
+
+ LoggerJS.log('overlay.src : '+overlay.src);
+ if ( overlay.src.match(/^data:/) ) {
+ // base64 string, just encode it to image
+ var bytes:ByteArray = Base64.decode(overlay.src);
+ // ByteArray to BitmapData
+ var decodeCommand:DecodeBytesToBitmapCommand = new DecodeBytesToBitmapCommand( bytes );
+ decodeCommand.addEventListener(DecodeBytesToBitmapCompleteEvent.TYPE, function(event:DecodeBytesToBitmapCompleteEvent):void {
+ event.currentTarget.removeEventListener(event.type, arguments.callee);
+ trace ("bitmap created, isSuccess", event.isSuccess);
+ if (event.isSuccess) {
+ overlay.imageData = new BitmapData( event.decodedBitmap.width, event.decodedBitmap.height );
+ overlay.imageData.copyPixels( event.decodedBitmap.bitmapData
+ , new Rectangle( 0, 0, event.decodedBitmap.width, event.decodedBitmap.height ), new Point( 0, 0 ));
+ event.decodedBitmap.bitmapData.dispose();
+ }
+ else {
+ LoggerJS.log('load overlay base64 error: '+event.error);
+ }
+
+ if (--overlayLoadCounter < 1){
+ onLoadImageAndOverlay(false, imageTransform);
+ }
+ });
+ decodeCommand.execute();
+
+ }
+ else {
+ // load image from url
+ // 1. load bytes
+ var loadOverlayLoader:SimpleGraphicLoader = new SimpleGraphicLoader(10);
+ loadOverlayLoader.addEventListener(GraphicLoaderCompleteEvent.TYPE, function(event:GraphicLoaderCompleteEvent):void {
+ event.currentTarget.removeEventListener(event.type, arguments.callee);
+ LoggerJS.log('ImageFactory load overlay image, success: '+event.isSuccess);
+ if (event.isSuccess && event.content != null) {
+ overlay.imageData = new BitmapData( event.content.width, event.content.height );
+ overlay.imageData.copyPixels( event.content.bitmapData
+ , new Rectangle( 0, 0, event.content.width, event.content.height ), new Point( 0, 0 ));
+ event.content.bitmapData.dispose();
+ LoggerJS.log('ImageFactory load overlay image success, overlay image: w='+overlay.imageData.width+', h='+overlay.imageData.height);
+ } else {
+ LoggerJS.log('ImageFactory load overlay image error: '+event.error);
+ }
+ if (--overlayLoadCounter < 1){
+ onLoadImageAndOverlay(false, imageTransform);
+ }
+ });
+ LoggerJS.log('ImageFactory load overlay image, src = '+overlay.src);
+ loadOverlayLoader.loadGraphic(new URLRequest(overlay.src));
+ // 2. bitmapData
+ }
+ }
+
+ if (overlayLoadCounter < 1)
+ onLoadImageAndOverlay(false, imageTransform);
+ } else {
+ LoggerJS.log('ImageFactory no overlay provided');
+ onLoadImageAndOverlay(false, imageTransform);
}
}
-
+
public function readExif():Object {
if (!file.fileData) {
return null;
}
-
+
var exif:Object = {};
try {
var exifReader:ExifReader2 = new ExifReader2();
@@ -81,16 +165,28 @@ package ru.mail.data
// more info about orientation
// http://sylvana.net/jpegcrop/exif_orientation.html
var orientation:uint = uint(exifReader.getValue("Orientation"));
-
+
exif["Orientation"] = orientation;
}
-
+
} catch (e:Error) {
trace ("read exif error: "+ e);
}
return exif;
}
-
+
+ private function onLoadImageAndOverlay(isFile:Boolean, imageTransform:ImageTransformVO):void
+ {
+ if (isFile)
+ isFileLoaded = true;
+ else
+ isOverlayLoaded = true;
+
+ if (isFileLoaded && isOverlayLoaded) {
+ checkImageData(imageTransform);
+ }
+ }
+
/**
* 1/2 Check if imageData has been loaded
* @param imageTransform
@@ -104,26 +200,27 @@ package ru.mail.data
decodeCommand.addEventListener(DecodeBytesToBitmapCompleteEvent.TYPE, function(event:DecodeBytesToBitmapCompleteEvent):void {
event.currentTarget.removeEventListener(event.type, arguments.callee);
trace ("bitmap created, isSuccess", event.isSuccess);
+ LoggerJS.log('ImageFactory bitmap created, success '+ event.isSuccess);
if (event.isSuccess) {
- file.imageData = event.decodedBitmap.bitmapData;
file.imageData = new BitmapData( event.decodedBitmap.width, event.decodedBitmap.height );
file.imageData.copyPixels( event.decodedBitmap.bitmapData
, new Rectangle( 0, 0, event.decodedBitmap.width, event.decodedBitmap.height ), new Point( 0, 0 ));
event.decodedBitmap.bitmapData.dispose();
-
+
createImageFromSource(imageTransform);
}
else {
complete( false, null, event.error );
}
});
+ LoggerJS.log('ImageFactory create Bitmap');
decodeCommand.execute();
}
else {
createImageFromSource(imageTransform);
}
}
-
+
/**
* 2/2 transform original imagedata
* @param imageTransform
@@ -141,6 +238,7 @@ package ru.mail.data
resizeCommand.addEventListener(ImageTransformCompleteEvent.TYPE, function(event:ImageTransformCompleteEvent):void {
event.currentTarget.removeEventListener(event.type, arguments.callee);
+ LoggerJS.log('ImageFactory resize complete, success '+ event.isSuccess);
if (event.isSuccess) {
complete(true, event.data);
}
@@ -148,11 +246,11 @@ package ru.mail.data
complete(false, null, event.error);
}
});
-
+ LoggerJS.log('ImageFactory resize');
resizeCommand.execute();
}
}
-
+
private function complete(isSuccess:Boolean, data:ByteArray, error:ErrorVO = null):void
{
trace ("imageFactory complete");
diff --git a/flash/src/ru/mail/data/builder/AbstractDataBuilder.as b/flash/core/src/ru/mail/data/builder/AbstractDataBuilder.as
similarity index 100%
rename from flash/src/ru/mail/data/builder/AbstractDataBuilder.as
rename to flash/core/src/ru/mail/data/builder/AbstractDataBuilder.as
diff --git a/flash/src/ru/mail/data/builder/FilesDataBuilder.as b/flash/core/src/ru/mail/data/builder/FilesDataBuilder.as
similarity index 55%
rename from flash/src/ru/mail/data/builder/FilesDataBuilder.as
rename to flash/core/src/ru/mail/data/builder/FilesDataBuilder.as
index 158dcd2a..fb29bc8e 100644
--- a/flash/src/ru/mail/data/builder/FilesDataBuilder.as
+++ b/flash/core/src/ru/mail/data/builder/FilesDataBuilder.as
@@ -1,9 +1,13 @@
package ru.mail.data.builder
{
+ import flash.display.BitmapData;
import flash.net.FileReference;
+ import flash.utils.ByteArray;
import ru.mail.data.AbstractImageFactory;
+ import ru.mail.data.vo.FakeFileVO;
import ru.mail.data.vo.FileVO;
+ import ru.mail.data.vo.PhotoFileVO;
/**
* Create and store files
@@ -20,6 +24,26 @@ package ru.mail.data.builder
super(TYPE);
}
+ /**
+ * Create fake file with empty data
+ * @param fileID
+ * @return
+ *
+ */
+ public function createFakeFileVO(fileID:String = ''):FakeFileVO
+ {
+ // create
+ var fileVO:FakeFileVO = new FakeFileVO();
+ // set props
+ fileVO.fileData = new ByteArray();
+ fileVO.imageData = new BitmapData(1,1);
+ fileVO.fileID = fileID;
+
+ fileVO.abstractImageFactory = new AbstractImageFactory(fileVO);
+
+ return fileVO;
+ }
+
public function createFileVO(fileReference:FileReference, fileID:String = '', addToCollection:Boolean = true):FileVO
{
if (fileReference == null) {
@@ -60,5 +84,27 @@ package ru.mail.data.builder
return fileVO;
}*/
+ public function createPhotoFileVO(image:BitmapData, fileID:String = '', addToCollection:Boolean = true):PhotoFileVO
+ {
+ if (image == null) {
+ throw new Error("FilesDataBuilder - createPhotoFileVO: image BitmapData is null");
+ }
+
+ // create
+ var fileVO:PhotoFileVO = new PhotoFileVO();
+ // set props
+ fileVO.imageData = image;
+ fileVO.fileID = fileID;
+ //fileVO.imageFactory = new ImageFactory(fileVO, true);
+ fileVO.abstractImageFactory = new AbstractImageFactory(fileVO);
+ // add
+ if(addToCollection)
+ {
+ addFile(fileVO);
+ }
+
+ return fileVO;
+ }
+
}
}
\ No newline at end of file
diff --git a/flash/src/ru/mail/data/vo/BaseFileVO.as b/flash/core/src/ru/mail/data/vo/BaseFileVO.as
similarity index 94%
rename from flash/src/ru/mail/data/vo/BaseFileVO.as
rename to flash/core/src/ru/mail/data/vo/BaseFileVO.as
index c47a6a1c..f908fee5 100644
--- a/flash/src/ru/mail/data/vo/BaseFileVO.as
+++ b/flash/core/src/ru/mail/data/vo/BaseFileVO.as
@@ -5,7 +5,7 @@ package ru.mail.data.vo
/**
* This class contains almost all information about the file. The difference is only the source of data -
- * it can be fileReference or loaded from url.
+ * it can be fileReference or loaded from url or image from web camera.
* load from url isn't implemented now.
*
* @author v.demidov
diff --git a/flash/src/ru/mail/data/vo/ErrorVO.as b/flash/core/src/ru/mail/data/vo/ErrorVO.as
similarity index 100%
rename from flash/src/ru/mail/data/vo/ErrorVO.as
rename to flash/core/src/ru/mail/data/vo/ErrorVO.as
diff --git a/flash/src/ru/mail/data/vo/RestoredFileVO.as b/flash/core/src/ru/mail/data/vo/FakeFileVO.as
similarity index 73%
rename from flash/src/ru/mail/data/vo/RestoredFileVO.as
rename to flash/core/src/ru/mail/data/vo/FakeFileVO.as
index fbb72d59..f9ef422d 100644
--- a/flash/src/ru/mail/data/vo/RestoredFileVO.as
+++ b/flash/core/src/ru/mail/data/vo/FakeFileVO.as
@@ -7,52 +7,55 @@ package ru.mail.data.vo
* Restored file (from url or any else bytearray). If it is image, it cannot be scaled or rotated.
* @author v.demidov
*
- */
- public class RestoredFileVO extends BaseFileVO implements IFileVO
+ */
+ public class FakeFileVO extends BaseFileVO implements IFileVO
{
- public var url:String;
-
private var _fileData:ByteArray;
public function get fileData():ByteArray
{
return _fileData;
}
-
+
public function set fileData(value:ByteArray):void
{
_fileData = value;
}
-
+
public function get fileSize():Number {
return _fileData? _fileData.length : 0;
}
-
+
public function get fileName():String {
- return "";
+ return fileID;
}
-
+
+ public function get fileNameModified():String
+ {
+ return fileID;
+ }
+
public function get fileType():String {
return "";
}
-
+
private var _imageData:BitmapData;
/**
* original image bitmapData;
* @return
*
*/
- public function get imageData():BitmapData
+ public function get imageData():BitmapData
{
return _imageData;
}
-
- public function set imageData(bd:BitmapData):void
+
+ public function set imageData(bd:BitmapData):void
{
_imageData = bd;
}
-
- public function RestoredFileVO()
+
+ public function FakeFileVO()
{
super();
}
diff --git a/flash/src/ru/mail/data/vo/FileStatesEnum.as b/flash/core/src/ru/mail/data/vo/FileStatesEnum.as
similarity index 100%
rename from flash/src/ru/mail/data/vo/FileStatesEnum.as
rename to flash/core/src/ru/mail/data/vo/FileStatesEnum.as
diff --git a/flash/src/ru/mail/data/vo/FileVO.as b/flash/core/src/ru/mail/data/vo/FileVO.as
similarity index 97%
rename from flash/src/ru/mail/data/vo/FileVO.as
rename to flash/core/src/ru/mail/data/vo/FileVO.as
index 95594440..3834a579 100644
--- a/flash/src/ru/mail/data/vo/FileVO.as
+++ b/flash/core/src/ru/mail/data/vo/FileVO.as
@@ -75,7 +75,7 @@ package ru.mail.data.vo
if ( fileNameParts.length < 2 )
return fileName;
- return fileNameParts[0] + 'jpg';
+ return fileNameParts[0] + '.png';
}
}
@@ -84,4 +84,4 @@ package ru.mail.data.vo
super();
}
}
-}
\ No newline at end of file
+}
diff --git a/flash/src/ru/mail/data/vo/IFileVO.as b/flash/core/src/ru/mail/data/vo/IFileVO.as
similarity index 100%
rename from flash/src/ru/mail/data/vo/IFileVO.as
rename to flash/core/src/ru/mail/data/vo/IFileVO.as
diff --git a/flash/core/src/ru/mail/data/vo/ImageTransformVO.as b/flash/core/src/ru/mail/data/vo/ImageTransformVO.as
new file mode 100644
index 00000000..159d00da
--- /dev/null
+++ b/flash/core/src/ru/mail/data/vo/ImageTransformVO.as
@@ -0,0 +1,74 @@
+package ru.mail.data.vo
+{
+ /**
+ * Value object with transformation matrix
+ *
+ * @author v.demidov
+ *
+ */
+ public class ImageTransformVO
+ {
+ public static const TYPE_PNG:String = 'image/png';
+ public static const TYPE_JPEG:String = 'image/jpeg';
+
+ public var sx:Number = 0;
+ public var sy:Number = 0;
+ public var sw:Number = 0;
+ public var sh:Number = 0;
+ public var dw:Number = 0;
+ public var dh:Number = 0;
+ public var deg:Number = 0;
+ public var type:String = 'image/png'; // encoded image type. If type value is unknown, png is used
+ public var quality:Number = 1; // encode quality (jpeg only)
+ public var overlay:Array = []; // array of OverlayVO instances
+ public var multiPassResize: Boolean = true;
+
+ public function ImageTransformVO(sx:Number = 0, sy:Number = 0, sw:Number = 0, sh:Number = 0, dw:Number = 0, dh:Number = 0, deg:Number = 0
+ , type:String = null, quality:Number = 1, overlay:Array = null, multiPassResize:Boolean = true)
+ {
+ super();
+
+ if ( !isNaN(sx) )
+ this.sx = sx;
+ if ( !isNaN(sy) )
+ this.sy = sy;
+ if ( !isNaN(sw) )
+ this.sw = sw;
+ if ( !isNaN(sh) )
+ this.sh = sh;
+ if ( !isNaN(dw) )
+ this.dw = dw;
+ if ( !isNaN(dh) )
+ this.dh = dh;
+ if ( !isNaN(deg) )
+ this.deg = deg;
+ if ( type )
+ this.type = type;
+ if ( !isNaN(quality) )
+ this.quality = quality;
+
+ this.multiPassResize = multiPassResize;
+
+ if ( overlay )
+ setOverlay( overlay );
+ }
+
+ /**
+ * @private fill overlay array with value objects
+ * @param overlay
+ *
+ */
+ private function setOverlay(overlay:Array):void
+ {
+ var item:Object;
+ var overlayVO:OverlayVO;
+ for (var i:uint = 0; i < overlay.length; i++) {
+ item = overlay[i];
+ if ( item && item.src ) {
+ overlayVO = new OverlayVO(item);
+ this.overlay.push(overlayVO);
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/flash/core/src/ru/mail/data/vo/OverlayVO.as b/flash/core/src/ru/mail/data/vo/OverlayVO.as
new file mode 100644
index 00000000..77768eaa
--- /dev/null
+++ b/flash/core/src/ru/mail/data/vo/OverlayVO.as
@@ -0,0 +1,55 @@
+package ru.mail.data.vo
+{
+ import flash.display.BitmapData;
+
+ /**
+ * Value object with params for overlay
+ *
+ * @author demidov
+ *
+ */
+ public class OverlayVO
+ {
+ public var x:Number;
+ public var y:Number;
+ public var w:Number;
+ public var h:Number;
+ /**
+ * 0 1 2
+ * 3 4 5
+ * 6 7 8
+ */
+ public var rel:uint;
+ public var opacity:Number;
+ public var src:String;
+
+ private var _imageData:BitmapData;
+ public function get imageData():BitmapData
+ {
+ return _imageData;
+ }
+ public function set imageData(value:BitmapData):void
+ {
+ _imageData = value;
+ // update default w, h
+ if (w == 0) {
+ w = _imageData.width;
+ }
+ if (h == 0) {
+ h = _imageData.height;
+ }
+ }
+
+
+ public function OverlayVO(item:Object)
+ {
+ this.x = item.x|0;
+ this.y = item.y|0;
+ this.w = item.w|0;
+ this.h = item.h|0;
+ this.rel = uint(item.rel)|0;
+ this.opacity = item.opacity||1;
+ this.src = item.src;
+ }
+ }
+}
\ No newline at end of file
diff --git a/flash/core/src/ru/mail/data/vo/PhotoFileVO.as b/flash/core/src/ru/mail/data/vo/PhotoFileVO.as
new file mode 100644
index 00000000..50a317ca
--- /dev/null
+++ b/flash/core/src/ru/mail/data/vo/PhotoFileVO.as
@@ -0,0 +1,61 @@
+package ru.mail.data.vo
+{
+ import flash.display.BitmapData;
+ import flash.geom.Rectangle;
+ import flash.utils.ByteArray;
+
+ public class PhotoFileVO extends BaseFileVO implements IFileVO
+ {
+ private var _image:BitmapData = null;
+ private var _filedata:ByteArray = null;
+
+ public function PhotoFileVO()
+ {
+ super();
+ }
+
+ public function get fileData():ByteArray
+ {
+ return _filedata;
+ }
+
+ public function get fileSize():Number
+ {
+ return _filedata? _filedata.length : 0;
+ }
+
+ public function get fileName():String
+ {
+ return _fileID+'.png';
+ }
+
+ /**
+ * use fileName, because modified filename makes sense only for fileRef files.
+ * @return
+ *
+ */
+ public function get fileNameModified():String
+ {
+ return fileName;
+ }
+
+ public function get fileType():String
+ {
+ return 'png';
+ }
+
+ public function get imageData():BitmapData
+ {
+ return _image;
+ }
+
+ public function set imageData(bd:BitmapData):void
+ {
+ _image = bd;
+ if (_image) {
+ // get bytearray now to avoid this operation every time we need it.
+ _filedata = _image.getPixels(new Rectangle(0, 0, _image.width, _image.height));
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/flash/src/ru/mail/engines/chain/AbstractModelJSEngine.as b/flash/core/src/ru/mail/engines/chain/AbstractModelJSEngine.as
similarity index 100%
rename from flash/src/ru/mail/engines/chain/AbstractModelJSEngine.as
rename to flash/core/src/ru/mail/engines/chain/AbstractModelJSEngine.as
diff --git a/flash/src/ru/mail/engines/chain/EnginesFactory.as b/flash/core/src/ru/mail/engines/chain/EnginesFactory.as
similarity index 100%
rename from flash/src/ru/mail/engines/chain/EnginesFactory.as
rename to flash/core/src/ru/mail/engines/chain/EnginesFactory.as
diff --git a/flash/src/ru/mail/engines/chain/IJsCallerHolder.as b/flash/core/src/ru/mail/engines/chain/IJsCallerHolder.as
similarity index 100%
rename from flash/src/ru/mail/engines/chain/IJsCallerHolder.as
rename to flash/core/src/ru/mail/engines/chain/IJsCallerHolder.as
diff --git a/flash/src/ru/mail/engines/chain/IModelHolder.as b/flash/core/src/ru/mail/engines/chain/IModelHolder.as
similarity index 100%
rename from flash/src/ru/mail/engines/chain/IModelHolder.as
rename to flash/core/src/ru/mail/engines/chain/IModelHolder.as
diff --git a/flash/src/ru/mail/engines/chain/manage/SelectFilesEngine.as b/flash/core/src/ru/mail/engines/chain/manage/SelectFilesEngine.as
similarity index 100%
rename from flash/src/ru/mail/engines/chain/manage/SelectFilesEngine.as
rename to flash/core/src/ru/mail/engines/chain/manage/SelectFilesEngine.as
diff --git a/flash/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as b/flash/core/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as
similarity index 100%
rename from flash/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as
rename to flash/core/src/ru/mail/engines/chain/presentation/MouseListenerEngine.as
diff --git a/flash/src/ru/mail/engines/commands/MouseListenerEngineCommand.as b/flash/core/src/ru/mail/engines/commands/MouseListenerEngineCommand.as
similarity index 100%
rename from flash/src/ru/mail/engines/commands/MouseListenerEngineCommand.as
rename to flash/core/src/ru/mail/engines/commands/MouseListenerEngineCommand.as
diff --git a/flash/src/ru/mail/engines/commands/SelectFilesCommand.as b/flash/core/src/ru/mail/engines/commands/SelectFilesCommand.as
similarity index 100%
rename from flash/src/ru/mail/engines/commands/SelectFilesCommand.as
rename to flash/core/src/ru/mail/engines/commands/SelectFilesCommand.as
diff --git a/flash/src/ru/mail/events/CompleteEvent.as b/flash/core/src/ru/mail/events/CompleteEvent.as
similarity index 100%
rename from flash/src/ru/mail/events/CompleteEvent.as
rename to flash/core/src/ru/mail/events/CompleteEvent.as
diff --git a/flash/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as b/flash/core/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as
similarity index 100%
rename from flash/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as
rename to flash/core/src/ru/mail/events/DecodeBytesToBitmapCompleteEvent.as
diff --git a/flash/src/ru/mail/events/ImageTransformCompleteEvent.as b/flash/core/src/ru/mail/events/ImageTransformCompleteEvent.as
similarity index 100%
rename from flash/src/ru/mail/events/ImageTransformCompleteEvent.as
rename to flash/core/src/ru/mail/events/ImageTransformCompleteEvent.as
diff --git a/flash/src/ru/mail/events/UploadCompleteEvent.as b/flash/core/src/ru/mail/events/UploadCompleteEvent.as
similarity index 100%
rename from flash/src/ru/mail/events/UploadCompleteEvent.as
rename to flash/core/src/ru/mail/events/UploadCompleteEvent.as
diff --git a/flash/src/ru/mail/utils/BMPDecoder.as b/flash/core/src/ru/mail/utils/BMPDecoder.as
similarity index 100%
rename from flash/src/ru/mail/utils/BMPDecoder.as
rename to flash/core/src/ru/mail/utils/BMPDecoder.as
diff --git a/flash/src/ru/mail/utils/ExifReader2.as b/flash/core/src/ru/mail/utils/ExifReader2.as
similarity index 100%
rename from flash/src/ru/mail/utils/ExifReader2.as
rename to flash/core/src/ru/mail/utils/ExifReader2.as
diff --git a/flash/core/src/ru/mail/utils/LoggerJS.as b/flash/core/src/ru/mail/utils/LoggerJS.as
new file mode 100644
index 00000000..89288592
--- /dev/null
+++ b/flash/core/src/ru/mail/utils/LoggerJS.as
@@ -0,0 +1,27 @@
+package ru.mail.utils
+{
+ import ru.mail.communication.JSCaller;
+
+ public class LoggerJS
+ {
+ private static var on:Boolean = false;
+
+ public function LoggerJS()
+ {
+ }
+
+ public static function enable(value:Boolean):void
+ {
+ on = value;
+ }
+
+ public static function log(str:String):void
+ {
+ if (on) {
+ trace ("# LoggerJs.log",str);
+ var data:Object = {type:"log", target:str};
+ JSCaller.jsCaller.callJS(JSCaller.callback, data);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/flash/image/.settings/org.eclipse.core.resources.prefs b/flash/image/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000..57dd049c
--- /dev/null
+++ b/flash/image/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,3 @@
+#Tue Sep 18 20:44:58 MSD 2012
+eclipse.preferences.version=1
+encoding/=utf-8
diff --git a/flash/image/html-template/history/history.css b/flash/image/html-template/history/history.css
new file mode 100644
index 00000000..dbc47c61
--- /dev/null
+++ b/flash/image/html-template/history/history.css
@@ -0,0 +1,6 @@
+/* This CSS stylesheet defines styles used by required elements in a flex application page that supports browser history */
+
+#ie_historyFrame { width: 0px; height: 0px; display:none }
+#firefox_anchorDiv { width: 0px; height: 0px; display:none }
+#safari_formDiv { width: 0px; height: 0px; display:none }
+#safari_rememberDiv { width: 0px; height: 0px; display:none }
diff --git a/flash/html-template/history/history.js b/flash/image/html-template/history/history.js
similarity index 100%
rename from flash/html-template/history/history.js
rename to flash/image/html-template/history/history.js
diff --git a/flash/image/html-template/history/historyFrame.html b/flash/image/html-template/history/historyFrame.html
new file mode 100644
index 00000000..07e3806f
--- /dev/null
+++ b/flash/image/html-template/history/historyFrame.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+ Hidden frame for Browser History support.
+
+
diff --git a/flash/html-template/index.template.html b/flash/image/html-template/index.template.html
similarity index 100%
rename from flash/html-template/index.template.html
rename to flash/image/html-template/index.template.html
diff --git a/flash/image/html-template/playerProductInstall.swf b/flash/image/html-template/playerProductInstall.swf
new file mode 100644
index 00000000..bdc34378
Binary files /dev/null and b/flash/image/html-template/playerProductInstall.swf differ
diff --git a/flash/image/html-template/swfobject.js b/flash/image/html-template/swfobject.js
new file mode 100644
index 00000000..bf35c07c
--- /dev/null
+++ b/flash/image/html-template/swfobject.js
@@ -0,0 +1,777 @@
+/*! SWFObject v2.2
+ is released under the MIT License
+*/
+
+var swfobject = function() {
+
+ var UNDEF = "undefined",
+ OBJECT = "object",
+ SHOCKWAVE_FLASH = "Shockwave Flash",
+ SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
+ FLASH_MIME_TYPE = "application/x-shockwave-flash",
+ EXPRESS_INSTALL_ID = "SWFObjectExprInst",
+ ON_READY_STATE_CHANGE = "onreadystatechange",
+
+ win = window,
+ doc = document,
+ nav = navigator,
+
+ plugin = false,
+ domLoadFnArr = [main],
+ regObjArr = [],
+ objIdArr = [],
+ listenersArr = [],
+ storedAltContent,
+ storedAltContentId,
+ storedCallbackFn,
+ storedCallbackObj,
+ isDomLoaded = false,
+ isExpressInstallActive = false,
+ dynamicStylesheet,
+ dynamicStylesheetMedia,
+ autoHideShow = true,
+
+ /* Centralized function for browser feature detection
+ - User agent string detection is only used when no good alternative is possible
+ - Is executed directly for optimal performance
+ */
+ ua = function() {
+ var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
+ u = nav.userAgent.toLowerCase(),
+ p = nav.platform.toLowerCase(),
+ windows = p ? /win/.test(p) : /win/.test(u),
+ mac = p ? /mac/.test(p) : /mac/.test(u),
+ webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
+ ie = !+"\v1", // feature detection based on Andrea Giammarchi's solution: http://webreflection.blogspot.com/2009/01/32-bytes-to-know-if-your-browser-is-ie.html
+ playerVersion = [0,0,0],
+ d = null;
+ if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
+ d = nav.plugins[SHOCKWAVE_FLASH].description;
+ if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
+ plugin = true;
+ ie = false; // cascaded feature detection for Internet Explorer
+ d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
+ playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
+ playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
+ playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0;
+ }
+ }
+ else if (typeof win.ActiveXObject != UNDEF) {
+ try {
+ var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
+ if (a) { // a will return null when ActiveX is disabled
+ d = a.GetVariable("$version");
+ if (d) {
+ ie = true; // cascaded feature detection for Internet Explorer
+ d = d.split(" ")[1].split(",");
+ playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
+ }
+ }
+ }
+ catch(e) {}
+ }
+ return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac };
+ }(),
+
+ /* Cross-browser onDomLoad
+ - Will fire an event as soon as the DOM of a web page is loaded
+ - Internet Explorer workaround based on Diego Perini's solution: http://javascript.nwbox.com/IEContentLoaded/
+ - Regular onload serves as fallback
+ */
+ onDomLoad = function() {
+ if (!ua.w3) { return; }
+ if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) { // function is fired after onload, e.g. when script is inserted dynamically
+ callDomLoadFunctions();
+ }
+ if (!isDomLoaded) {
+ if (typeof doc.addEventListener != UNDEF) {
+ doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false);
+ }
+ if (ua.ie && ua.win) {
+ doc.attachEvent(ON_READY_STATE_CHANGE, function() {
+ if (doc.readyState == "complete") {
+ doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee);
+ callDomLoadFunctions();
+ }
+ });
+ if (win == top) { // if not inside an iframe
+ (function(){
+ if (isDomLoaded) { return; }
+ try {
+ doc.documentElement.doScroll("left");
+ }
+ catch(e) {
+ setTimeout(arguments.callee, 0);
+ return;
+ }
+ callDomLoadFunctions();
+ })();
+ }
+ }
+ if (ua.wk) {
+ (function(){
+ if (isDomLoaded) { return; }
+ if (!/loaded|complete/.test(doc.readyState)) {
+ setTimeout(arguments.callee, 0);
+ return;
+ }
+ callDomLoadFunctions();
+ })();
+ }
+ addLoadEvent(callDomLoadFunctions);
+ }
+ }();
+
+ function callDomLoadFunctions() {
+ if (isDomLoaded) { return; }
+ try { // test if we can really add/remove elements to/from the DOM; we don't want to fire it too early
+ var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span"));
+ t.parentNode.removeChild(t);
+ }
+ catch (e) { return; }
+ isDomLoaded = true;
+ var dl = domLoadFnArr.length;
+ for (var i = 0; i < dl; i++) {
+ domLoadFnArr[i]();
+ }
+ }
+
+ function addDomLoadEvent(fn) {
+ if (isDomLoaded) {
+ fn();
+ }
+ else {
+ domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
+ }
+ }
+
+ /* Cross-browser onload
+ - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
+ - Will fire an event as soon as a web page including all of its assets are loaded
+ */
+ function addLoadEvent(fn) {
+ if (typeof win.addEventListener != UNDEF) {
+ win.addEventListener("load", fn, false);
+ }
+ else if (typeof doc.addEventListener != UNDEF) {
+ doc.addEventListener("load", fn, false);
+ }
+ else if (typeof win.attachEvent != UNDEF) {
+ addListener(win, "onload", fn);
+ }
+ else if (typeof win.onload == "function") {
+ var fnOld = win.onload;
+ win.onload = function() {
+ fnOld();
+ fn();
+ };
+ }
+ else {
+ win.onload = fn;
+ }
+ }
+
+ /* Main function
+ - Will preferably execute onDomLoad, otherwise onload (as a fallback)
+ */
+ function main() {
+ if (plugin) {
+ testPlayerVersion();
+ }
+ else {
+ matchVersions();
+ }
+ }
+
+ /* Detect the Flash Player version for non-Internet Explorer browsers
+ - Detecting the plug-in version via the object element is more precise than using the plugins collection item's description:
+ a. Both release and build numbers can be detected
+ b. Avoid wrong descriptions by corrupt installers provided by Adobe
+ c. Avoid wrong descriptions by multiple Flash Player entries in the plugin Array, caused by incorrect browser imports
+ - Disadvantage of this method is that it depends on the availability of the DOM, while the plugins collection is immediately available
+ */
+ function testPlayerVersion() {
+ var b = doc.getElementsByTagName("body")[0];
+ var o = createElement(OBJECT);
+ o.setAttribute("type", FLASH_MIME_TYPE);
+ var t = b.appendChild(o);
+ if (t) {
+ var counter = 0;
+ (function(){
+ if (typeof t.GetVariable != UNDEF) {
+ var d = t.GetVariable("$version");
+ if (d) {
+ d = d.split(" ")[1].split(",");
+ ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
+ }
+ }
+ else if (counter < 10) {
+ counter++;
+ setTimeout(arguments.callee, 10);
+ return;
+ }
+ b.removeChild(o);
+ t = null;
+ matchVersions();
+ })();
+ }
+ else {
+ matchVersions();
+ }
+ }
+
+ /* Perform Flash Player and SWF version matching; static publishing only
+ */
+ function matchVersions() {
+ var rl = regObjArr.length;
+ if (rl > 0) {
+ for (var i = 0; i < rl; i++) { // for each registered object element
+ var id = regObjArr[i].id;
+ var cb = regObjArr[i].callbackFn;
+ var cbObj = {success:false, id:id};
+ if (ua.pv[0] > 0) {
+ var obj = getElementById(id);
+ if (obj) {
+ if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) { // Flash Player version >= published SWF version: Houston, we have a match!
+ setVisibility(id, true);
+ if (cb) {
+ cbObj.success = true;
+ cbObj.ref = getObjectById(id);
+ cb(cbObj);
+ }
+ }
+ else if (regObjArr[i].expressInstall && canExpressInstall()) { // show the Adobe Express Install dialog if set by the web page author and if supported
+ var att = {};
+ att.data = regObjArr[i].expressInstall;
+ att.width = obj.getAttribute("width") || "0";
+ att.height = obj.getAttribute("height") || "0";
+ if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); }
+ if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); }
+ // parse HTML object param element's name-value pairs
+ var par = {};
+ var p = obj.getElementsByTagName("param");
+ var pl = p.length;
+ for (var j = 0; j < pl; j++) {
+ if (p[j].getAttribute("name").toLowerCase() != "movie") {
+ par[p[j].getAttribute("name")] = p[j].getAttribute("value");
+ }
+ }
+ showExpressInstall(att, par, id, cb);
+ }
+ else { // Flash Player and SWF version mismatch or an older Webkit engine that ignores the HTML object element's nested param elements: display alternative content instead of SWF
+ displayAltContent(obj);
+ if (cb) { cb(cbObj); }
+ }
+ }
+ }
+ else { // if no Flash Player is installed or the fp version cannot be detected we let the HTML object element do its job (either show a SWF or alternative content)
+ setVisibility(id, true);
+ if (cb) {
+ var o = getObjectById(id); // test whether there is an HTML object element or not
+ if (o && typeof o.SetVariable != UNDEF) {
+ cbObj.success = true;
+ cbObj.ref = o;
+ }
+ cb(cbObj);
+ }
+ }
+ }
+ }
+ }
+
+ function getObjectById(objectIdStr) {
+ var r = null;
+ var o = getElementById(objectIdStr);
+ if (o && o.nodeName == "OBJECT") {
+ if (typeof o.SetVariable != UNDEF) {
+ r = o;
+ }
+ else {
+ var n = o.getElementsByTagName(OBJECT)[0];
+ if (n) {
+ r = n;
+ }
+ }
+ }
+ return r;
+ }
+
+ /* Requirements for Adobe Express Install
+ - only one instance can be active at a time
+ - fp 6.0.65 or higher
+ - Win/Mac OS only
+ - no Webkit engines older than version 312
+ */
+ function canExpressInstall() {
+ return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312);
+ }
+
+ /* Show the Adobe Express Install dialog
+ - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
+ */
+ function showExpressInstall(att, par, replaceElemIdStr, callbackFn) {
+ isExpressInstallActive = true;
+ storedCallbackFn = callbackFn || null;
+ storedCallbackObj = {success:false, id:replaceElemIdStr};
+ var obj = getElementById(replaceElemIdStr);
+ if (obj) {
+ if (obj.nodeName == "OBJECT") { // static publishing
+ storedAltContent = abstractAltContent(obj);
+ storedAltContentId = null;
+ }
+ else { // dynamic publishing
+ storedAltContent = obj;
+ storedAltContentId = replaceElemIdStr;
+ }
+ att.id = EXPRESS_INSTALL_ID;
+ if (typeof att.width == UNDEF || (!/%$/.test(att.width) && parseInt(att.width, 10) < 310)) { att.width = "310"; }
+ if (typeof att.height == UNDEF || (!/%$/.test(att.height) && parseInt(att.height, 10) < 137)) { att.height = "137"; }
+ doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
+ var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
+ fv = "MMredirectURL=" + encodeURI(window.location).toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title;
+ if (typeof par.flashvars != UNDEF) {
+ par.flashvars += "&" + fv;
+ }
+ else {
+ par.flashvars = fv;
+ }
+ // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it,
+ // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
+ if (ua.ie && ua.win && obj.readyState != 4) {
+ var newObj = createElement("div");
+ replaceElemIdStr += "SWFObjectNew";
+ newObj.setAttribute("id", replaceElemIdStr);
+ obj.parentNode.insertBefore(newObj, obj); // insert placeholder div that will be replaced by the object element that loads expressinstall.swf
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ obj.parentNode.removeChild(obj);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ createSWF(att, par, replaceElemIdStr);
+ }
+ }
+
+ /* Functions to abstract and display alternative content
+ */
+ function displayAltContent(obj) {
+ if (ua.ie && ua.win && obj.readyState != 4) {
+ // IE only: when a SWF is loading (AND: not available in cache) wait for the readyState of the object element to become 4 before removing it,
+ // because you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
+ var el = createElement("div");
+ obj.parentNode.insertBefore(el, obj); // insert placeholder div that will be replaced by the alternative content
+ el.parentNode.replaceChild(abstractAltContent(obj), el);
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ obj.parentNode.removeChild(obj);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ else {
+ obj.parentNode.replaceChild(abstractAltContent(obj), obj);
+ }
+ }
+
+ function abstractAltContent(obj) {
+ var ac = createElement("div");
+ if (ua.win && ua.ie) {
+ ac.innerHTML = obj.innerHTML;
+ }
+ else {
+ var nestedObj = obj.getElementsByTagName(OBJECT)[0];
+ if (nestedObj) {
+ var c = nestedObj.childNodes;
+ if (c) {
+ var cl = c.length;
+ for (var i = 0; i < cl; i++) {
+ if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) {
+ ac.appendChild(c[i].cloneNode(true));
+ }
+ }
+ }
+ }
+ }
+ return ac;
+ }
+
+ /* Cross-browser dynamic SWF creation
+ */
+ function createSWF(attObj, parObj, id) {
+ var r, el = getElementById(id);
+ if (ua.wk && ua.wk < 312) { return r; }
+ if (el) {
+ if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
+ attObj.id = id;
+ }
+ if (ua.ie && ua.win) { // Internet Explorer + the HTML object element + W3C DOM methods do not combine: fall back to outerHTML
+ var att = "";
+ for (var i in attObj) {
+ if (attObj[i] != Object.prototype[i]) { // filter out prototype additions from other potential libraries
+ if (i.toLowerCase() == "data") {
+ parObj.movie = attObj[i];
+ }
+ else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
+ att += ' class="' + attObj[i] + '"';
+ }
+ else if (i.toLowerCase() != "classid") {
+ att += ' ' + i + '="' + attObj[i] + '"';
+ }
+ }
+ }
+ var par = "";
+ for (var j in parObj) {
+ if (parObj[j] != Object.prototype[j]) { // filter out prototype additions from other potential libraries
+ par += ' ';
+ }
+ }
+ el.outerHTML = '' + par + ' ';
+ objIdArr[objIdArr.length] = attObj.id; // stored to fix object 'leaks' on unload (dynamic publishing only)
+ r = getElementById(attObj.id);
+ }
+ else { // well-behaving browsers
+ var o = createElement(OBJECT);
+ o.setAttribute("type", FLASH_MIME_TYPE);
+ for (var m in attObj) {
+ if (attObj[m] != Object.prototype[m]) { // filter out prototype additions from other potential libraries
+ if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
+ o.setAttribute("class", attObj[m]);
+ }
+ else if (m.toLowerCase() != "classid") { // filter out IE specific attribute
+ o.setAttribute(m, attObj[m]);
+ }
+ }
+ }
+ for (var n in parObj) {
+ if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // filter out prototype additions from other potential libraries and IE specific param element
+ createObjParam(o, n, parObj[n]);
+ }
+ }
+ el.parentNode.replaceChild(o, el);
+ r = o;
+ }
+ }
+ return r;
+ }
+
+ function createObjParam(el, pName, pValue) {
+ var p = createElement("param");
+ p.setAttribute("name", pName);
+ p.setAttribute("value", pValue);
+ el.appendChild(p);
+ }
+
+ /* Cross-browser SWF removal
+ - Especially needed to safely and completely remove a SWF in Internet Explorer
+ */
+ function removeSWF(id) {
+ var obj = getElementById(id);
+ if (obj && obj.nodeName == "OBJECT") {
+ if (ua.ie && ua.win) {
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ removeObjectInIE(id);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ else {
+ obj.parentNode.removeChild(obj);
+ }
+ }
+ }
+
+ function removeObjectInIE(id) {
+ var obj = getElementById(id);
+ if (obj) {
+ for (var i in obj) {
+ if (typeof obj[i] == "function") {
+ obj[i] = null;
+ }
+ }
+ obj.parentNode.removeChild(obj);
+ }
+ }
+
+ /* Functions to optimize JavaScript compression
+ */
+ function getElementById(id) {
+ var el = null;
+ try {
+ el = doc.getElementById(id);
+ }
+ catch (e) {}
+ return el;
+ }
+
+ function createElement(el) {
+ return doc.createElement(el);
+ }
+
+ /* Updated attachEvent function for Internet Explorer
+ - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks
+ */
+ function addListener(target, eventType, fn) {
+ target.attachEvent(eventType, fn);
+ listenersArr[listenersArr.length] = [target, eventType, fn];
+ }
+
+ /* Flash Player and SWF content version matching
+ */
+ function hasPlayerVersion(rv) {
+ var pv = ua.pv, v = rv.split(".");
+ v[0] = parseInt(v[0], 10);
+ v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0"
+ v[2] = parseInt(v[2], 10) || 0;
+ return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
+ }
+
+ /* Cross-browser dynamic CSS creation
+ - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
+ */
+ function createCSS(sel, decl, media, newStyle) {
+ if (ua.ie && ua.mac) { return; }
+ var h = doc.getElementsByTagName("head")[0];
+ if (!h) { return; } // to also support badly authored HTML pages that lack a head element
+ var m = (media && typeof media == "string") ? media : "screen";
+ if (newStyle) {
+ dynamicStylesheet = null;
+ dynamicStylesheetMedia = null;
+ }
+ if (!dynamicStylesheet || dynamicStylesheetMedia != m) {
+ // create dynamic stylesheet + get a global reference to it
+ var s = createElement("style");
+ s.setAttribute("type", "text/css");
+ s.setAttribute("media", m);
+ dynamicStylesheet = h.appendChild(s);
+ if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
+ dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1];
+ }
+ dynamicStylesheetMedia = m;
+ }
+ // add style rule
+ if (ua.ie && ua.win) {
+ if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) {
+ dynamicStylesheet.addRule(sel, decl);
+ }
+ }
+ else {
+ if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) {
+ dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
+ }
+ }
+ }
+
+ function setVisibility(id, isVisible) {
+ if (!autoHideShow) { return; }
+ var v = isVisible ? "visible" : "hidden";
+ if (isDomLoaded && getElementById(id)) {
+ getElementById(id).style.visibility = v;
+ }
+ else {
+ createCSS("#" + id, "visibility:" + v);
+ }
+ }
+
+ /* Filter to avoid XSS attacks
+ */
+ function urlEncodeIfNecessary(s) {
+ var regex = /[\\\"<>\.;]/;
+ var hasBadChars = regex.exec(s) != null;
+ return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s;
+ }
+
+ /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only)
+ */
+ var cleanup = function() {
+ if (ua.ie && ua.win) {
+ window.attachEvent("onunload", function() {
+ // remove listeners to avoid memory leaks
+ var ll = listenersArr.length;
+ for (var i = 0; i < ll; i++) {
+ listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]);
+ }
+ // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect
+ var il = objIdArr.length;
+ for (var j = 0; j < il; j++) {
+ removeSWF(objIdArr[j]);
+ }
+ // cleanup library's main closures to avoid memory leaks
+ for (var k in ua) {
+ ua[k] = null;
+ }
+ ua = null;
+ for (var l in swfobject) {
+ swfobject[l] = null;
+ }
+ swfobject = null;
+ });
+ }
+ }();
+
+ return {
+ /* Public API
+ - Reference: http://code.google.com/p/swfobject/wiki/documentation
+ */
+ registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) {
+ if (ua.w3 && objectIdStr && swfVersionStr) {
+ var regObj = {};
+ regObj.id = objectIdStr;
+ regObj.swfVersion = swfVersionStr;
+ regObj.expressInstall = xiSwfUrlStr;
+ regObj.callbackFn = callbackFn;
+ regObjArr[regObjArr.length] = regObj;
+ setVisibility(objectIdStr, false);
+ }
+ else if (callbackFn) {
+ callbackFn({success:false, id:objectIdStr});
+ }
+ },
+
+ getObjectById: function(objectIdStr) {
+ if (ua.w3) {
+ return getObjectById(objectIdStr);
+ }
+ },
+
+ embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) {
+ var callbackObj = {success:false, id:replaceElemIdStr};
+ if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) {
+ setVisibility(replaceElemIdStr, false);
+ addDomLoadEvent(function() {
+ widthStr += ""; // auto-convert to string
+ heightStr += "";
+ var att = {};
+ if (attObj && typeof attObj === OBJECT) {
+ for (var i in attObj) { // copy object to avoid the use of references, because web authors often reuse attObj for multiple SWFs
+ att[i] = attObj[i];
+ }
+ }
+ att.data = swfUrlStr;
+ att.width = widthStr;
+ att.height = heightStr;
+ var par = {};
+ if (parObj && typeof parObj === OBJECT) {
+ for (var j in parObj) { // copy object to avoid the use of references, because web authors often reuse parObj for multiple SWFs
+ par[j] = parObj[j];
+ }
+ }
+ if (flashvarsObj && typeof flashvarsObj === OBJECT) {
+ for (var k in flashvarsObj) { // copy object to avoid the use of references, because web authors often reuse flashvarsObj for multiple SWFs
+ if (typeof par.flashvars != UNDEF) {
+ par.flashvars += "&" + k + "=" + flashvarsObj[k];
+ }
+ else {
+ par.flashvars = k + "=" + flashvarsObj[k];
+ }
+ }
+ }
+ if (hasPlayerVersion(swfVersionStr)) { // create SWF
+ var obj = createSWF(att, par, replaceElemIdStr);
+ if (att.id == replaceElemIdStr) {
+ setVisibility(replaceElemIdStr, true);
+ }
+ callbackObj.success = true;
+ callbackObj.ref = obj;
+ }
+ else if (xiSwfUrlStr && canExpressInstall()) { // show Adobe Express Install
+ att.data = xiSwfUrlStr;
+ showExpressInstall(att, par, replaceElemIdStr, callbackFn);
+ return;
+ }
+ else { // show alternative content
+ setVisibility(replaceElemIdStr, true);
+ }
+ if (callbackFn) { callbackFn(callbackObj); }
+ });
+ }
+ else if (callbackFn) { callbackFn(callbackObj); }
+ },
+
+ switchOffAutoHideShow: function() {
+ autoHideShow = false;
+ },
+
+ ua: ua,
+
+ getFlashPlayerVersion: function() {
+ return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
+ },
+
+ hasFlashPlayerVersion: hasPlayerVersion,
+
+ createSWF: function(attObj, parObj, replaceElemIdStr) {
+ if (ua.w3) {
+ return createSWF(attObj, parObj, replaceElemIdStr);
+ }
+ else {
+ return undefined;
+ }
+ },
+
+ showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) {
+ if (ua.w3 && canExpressInstall()) {
+ showExpressInstall(att, par, replaceElemIdStr, callbackFn);
+ }
+ },
+
+ removeSWF: function(objElemIdStr) {
+ if (ua.w3) {
+ removeSWF(objElemIdStr);
+ }
+ },
+
+ createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) {
+ if (ua.w3) {
+ createCSS(selStr, declStr, mediaStr, newStyleBoolean);
+ }
+ },
+
+ addDomLoadEvent: addDomLoadEvent,
+
+ addLoadEvent: addLoadEvent,
+
+ getQueryParamValue: function(param) {
+ var q = doc.location.search || doc.location.hash;
+ if (q) {
+ if (/\?/.test(q)) { q = q.split("?")[1]; } // strip question mark
+ if (param == null) {
+ return urlEncodeIfNecessary(q);
+ }
+ var pairs = q.split("&");
+ for (var i = 0; i < pairs.length; i++) {
+ if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
+ return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1)));
+ }
+ }
+ }
+ return "";
+ },
+
+ // For internal usage only
+ expressInstallCallback: function() {
+ if (isExpressInstallActive) {
+ var obj = getElementById(EXPRESS_INSTALL_ID);
+ if (obj && storedAltContent) {
+ obj.parentNode.replaceChild(storedAltContent, obj);
+ if (storedAltContentId) {
+ setVisibility(storedAltContentId, true);
+ if (ua.ie && ua.win) { storedAltContent.style.display = "block"; }
+ }
+ if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); }
+ }
+ isExpressInstallActive = false;
+ }
+ }
+ };
+}();
diff --git a/flash/image/lib/blooddy_crypto.swc b/flash/image/lib/blooddy_crypto.swc
new file mode 100644
index 00000000..5aaca952
Binary files /dev/null and b/flash/image/lib/blooddy_crypto.swc differ
diff --git a/flash/image/src/Base64.as b/flash/image/src/Base64.as
new file mode 100644
index 00000000..3005ba59
--- /dev/null
+++ b/flash/image/src/Base64.as
@@ -0,0 +1,76 @@
+package{
+ import flash.utils.ByteArray;
+ public class Base64 {
+ private static const decodeChars:Array =
+ [-1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, 62, -1, -1, -1, 63,
+ 52, 53, 54, 55, 56, 57, 58, 59,
+ 60, 61, -1, -1, -1, -1, -1, -1,
+ -1, 0, 1, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14,
+ 15, 16, 17, 18, 19, 20, 21, 22,
+ 23, 24, 25, -1, -1, -1, -1, -1,
+ -1, 26, 27, 28, 29, 30, 31, 32,
+ 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48,
+ 49, 50, 51, -1, -1, -1, -1, -1];
+ public static function decode(str:String):ByteArray {
+ var c1:int;
+ var c2:int;
+ var c3:int;
+ var c4:int;
+ var i:int;
+ var len:int;
+ var out:ByteArray;
+ len = str.length;
+ i = 0;
+ out = new ByteArray();
+ while (i < len) {
+ // c1
+ do {
+ c1 = decodeChars[str.charCodeAt(i++) & 0xff];
+ } while (i < len && c1 == -1);
+ if (c1 == -1) {
+ break;
+ }
+ // c2
+ do {
+ c2 = decodeChars[str.charCodeAt(i++) & 0xff];
+ } while (i < len && c2 == -1);
+ if (c2 == -1) {
+ break;
+ }
+ out.writeByte((c1 << 2) | ((c2 & 0x30) >> 4));
+ // c3
+ do {
+ c3 = str.charCodeAt(i++) & 0xff;
+ if (c3 == 61) {
+ return out;
+ }
+ c3 = decodeChars[c3];
+ } while (i < len && c3 == -1);
+ if (c3 == -1) {
+ break;
+ }
+ out.writeByte(((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2));
+ // c4
+ do {
+ c4 = str.charCodeAt(i++) & 0xff;
+ if (c4 == 61) {
+ return out;
+ }
+ c4 = decodeChars[c4];
+ } while (i < len && c4 == -1);
+ if (c4 == -1) {
+ break;
+ }
+ out.writeByte(((c3 & 0x03) << 6) | c4);
+ }
+ return out;
+ }
+ }
+}
\ No newline at end of file
diff --git a/flash/image/src/FileAPI_flash_image.as b/flash/image/src/FileAPI_flash_image.as
new file mode 100644
index 00000000..3912d1d9
--- /dev/null
+++ b/flash/image/src/FileAPI_flash_image.as
@@ -0,0 +1,97 @@
+package{
+ import flash.display.Bitmap;
+ import flash.display.Loader;
+ import flash.display.Sprite;
+ import flash.display.StageScaleMode;
+ import flash.events.Event;
+ import flash.external.ExternalInterface;
+ import flash.utils.Timer;
+
+ public class FileAPI_flash_image extends Sprite
+ {
+// private const testImageBase64:String = '/9j/4AAQSkZJRgABAQAAAQABAAD/4QBGRXhpZgAASUkqAAgAAAABADEBAgAjAAAAGgAAAAAAAABieS5ibG9vZGR5LmNyeXB0by5pbWFnZS5KUEVHRW5jb2RlcgD/2wCEAAMCAgMCAgMDAwMEAwMEBQgFBQQEBQoHBwYIDAoMDAsKCwsNDhIQDQ4RDgsLEBYQERMUFRUVDA8XGBYUGBIUFRQBAwQEBQQFCQUFCRQNCw0UFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFP/AABEIAJoAzQMBEQACEQEDEQH/xAGiAAABBQEBAQEBAQAAAAAAAAAAAQIDBAUGBwgJCgsQAAIBAwMCBAMFBQQEAAABfQECAwAEEQUSITFBBhNRYQcicRQygZGhCCNCscEVUtHwJDNicoIJChYXGBkaJSYnKCkqNDU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3eHl6g4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2drh4uPk5ebn6Onq8fLz9PX29/j5+gEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoLEQACAQIEBAMEBwUEBAABAncAAQIDEQQFITEGEkFRB2FxEyIygQgUQpGhscEJIzNS8BVictEKFiQ04SXxFxgZGiYnKCkqNTY3ODk6Q0RFRkdISUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqCg4SFhoeIiYqSk5SVlpeYmZqio6Slpqeoqaqys7S1tre4ubrCw8TFxsfIycrS09TV1tfY2dri4+Tl5ufo6ery8/T19vf4+fr/2gAMAwEAAhEDEQA/APatfZj4h1IDp9pk/wDQzX6JSX7uPoj8qrX9rL1f5kakhetbrQnoOTJPJNVYlLUuRuI+5pGi0JhcNj0FAXDzi3UmlYVx24kdaLCFCE9zQTykiN5fG6kykrAbg5wDRYTbJYdxHJNOw46krZHrVJFWHRkk0MuKGzjA4ppCkl0M+RHdqqxha41bRieTTsJQZILU0aGigSiE7cc0WuWojTZbupNIr2VyCa28odSaLEuHKVy56c1XKKxXlYmhokgMZkHOaOUOW5VuLE4JBNGxLpmfLp5JppmDpI+X/wBv+0+z/B7R25516Ef+S9xXgZ0/9nj/AIl+TPo8gp8uKk/7r/NH2H4hG3xBqXr9pk/9CNelS+CPojyKy/eS9WQR5NbGaVyxGnNMu1iZI2LcCgOUmFuxH9KB8rHpCF6igaiuorShBwKdiXoQG4bdxQ0ZNsfGC55ySaVgSY2HULCTUDYLfWz3yjLWqyqZVHqVzkCoU4X5b6nU8PUSvJWNqCAL1qmVCCW4+ZAFoRUtEVi+08VVjC9hjyFuKexLdx8Ue807jim2WVtR34qObsbKHcf9nwKd7mqiQyqsSszMFVQSSTgAU9h7HJ6P8UvB/iHVJNN03xLpt9fISDDDcKST7c4P4Zrmni6EFec0l5tI2jh6snaMW36HQTQ+auRgg9xXUndXRyTi7lcWBzzV3RKgD6eB2ovcv2aIZLYIMChMOWyIJIRSY3FMrNbKc8YNJGfJc+Vv+Citv5XwV0Rv+phgH/ktc14Odf7vH/F+jPdyaNsRL/D+qPrrXrMHXtRP/TzJ/wChGvTpfw4+iPHq0v3kvVkcNso61tYSpotLAo7UFciHnCVVh8ohm44oSJ2MfxL4itvC+g6hrOos8enWELXFxIkbPsRRknABNcuJxuGwSi8TUUOZ2V2ldvZLu32RVHDVsS3GlFu2rt0XdnxZ40/4KQzWuuSR+GfC1vdaVExXzNSmZZpR/eAXhPp839K8qtm6i7U43R7NLJuaN6krM9U8C/tL+MfHvg+LxDH4G0fw3pUilhquua/styASCwRYd+3IP5V8xjuN8Jgqn1dU5Tq/yx1/yX4ntYXg/E4mPtlJKn3eh13h+28X/FOD7UuttLpkw+SSygksNPCnglW3efdD0IaFTXRl2PzjOJc86KpUvN3b+St+bIxuDy3KlyQqe0qf3dl6yf5JfM9T8GfDqDwhbRxrfXFwVYv5cQW2gDHqRFHgHP8AtFie5J5r7Slh1T6/ov69T5WpVdTdGx4jvNX0mzFxpWkprRXmS2F0IJSvrHuUqzf7LMg/2q0qOas4q5MIxeknY5jwj8Z/C/jHVptFW6k0jxDCcS6LqyfZ7tD7KThh7qSDU069Oo7LR9h1MPNK+6O6+wFjzmuk5PZ33Hpp2O1CK9lYnjtCOi0GsYWFuLQyQ7VlaFv76AE/qCKyqQ5043sax9181rmBeaFq7X5uU1+adcYNvcxrtP0ZNpU+/P0NfPYvKcTOH+x4h05eauvzT/E9rD5hShK2IoqcfLR/LR/kfNf7aPiW80jwLDYroHibTnu5lhuLuDVxcabNHg5Qk4fJwOGRe/WvkcPT4hwleUczqxlRa0cb3b9GtPvZ69eeV1qaeEi1Ps0vzTOW/Yb+GPjPTdW1nxKfC11b6LeWXlwXssflNK28MAhfGQcHLcDpX53xzldfimhQwWW+9KM7t/YSs1q9m12V2fY8NYillFWeIxisnHRdfkv87HpniD4oeJfAHx00Kz1y50uDwtqpFlLawStJJHKxxHKzEAfewDgdCck4FfovAnD2L4ZwP1XEV3UTd7fZj5Rvsu/TrZHyXFOa4fNcYp06Cg7brd/4j6Ha2OemK/VbnxXKRvasR0ouHKVZLJj2NFw5LkQ04nqKTaD2Y19LI6ChSQeza0Pkz/gpLbGH4G6GcY/4qOAf+S11XhZ074eP+Jfkz2Mqjy1n6fqj6y1x/wDie6iMf8vEn/oRr1KT/dx9EeXUV6kvUrx5Pat7mfKy0kbEdKRfIxZLWRj0qtCXFodHYZPPU9hSuSqZwv7Vfw0+I/ib4N6h4a8D6OZ77U9sVyxkCbrc8uATxkjCkHsTX8x5pgc2zDjH+08xpynhaN/YqOqT0s2t7t6t23S6JH7Fl7weFyZ4fDySrT+K+j89fwPy18ffsu/FD4c7n13wnfQoqs5kiAlXaoyTlc9K/QJ46lTUXXvDmdlzK1321PJjhqk7qkuayu7a2XfQ+nP2I/2PE8V6JZ+OPH0D3WkOwl0fRLhiYpBn/XyJ02k/dXvyTxjP1WX5bGs/b1Vp+f8AwD53G42cf3VJn6DxWCRIqIoRVACqBgAegFfYJKKSWx826d9WOFmSe1VcFTuSpZ4HrQmP2R578ZPgF4Z+Nuiraa1A1vqVup+w6va/JdWjdQVbuM8lTwa4sVhY4iN0+WS2f+fdHTh6joy1V4vdHwt/aPxg+APxKu/CfibxrrCaXaxefZTpK7wXcBbClAThecAgcg18xCtiacpUqjakv6uvI9evTo2jUprR/wBWPP8AxN+1P4/8O+K21PRfGV8hjkw0TzGWOUejo2QenXH0rRYmdP3udmdOgqjtKOh96fshftT6b+0f4amtbyOPTvGWmqPttkh+SdO00Xse6/wn1BBr0cLm9OtWWHqaSa07O26Xmt7dtutsq+WzpwdWGsVv5H0KbKve5jynALXRpb67jggjMk0jYVR3NY18RChTlVqOyRVOjKpJRjuyfxz8M0l0Ga2uLG11G6UedDHdQLPF5y/MhKMCCAwHav4q4z474gq5zSpxXJhlNWgteZJ68763XRaL8T9YyfKsHQoSc1zTaer6ena3fc4bUv2jtP1v4bWOtTSJYpcWqzGDeB5fAJT8OR0r+qsPCCinTWh8bUqpJymz8zP2gfjfYfEnxck1us1s9q+wTFsq2DncD2r2oVVFcjZ89Vpyr1FWitEfqLoOqR634f0zUYmEiXlrFcK/qHQN/WvWTTVyGuhbfNK47CKoYfMRU3HYRliTnNK5XKRu8WDSuylY+PP+Cmjq3wL0IKQf+Kkg/wDSW6rxs3v9Xjfv+jPQy9JVnbt+qPrvV9Bmk1y/OODcSH/x416dOSUI+iPOnTbmySLw7IoBbgVfONUjQt9AJGTk0nUZSpFptDwmABSUynTQy20XypkkYfIrBj9BUzn7j9AjSTkj1GPxXZPp0TIhuZpyIoIkGWd8Z/AADJPYAmvhsXWhg6UsRUdox1Z9NCm5y5UeK/FT4e6r4qElzPMRERgROTgDocDuPrX8gZ9xPm2e4v6zXpONKL9yPZd+l2+r+R+w5LUwmWYd0Y2c5fFLv5ehpeEtGe18O2Ns6IrW0awYiUKuFGFwB04A4r+vuBuIJZ9ktOvWVqkfdkvTZ/NWfrc/F89wFPB46caPwPVfPp8mactnt7EV+hcx8+4BDZsxzg0cwlAs/YGIwB+lTzmjigi0slhnNDmSqaPNv2iv2bNG/aE8DPo1/NLpupW4d9P1OAZe3kI7jI3IcDK8ZwOQQDXDiaSrxsnZnRTaptNq5+Mvi34NeLvBnirWNA1nS5LTUtMm8qcSnaGJztZScblYDII6ivgMViaeEmoVnZs+swuGqYuLlRV0hvwk8YeIPhx490/XfD+oS6XqVlMCXTkOM/NGw6MpHBBrto4eOJlGT+y00+z6Nf15HBiMRLCwkl1TTXdeZ+73gDWbbx/4K0fxFZrtg1G2ScJnOwkfMv4HI/CvtIYhTVz53l7bHpnw70MJe3F46f6tdiHHc9f0/nXz2dYi9ONFdT0sDStJz7HWalpsciMzKM4PJr8hzHKqM37aUVdH09KrL4UfhR8W9a1n4e/E3xt8Nb25322kahMLbGQDCx3KOe20j86/XMhxMcRhoPrZHwmcUKlOV7+7c+eNckZ9SYEAEt1zXoVm+c68KkqaP278Bada23gPw1Fp0guLBNNtlgmH8aCJdrfiOa96m1yI4px95m9/ZryVTaEoMqXGnSRg0uYXI7XMyaF84yc07kWbK32C4kJ25p8yQckmfJH/AAUt0+W0+BuhPJ0PiOAf+S11Xi5vJOhH1/Rnp5fBxqtvt+qPvfVDHHql0SBnzW/ma66fwL0M5/ExqFJMYGaq5Bo20SkAYGalmqRbSxVutQ5W2C1ya+0yJdHvQflzA4LDt8p5rnnNs0jGzTR5f+yV4xHj2LW9VlKyW2hRQ2EIxk+fKgmlPpwjQqMe9fnWe19FQtfS/wB7t+h9Bhff947/AMYarPqF2Ym4jXgBa+IqZN7aN6kT0Fi+R2iyp4fCxz+W33XGOfWvsOFKDy2rKlFWUvzPMzGft4qfY6D+zoZDkqDX6l7Ro+dcUyxb6PDjIUVnKs0CgWZNKijGcCslWbKcUVhbxq3AFbczZFlcSdIoYmkkZY0QFmZzgAdyTS5rbjsz5M/bu8K+FfHPw9Se3uoR4wsctCIQvmyQ8/u2P1JZQfU4618zi8Dh8xxKr1E/dVlqexRxeJwGHnGhKzep+TdvBHZajsjVgA/8XXPv717cKUaStBaHi1Jzq0+ab1P2S/YQ1x7/AOAGnW8rhms7iSJFJzhCFYfqWrHBvmdSPZ/ov+CVa1OD8j7D8Mx+RokTYwXy5/z9AK8DMZc1d+R7eGVqaMfxd4pGlwSbeWwQv1r5LMebk8j0KMlzWPxA/bU826/bE8Szn/WTQWztgdcwJ+fSvpeFtcPBrz/M8XOWlCV+6PEPG2kSWYW5EeBk8gV9zjKLjFTSPByzEKpem2fr5+xTrTeLf2X/AAJePJ5skVm1ozE5OYpGj5/75rTDzvTR6NWNpM9zhsAck4GK2lOxkkUr+1TJHBqoy0HboY0liobO2r5ieUcsUUf3lApXLUT44/4KlsjfALw/tIOPE9vz/wBut3Xj5p/BXr+jO3Cq036H2fr7r/al0A3/AC1b+Zr0qUvcXocdRe8xbCZAnXmqsyEkzSgulRgc1LTZoXI9UQcDmpdO4rmZ488YW+g+B9bvZw7RRWcuQnUkqQBXPUjyR5jWCcnY8c/4J7ZufgVr2rJKDDrHiO6uo03Z2RpHDCo/8g57dRX5dj5OrmiguiX+f6nuYf3cM33Z67q0rfaX3EHk5Nfc0cPGcLSR5c207oSwlEkmFYox43Dt710qjTg7xWqE22rNnFeAP2gtI1bxPf8AgvWtQg07xnps7W0tnKdgucH5ZIs9dwwdvUc9RgnvpVI1Yrm0Z5/NaTj2PWYtcMXB5rZ0VI0vbcJ9fMgwBRGgkTKV9CBLuVhv7DmrajFakRTbsjgfiR8SItH0PU7w/vLeziyrsuYzNnC5HfB5/CvHqydR2Wx6lOCpLmlufmR8evjjJf31zBY3D/ankLSythiTnOc5P/66unDl1Z5lWo6ztHY+f7W/jv8AUJJJlDyu24kYHNdaknoctWnKENGfoh+wB8SvsmvW/he4mEcN9bSeTGT1kTDAj3wH/WuWFF0KvOvtDw9f2kfZPoffnhT4mR3M17pVzhGt3KQSDgMB2PvXkVsI6rdWPVs+ihU5YqJyXjnXWmuGVWyo9683G5c3RbQ6da0lc/KL9s62Nj+1NPfPEEW80mCUORwxG5M/+OAfhXTw3QdGmoS8/wAzzc4n7WlK3dfkePanfHV7CeCZlYBDjjoetfoE5e1puLPlKFL6vUjOJ+iP/BKzxDHrfwE1fRGlP2rRdblXYD0hljR0P4uJvyrxKEuWLifYVFzNM+x7xWtwTgkfSuyL5jG1ipBAL5zwfyq2+VCVmNvNKMC7gfwNKM76FWMW6tZpN2I8itbok+Kf+CodrNB8BtBZ1IQ+Jrcc+v2W7rycza9ivX9GdeG+N+h9daxeMdZvsk8TuP8Ax4169KPuR9DzJy95iQXEhOQSAa15TNSbNewWSbucetGiNE2XWhKNtU5NGjGfIH7fvxpn8KaZpXgCwuRHeawn2q7AYA+SGIRfxYMf+A1zzcLpMwxEqkaT5NO/od7/AME+NfTT/wBmLSrSNI4il9eh2XO6RvPf5m/2sEfgBXxH1FSx8qjPep1nKgke0Xt08kuS2ATx719TCnyqyOaUjU0pAZEC+3Nc84W1NE7nxP8Atkfst+IvHX7QEuvaTPZWmlanp8Es11dylfKnjBjICqCfuohBHcmuKpXVOytucs8LKpLmi7FjwZpH7Q/wws0bTPGejeL9EtlG6116aTMaj+ESuoYegy/0qqWPmnyxX6mn1aajdtP8D2zSf2odNsPCSX/im0t9L1pWKy6fYXyXiYGPnEi8AHPQ8/WvahiHOOsbM5mow1lI4rRP2utQ+JnxY0Xw3oQg0zSLtmQtMdzzYVicevA6e3euarP+YuhWVWoo01p3H/tffFKy0/RxoFhNmO3X986n7zAD8+vt+FcyTZ04moox5UfmH4vuJrrUbmeVyeeBn/PPNbSWlzz6Ftupl+G7d7rVokUEgMM4NVh489RI0xklCi2z6Q+EPjWy+HXjfwzrjytGNP1GCaXbyTCHHmADvlNw/GvdrUVKm0t+h8bh60qdeM2tL6+nU/Qrwf43sPEGsaxcaZeR3tqbtzFNE2VZScqR9QRXz+ETdJcy7/mfeymnJ2Ot1CY3YJY5z1Nd3IpLlZlJvc+C/wDgof4Au7a88O+NoIme1ihbTrp1BPl/MXiJ7YJLD6/p5ipfVZO2xm17TTufElxqs8F1lXID4yK6KlaUJadS4UIThqtj7Z/4JSfEiHw18YvE/hW6kAh1/T1mgUnGZoGZh/45JJWCu52R0r4D9QNV1KJomAOK74U2mYNmJb6v9mYlWGK6ZQ5tyU+o99aF6209vSkococzZIl7Fb43MMe9KSYI+L/+Cr93Bc/s7eHfLxu/4Sq2PHp9kvK8jMYtUl6/ozsw3xfI+q9Q0UNqt6xbrO5/8eNe3Tn7kfQ890/eY4abHCnUmtOZhyJFqGdbdQAOKhsfKhxvlV9zfKo5JPpQnYHG5+Of7UXjbUfiX8fvFniuBJJtIgvGtrWT+Dy4V8pcfXaT9WPrXiVarlVU+iubTjGUXS6ux+iP7Kfg2XwR8D/DWk3EpN15TXcykbcSSsZCuPbdj8KdKF3zvqOLsrI9PvZDFIMNXobIze50Xhi7MuN3LAivMrzsddOPMfM37Vv7TnhXT/FttoGizSa1r+npIlzDAd1upYD5GI6spAPHToT1FY+wVRJz2OariVSbUdX+B8oeNPjj4k8c3X2bVtXkWztwPKtvM2xR57LGvA7ZOPXmuyEIQVoI8qpWq1fjZynxFuL3RtIs5Yr77RDeASMyMdqgNwpHc55rSE9WTKle2t0zqv2aJ9V0/wAUzePtQgktrPT4JBp7yDarSshjGwd8KW/GuZyUpWPRpRVH3uxi/GHxzNrc1zJLKzFnywdslue/r1z+NbqNtWcUqntJaHhnibVBNCiIclsEccjp/UVnVlaNkduGp3lzMb4MmMWoq+TnqT/OunBP3rkZjG9Oxo6rr00msMgO2OM4Ce/etaleXtuXscdDCxVHm6s92/Z8/aIm+FetRrc77nRrhgJ4VOXT/aT/AArqtGWqOalKeHlrrE/Rnwj410rxxoFvqujXsV9YzLlZEboe4I7EehpRVj2VNTV0cH+0i9ifgt4yOoQR3dsumTuYpB8rMqEr9DuAwfWliIp0nczV7n4/31wGuSpGQDjNeHWqJz5WenShaN0ehfs9+PB8NvjZ4F8SySmG3s9UiF03TFuzBJf/ABx3oU+WUZAlduJ+5txaI3Vs/jXuqRyWsZN9boD9fSruKxZ0awXzh83U4rKc7DUTrIPCdvekb8k1wzxLiaKCbPir/grp4aj0b9nDw1MnAbxZbJ/5J3h/pXl4uv7Wml5/5nVQjyzPsm68I3k99ct8oBlYj6Zr044mKikc0o3bJ/8AhBJGiy7c+1T9cV9A9mZ174Nmt/mAJWt44lSJ5LHn3x91S4+Hfwc8RaxEVXUHhWxsM/8AP1OwhhP4PIpPsDSnWVmkNRS959D5E/4ZvjufgZdXxgEUNzqNhpum7hy0UU6yXEuPViGX1yreua81yTTXb8/6sZRg9Zvdn0V4Kv8AbZRwsdoVQBXqUY3ikK9jV1G72ncGwPetZrQVz5t/a2/aluvhVoK+F/DV15PiXVIWMtxGfmtLc5UsPR25Cntgn0ry6kVKep0KTUGkfnvpmsX51fNq8st7OfLXy8s7luMDuSatzSepyyopx0Ptv9mL/gnTr/xGhGufEe4vPC+lzANb6dCo+2TDOd7lgRGPQfePcAYynKTV1oilRj1PtPTv+Cc/wdZLc6nbatrSwIVjivL0BQSMbjsVST9SRx0rzZVZp7nXGlTtY+Pv2tvD2j/BXxPN4Q0HUby+sbcLMPtrK0kBKDEYIAyoGTnrkkGu7CxW/c8zFtwtCJ8TeKNTa5mZM/IWzz/hXVJ9DOjFfEcTdyGebJ7niuKb5me1TXKixos5huQ2cfjW2HlaRliY80bGrLYz391d3cUTyx2+1pnAyFB6Z/KrqSiq++py0/dpJEBupY5FdMYHBxW3tGndByRaaZ6t8Evj/r/wi1v7XpsxltZSFutOlY+VOo/k3XDDke/SuuFXm9TkcJUXeOx9ZfH/AOMekfEH9mTV9W0ibMd9DHEYmPzxsZEDIw9RzVVmpU9DeMk5WPzfuI/MvW9M14dSPNVPVg7Uxk3yyooHK8n3NYTXvJFwejZ/Qd8CbVfiJ8FvBfiQSGRtS0m3ndwc5fywH/8AHga7PrvLozB0m3dHWt4AXfmRyfrWn1xPYn2bQ5tAi00blAOPWmqrqaA42NDTrwxkA4ArGpC400fFf/BYycTfsxeGMf8AQ32v/pFe15daPLE6aLuz753RJNJkZO40nzaEOyLkKxzrwBWEm4s0jaQS6cjr9wU1UfcJUz5L/bZspvEfiP4SeCbVzG2q66bhwDwfLUKhI9A8qH8K7oVLU5TZy1I7R7nWfHjSrLwD4D8A2EEKJpdnr9jaMH/55MHRj9SCT+dQn+7lbe1xy9zl9TyS8tJvDGuXVhJnMEjJ+Vexhql0kc9RcrJbzUPtNucHJx1Fd0ppoxs0fkt8afFN74t+K/ia9nLS3El88Ea9SFQ7EUfgorwatXllI9ClC8Vc/RX9g79ka0+Fq6L418VQpd+MNRaL7PbSKCunRuw4HrIQeT2zgdyeJScnzM6HFJH6UQ6fCqghRTdWTZMaaJfKEYIAqb3E42eh+M37bmqvrPxr8WXCybjFeOFPT5QdoXHsc/WvZoq0EeBiXerZnyNrhICMwK5Xj+X4dqueiub0NXZHNROWLd+OBXJBt3PVkrWJ9OjP2xAADmtqC99GdZ+4z7b/AGS/ghceLdBihiUHUfEP2pomZRgIsLrH+qs3P96ufHU2pKp5nHQfM+W2yPnr4Z+Crb4pazqPh68Eei+IYLSSWKaKDy1MsbgOksa4UDB/hAIwTg1Ea/Ko36nS4N8z6dDg9b0m88Oaxc6bqMBtb+1cpIh9fUHuO4Peu6M9THl08i0PE2oWfh69023mb+zrt43mgLHbuU5DD39fXj0ronJtXRlCK57SOflGbksDzXPP4rnXH4LFeePPPXgfhXLJdTaL6H7U/wDBI/4tR+Pf2bp/C1xMG1PwnftbeWfvfZZf3kLf99eav/bOvOxEbSv3OqnqrM+1Lq1DA8CsoysEomPNoguH+bpXZGvynP7NsfDoEUf8NKWJbF7I+Gf+CzNklt+y34WZRyfGNqP/ACSvq5JTc9zphFJ6H3LcRj7Q+WAGTXUpabHK9yezmWFsBsis5xci4yUdjRF0pXrXNyO5vzo+avFwTxh+3H4QsSoltfC/hqTU5SeVWW4lkjUfX90jCuySaopd3+X/AATmbUqq7JFr9tS9UeBvDtrCGeaTVllQIQCCkTkE+2SM/wBadBNtpirtNJLuec/E9/7RvbfVEUj7XbxTk9M70B/qa6aEnGKIqxurnD2F4zfu2yDmu/ndjkR5V4Z/ZN8HaB8TZfGeZ7+6e5a6htboqYYZGOSwGOSMkjPQ47gV5dalzScrnbCbson2N8Prj+1fEmi2w6JKGHf7gLf+y0kkkattn0CkrIetEopkJtF2OUMuScVztWNE7n4dftOayl98TvF0mUbOpz5brhd5xz1r3KSfKj5us71WfM2v3f2luhI7ZpVXdWPRw8bMw4jgsD0xzXJF2dj0ZdGbfhTTLjWdbsrK3ANxeTx28e7pudgo/DJrsoJt37nHiGlHXofsp+xl4Oh03xPbGGPda6Np/kxtjuRsB+uM0ZklZQRhgbtubOu8ZfsAfD7WvjHdfFHSTf6Jr0sU7zWNlIq2lxO8bIXZcZGd2SAQCea8LTY9Wz6Hwn+0F8Crfx5bSTQhLPxBYKyQTMMCQAk+U/tnOD2yfU13QvZHBzcjt0PiSYz2M0lncoYJYXaOSNhhlYHBFdtOppYbgr8yM6QmJ9ozx61lN22OiPvK5aFuJbZZQ2DjJFaqF4cxhz8s+U+7f+COfjGbRfj/AOItA34g1nRnYoT1eF1ZT+AZx+Jrza8fdTO2m9T9jgrOtef1N9WNEe3rSuAyQgcimrsR8C/8Fn5A/wCy54WGenjK1/8ASK+ptWHGVz7Pb7TLdyg/d3kfrXre4oo4Le8y4ls6DOayc4sfIwkndTtANCitxO6Pn74DQyeLPj98cvFrNuSDVbbw7bnqAttbRmQf99uT/wACpzklNR7L89SYxbbZsfFrw5P44+KnhvQEkRjFZtOI2UMqM7kb29gsTdPWiVRQjzo6IQu7Md+0B4S0/Q9J06Wz2/6NAsBjXqY0AGa1wUJVqcrrVGdeSiz5ke9EE5dCGGSRz71vzaHH1LV34njtLUqcMzAZ/OspvQ0TPcP2Yyda8SXF0EZ0src5c/dDMcD8cZ/KuOb0sdMHc+lJIT1A5qVItoq6rcjStF1C/kOI7a3kmb6KpJ/lSbTdgSe5+Bvxc1yPU9cvZlw7zzPI7EDuzYJ49TXuReh85a82zxbU3Ltk59x/n2rlq6ntUUkUo4znpmsYxOmTPXv2b9GGpfFXw+jxB1ikecq3ONsbFT/31j9K9rCQ96NzxsXPR2P25/ZX8GDw/wDDwajMuLjUpfMBPXy1+Vf13H8RXjY2pzVnFdD0cLT5aSbPbFZcYry2md6a6H59ftM+FW8H/ETVYgmy3un+0xcZBR+ePx3D8K7qLurHm142lofB/wC0l8KzBb/8Jdp0GDwL9EGBg9JMfofw962lePvE0n9lnzk0m+4AJ6jqanm5pWO1K0TqNV0mXS9D0u4MbfZ72ASxyZyCwJVx9cjp9K9Vpwox81/w55UJc9aSe6f4dD3z/gmf4jXQf2x/BSu4Rb/7TZnJxy0DkD8SoFeNV96DPWirNH7wCchq4OQ2TEkmyKSj3E2V5JeDzWtiXJs+BP8Agsqxb9mLwxk5/wCKwtf/AEivaU1oOnufbr3AW5lAdR8x/nXdbRHM9yeKVyuTIuKzaXYauOSRXbJZeKTVlYe54z+zJpzeH/BviKe6CwXuteKdY1KZehJN28Sk/VIUP0IqowcpSm11/r8bg9Cxp1/b6V8avG+uXkpzBY2tvbDGSMxAkj2yzfjmiVJ1JQgtv+CXF8qcmeQfEf4k3F9qMwnm3xkEBW7CvoINUYWSPNqO8jw/UdZtrSBMzfviudvevLdkrsjm6GFpt5f+IL1LWxUzzXMqxxoT3JGK5JNvYqLvsfo/8IPhnbfCXwZb6W9yLu/kPnXlz2eTAGFz/CAMD8T3rmV5Hpxgqa3O4edGTKuKai07MG10OS+JuspZ/DbxZM8ihU0m6PPr5TYH4nA/GtHDl1Ju2rH4K+KvKub26lJGGbcjEZPtXtQ2Pmk9dDzXVgBPIOMDpkVz1T2aHwop2YLygAcn0rOnqzoqaK59I/sl+FpdR8ZX2qNuWOxgVAQSCWdgMA/QGvocLB3cn0R8/iJp2ij9xdAhj0Pw7pdgjBBBbRx4HHIUZr5SSdScpPqfRJcqSRpedtXcZM1lbW1ivmeTftJ/CKT4o+DBd6Wqv4g01Wkt04BnTq0eT37jPf60oycJEzhzR8z8+NQu4bW5ns9Yg3qN0UttJHlSOQysp7deDXoRakeY5cjsz4n+MHgaDwH8Rb+xs0aPTJQLixLMW/dMM7cnrg5X8KwjBQqP8D0oz56Z6JpXh6Pxr+zS12ygXnhy9m8p92C0TFWce/3hx7e9fR0YKtgrveLZ4VWbo4vT7VjzX4QePJvhV8XPCniyBfMk0PVLe+MY/wCWixyBmX/gQBH41864+84n0CfuqR/Rxo/iC08RaRZapp1wlzYXsKXEEy9HjdQyt+IIrj5bblO5O90c4yPzq1ETbIXnHUsPzq1Em58Hf8FjX3/sx+GOQf8AisLXp/15XtZVlaJpR3O51r4yeI7LxDqMMUsYSO5kRcjsHIpqrPQHYlt/jr4mRdrPAw/3TR7SQtCaP42+ICCytDnvwf8AGn7Wdy+WNrlC2+I2smYsk0cCglgkaBVyTk8fUk1Xtp9xcqe5nx+J7i9uvFGrXsgNxJFBCrqOyh8jOOOg/MV6GFbnNN9jCt7sWfP3j/x+/wBoYrkyMdqqpIJ+tbVp3djyZz5dTzaTUJL24aSS4ZHYYznIHPb864pK5zXu9T2L4LWLwa1aXaMzmFvOBI4G3kD88UkuRczPRoRu0kfTs/xX8Q3aBXugPoK5lVktj1uRMmtvizrtvF5YnV/dl5o9rK92HJE4r43/ABB1W9+B3j8zTgGLTUlXHHIuYePyyKtTlUaizCquSDkflhrlmZtA/tEhgMqqMqnBOOcn1xj8q9Wnqj59WU7M8xu2DSNkbRnAFcVR6u571NWSGadLslGRk+hqaEuWRVaN0fon+zL4XsNC8AeGrSEia91aVdSu24O3jO0egCKB9frX16nGjhXLyufNqPtcQo+Z9cS/EjXSwZ7vkcjivifbTPq3BEl18V9fuIVj+0iNR3QYJpe0luHIizH8adfggWMSRtjqxHNTzthyI8a+K3gSx+K+pTapCiab4gaMnfAPkunAyFdc43HpuHOcZzVKbuctbDxqarc+Svir4Dh8Y+G0ivIdt7YyeWZIhh4vVv8Ad55B/u+tdUf3mjPM550fej8ziIdJTwL8M9e0SC4eeR4nlmdOhLYBHTphV/XpXv0I+yw8oJ9zxq1d18TGTXVfmfOVyWjuSce9fNVW4zufZwtKJ+vH7J/7Q2sQfs/eDoXCXH2az+zBnY5wjFR+goqTs7JEpK2p6e/7QesEsxijH0Y8VPtn2HZFCf8AaA1mQnCoB/vGn7eXYTSPlH/go38Ur/xt8D9C0+6UBI/EUE4IOeRbXK/+zGsa1VzjZo1p25j2nxPdAeJtY46Xkwz/AMDNY30Ie5VW7CxZ71V9Bli3ueh9eopjTXUtLciMAnoelIdzgpPGok0TxLKsvyC+MStuOPlRT+XOM/WvZwuibPMxFXRng2s+J4rq4dTKj3Wd8vzD5O4z9cUpy+88fm5tSxo4S6mRy+c/Nt6jPrRGLlqxRaWh9LfC3TBp2gi6YYknxtz1Cjv+JrmxE7vlR7+Fhyx5mdsJyoyOT/OuE9AVLncQw4zRcDhvj7fsnwW8XRsV8uW2ijbJxwbmIf8A163oK8zkxbtRkfG+p+F7kfBLTJmt9iszO0zDsXOAPXqDgdmr26Cu2jwaqcYxnY+f/Etqtvc/LnOeV6YP/wBeufGQ5XoelgpucdShpluwBlcEDJFcuHi/iZ1V5r4UfpD+y3Y+b4M0u8KMjQ2MaKrHOCy88n6H6Zr1cdWUcPGHc8vAUuatOfY9wfzGYEk49q+aPoXoQXt9Dp9s008yxRr1Z2AFJyS3HtqZkfifTLvzPJv4JNi5YJIDj8qSaJ5l3K2meJLDVo2e0uld0PKg4Zfwp3sZqSlseVfH6wj8OXkHjGzg+0W1+PI1CzQYO8jDdsfN94e/Hau2lLm0PMxcPZy9otnufKPxC1W+vHvkV1GnyQErKiY3L95WOB14AI+tetCpKUXE8RU4xqqW7ujxd9jofMGJtuFYjqa5Hyta7n0iun7ux9m/sVeOm1DwPqnh2Rdz6VOssLk8GObPy/gysf8AgftXBWWqkjWPmfRMjnY7EkgDPFcjfQ023M95yqHnHORg0Gdz53/bcct8KNIzn/kNRf8Aoieokb0tz6c8THb4q1cA7s3s3/oZq7mXUrCRwqjApXKHeaRnb8p9fQU7gTxTGZBG7YJON31707gt9Twz40fEPwR8M/CUWk6TqMWs3zTTLL5LZeWQsG3MDyAM7efTHavZoVI06Tctzy8TD2j5KfzPPP2TPhVp3xNvNZ8T+I1lvLI3Zs/LLkK74V3fIIOUDLjHrXmTqNtuL1R2U6EXaNRaaEXhXXbfQ/i/qPgi8uFlNteNBFdIwZZEDcHPQHGK9LD1lL3ZbnlYjC+wnzRd43sfaEBe3jht4ISqxjZtXtjgV5U5XbbPeitFFEUWs3cF4IJdOn8vBdZkIKHtjrnNZ3fYLvmsy7bX63UiOFljDD7ko24p3uWmtzz/APaTudvwh1uJo5PKuPIjygzz9ojP64/SunDt85x43+CzwX4/+JrbQvh94N8OWvyb7M37qCCBGWKJwOh/dsfy9TXt4SXLFzZ4+MSny04+p8zad4a1j4h6zLY6PZS3k8cbSMkY4CqOST0Hbn3rnr1faS1djuoR9jBNK4yTRLzS7iTTry2e1vIpRC0EhAYMSBj+taUtY6GNSXvn6V/Ci1g8LeDdK09tgn8nfIR0yAB19gFH4Vw4+rzV3D+XQ68DH2dFPq9Tpn8V2sc6QLKDK+WAB6D3rzOZXPQ5uhW1nVdKv7BxfGKeJMuY5E3gn6UXiyZNWOSk8YeD9KsZLn7JFZuOsItGSRj7DFK9+hl7SCV7EJ+IOgW8st1a2xljIHmTxw4Y4GeARk/hUt9gVSGrK3irXrXx1oGoaIYpLPT9StJlGoSIdtvKqFo224yQGC5x0GetXGo4smpapFxtoz4R8L6zcaLaa/4d1/53tzlGlbkK5w4DHschh9W9TXp4OrabjPY87H0VOEKlFak/wy+FLfErSfEkdtbXU13ahBYXEbBY1kznbITxgj056VFaSSUV3OyMpJqVt0ep/suzax8OPE/iPQdb0S7huZVgLKycoFdlZgR1B3g8HB21hOTlBM0dovQ+ir7x3axNaRPb3hjmbYZFgdgvPGcA1yp3JdTa5n6p8S7HS0RLu0uoWxub91936n096LkurZbHzp+1d8SLXxl4MsbWxktpbRNSjlR4598hxFMDlQMAfN61Er21OihPmm15H2Z4qvBF4s1gAj/j8mH/AJENWDepSF393oWNAXJVlcsVwMgZPpRcepKiOzKoQ7up2mk2UfEHxw8PeEn+IWt2V7rF/o93DMGjmSyNxB+8Jdt4BDDG4YI3dOlb875bGMVaT03Pe/gH8SPh34Z+G2l6Hp2v2kaWYCXdzcKYRNO5yWw2COTjLAdgCa50tLGrmr2lofHXj/VLuL4y6ldxrumttSJi8oZ3hH4YY65xn3zWt3KQ1FKnZn6HeGPjl4L1vTY7qPxFZQIR88d7J9nlibupVwOh/Cm4TW6M1Vhs2dVpnjLRtRtIbyy1azvbSTO2SOdSG+nOKxlKyNouPc4fxh8abXRfE6aNb6RdavFPBl7iEExRvuPDN0Axzxk+1TFpxuZVKtpWSucj+0/8S4Lz4I6osJltrq5MFtAIQwKkyK5LMBjOFI655qqTak2tyqk1UilJaHyR46n1HxPr+l6Fpjpqg0vR7WyM8LqVO1PMkd33Y+VnYbieAo9K9KnV5YWuc86cL8z0sfYvw30a48BeBdI07SdFtba9ESJc3jLG32jIJLOQ2SMZ5+mAa8+pUlJ26Fq6V4rVnxF8RvG9+fiV4gmuIY/Pj1KRwG5Ksr+vccV2UcZOilG10jOeChVTldq61Pt7wBrNt4s8AaJquoXsKx3GmJMyK0anzR95Dls9hxgDkcmuOo+ebm92XGCpx5G9EjflfwjprB70wW7piHfNujjYHggH7p5PUHvWF9S/3a3IL34t+DvDj6g1xc2dnBZxmaMJKsn2mEjG5FBLA78rtPPA45qr3d0ilOKRhXv7QGgCws7q0sLe9tr0KSqOEdc5A+THsM5x1OelWoz2sZSrxTLtp8bNFuJhEy2Fpc7W/wBHnfYqRgZ+Z+QuQM+uMHjIovJoamrmDfftE+HZ9Pvba58TeH7FAjRKIZbiUsTkbSwi+Xt8w3davkktQ5+bRfkz4z8a63peox6zeNqUV5rF9JEscdjE/kRRLnILuqEnhMYXGO9Pmtc2jT1Vuh6l8B/Fs/gvwvY2dpeCye7mkupZfNi2tnapDBzjgImOh+9jg8ihJ6J/mcmIqNT0Oz1TxVYojfbfEs142c7JbhLwhSeg2Hjr0Hqa0VCpLZHFKtBbv8mXrv4i6Rqf2fdrtyYLdikcQidMDoMFhk498dRVfVaz1a/ITxNLuZOoaL4OuHeZ/Grz3EjN5zXMbO0gyDsZhHkKfRSO+O+V9XqL7Jo69FrSX4M88+P+l+HV8G2moWGpW15qzaksTJbxKo8kxuwbhAfvcck9qyqU5QiuZHbhJ05Sai9f+GPuPxZKieLNXG1nxezMMdz5hwfTv9eKzLe5TSaPcMh144IHTjv16e/WjoCHrcbDhMSJyBtbJPGfrnJNJvTUpWJJJZ1YpuwDz8r9VBPHGOw4PvUt3Kszn9e8JnWpPkuZY7lidgh2gOccZwAfwJ9+9SpO4pRbW54xqn7JEN9etdtr8cK7WOHsUk+ViSQSSckEnk8jAweBVe18gULaNlrSP2VX0OLZaeJ9WBZiUa2MasmAOQ28YHHr2461Sraar8yJU23dP8ihL+ybpTzmaY61cl3DM7tbEk8ZyfP565roVePVL8f8jB06nRv8P8zc0X9nWDw6yDT21C0kkwTsljAx3J2sR/n8tPrFPrFfic7w1WW83f5GxZ/CDWtPZPseoPbv8yoJLwqCW45AQ5ycZpPEUv5V9wo4Or/O/vOX8Qfs4+MfGVtcQ614xwsrq728UskkGQF2kIe4CgZPp2rOVal2OqFGrB6SVvM5fTv2LZ7Mm6XxaQ4JAEcBQkcHqTx1HXv0rONVRldXOiac42djo9R+APjCS1it4vE99JFGfkmm1J4+gwR8qnsT9Oa39tQlpJf1+BxOhiIu8LfO5wFz+xt4murmV5dW0+STOWCu7yE9+dnJ9efzNZSq0vP7jqj7ZK1l97NKx/Y51qxJU+JFXZ/z7yMoOeuOOTxziqjUpdbk1PbPblOvsP2ZdUe1Bu9Tafy12A3cjSHYPl9SO3A9xXRHEUI6KJwzwuInduVvQtWn7Nl1Y+ZJHqVrascpGyIxkU9M/NwM5GOvf05r63Ta+Ej6jVWrmPvP2bLy6vPOvfFDXN3GBtYxBZBjleQRkZ9eOD71k8VTa1ibrC1IvSf5/wCZmXX7J1qSSdYN1K7KrIY2PJPXJJBPP09ah4mHSJaw9Sy9/wDAqS/sn2wgZPNgd1O+KJIAQ/TPzHABPv7daXt4W1givZVE7qbHwfsq6Vabtt4Mk5zHaglgeB/ESB+AH1qlXitooiVGpL4ps1JP2ZvD9sgVdVu5ZO6mCNQCcA45Oecf4Dvr9adtImTwmvxDZf2bdIt1bbqVw5UAbBCjDjsCOpHrT+uPaMRPCJ6uRE37NenwmJjqE6MxxIggUc+mSRnOB09Rg0LGXbXLqDwbSXvOxJD+z9o9rnfqVw+SSxjiCgDGc/oeg7U/rk9uUj6nDrJnmn7R/wAN9L8GeA7O5spZ5pn1KOImVuFXypicADHJUevT3rmrV5VVaSO/B0I0pNx7HB3f7UnxPvrue5n8Tb555Glkb7BajLE5JwIsDnsOK5bno+zg9bEf/DTfxKwAfEgIAxg2Fse5P/PP3pD9nHsPX9qP4nJ93xNjr/zD7X/41QHJHsIP2oficsfljxOdvobG29Mf886A9nEeP2pvigAAPE5GDkYsLXP/AKKoHyRD/hqf4obg3/CT8jIH/Evtcc/9svc0rIfKhD+1L8UDjPignGQAbG279f8AlnRZC5EKP2p/igGVh4n+ZSSD9gte/wD2yphyRBP2p/ijG5YeKDuJySbC1Of/ACFSsg5EP/4ar+KOQf8AhJlOBgf8S20/+NUWHyrsKv7VvxSUDHicD3/s60z+flUuVByoYf2p/igR/wAjQfX/AI8LX1B/55ew/KnZByoen7V3xUjQIvinCjoP7Ptff/pl7mlyq9x2Wwo/av8Aimrlx4nUMc8jTbTv/wBsvejlQuVDT+1V8USwY+JlyOn/ABLbTj/yFQopbByoUftX/FQY/wCKqPGP+XC17dP+WVOyDlQ1/wBqv4pSdfFJ9/8AQLUZ+v7rmlyoOVB/w1T8UdzsPEwDOAGYadaAn8fK96LIOVDT+1N8Ty4f/hJuQSw/4l9rjP08qiyFyIR/2pPifIgRvE+5QMYNha//ABr9adkHJF9CIftNfEpRgeJOpBObG25x/wBs6YvZx7B/w018SsAf8JKeDn/jxtv/AI3QHs49hB+0x8SQEH/CRj5M4zYWxP8A6Lo30D2cF0Irr9pD4j3hQy+JZPlYP+7tYEyR67YxuHscigTpQfQdJ+0n8R5U2P4j3KOgNlbcf+Q6d2J0oPdGF4v+Lfivx5pkWna7qgvrOKUTpH9mhjw4VlByiAnhm796RcYRjsj/AP/Z';
+ private var callback:String = 'callback';
+ private var t:Timer = new Timer(300, 10);
+ private var b:Bitmap;
+ private var scaleMode:String = 'exactFit';
+
+ public function FileAPI_flash_image(){
+ if (stage) {
+ init();
+ }else {
+ addEventListener(Event.ADDED_TO_STAGE, init);
+ }
+ }
+ //===== init
+ private function init(e:Event = null):void {
+ removeEventListener(Event.ADDED_TO_STAGE, init);
+ // config stage
+ stage.align = "TL";
+ stage.scaleMode = 'noScale';
+ stage.quality = "best";
+
+ stage.addEventListener(Event.RESIZE, onStageRecize);
+
+ scaleMode = loaderInfo.parameters['scale'] ? loaderInfo.parameters['scale'] : 'exactFit';
+ // parse fv
+ var p:String = loaderInfo.parameters['callback'];
+ if (p != null && p != "") {
+ callback = p;
+ }
+ addCallbacks();
+ ready();
+ }
+
+ private function ready():void{
+ t.addEventListener("timer", onTimer);
+ t.start();
+ }
+ private function onTimer(e:Event):void{
+ if ( callReady() ) {
+ t.removeEventListener("timer", onTimer);
+ t.stop();
+ }
+ }
+ //===== js callback
+ private function callReady():Boolean{
+ var isReady:Boolean = false;
+ try {
+ if ( callback.match(/^FileAPI\.Flash\.(onEvent|_fn\.fileapi\d+)$/) ) {
+ var r:* = ExternalInterface.call(callback);
+ isReady = ( r != null );
+ }
+ }
+ catch ( e:Error ) {}
+ return isReady;
+ }
+ private function addCallbacks():void {
+ ExternalInterface.addCallback('setImage', function(base64:String):void {
+ t.stop();
+ setImage(base64);
+ });
+ }
+ //===== image processing
+ private function setImage(base64:String):void {
+ try {
+ // create bitmap data
+ var l:Loader = new Loader();
+ l.contentLoaderInfo.addEventListener(Event.COMPLETE, function(e:Event):void {
+ e.currentTarget.removeEventListener(e.type, arguments.callee);
+ // add to stage
+ b = l.contentLoaderInfo.content as Bitmap
+ addChild(b);
+ onStageRecize();
+ });
+ l.loadBytes(Base64.decode(base64));
+ }
+ catch (err:Error) { }
+ }
+
+ private function onStageRecize(event:Event = null):void {
+ if(scaleMode == 'exactFit' && b) {
+ b.width = stage.stageWidth;
+ b.height = stage.stageHeight;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/flash/src/ru/mail/data/vo/ImageTransformVO.as b/flash/src/ru/mail/data/vo/ImageTransformVO.as
deleted file mode 100644
index 39824c0d..00000000
--- a/flash/src/ru/mail/data/vo/ImageTransformVO.as
+++ /dev/null
@@ -1,39 +0,0 @@
-package ru.mail.data.vo
-{
- /**
- * Value object with transformation matrix
- *
- * @author v.demidov
- *
- */
- public class ImageTransformVO
- {
- public var sx:Number = 0;
- public var sy:Number = 0;
- public var sw:Number = 0;
- public var sh:Number = 0;
- public var dw:Number = 0;
- public var dh:Number = 0;
- public var deg:Number = 0;
-
- public function ImageTransformVO(sx:Number = 0, sy:Number = 0, sw:Number = 0, sh:Number = 0, dw:Number = 0, dh:Number = 0, deg:Number = 0)
- {
- super();
-
- if ( !isNaN(sx) )
- this.sx = sx;
- if ( !isNaN(sy) )
- this.sy = sy;
- if ( !isNaN(sw) )
- this.sw = sw;
- if ( !isNaN(sh) )
- this.sh = sh;
- if ( !isNaN(dw) )
- this.dw = dw;
- if ( !isNaN(dh) )
- this.dh = dh;
- if ( !isNaN(deg) )
- this.deg = deg;
- }
- }
-}
\ No newline at end of file
diff --git a/flash/src/ru/mail/utils/LoggerJS.as b/flash/src/ru/mail/utils/LoggerJS.as
deleted file mode 100644
index cdf82bab..00000000
--- a/flash/src/ru/mail/utils/LoggerJS.as
+++ /dev/null
@@ -1,18 +0,0 @@
-package ru.mail.utils
-{
- import ru.mail.communication.JSCaller;
-
- public class LoggerJS
- {
- public function LoggerJS()
- {
- }
-
- public static function log(str:String):void
- {
-// trace ("# LoggerJs.log",args);
- var data:Object = {type:"log", target:str};
- JSCaller.jsCaller.callJS(JSCaller.callback, data);
- }
- }
-}
\ No newline at end of file
diff --git a/html5.png b/html5.png
deleted file mode 100644
index 282374c8..00000000
Binary files a/html5.png and /dev/null differ
diff --git a/index.html b/index.html
new file mode 100644
index 00000000..a7439a45
--- /dev/null
+++ b/index.html
@@ -0,0 +1,254 @@
+
+
+
+
+
+
+ FileAPI — a set of javascript tools for working with files.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/index.php b/index.php
deleted file mode 100644
index 22093a59..00000000
--- a/index.php
+++ /dev/null
@@ -1,372 +0,0 @@
- $images
- , 'data' => print_r(array(
- '_REQUEST' => $_REQUEST
- , '_FILES' => FileAPI::getFiles()
- ), true)
- ));
-
- if( empty($jsonp) ){
- echo $json;
- }
- else {
- echo ''
- ;
- }
- exit;
- }
-
-
-
-
-
- function fetchImages($files, &$images, $name = 'file'){
- if( isset($files['tmp_name']) ){
- $filename = $files['tmp_name'];
- list($mime) = explode(';', @mime_content_type($filename));
-
- if( strpos($mime, 'image') !== false ){
- $content = file_get_contents($filename);
- $images[$name] = 'data:'. $mime .';base64,'. base64_encode($content);
- }
- }
- else {
- foreach( $files as $name => $file ){
- fetchImages($file, $images, $name);
- }
- }
- }
-?>
-
-
-
-
-
- FileAPI :: TEST
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
,
-
-
-
-
or
-
-
-
-
-
-
-
- Drag'n'Drop
-
-
-
-
-
-
-
-
-
-
-
diff --git a/lib/FileAPI.Camera.js b/lib/FileAPI.Camera.js
new file mode 100644
index 00000000..d97eb983
--- /dev/null
+++ b/lib/FileAPI.Camera.js
@@ -0,0 +1,297 @@
+/**
+ * @class FileAPI.Camera
+ * @author RubaXa
+ * @support Chrome 21+, FF 18+, Opera 12+
+ */
+
+/*global window, FileAPI, jQuery */
+/** @namespace LocalMediaStream -- https://developer.mozilla.org/en-US/docs/WebRTC/MediaStream_API#LocalMediaStream */
+(function (window, api){
+ "use strict";
+
+ var
+ URL = window.URL || window.webkitURL,
+
+ document = window.document,
+ navigator = window.navigator,
+
+ getMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia,
+
+ html5 = !!getMedia
+ ;
+
+
+ // Support "media"
+ api.support.media = html5;
+
+
+ var Camera = function (video){
+ this.video = video;
+ };
+
+
+ Camera.prototype = {
+ isActive: function (){
+ return !!this._active;
+ },
+
+
+ /**
+ * Start camera streaming
+ * @param {Function} callback
+ */
+ start: function (callback){
+ var
+ _this = this
+ , video = _this.video
+ , _successId
+ , _failId
+ , _complete = function (err){
+ _this._active = !err;
+ clearTimeout(_failId);
+ clearTimeout(_successId);
+// api.event.off(video, 'loadedmetadata', _complete);
+ callback && callback(err, _this);
+ }
+ ;
+
+ getMedia.call(navigator, { video: true }, function (stream/**LocalMediaStream*/){
+ // Success
+ _this.stream = stream;
+
+// api.event.on(video, 'loadedmetadata', function (){
+// _complete(null);
+// });
+
+ // Set camera stream
+ try {
+ video.src = URL.createObjectURL(stream);
+ } catch (err) {
+ video.srcObject = stream;
+ }
+
+ // Note: onloadedmetadata doesn't fire in Chrome when using it with getUserMedia.
+ // See crbug.com/110938.
+ _successId = setInterval(function (){
+ if( _detectVideoSignal(video) ){
+ _complete(null);
+ }
+ }, 1000);
+
+ _failId = setTimeout(function (){
+ _complete('timeout');
+ }, 5000);
+
+ // Go-go-go!
+ video.play();
+ }, _complete/*error*/);
+ },
+
+
+ /**
+ * Stop camera streaming
+ */
+ stop: function (){
+ try {
+ this._active = false;
+ this.video.pause();
+
+ try {
+ this.stream.stop();
+ } catch (err) {
+ api.each(this.stream.getTracks(), function (track) {
+ track.stop();
+ });
+ }
+
+ this.stream = null;
+ } catch( err ){
+ api.log('[FileAPI.Camera] stop:', err);
+ }
+ },
+
+
+ /**
+ * Create screenshot
+ * @return {FileAPI.Camera.Shot}
+ */
+ shot: function (){
+ return new Shot(this.video);
+ }
+ };
+
+
+ /**
+ * Get camera element from container
+ *
+ * @static
+ * @param {HTMLElement} el
+ * @return {Camera}
+ */
+ Camera.get = function (el){
+ return new Camera(el.firstChild);
+ };
+
+
+ /**
+ * Publish camera element into container
+ *
+ * @static
+ * @param {HTMLElement} el
+ * @param {Object} options
+ * @param {Function} [callback]
+ */
+ Camera.publish = function (el, options, callback){
+ if( typeof options == 'function' ){
+ callback = options;
+ options = {};
+ }
+
+ // Dimensions of "camera"
+ options = api.extend({}, {
+ width: '100%'
+ , height: '100%'
+ , start: true
+ }, options);
+
+
+ if( el.jquery ){
+ // Extract first element, from jQuery collection
+ el = el[0];
+ }
+
+
+ var doneFn = function (err){
+ if( err ){
+ callback(err);
+ }
+ else {
+ // Get camera
+ var cam = Camera.get(el);
+ if( options.start ){
+ cam.start(callback);
+ }
+ else {
+ callback(null, cam);
+ }
+ }
+ };
+
+
+ el.style.width = _px(options.width);
+ el.style.height = _px(options.height);
+
+
+ if( api.html5 && html5 && !api.insecureChrome ){
+ // Create video element
+ var video = document.createElement('video');
+
+ // Set dimensions
+ video.style.width = _px(options.width);
+ video.style.height = _px(options.height);
+
+ // Clean container
+ if( window.jQuery ){
+ jQuery(el).empty();
+ } else {
+ el.innerHTML = '';
+ }
+
+ // Add "camera" to container
+ el.appendChild(video);
+
+ // end
+ doneFn();
+ }
+ else {
+ Camera.fallback(el, options, doneFn);
+ }
+ };
+
+
+ Camera.fallback = function (el, options, callback){
+ callback('not_support_camera');
+ };
+
+ Camera.checkAlreadyCaptured = (function () {
+ var mediaDevices = navigator.mediaDevices,
+ MediaStreamTrack = window.MediaStreamTrack,
+ navigatorEnumerateDevices = navigator.enumerateDevices,
+ enumerateDevices;
+
+ if (mediaDevices && mediaDevices.enumerateDevices) {
+ enumerateDevices = function (callback) {
+ mediaDevices.enumerateDevices().then(callback);
+ };
+ } else if (MediaStreamTrack && MediaStreamTrack.getSources) {
+ enumerateDevices = MediaStreamTrack.getSources.bind(MediaStreamTrack);
+ } else if (navigatorEnumerateDevices) {
+ enumerateDevices = navigatorEnumerateDevices.bind(navigator);
+ } else {
+ enumerateDevices = function (fn) {
+ fn([]);
+ };
+ }
+
+ return function (callback) {
+ enumerateDevices(function (devices) {
+ var deviceExists = devices.some(function (device) {
+ return (device.kind === 'videoinput' || device.kind === 'video') && device.label;
+ });
+
+ callback(deviceExists);
+ });
+ };
+
+ })();
+
+
+ /**
+ * @class FileAPI.Camera.Shot
+ */
+ var Shot = function (video){
+ var canvas = video.nodeName ? api.Image.toCanvas(video) : video;
+ var shot = api.Image(canvas);
+ shot.type = 'image/png';
+ shot.width = canvas.width;
+ shot.height = canvas.height;
+ shot.size = canvas.width * canvas.height * 4;
+ return shot;
+ };
+
+
+ /**
+ * Add "px" postfix, if value is a number
+ *
+ * @private
+ * @param {*} val
+ * @return {String}
+ */
+ function _px(val){
+ return val >= 0 ? val + 'px' : val;
+ }
+
+
+ /**
+ * @private
+ * @param {HTMLVideoElement} video
+ * @return {Boolean}
+ */
+ function _detectVideoSignal(video){
+ var canvas = document.createElement('canvas'), ctx, res = false;
+ try {
+ ctx = canvas.getContext('2d');
+ ctx.drawImage(video, 0, 0, 1, 1);
+ res = ctx.getImageData(0, 0, 1, 1).data[4] != 255;
+ }
+ catch( err ){
+ api.log('[FileAPI.Camera] detectVideoSignal:', err);
+ }
+ return res;
+ }
+
+
+ // @export
+ Camera.Shot = Shot;
+ api.Camera = Camera;
+})(window, FileAPI);
diff --git a/lib/FileAPI.Flash.Camera.js b/lib/FileAPI.Flash.Camera.js
new file mode 100644
index 00000000..ddf97e65
--- /dev/null
+++ b/lib/FileAPI.Flash.Camera.js
@@ -0,0 +1,111 @@
+/**
+ * FileAPI fallback to Flash
+ *
+ * @flash-developer "Vladimir Demidov"
+ */
+
+/*global window, FileAPI */
+(function (window, jQuery, api) {
+ "use strict";
+
+ var _each = api.each,
+ _cameraQueue = [];
+
+ if (api.support.flash && (api.media && (!api.support.media || !api.html5 || api.insecureChrome))) {
+ (function () {
+ function _wrap(fn) {
+ var id = fn.wid = api.uid();
+ api.Flash._fn[id] = fn;
+ return 'FileAPI.Flash._fn.' + id;
+ }
+
+
+ function _unwrap(fn) {
+ try {
+ api.Flash._fn[fn.wid] = null;
+ delete api.Flash._fn[fn.wid];
+ } catch (e) {
+ }
+ }
+
+ var flash = api.Flash;
+ api.extend(api.Flash, {
+
+ patchCamera: function () {
+ api.Camera.fallback = function (el, options, callback) {
+ var camId = api.uid();
+ api.log('FlashAPI.Camera.publish: ' + camId);
+ flash.publish(el, camId, api.extend(options, {
+ camera: true,
+ onEvent: _wrap(function _(evt) {
+ if (evt.type === 'camera') {
+ _unwrap(_);
+
+ if (evt.error) {
+ api.log('FlashAPI.Camera.publish.error: ' + evt.error);
+ callback(evt.error);
+ } else {
+ api.log('FlashAPI.Camera.publish.success: ' + camId);
+ callback(null);
+ }
+ }
+ })
+ }));
+ };
+ // Run
+ _each(_cameraQueue, function (args) {
+ api.Camera.fallback.apply(api.Camera, args);
+ });
+ _cameraQueue = [];
+
+
+ // FileAPI.Camera:proto
+ api.extend(api.Camera.prototype, {
+ _id: function () {
+ return this.video.id;
+ },
+
+ start: function (callback) {
+ var _this = this;
+ flash.cmd(this._id(), 'camera.on', {
+ callback: _wrap(function _(evt) {
+ _unwrap(_);
+
+ if (evt.error) {
+ api.log('FlashAPI.camera.on.error: ' + evt.error);
+ callback(evt.error, _this);
+ } else {
+ api.log('FlashAPI.camera.on.success: ' + _this._id());
+ _this._active = true;
+ callback(null, _this);
+ }
+ })
+ });
+ },
+
+ stop: function () {
+ this._active = false;
+ flash.cmd(this._id(), 'camera.off');
+ },
+
+ shot: function () {
+ api.log('FlashAPI.Camera.shot:', this._id());
+
+ var shot = api.Flash.cmd(this._id(), 'shot', {});
+ shot.type = 'image/png';
+ shot.flashId = this._id();
+ shot.isShot = true;
+
+ return new api.Camera.Shot(shot);
+ }
+ });
+ }
+ });
+
+ api.Camera.fallback = function () {
+ _cameraQueue.push(arguments);
+ };
+
+ }());
+ }
+}(window, window.jQuery, FileAPI));
diff --git a/lib/FileAPI.Flash.js b/lib/FileAPI.Flash.js
index d26feb47..6ec95736 100644
--- a/lib/FileAPI.Flash.js
+++ b/lib/FileAPI.Flash.js
@@ -3,35 +3,50 @@
*
* @flash-developer "Vladimir Demidov"
*/
-(function (api, window, document){
+
+/*global window, ActiveXObject, FileAPI */
+(function (window, jQuery, api) {
+ "use strict";
+
+ var
+ document = window.document
+ , location = window.location
+ , navigator = window.navigator
+ , _each = api.each
+ ;
+
+
api.support.flash = (function (){
- var nav = window.navigator, mime = nav.mimeTypes, has = false;
+ var mime = navigator.mimeTypes, has = false;
- if( nav.plugins && typeof nav.plugins['Shockwave Flash'] == 'object' ){
- has = nav.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
+ if( navigator.plugins && typeof navigator.plugins['Shockwave Flash'] == 'object' ){
+ has = navigator.plugins['Shockwave Flash'].description && !(mime && mime['application/x-shockwave-flash'] && !mime['application/x-shockwave-flash'].enabledPlugin);
}
else {
try {
has = !!(window.ActiveXObject && new ActiveXObject('ShockwaveFlash.ShockwaveFlash'));
}
- catch(er){}
+ catch(er){
+ api.log('Flash -- does not supported.');
+ }
}
- if( !has ){
- api.log('Flash -- does not supported.');
+ if( has && /^file:/i.test(location) ){
+ api.log('[warn] Flash does not work on `file:` protocol.');
}
return has;
})();
- if( /^file:/i.test(window.location) ){
- api.log('[warn] Flash does not work on `file:` protocol.');
- return;
- }
-
-
- if( 1 && api.support.flash && (0 || !api.html5 || !api.support.html5 || api.cors && !api.support.cors) ) (function (){
+ api.support.flash
+ && (0
+ || !api.html5 || !api.support.html5
+ || (api.cors && !api.support.cors)
+ || (api.media && !api.support.media)
+ || api.insecureChrome
+ )
+ && (function (){
var
_attr = api.uid()
, _retry = 0
@@ -51,16 +66,19 @@
if( child ){
do {
if( child.nodeType == 1 ){
- api.log('FlashAPI.Flash.init...');
+ api.log('FlashAPI.state: awaiting');
var dummy = document.createElement('div');
+ dummy.id = '_' + _attr;
+
_css(dummy, {
top: 1
, right: 1
, width: 5
, height: 5
, position: 'absolute'
+ , zIndex: 2147483647+'' // set max zIndex
});
child.parentNode.insertBefore(dummy, child);
@@ -69,7 +87,7 @@
return;
}
}
- while( child = child.nextSibling )
+ while( child = child.nextSibling );
}
if( _retry < 10 ){
@@ -83,27 +101,33 @@
*
* @param {HTMLElement} el
* @param {String} id
+ * @param {Object} [opts]
*/
- publish: function (el, id){
+ publish: function (el, id, opts){
+ opts = opts || {};
el.innerHTML = _makeFlashHTML({
id: id
, src: _getUrl(api.flashUrl, 'r=' + api.version)
// , src: _getUrl('http://v.demidov.boom.corp.mail.ru/uploaderfileapi/FlashFileAPI.swf?1')
- , wmode: 'transparent'
- , flashvars: 'callback=FileAPI.Flash.event'
+ , wmode: opts.camera ? '' : 'transparent'
+ , flashvars: 'callback=' + (opts.onEvent || 'FileAPI.Flash.onEvent')
+ '&flashId='+ id
+ '&storeKey='+ navigator.userAgent.match(/\d/ig).join('') +'_'+ api.version
+ (flash.isReady || (api.pingUrl ? '&ping='+api.pingUrl : ''))
+ '&timeout='+api.flashAbortTimeout
- });
+ + (opts.camera ? '&useCamera=' + _getUrl(api.flashWebcamUrl) : '')
+ + '&debug='+(api.debug?"1":"")
+ }, opts);
},
ready: function (){
+ api.log('FlashAPI.state: ready');
+
flash.ready = api.F;
flash.isReady = true;
flash.patch();
-
+ flash.patchCamera && flash.patchCamera();
api.event.on(document, 'mouseover', flash.mouseover);
api.event.on(document, 'click', function (evt){
if( flash.mouseover(evt) ){
@@ -115,6 +139,12 @@
});
},
+
+ getEl: function (){
+ return document.getElementById('_'+_attr);
+ },
+
+
getWrapper: function (node){
do {
if( /js-fileapi-wrapper/.test(node.className) ){
@@ -124,53 +154,70 @@
while( (node = node.parentNode) && (node !== document.body) );
},
+
mouseover: function (evt){
var target = api.event.fix(evt).target;
- if( /input/i.test(target.nodeName) && target.type == 'file' ){
- var state = target.getAttribute(_attr);
-
- // check state
- if( state == 'i' || state == 'r' ){
- // publish fail
- return false;
- }
- else if( state != 'p' ){
- // set "init" state
- target.setAttribute(_attr, 'i');
+ if( /input/i.test(target.nodeName) && target.type == 'file' && !target.disabled ){
+ var
+ state = target.getAttribute(_attr)
+ , wrapper = flash.getWrapper(target)
+ ;
+
+ if( api.multiFlash ){
+ // check state:
+ // p — published
+ // i — initialization
+ // r — ready
+ if( state == 'i' || state == 'r' ){
+ // publish fail
+ return false;
+ }
+ else if( state != 'p' ){
+ // set "init" state
+ target.setAttribute(_attr, 'i');
- var
- dummy = document.createElement('div')
- , wrapper = flash.getWrapper(target)
- ;
+ var dummy = document.createElement('div');
- if( !wrapper ){
- api.log('flash.mouseover.error: js-fileapi-wrapper not found');
- return
- }
+ if( !wrapper ){
+ api.log('[err] FlashAPI.mouseover: js-fileapi-wrapper not found');
+ return;
+ }
- _css(dummy, {
- top: 0
- , left: 0
- , width: target.offsetWidth + 100
- , height: target.offsetHeight + 100
- , zIndex: 1e6+'' // set max zIndex
- , position: 'absolute'
- });
+ _css(dummy, {
+ top: 0
+ , left: 0
+ , width: target.offsetWidth
+ , height: target.offsetHeight
+ , zIndex: 2147483647+'' // set max zIndex
+ , position: 'absolute'
+ });
+ wrapper.appendChild(dummy);
+ flash.publish(dummy, api.uid());
- wrapper.appendChild(dummy);
- flash.publish(dummy, api.uid());
+ // set "publish" state
+ target.setAttribute(_attr, 'p');
+ }
- // set "publish" state
- target.setAttribute(_attr, 'p');
+ return true;
}
+ else if( wrapper ){
+ // Use one flash element
+ var box = _getDimensions(wrapper);
- return true;
+ _css(flash.getEl(), box);
+
+ // Set current input
+ flash.curInp = target;
+ }
+ }
+ else if( !/object|embed/i.test(target.nodeName) ){
+ _css(flash.getEl(), { top: 1, left: 1, width: 5, height: 5 });
}
},
- event: function (evt){
+ onEvent: function (evt){
var type = evt.type;
if( type == 'ready' ){
@@ -190,14 +237,18 @@
else if( type === 'log' ){
api.log('(flash -> js).log:', evt.target);
}
- else if( type in flash ) setTimeout(function (){
- api.log('Flash.event.'+evt.type+':', evt);
- flash[type](evt);
- }, 1);
+ else if( type in flash ){
+ setTimeout(function (){
+ api.log('FlashAPI.event.'+evt.type+':', evt);
+ flash[type](evt);
+ }, 1);
+ }
},
+
mouseenter: function (evt){
var node = flash.getInput(evt.flashId);
+
if( node ){
// Set multiple mode
flash.cmd(evt, 'multiple', node.getAttribute('multiple') != null);
@@ -206,13 +257,13 @@
// Set files filter
var accept = [], exts = {};
- api.each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
- api.accept[mime] && api.each(api.accept[mime].split(' '), function (ext){
+ _each((node.getAttribute('accept') || '').split(/,\s*/), function (mime){
+ api.accept[mime] && _each(api.accept[mime].split(' '), function (ext){
exts[ext] = 1;
});
});
- api.each(exts, function (i, ext){
+ _each(exts, function (i, ext){
accept.push( ext );
});
@@ -220,19 +271,28 @@
}
},
+
get: function (id){
return document[id] || window[id] || document.embeds[id];
},
+
getInput: function (id){
- try {
- var node = flash.getWrapper(flash.get(id));
- if( node ) return node.getElementsByTagName('input')[0];
- } catch (e){
- api.log('Can not find "input" by flashId:', id, e);
+ if( api.multiFlash ){
+ try {
+ var node = flash.getWrapper(flash.get(id));
+ if( node ){
+ return node.getElementsByTagName('input')[0];
+ }
+ } catch (e){
+ api.log('[err] Can not find "input" by flashId:', id, e);
+ }
+ } else {
+ return flash.curInp;
}
},
+
select: function (evt){
var
inp = flash.getInput(evt.flashId)
@@ -241,7 +301,7 @@
, event
;
- api.each(files, function (file){
+ _each(files, function (file){
api.checkFileObj(file);
});
@@ -249,12 +309,17 @@
if( document.createEvent ){
event = document.createEvent('Event');
- event.initEvent ('change', true, false);
- inp.dispatchEvent(event)
+ event.files = files;
+ event.initEvent('change', true, true);
+ inp.dispatchEvent(event);
+ }
+ else if( jQuery ){
+ jQuery(inp).trigger({ type: 'change', files: files });
}
- else if( document.createEventObject ){
+ else {
event = document.createEventObject();
- inp.fireEvent('onchange', event)
+ event.files = files;
+ inp.fireEvent('onchange', event);
}
},
@@ -263,8 +328,8 @@
try {
api.log('(js -> flash).'+name+':', data);
return flash.get(id.flashId || id).cmd(name, data);
- } catch (e){
- api.log('(js -> flash).onError:', e);
+ } catch (err){
+ api.log('(js -> flash).onError:', err.toString());
if( !last ){
// try again
setTimeout(function (){ flash.cmd(id, name, data, true); }, 50);
@@ -274,8 +339,7 @@
patch: function (){
- api.flashEngine =
- api.support.transform = true;
+ api.flashEngine = true;
// FileAPI
_inherit(api, {
@@ -307,6 +371,12 @@
if( _isHtmlFile(file) ){
this.parent.apply(this, arguments);
}
+ else if( file.isShot ){
+ fn(null, file.info = {
+ width: file.width,
+ height: file.height
+ });
+ }
else {
if( !file.__info ){
var defer = file.__info = api.defer();
@@ -315,10 +385,11 @@
id: file.id
, callback: _wrap(function _(err, info){
_unwrap(_);
- defer.resolve(err, file.info = info)
+ defer.resolve(err, file.info = info);
})
});
}
+
file.__info.then(fn);
}
}
@@ -330,51 +401,64 @@
api.Image && _inherit(api.Image.prototype, {
get: function (fn, scaleMode){
this.set({ scaleMode: scaleMode || 'noScale' }); // noScale, exactFit
- this.parent(fn);
+ return this.parent(fn);
},
_load: function (file, fn){
- api.log('FileAPI.Image._load:', file);
+ api.log('FlashAPI.Image._load:', file);
if( _isHtmlFile(file) ){
this.parent.apply(this, arguments);
}
else {
var _this = this;
- api.getInfo(file, function (err, info){
+ api.getInfo(file, function (err){
fn.call(_this, err, file);
});
}
},
_apply: function (file, fn){
- api.log('FileAPI.Image._apply:', file);
+ api.log('FlashAPI.Image._apply:', file);
if( _isHtmlFile(file) ){
this.parent.apply(this, arguments);
}
else {
- var m = this.getMatrix(file.info);
+ var m = this.getMatrix(file.info), doneFn = fn;
flash.cmd(file, 'imageTransform', {
id: file.id
, matrix: m
, callback: _wrap(function _(err, base64){
- api.log('FileAPI.Image._apply.callback:', err);
+ api.log('FlashAPI.Image._apply.callback:', err);
_unwrap(_);
if( err ){
- fn(err);
+ doneFn(err);
}
- else if( !api.support.dataURI || base64.length > 3e4 ){
+ else if( !api.support.html5 && (!api.support.dataURI || base64.length > 3e4) ){
_makeFlashImage({
- width: !(m.deg % 180) ? m.dw : m.dh
+ width: (m.deg % 180) ? m.dh : m.dw
, height: (m.deg % 180) ? m.dw : m.dh
, scale: m.scaleMode
- }, base64, fn);
+ }, base64, doneFn);
}
else {
- api.newImage('data:'+ file.type +';base64,'+ base64, fn);
+ if( m.filter ){
+ doneFn = function (err, img){
+ if( err ){
+ fn(err);
+ }
+ else {
+ api.Image.applyFilter(img, m.filter, function (){
+ fn(err, this.canvas);
+ });
+ }
+ };
+ }
+
+ api.newImage('data:'+ file.type +';base64,'+ base64, doneFn);
}
})
});
@@ -382,7 +466,12 @@
},
toData: function (fn){
- var file = this.file, info = file.info, matrix = this.getMatrix(info);
+ var
+ file = this.file
+ , info = file.info
+ , matrix = this.getMatrix(info)
+ ;
+ api.log('FlashAPI.Image.toData');
if( _isHtmlFile(file) ){
this.parent.apply(this, arguments);
@@ -404,6 +493,21 @@
});
+ api.Image && _inherit(api.Image, {
+ fromDataURL: function (dataURL, size, callback){
+ if( !api.support.dataURI || dataURL.length > 3e4 ){
+ _makeFlashImage(
+ api.extend({ scale: 'exactFit' }, size)
+ , dataURL.replace(/^data:[^,]+,/, '')
+ , function (err, el){ callback(el); }
+ );
+ }
+ else {
+ this.parent(dataURL, size, callback);
+ }
+ }
+ });
+
// FileAPI.Form
_inherit(api.Form.prototype, {
toData: function (fn){
@@ -415,7 +519,7 @@
}
}
- api.log('flash.Form.toData');
+ api.log('FlashAPI.Form.toData');
fn(items);
}
});
@@ -442,7 +546,7 @@
, fileId
;
- api.each(formData, function (item){
+ _each(formData, function (item){
if( item.file ){
files[item.name] = item = _getFileDescr(item.blob);
fileId = item.id;
@@ -453,10 +557,16 @@
}
});
- if( !(fileId || flashId) ){
+ if( !fileId ){
+ flashId = _attr;
+ }
+
+ if( !flashId ){
+ api.log('[err] FlashAPI._send: flashId -- undefined');
return this.parent.apply(this, arguments);
- } else {
- api.log('flash.XHR._send:', flashId, fileId, files);
+ }
+ else {
+ api.log('FlashAPI.XHR._send: '+ flashId +' -> '+ fileId);
}
_this.xhr = {
@@ -468,14 +578,14 @@
var queue = api.queue(function (){
flash.cmd(flashId, 'upload', {
- url: _getUrl(options.url)
+ url: _getUrl(options.url.replace(/([a-z]+)=(\?)&?/i, ''))
, data: data
- , files: files
- , headers: options.headers
+ , files: fileId ? files : null
+ , headers: options.headers || {}
, callback: _wrap(function upload(evt){
var type = evt.type, result = evt.result;
- api.log('flash.upload.'+type+':', evt);
+ api.log('FlashAPI.upload.'+type);
if( type == 'progress' ){
evt.loaded = Math.min(evt.loaded, evt.total); // @todo fixme
@@ -501,7 +611,7 @@
// #2174: FileReference.load() call while FileReference.upload() or vice versa
- api.each(files, function (file){
+ _each(files, function (file){
queue.inc();
api.getInfo(file, queue.next);
});
@@ -534,7 +644,9 @@
var key, val;
for( key in css ){
val = css[key];
- if( typeof val == 'number' ) val += 'px';
+ if( typeof val == 'number' ){
+ val += 'px';
+ }
try { el.style[key] = val; } catch (e) {}
}
}
@@ -542,7 +654,7 @@
function _inherit(obj, methods){
- api.each(methods, function (fn, name){
+ _each(methods, function (fn, name){
var prev = obj[name];
obj[name] = function (){
this.parent = prev;
@@ -597,21 +709,32 @@
function _makeFlashImage(opts, base64, fn){
- var _id, flashId = api.uid(), el = document.createElement('div');
+ var
+ key
+ , flashId = api.uid()
+ , el = document.createElement('div')
+ , attempts = 10
+ ;
- for( _id in opts ){
- el.setAttribute('data-img-' + _id, opts[_id]);
+ for( key in opts ){
+ el.setAttribute(key, opts[key]);
+ el[key] = opts[key];
}
_css(el, opts);
+ opts.width = '100%';
+ opts.height = '100%';
+
el.innerHTML = _makeFlashHTML(api.extend({
id: flashId
, src: _getUrl(api.flashImageUrl, 'r='+ api.uid())
, wmode: 'opaque'
- , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _setData(){
- _unwrap(_setData);
- setTimeout(_setImage, 99);
+ , flashvars: 'scale='+ opts.scale +'&callback='+_wrap(function _(){
+ _unwrap(_);
+ if( --attempts > 0 ){
+ _setImage();
+ }
return true;
})
}, opts));
@@ -622,7 +745,7 @@
var img = flash.get(flashId);
img.setImage(base64);
} catch (e){
- api.log('flash.setImage -- can not set "base64":', e);
+ api.log('[err] FlashAPI.Preview.setImage -- can not set "base64":', e);
}
}
@@ -641,6 +764,20 @@
}
+ function _getDimensions(el){
+ var
+ box = el.getBoundingClientRect()
+ , body = document.body
+ , docEl = (el && el.ownerDocument).documentElement
+ ;
+
+ return {
+ top: box.top + (window.pageYOffset || docEl.scrollTop) - (docEl.clientTop || body.clientTop || 0)
+ , left: box.left + (window.pageXOffset || docEl.scrollLeft) - (docEl.clientLeft || body.clientLeft || 0)
+ , width: box.right - box.left
+ , height: box.bottom - box.top
+ };
+ }
// @export
api.Flash = flash;
@@ -652,4 +789,4 @@
flash.init();
});
})();
-})(FileAPI, window, document);
+})(window, window.jQuery, FileAPI);
diff --git a/lib/FileAPI.Form.js b/lib/FileAPI.Form.js
index 699adf1b..b89bac37 100644
--- a/lib/FileAPI.Form.js
+++ b/lib/FileAPI.Form.js
@@ -1,10 +1,13 @@
-(function (api, window, document){
+/*global window, FileAPI */
+
+(function (api, window){
+ "use strict";
+
var
- encode = window.encodeURIComponent,
- FormData = window.FormData,
- Form = function (){
- this.items = [];
- }
+ document = window.document
+ , FormData = window.FormData
+ , Form = function (){ this.items = []; }
+ , encodeURIComponent = window.encodeURIComponent
;
@@ -35,7 +38,7 @@
api.log('FileAPI.Form.toHtmlData');
this.toHtmlData(fn);
}
- else if( this.multipart || !FormData ){
+ else if( !api.formData || this.multipart || !FormData ){
api.log('FileAPI.Form.toMultipartData');
this.toMultipartData(fn);
}
@@ -55,7 +58,13 @@
});
this.each(function (file){
- next(file, data, queue, arg);
+ try{
+ next(file, data, queue, arg);
+ }
+ catch( err ){
+ api.log('FileAPI.Form._to: ' + err.message);
+ complete(err);
+ }
});
queue.check();
@@ -70,6 +79,7 @@
api.reset(blob, true);
// set new name
blob.name = file.name;
+ blob.disabled = false;
data.appendChild(blob);
}
else {
@@ -87,19 +97,20 @@
if( file.file ){
data.type = file.file;
}
+
if( file.blob.toBlob ){
// canvas
queue.inc();
- file.blob.toBlob(function (blob){
+ _convertFile(file, function (file, blob){
data.name = file.name;
data.file = blob;
data.size = blob.length;
data.type = file.type;
queue.next();
- }, 'image/png');
+ });
}
else if( file.file ){
- //file
+ // file
data.name = file.blob.name;
data.file = file.blob;
data.size = file.blob.size;
@@ -107,11 +118,12 @@
}
else {
// additional data
- if (!data.params) {
+ if( !data.params ){
data.params = [];
}
- data.params.push(encodeURIComponent(file.name) + "=" + encodeURIComponent(file.blob));
+ data.params.push(encodeURIComponent(file.name) +"="+ encodeURIComponent(file.blob));
}
+
data.start = -1;
data.end = data.file && data.file.FileAPIReadPosition || -1;
data.retry = 0;
@@ -120,16 +132,12 @@
toFormData: function (fn){
this._to(new FormData, fn, function (file, data, queue){
- if( file.file ){
- data.append('_'+file.name, file.file);
- }
-
if( file.blob && file.blob.toBlob ){
queue.inc();
- file.blob.toBlob(function (blob){
+ _convertFile(file, function (file, blob){
data.append(file.name, blob, file.file);
queue.next();
- }, 'image/png');
+ });
}
else if( file.file ){
data.append(file.name, file.blob, file.file);
@@ -137,44 +145,76 @@
else {
data.append(file.name, file.blob);
}
+
+ if( file.file ){
+ data.append('_'+file.name, file.file);
+ }
});
},
toMultipartData: function (fn){
this._to([], fn, function (file, data, queue, boundary){
- var
- isFile = !!file.file
- , blob = file.blob
- , done = function (blob){
- data.push(
- '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (isFile ? '; filename="'+ encode(file.file) +'"' : '')
- + (isFile ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
- + '\r\n'
- + '\r\n'+ (isFile ? blob : encode(blob))
- + '\r\n')
- );
- queue.next();
- }
- ;
-
queue.inc();
-
- if( api.isFile(blob) ){
- api.readAsBinaryString(blob, function (evt/**Object*/){
- if( evt.type == 'load' ){
- done(evt.result);
- }
- });
- }
- else {
- done(blob);
- }
+ _convertFile(file, function (file, blob){
+ data.push(
+ '--_' + boundary + ('\r\nContent-Disposition: form-data; name="'+ file.name +'"'+ (file.file ? '; filename="'+ encodeURIComponent(file.file) +'"' : '')
+ + (file.file ? '\r\nContent-Type: '+ (file.type || 'application/octet-stream') : '')
+ + '\r\n'
+ + '\r\n'+ (file.file ? blob : encodeURIComponent(blob))
+ + '\r\n')
+ );
+ queue.next();
+ }, true);
}, api.expando);
}
};
+ function _convertFile(file, fn, useBinaryString){
+ var blob = file.blob, filename = file.file;
+
+ if( filename ){
+ if( !blob.toDataURL ){
+ // The Blob is not an image.
+ api.readAsBinaryString(blob, function (evt){
+ if( evt.type == 'load' ){
+ fn(file, evt.result);
+ }
+ });
+ return;
+ }
+
+ var
+ mime = { 'image/jpeg': '.jpe?g', 'image/png': '.png' }
+ , type = mime[file.type] ? file.type : 'image/png'
+ , ext = mime[type] || '.png'
+ , quality = blob.quality || 1
+ ;
+
+ if( !filename.match(new RegExp(ext+'$', 'i')) ){
+ // Does not change the current extension, but add a new one.
+ filename += ext.replace('?', '');
+ }
+
+ file.file = filename;
+ file.type = type;
+
+ if( !useBinaryString && blob.toBlob ){
+ blob.toBlob(function (blob){
+ fn(file, blob);
+ }, type, quality);
+ }
+ else {
+ fn(file, api.toBinaryString(blob.toDataURL(type, quality)));
+ }
+ }
+ else {
+ fn(file, blob);
+ }
+ }
+
+
// @export
api.Form = Form;
-})(FileAPI, window, document);
+})(FileAPI, window);
diff --git a/lib/FileAPI.Image.js b/lib/FileAPI.Image.js
index c551b6cf..87b7e500 100644
--- a/lib/FileAPI.Image.js
+++ b/lib/FileAPI.Image.js
@@ -1,15 +1,20 @@
-(function (api, document, undef){
+/*global window, FileAPI, document */
+
+(function (api, document, undef) {
'use strict';
var
min = Math.min,
round = Math.round,
- getCanvas = function (){ return document.createElement('canvas'); },
+ getCanvas = function () { return document.createElement('canvas'); },
support = false,
exifOrientation = {
8: 270
, 3: 180
, 6: 90
+ , 7: 270
+ , 4: 180
+ , 5: 90
}
;
@@ -19,13 +24,19 @@
catch (e){}
- function Image(file, low){
- if( !(this instanceof Image) ){
+ function Image(file){
+ if( file instanceof Image ){
+ var img = new Image(file.file);
+ api.extend(img.matrix, file.matrix);
+ return img;
+ }
+ else if( !(this instanceof Image) ){
return new Image(file);
}
this.file = file;
- this.better = !low;
+ this.size = file.size || 100;
+
this.matrix = {
sx: 0,
sy: 0,
@@ -36,10 +47,15 @@
dw: 0,
dh: 0,
resize: 0, // min, max OR preview
- deg: 0
+ deg: 0,
+ quality: 1, // jpeg quality
+ filter: 0
};
}
+
+
Image.prototype = {
+ image: true,
constructor: Image,
set: function (attrs){
@@ -56,28 +72,46 @@
return this.set({ sx: x, sy: y, sw: w, sh: h || w });
},
- resize: function (w, h, type){
- if( typeof h == 'string' ){
- type = h;
+ resize: function (w, h, strategy){
+ if( /min|max|height|width/.test(h) ){
+ strategy = h;
h = w;
}
- return this.set({ dw: w, dh: h, resize: type });
+ return this.set({ dw: w, dh: h || w, resize: strategy });
},
preview: function (w, h){
- return this.set({ dw: w, dh: h || w, resize: 'preview' });
+ return this.resize(w, h || w, 'preview');
},
rotate: function (deg){
return this.set({ deg: deg });
},
+ filter: function (filter){
+ return this.set({ filter: filter });
+ },
+
+ overlay: function (images){
+ return this.set({ overlay: images });
+ },
+
+ clone: function (){
+ return new Image(this);
+ },
+
_load: function (image, fn){
var self = this;
- api.readAsImage(image, function (evt){
- fn.call(self, evt.type != 'load', evt.result);
- });
+
+ if( /img|video/i.test(image.nodeName) ){
+ fn.call(self, null, image);
+ }
+ else {
+ api.readAsImage(image, function (evt){
+ fn.call(self, evt.type != 'load', evt.result);
+ });
+ }
},
_apply: function (image, fn){
@@ -85,44 +119,55 @@
canvas = getCanvas()
, m = this.getMatrix(image)
, ctx = canvas.getContext('2d')
+ , width = image.videoWidth || image.width
+ , height = image.videoHeight || image.height
, deg = m.deg
, dw = m.dw
, dh = m.dh
- , w = image.width
- , h = image.height
- , copy, buffer = image
+ , w = width
+ , h = height
+ , filter = m.filter
+ , copy // canvas copy
+ , buffer = image
+ , overlay = m.overlay
+ , queue = api.queue(function (){ image.src = api.EMPTY_PNG; fn(false, canvas); })
, renderImageToCanvas = api.renderImageToCanvas
;
+ // Normalize angle
+ deg = deg - Math.floor(deg/360)*360;
+
// For `renderImageToCanvas`
image._type = this.file.type;
- if( this.better ){
- while( Math.min(w/dw, h/dh) > 2 ){
- w = ~~(w/2 + .5);
- h = ~~(h/2 + .5);
+ while(m.multipass && min(w/dw, h/dh) > 2 ){
+ w = (w/2 + 0.5)|0;
+ h = (h/2 + 0.5)|0;
- copy = getCanvas();
- copy.width = w;
- copy.height = h;
+ copy = getCanvas();
+ copy.width = w;
+ copy.height = h;
- if( buffer !== image ){
- renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
- buffer = copy;
- }
- else {
- buffer = copy;
- renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
- m.sx = m.sy = m.sw = m.sh = 0;
- }
+ if( buffer !== image ){
+ renderImageToCanvas(copy, buffer, 0, 0, buffer.width, buffer.height, 0, 0, w, h);
+ buffer = copy;
+ }
+ else {
+ buffer = copy;
+ renderImageToCanvas(buffer, image, m.sx, m.sy, m.sw, m.sh, 0, 0, w, h);
+ m.sx = m.sy = m.sw = m.sh = 0;
}
}
- canvas.width = !(deg % 180) ? dw : dh;
- canvas.height = (deg % 180) ? dw : dh;
+
+ canvas.width = (deg % 180) ? dh : dw;
+ canvas.height = (deg % 180) ? dw : dh;
+
+ canvas.type = m.type;
+ canvas.quality = m.quality;
ctx.rotate(deg * Math.PI / 180);
- renderImageToCanvas(canvas, buffer
+ renderImageToCanvas(ctx.canvas, buffer
, m.sx, m.sy
, m.sw || buffer.width
, m.sh || buffer.height
@@ -130,22 +175,67 @@
, (deg == 90 || deg == 180 ? -dh : 0)
, dw, dh
);
+ dw = canvas.width;
+ dh = canvas.height;
+
+ // Apply overlay
+ overlay && api.each([].concat(overlay), function (over){
+ queue.inc();
+ // preload
+ var img = new window.Image, fn = function (){
+ var
+ x = over.x|0
+ , y = over.y|0
+ , w = over.w || img.width
+ , h = over.h || img.height
+ , rel = over.rel
+ ;
+
+ // center | right | left
+ x = (rel == 1 || rel == 4 || rel == 7) ? (dw - w + x)/2 : (rel == 2 || rel == 5 || rel == 8 ? dw - (w + x) : x);
+
+ // center | bottom | top
+ y = (rel == 3 || rel == 4 || rel == 5) ? (dh - h + y)/2 : (rel >= 6 ? dh - (h + y) : y);
+
+ api.event.off(img, 'error load abort', fn);
+
+ try {
+ ctx.globalAlpha = over.opacity || 1;
+ ctx.drawImage(img, x, y, w, h);
+ }
+ catch (er){}
+
+ queue.next();
+ };
+
+ api.event.on(img, 'error load abort', fn);
+ img.src = over.src;
- fn.call(this, false, canvas);
+ if( img.complete ){
+ fn();
+ }
+ });
+
+ if( filter ){
+ queue.inc();
+ Image.applyFilter(canvas, filter, queue.next);
+ }
+
+ queue.check();
},
getMatrix: function (image){
var
m = api.extend({}, this.matrix)
- , sw = m.sw = m.sw || image.width
- , sh = m.sh = m.sh || image.height
- , dw = m.dw = m.dw || m.sw
- , dh = m.dh = m.dh || m.sh
+ , sw = m.sw = m.sw || image.videoWidth || image.naturalWidth || image.width
+ , sh = m.sh = m.sh || image.videoHeight || image.naturalHeight || image.height
+ , dw = m.dw = m.dw || sw
+ , dh = m.dh = m.dh || sh
, sf = sw/sh, df = dw/dh
- , type = m.resize
+ , strategy = m.resize
;
- if( type == 'preview' ){
+ if( strategy == 'preview' ){
if( dw != sw || dh != sh ){
// Make preview
var w, h;
@@ -166,12 +256,18 @@
}
}
}
- else if( type ){
+ else if( strategy == 'height' ){
+ dw = dh * sf;
+ }
+ else if( strategy == 'width' ){
+ dh = dw / sf;
+ }
+ else if( strategy ){
if( !(sw > dw || sh > dh) ){
dw = sw;
dh = sh;
}
- else if( type == 'min' ){
+ else if( strategy == 'min' ){
dw = round(sf < df ? min(sw, dw) : dh*sf);
dh = round(sf < df ? dw/sf : min(sh, dh));
}
@@ -185,7 +281,7 @@
m.sh = sh;
m.dw = dw;
m.dh = dh;
-
+ m.multipass = api.multiPassResize;
return m;
},
@@ -195,19 +291,25 @@
fn(err);
}
else {
- this._apply(image, fn);
+ try {
+ this._apply(image, fn);
+ } catch (err){
+ api.log('[err] FileAPI.Image.fn._apply:', err);
+ fn(err);
+ }
}
});
},
+
get: function (fn){
if( api.support.transform ){
- var _this = this;
+ var _this = this, matrix = _this.matrix;
- if( _this.matrix.deg == 'auto' ){
- api.getInfo(this.file, function (err, info){
+ if( matrix.deg == 'auto' ){
+ api.getInfo(_this.file, function (err, info){
// rotate by exif orientation
- _this.matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
+ matrix.deg = exifOrientation[info && info.exif && info.exif.Orientation] || 0;
_this._trans(fn);
});
}
@@ -216,12 +318,15 @@
}
}
else {
- fn('not_support');
+ fn('not_support_transform');
}
+
+ return this;
},
+
toData: function (fn){
- this.get(fn);
+ return this.get(fn);
}
};
@@ -231,9 +336,8 @@
Image.transform = function (file, transform, autoOrientation, fn){
- api.getInfo(file, function (err, img){
+ function _transform(err, img){
// img -- info object
-
var
images = {}
, queue = api.queue(function (err){
@@ -244,13 +348,13 @@
if( !err ){
api.each(transform, function (params, name){
if( !queue.isFail() ){
- var ImgTrans = Image(img.nodeType ? img : file);
+ var ImgTrans = new Image(img.nodeType ? img : file), isFn = typeof params == 'function';
- if( typeof params == 'function' ){
+ if( isFn ){
params(img, ImgTrans);
}
else if( params.width ){
- ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.type);
+ ImgTrans[params.preview ? 'preview' : 'resize'](params.width, params.height, params.strategy);
}
else {
if( params.maxWidth && (img.width > params.maxWidth || img.height > params.maxHeight) ){
@@ -258,11 +362,25 @@
}
}
+ if( params.crop ){
+ var crop = params.crop;
+ ImgTrans.crop(crop.x|0, crop.y|0, crop.w || crop.width, crop.h || crop.height);
+ }
+
if( params.rotate === undef && autoOrientation ){
params.rotate = 'auto';
}
- ImgTrans.rotate(params.rotate);
+ ImgTrans.set({ type: ImgTrans.matrix.type || params.type || file.type || 'image/png' });
+
+ if( !isFn ){
+ ImgTrans.set({
+ deg: params.rotate
+ , overlay: params.overlay
+ , filter: params.filter
+ , quality: params.quality || 1
+ });
+ }
queue.inc();
ImgTrans.toData(function (err, image){
@@ -280,7 +398,80 @@
else {
queue.fail();
}
+ }
+
+
+ // @todo: Оло-ло, нужно рефакторить это место
+ if( file.width ){
+ _transform(false, file);
+ } else {
+ api.getInfo(file, _transform);
+ }
+ };
+
+
+ // @const
+ api.each(['TOP', 'CENTER', 'BOTTOM'], function (x, i){
+ api.each(['LEFT', 'CENTER', 'RIGHT'], function (y, j){
+ Image[x+'_'+y] = i*3 + j;
+ Image[y+'_'+x] = i*3 + j;
});
+ });
+
+
+ /**
+ * Trabsform element to canvas
+ *
+ * @param {Image|HTMLVideoElement} el
+ * @returns {Canvas}
+ */
+ Image.toCanvas = function(el){
+ var canvas = document.createElement('canvas');
+ canvas.width = el.videoWidth || el.width;
+ canvas.height = el.videoHeight || el.height;
+ canvas.getContext('2d').drawImage(el, 0, 0);
+ return canvas;
+ };
+
+
+ /**
+ * Create image from DataURL
+ * @param {String} dataURL
+ * @param {Object} size
+ * @param {Function} callback
+ */
+ Image.fromDataURL = function (dataURL, size, callback){
+ var img = api.newImage(dataURL);
+ api.extend(img, size);
+ callback(img);
+ };
+
+
+ /**
+ * Apply filter (caman.js)
+ *
+ * @param {Canvas|Image} canvas
+ * @param {String|Function} filter
+ * @param {Function} doneFn
+ */
+ Image.applyFilter = function (canvas, filter, doneFn){
+ if( typeof filter == 'function' ){
+ filter(canvas, doneFn);
+ }
+ else if( window.Caman ){
+ // http://camanjs.com/guides/
+ window.Caman(canvas.tagName == 'IMG' ? Image.toCanvas(canvas) : canvas, function (){
+ if( typeof filter == 'string' ){
+ this[filter]();
+ }
+ else {
+ api.each(filter, function (val, method){
+ this[method](val);
+ }, this);
+ }
+ this.render(doneFn);
+ });
+ }
};
@@ -288,8 +479,12 @@
* For load-image-ios.js
*/
api.renderImageToCanvas = function (canvas, img, sx, sy, sw, sh, dx, dy, dw, dh){
- canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
- return canvas;
+ try {
+ return canvas.getContext('2d').drawImage(img, sx, sy, sw, sh, dx, dy, dw, dh);
+ } catch (ex) {
+ api.log('renderImageToCanvas failed');
+ throw ex;
+ }
};
diff --git a/lib/FileAPI.XHR.js b/lib/FileAPI.XHR.js
index 88f3bcbf..d4f2472b 100644
--- a/lib/FileAPI.XHR.js
+++ b/lib/FileAPI.XHR.js
@@ -1,6 +1,11 @@
+/*global window, FileAPI, Uint8Array */
+
(function (window, api){
+ "use strict";
+
var
noop = function (){}
+ , document = window.document
, XHR = function (options){
this.uid = api.uid();
@@ -10,13 +15,16 @@
, getAllResponseHeaders: noop
};
this.options = options;
- }
+ },
+
+ _xhrResponsePostfix = { '': 1, XML: 1, Text: 1, Body: 1 }
;
XHR.prototype = {
status: 0,
statusText: '',
+ constructor: XHR,
getResponseHeader: function (name){
return this.xhr.getResponseHeader(name);
@@ -54,7 +62,7 @@
this.end(0, 'abort');
if( this.xhr ){
- this.xhr.aborted = true;
+ this.xhr.aborted = true;
this.xhr.abort();
}
},
@@ -63,14 +71,19 @@
var _this = this, options = this.options;
FormData.toData(function (data){
- // Start uploading
- options.upload(options, _this);
- _this._send.call(_this, options, data);
+ if( data instanceof Error ){
+ _this.end(0, data.message);
+ }
+ else{
+ // Start uploading
+ options.upload(options, _this);
+ _this._send.call(_this, options, data);
+ }
}, options);
},
_send: function (options, data){
- var _this = this, xhr, uid = _this.uid, url = options.url;
+ var _this = this, xhr, uid = _this.uid, onLoadFnName = _this.uid + "Load", url = options.url;
api.log('XHR._send:', data);
@@ -80,31 +93,77 @@
}
if( data.nodeName ){
+ var jsonp = options.jsonp;
+
+ // prepare callback in GET
+ url = url.replace(/([a-z]+)=(\?)/i, '$1='+uid);
+
// legacy
options.upload(options, _this);
- xhr = document.createElement('div');
- xhr.innerHTML = ''
+ var
+ onPostMessage = function (evt){
+ if( ~url.indexOf(evt.origin) ){
+ try {
+ var result = api.parseJSON(evt.data);
+ if( result.id == uid ){
+ complete(result.status, result.statusText, result.response);
+ }
+ } catch( err ){
+ complete(0, err.message);
+ }
+ }
+ },
+
+ // jsonp-callack
+ complete = window[uid] = function (status, statusText, response){
+ _this.readyState = 4;
+ _this.responseText = response;
+ _this.end(status, statusText);
+
+ api.event.off(window, 'message', onPostMessage);
+ window[uid] = xhr = transport = window[onLoadFnName] = null;
+ }
;
_this.xhr.abort = function (){
- var transport = xhr.getElementsByName('iframe')[0];
- if( transport ){
- try {
- if( transport.stop ) transport.stop();
- else if( transport.contentWindow.stop ) transport.contentWindow.stop();
- else transport.contentWindow.document.execCommand('Stop');
- }
- catch (er) {}
+ try {
+ if( transport.stop ){ transport.stop(); }
+ else if( transport.contentWindow.stop ){ transport.contentWindow.stop(); }
+ else { transport.contentWindow.document.execCommand('Stop'); }
+ }
+ catch (er) {}
+ complete(0, "abort");
+ };
+
+ api.event.on(window, 'message', onPostMessage);
+
+ window[onLoadFnName] = function (){
+ try {
+ var
+ win = transport.contentWindow
+ , doc = win.document
+ , result = win.result || api.parseJSON(doc.body.innerHTML)
+ ;
+ complete(result.status, result.statusText, result.response);
+ } catch (e){
+ api.log('[transport.onload]', e);
}
- xhr = null;
};
- // append form-data
- var form = xhr.getElementsByTagName('form')[0];
+ xhr = document.createElement('div');
+ xhr.innerHTML = ''
+ ;
+
+ // get form-data & transport
+ var
+ form = xhr.getElementsByTagName('form')[0]
+ , transport = xhr.getElementsByTagName('iframe')[0]
+ ;
+
form.appendChild(data);
api.log(form.parentNode.innerHTML);
@@ -115,20 +174,19 @@
// keep a reference to node-transport
_this.xhr.node = xhr;
- // jsonp-callack
- window[uid] = function (status, statusText, response){
- _this.readyState = 4;
- _this.responseText = response;
- _this.end(status, statusText);
- xhr = null;
- };
-
// send
_this.readyState = 2; // loaded
- form.submit();
+ try {
+ form.submit();
+ } catch (err) {
+ api.log('iframe.error: ' + err);
+ }
form = null;
}
else {
+ // Clean url
+ url = url.replace(/([a-z]+)=(\?)&?/i, '');
+
// html5
if (this.xhr && this.xhr.aborted) {
api.log("Error: already aborted");
@@ -137,12 +195,14 @@
xhr = _this.xhr = api.getXHR();
if (data.params) {
- url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
+ url += (url.indexOf('?') < 0 ? "?" : "&") + data.params.join("&");
}
- xhr.open('POST', url, true);
+ xhr.open(options.uploadMethod || 'POST', url, true);
- if( api.withCredentials ){
+ if (typeof options.uploadCredentials === 'boolean') {
+ xhr.withCredentials = options.uploadCredentials ? 'true' : null;
+ } else if( api.withCredentials ){
xhr.withCredentials = "true";
}
@@ -154,13 +214,13 @@
xhr.setRequestHeader(key, val);
});
-
+
if ( options._chunked ) {
// chunked upload
if( xhr.upload ){
- xhr.upload.addEventListener('progress', function (/**Event*/evt){
+ xhr.upload.addEventListener('progress', api.throttle(function (/**Event*/evt){
if (!data.retry) {
- // show progress only for correct chunk uploads
+ // show progress only for correct chunk uploads
options.progress({
type: evt.type
, total: data.size
@@ -168,47 +228,49 @@
, totalSize: data.size
}, _this, options);
}
- }, false);
+ }, 100), false);
}
xhr.onreadystatechange = function (){
+ var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
+
_this.status = xhr.status;
_this.statusText = xhr.statusText;
_this.readyState = xhr.readyState;
if( xhr.readyState == 4 ){
- for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){
+ for( var k in _xhrResponsePostfix ){
_this['response'+k] = xhr['response'+k];
}
xhr.onreadystatechange = null;
-
+
if (!xhr.status || xhr.status - 201 > 0) {
- api.log("Error: " + xhr.status);
+ api.log("Error: " + xhr.status);
// some kind of error
// 0 - connection fail or timeout, if xhr.aborted is true, then it's not recoverable user action
// up - server error
if (((!xhr.status && !xhr.aborted) || 500 == xhr.status || 416 == xhr.status) && ++data.retry <= options.chunkUploadRetry) {
// let's try again the same chunk
// only applicable for recoverable error codes 500 && 416
+ var delay = xhr.status ? 0 : api.chunkNetworkDownRetryTimeout;
- var to = xhr.status ? 0
- : api.chunkNetworkDownRetryTimeout;
-
- // inform about recoverable problems
+ // inform about recoverable problems
options.pause(data.file, options);
// smart restart if server reports about the last known byte
- var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
api.log("X-Last-Known-Byte: " + lkb);
if (lkb) {
data.end = lkb;
} else {
data.end = data.start - 1;
+ if (416 == xhr.status) {
+ data.end = data.end - options.chunkSize;
+ }
}
setTimeout(function () {
- _this._send(options, data);
- }, to);
+ _this._send(options, data);
+ }, delay);
} else {
// no mo retries
_this.end(xhr.status);
@@ -224,7 +286,6 @@
// next chunk
// shift position if server reports about the last known byte
- var lkb = parseInt(xhr.getResponseHeader('X-Last-Known-Byte'), 10);
api.log("X-Last-Known-Byte: " + lkb);
if (lkb) {
data.end = lkb;
@@ -232,28 +293,37 @@
data.file.FileAPIReadPosition = data.end;
setTimeout(function () {
- _this._send(options, data);
+ _this._send(options, data);
}, 0);
}
}
+
xhr = null;
}
};
data.start = data.end + 1;
- data.end = Math.max(Math.min(data.start + options.chunkSize, data.size ) - 1, data.start);
-
- var slice;
- (slice = 'slice') in data.file || (slice = 'mozSlice') in data.file || (slice = 'webkitSlice') in data.file;
-
- xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
- xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
- xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
-
- slice = data.file[slice](data.start, data.end + 1);
-
- xhr.send(slice);
- slice = null;
+ data.end = Math.max(Math.min(data.start + options.chunkSize, data.size) - 1, data.start);
+
+ // Retrieve a slice of file
+ var
+ file = data.file
+ , slice = (file.slice || file.mozSlice || file.webkitSlice).call(file, data.start, data.end + 1)
+ ;
+
+ if( data.size && !slice.size ){
+ setTimeout(function (){
+ _this.end(-1);
+ });
+ } else {
+ xhr.setRequestHeader("Content-Range", "bytes " + data.start + "-" + data.end + "/" + data.size);
+ xhr.setRequestHeader("Content-Disposition", 'attachment; filename=' + encodeURIComponent(data.name));
+ xhr.setRequestHeader("Content-Type", data.type || "application/octet-stream");
+
+ xhr.send(slice);
+ }
+
+ file = slice = null;
} else {
// single piece upload
if( xhr.upload ){
@@ -262,18 +332,39 @@
options.progress(evt, _this, options);
}, 100), false);
}
-
+
xhr.onreadystatechange = function (){
_this.status = xhr.status;
_this.statusText = xhr.statusText;
_this.readyState = xhr.readyState;
if( xhr.readyState == 4 ){
- for( var k in { '': 1, XML: 1, Text: 1, Body: 1 } ){
+ for( var k in _xhrResponsePostfix ){
_this['response'+k] = xhr['response'+k];
}
xhr.onreadystatechange = null;
- _this.end(xhr.status);
+
+ if (!xhr.status || xhr.status > 201) {
+ api.log("Error: " + xhr.status);
+ if (((!xhr.status && !xhr.aborted) || 500 == xhr.status) && (options.retry || 0) < options.uploadRetry) {
+ options.retry = (options.retry || 0) + 1;
+ var delay = api.networkDownRetryTimeout;
+
+ // inform about recoverable problems
+ options.pause(options.file, options);
+
+ setTimeout(function () {
+ _this._send(options, data);
+ }, delay);
+ } else {
+ //success
+ _this.end(xhr.status);
+ }
+ } else {
+ //success
+ _this.end(xhr.status);
+ }
+
xhr = null;
}
};
@@ -281,19 +372,19 @@
if( api.isArray(data) ){
// multipart
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=_'+api.expando);
- data = data.join('') +'--_'+ api.expando +'--';
+ var rawData = data.join('') +'--_'+ api.expando +'--';
/** @namespace xhr.sendAsBinary https://developer.mozilla.org/ru/XMLHttpRequest#Sending_binary_content */
if( xhr.sendAsBinary ){
- xhr.sendAsBinary(data);
+ xhr.sendAsBinary(rawData);
}
else {
- var bytes = Array.prototype.map.call(data, function(c){ return c.charCodeAt(0) & 0xff; });
+ var bytes = Array.prototype.map.call(rawData, function(c){ return c.charCodeAt(0) & 0xff; });
xhr.send(new Uint8Array(bytes).buffer);
}
} else {
- // FormData
+ // FormData
xhr.send(data);
}
}
diff --git a/lib/FileAPI.core.js b/lib/FileAPI.core.js
index 5d6f82bb..14c7b467 100644
--- a/lib/FileAPI.core.js
+++ b/lib/FileAPI.core.js
@@ -1,4 +1,5 @@
-/*global URL, webkitURL, dataURLtoBlob*/
+/*jslint evil: true */
+/*global window, URL, webkitURL, ActiveXObject */
(function (window, undef){
'use strict';
@@ -6,7 +7,13 @@
var
gid = 1,
noop = function (){},
- userAgent = navigator.userAgent,
+
+ document = window.document,
+ doctype = document.doctype || {},
+ userAgent = window.navigator.userAgent,
+ safari = /safari\//i.test(userAgent) && !/chrome\//i.test(userAgent),
+ iemobile = /iemobile\//i.test(userAgent),
+ insecureChrome = !safari && /chrome\//i.test(userAgent) && window.location.protocol === 'http:',
// https://github.com/blueimp/JavaScript-Load-Image/blob/master/load-image.js#L48
apiURL = (window.createObjectURL && window) || (window.URL && URL.revokeObjectURL && URL) || (window.webkitURL && webkitURL),
@@ -15,36 +22,42 @@
File = window.File,
FileReader = window.FileReader,
FormData = window.FormData,
+
+
XMLHttpRequest = window.XMLHttpRequest,
jQuery = window.jQuery,
html5 = !!(File && (FileReader && (window.Uint8Array || FormData || XMLHttpRequest.prototype.sendAsBinary)))
- && !(/safari\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // BugFix: https://github.com/mailru/FileAPI/issues/25
+ && !(safari && /windows/i.test(userAgent) && !iemobile), // BugFix: https://github.com/mailru/FileAPI/issues/25
cors = html5 && ('withCredentials' in (new XMLHttpRequest)),
-
- chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice||Blob.prototype.mozSlice||Blob.prototype.slice),
- document = window.document,
+ chunked = html5 && !!Blob && !!(Blob.prototype.webkitSlice || Blob.prototype.mozSlice || Blob.prototype.slice),
+
+ normalize = ('' + ''.normalize).indexOf('[native code]') > 0,
// https://github.com/blueimp/JavaScript-Canvas-to-Blob
dataURLtoBlob = window.dataURLtoBlob,
+
_rimg = /img/i,
_rcanvas = /canvas/i,
- _rimgcanvas = /img|canvas/,
+ _rimgcanvas = /img|canvas/i,
_rinput = /input/i,
_rdata = /^data:[^,]+,/,
- _pow = Math.pow,
- _round = Math.round,
- _num = Number,
- _from = function (sz) { return _round(sz * this); },
+ _toString = {}.toString,
+ _supportConsoleLog,
+ _supportConsoleLogApply,
+
- _KB = new _num(1024),
- _MB = new _num(_pow(_KB, 2)),
- _GB = new _num(_pow(_KB, 3)),
- _TB = new _num(_pow(_KB, 4)),
+ Math = window.Math,
+
+ _SIZE_CONST = function (pow){
+ pow = new window.Number(Math.pow(1024, pow));
+ pow.from = function (sz){ return Math.round(sz * this); };
+ return pow;
+ },
_elEvents = {}, // element event listeners
_infoReader = [], // list of file info processors
@@ -52,31 +65,162 @@
_readerEvents = 'abort progress error load loadend',
_xhrPropsExport = 'status statusText readyState response responseXML responseText responseBody'.split(' '),
- currentTarget = 'currentTarget',
- preventDefault = 'preventDefault',
+ currentTarget = 'currentTarget', // for minimize
+ preventDefault = 'preventDefault', // and this too
+
+ _isArray = function (ar) {
+ return ar && ('length' in ar);
+ },
+
+ /**
+ * Iterate over a object or array
+ */
+ _each = function (obj, fn, ctx){
+ if( obj ){
+ if( _isArray(obj) ){
+ for( var i = 0, n = obj.length; i < n; i++ ){
+ if( i in obj ){
+ fn.call(ctx, obj[i], i, obj);
+ }
+ }
+ }
+ else {
+ for( var key in obj ){
+ if( obj.hasOwnProperty(key) ){
+ fn.call(ctx, obj[key], key, obj);
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Merge the contents of two or more objects together into the first object
+ */
+ _extend = function (dst){
+ var args = arguments, i = 1, _ext = function (val, key){ dst[key] = val; };
+ for( ; i < args.length; i++ ){
+ _each(args[i], _ext);
+ }
+ return dst;
+ },
+
+ /**
+ * Add event listener
+ */
+ _on = function (el, type, fn){
+ if( el ){
+ var uid = api.uid(el);
+
+ if( !_elEvents[uid] ){
+ _elEvents[uid] = {};
+ }
+
+ var isFileReader = (FileReader && el) && (el instanceof FileReader);
+ _each(type.split(/\s+/), function (type){
+ if( jQuery && !isFileReader){
+ jQuery.event.add(el, type, fn);
+ } else {
+ if( !_elEvents[uid][type] ){
+ _elEvents[uid][type] = [];
+ }
+ _elEvents[uid][type].push(fn);
+
+ if( el.addEventListener ){ el.addEventListener(type, fn, false); }
+ else if( el.attachEvent ){ el.attachEvent('on'+type, fn); }
+ else { el['on'+type] = fn; }
+ }
+ });
+ }
+ },
+
+
+ /**
+ * Remove event listener
+ */
+ _off = function (el, type, fn){
+ if( el ){
+ var uid = api.uid(el), events = _elEvents[uid] || {};
+
+ var isFileReader = (FileReader && el) && (el instanceof FileReader);
+ _each(type.split(/\s+/), function (type){
+ if( jQuery && !isFileReader){
+ jQuery.event.remove(el, type, fn);
+ }
+ else {
+ var fns = events[type] || [], i = fns.length;
+
+ while( i-- ){
+ if( fns[i] === fn ){
+ fns.splice(i, 1);
+ break;
+ }
+ }
+
+ if( el.addEventListener ){ el.removeEventListener(type, fn, false); }
+ else if( el.detachEvent ){ el.detachEvent('on'+type, fn); }
+ else { el['on'+type] = null; }
+ }
+ });
+ }
+ },
+
+
+ _one = function(el, type, fn){
+ _on(el, type, function _(evt){
+ _off(el, type, _);
+ fn(evt);
+ });
+ },
+
+
+ _fixEvent = function (evt){
+ if( !evt.target ){ evt.target = window.event && window.event.srcElement || document; }
+ if( evt.target.nodeType === 3 ){ evt.target = evt.target.parentNode; }
+ return evt;
+ },
+
+
+ _supportInputAttr = function (attr){
+ var input = document.createElement('input');
+ input.setAttribute('type', "file");
+ return attr in input;
+ },
+
+
+ /**
+ * FileAPI (core object)
+ */
api = {
- version: '1.2.6',
+ version: '2.1.1',
cors: false,
html5: true,
+ media: false,
+ formData: true,
+ multiPassResize: true,
+ insecureChrome: insecureChrome,
+
debug: false,
pingUrl: false,
+ multiFlash: false,
flashAbortTimeout: 0,
withCredentials: true,
- staticPath: './',
+ staticPath: './dist/',
flashUrl: 0, // @default: './FileAPI.flash.swf'
flashImageUrl: 0, // @default: './FileAPI.flash.image.swf'
postNameConcat: function (name, idx){
- return name + (idx != null ? '['+ idx +']' : '');
+ return name + (idx != null ? '['+ idx +']' : '');
},
ext2mime: {
- jpg: 'image/jpeg'
- , tif: 'image/tiff'
+ jpg: 'image/jpeg'
+ , tif: 'image/tiff'
+ , txt: 'text/plain'
},
// Fallback for flash
@@ -86,14 +230,19 @@
, 'video/*': 'm4v 3gp nsv ts ty strm rm rmvb m3u ifo mov qt divx xvid bivx vob nrg img iso pva wmv asf asx ogm m2v avi bin dat dvr-ms mpg mpeg mp4 mkv avc vp3 svq3 nuv viv dv fli flv wpl'
},
+ uploadRetry : 0,
+ networkDownRetryTimeout : 5000, // milliseconds, don't flood when network is down
+
chunkSize : 0,
chunkUploadRetry : 0,
chunkNetworkDownRetryTimeout : 2000, // milliseconds, don't flood when network is down
- KB: (_KB.from = _from, _KB),
- MB: (_MB.from = _from, _MB),
- GB: (_GB.from = _from, _GB),
- TB: (_TB.from = _from, _TB),
+ KB: _SIZE_CONST(1),
+ MB: _SIZE_CONST(2),
+ GB: _SIZE_CONST(3),
+ TB: _SIZE_CONST(4),
+
+ EMPTY_PNG: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=',
expando: 'fileapi' + (new Date).getTime(),
@@ -105,8 +254,8 @@
},
log: function (){
- if( api.debug && window.console && console.log ){
- if( console.log.apply ){
+ if( api.debug && _supportConsoleLog ){
+ if( _supportConsoleLogApply ){
console.log.apply(console, arguments);
}
else {
@@ -155,14 +304,16 @@
return xhr;
},
- isArray: isArray,
+ isArray: _isArray,
support: {
dnd: cors && ('ondrop' in document.createElement('div')),
cors: cors,
html5: html5,
chunked: chunked,
- dataURI: true
+ dataURI: true,
+ accept: _supportInputAttr('accept'),
+ multiple: _supportInputAttr('multiple')
},
event: {
@@ -294,13 +445,13 @@
/**
* Async for
- *
- * @param {Array} array
+ * @param {Array} array
+ * @param {Function} callback
*/
afor: function (array, callback){
var i = 0, n = array.length;
- if( isArray(array) && n-- ){
+ if( _isArray(array) && n-- ){
(function _next(){
callback(n != i && _next, array[i], i++);
})();
@@ -311,32 +462,32 @@
},
-
/**
* Merge the contents of two or more objects together into the first object
*
* @param {Object} dst
- * @param {Object} [src]
* @return {Object}
*/
- extend: function (dst){
- _each(arguments, function (src){
- _each(src, function (val, key){
- dst[key] = val;
- });
- });
- return dst;
- },
+ extend: _extend,
/**
- * Is file instance
- *
+ * Is file?
* @param {File} file
* @return {Boolean}
*/
isFile: function (file){
- return html5 && file && (file instanceof File);
+ return _toString.call(file) === '[object File]';
+ },
+
+
+ /**
+ * Is blob?
+ * @param {Blob} blob
+ * @returns {Boolean}
+ */
+ isBlob: function (blob) {
+ return this.isFile(blob) || (_toString.call(blob) === '[object Blob]');
},
@@ -417,7 +568,7 @@
*
* @param {File} file
* @param {String} encoding
- * @param {Function} fn
+ * @param {Function} [fn]
*/
readAsText: function(file, encoding, fn){
if( !fn ){
@@ -432,15 +583,16 @@
/**
* Convert image or canvas to DataURL
*
- * @param {Element} el Image or Canvas element
+ * @param {Element} el Image or Canvas element
+ * @param {String} [type] mime-type
* @return {String}
*/
- toDataURL: function (el){
+ toDataURL: function (el, type){
if( typeof el == 'string' ){
return el;
}
else if( el.toDataURL ){
- return el.toDataURL('image/png');
+ return el.toDataURL(type || 'image/png');
}
},
@@ -464,7 +616,7 @@
* @param {Boolean} [progress]
*/
readAsImage: function (file, fn, progress){
- if( api.isFile(file) ){
+ if( api.isBlob(file) ){
if( apiURL ){
/** @namespace apiURL.createObjectURL */
var data = apiURL.createObjectURL(file);
@@ -539,8 +691,8 @@
_each(accept, function (ext, type){
ext = new RegExp(ext.replace(/\s/g, '|'), 'i');
- if( ext.test(file.type) ){
- file.type = api.ext2mime[file.type] || type.split('/')[0] +'/'+ file.type;
+ if( ext.test(file.type) || api.ext2mime[file.type] ){
+ file.type = api.ext2mime[file.type] || (type.split('/')[0] +'/'+ file.type);
}
});
@@ -557,30 +709,91 @@
getDropFiles: function (evt, callback){
var
files = []
+ , all = []
+ , items
, dataTransfer = _getDataTransfer(evt)
- , entrySupport = isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0])
- , queue = api.queue(function (){ callback(files); })
+ , transFiles = dataTransfer.files
+ , transItems = dataTransfer.items
+ , entrySupport = _isArray(transItems) && transItems[0] && _getAsEntry(transItems[0])
+ , queue = api.queue(function (){ callback(files, all); })
;
- _each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){
+ if( entrySupport ){
+ if( normalize && transFiles ){
+ var
+ i = transFiles.length
+ , file
+ , entry
+ ;
+
+ items = new Array(i);
+ while( i-- ){
+ file = transFiles[i];
+
+ try {
+ entry = _getAsEntry(transItems[i]);
+ }
+ catch( err ){
+ api.log('[err] getDropFiles: ', err);
+ entry = null;
+ }
+
+ if( _isEntry(entry) ){
+ // OSX filesystems use Unicode Normalization Form D (NFD),
+ // and entry.file(…) can't read the files with the same names
+ if( entry.isDirectory || (entry.isFile && file.name == file.name.normalize('NFC')) ){
+ items[i] = entry;
+ }
+ else {
+ items[i] = file;
+ }
+ }
+ else {
+ items[i] = file;
+ }
+ }
+ }
+ else {
+ items = transItems;
+ }
+ }
+ else {
+ items = transFiles;
+ }
+
+ _each(items || [], function (item){
queue.inc();
+
try {
- if( entrySupport ){
- _readEntryAsFiles(item, function (err, entryFiles){
- !err && files.push.apply(files, entryFiles);
+ if( entrySupport && _isEntry(item) ){
+ _readEntryAsFiles(item, function (err, entryFiles, allEntries){
+ if( err ){
+ api.log('[err] getDropFiles:', err);
+ } else {
+ files.push.apply(files, entryFiles);
+ }
+ all.push.apply(all, allEntries);
+
queue.next();
});
}
else {
- _isRegularFile(item, function (yes){
- yes && files.push(item);
+ _isRegularFile(item, function (yes, err){
+ if( yes ){
+ files.push(item);
+ }
+ else {
+ item.error = err;
+ }
+ all.push(item);
+
queue.next();
});
}
}
catch( err ){
queue.next();
- api.log('getDropFiles.error:', err.toString());
+ api.log('[err] getDropFiles: ', err);
}
});
@@ -591,7 +804,7 @@
/**
* Get file list
*
- * @param {HTMLInput|Event} input
+ * @param {HTMLInputElement|Event} input
* @param {String|Function} [filter]
* @param {Function} [callback]
* @return {Array|Null}
@@ -653,7 +866,7 @@
files[0].iframe = true;
}
}
- else if( isArray(input) ){
+ else if( _isArray(input) ){
files = input;
}
@@ -661,6 +874,20 @@
},
+ /**
+ * Get total file size
+ * @param {Array} files
+ * @return {Number}
+ */
+ getTotalSize: function (files){
+ var size = 0, i = files && files.length;
+ while( i-- ){
+ size += files[i].size;
+ }
+ return size;
+ },
+
+
/**
* Get image information
*
@@ -670,7 +897,7 @@
getInfo: function (file, fn){
var info = {}, readers = _infoReader.concat();
- if( api.isFile(file) ){
+ if( api.isBlob(file) ){
(function _next(){
var reader = readers.shift();
if( reader ){
@@ -680,7 +907,7 @@
fn(err);
}
else {
- api.extend(info, res);
+ _extend(info, res);
_next();
}
});
@@ -695,7 +922,7 @@
})();
}
else {
- fn('not_support', info);
+ fn('not_support_info', info);
}
},
@@ -707,7 +934,7 @@
* @param {Function} fn
*/
addInfoReader: function (mime, fn){
- fn.test = function (type){ return mime.test(type) };
+ fn.test = function (type){ return mime.test(type); };
_infoReader.push(fn);
},
@@ -721,12 +948,16 @@
*/
filter: function (input, fn){
var result = [], i = 0, n = input.length, val;
- for( ; i < n; i++ ) if( i in input ){
- val = input[i];
- if( fn.call(val, val, i, input) ){
- result.push(val);
+
+ for( ; i < n; i++ ){
+ if( i in input ){
+ val = input[i];
+ if( fn.call(val, val, i, input) ){
+ result.push(val);
+ }
}
}
+
return result;
},
@@ -763,8 +994,9 @@
upload: function (options){
- options = api.extend({
- prepare: api.F
+ options = _extend({
+ jsonp: 'callback'
+ , prepare: api.F
, beforeupload: api.F
, upload: api.F
, fileupload: api.F
@@ -773,21 +1005,24 @@
, progress: api.F
, complete: api.F
, pause: api.F
+ , imageOriginal: true
, chunkSize: api.chunkSize
- , chunkUpoloadRetry: api.chunkUploadRetry
+ , chunkUploadRetry: api.chunkUploadRetry
+ , uploadRetry: api.uploadRetry
}, options);
+
if( options.imageAutoOrientation && !options.imageTransform ){
- options.imageTransform = { rotate: 'auto' };
+ options.imageTransform = { rotate: 'auto' };
}
var
proxyXHR = new api.XHR(options)
, dataArray = this._getFilesDataArray(options.files)
+ , _this = this
, _total = 0
, _loaded = 0
- , _this = this
, _nextFile
, _complete = false
;
@@ -811,8 +1046,9 @@
// emit "beforeupload" event
options.beforeupload(proxyXHR, options);
+
// Upload by file
- ( _nextFile = function _nextFile(){
+ _nextFile = function (){
var
data = dataArray.shift()
, _file = data && data.file
@@ -824,26 +1060,30 @@
if( _file && _file.name === api.expando ){
_file = null;
- api.log('[warn] FileAPI.upload() — called without files')
+ api.log('[warn] FileAPI.upload() — called without files');
}
if( ( proxyXHR.statusText != 'abort' || proxyXHR.current ) && data ){
- // Mark active job
- _complete = false;
+ // Mark active job
+ _complete = false;
// Set current upload file
proxyXHR.currentFile = _file;
// Prepare file options
- _file && options.prepare(_file, _fileOptions);
+ if (_file && options.prepare(_file, _fileOptions) === false) {
+ _nextFile.call(_this);
+ return;
+ }
+ _fileOptions.file = _file;
- this._getFormData(_fileOptions, data, function (form){
+ _this._getFormData(_fileOptions, data, function (form){
if( !_loaded ){
// emit "upload" event
options.upload(proxyXHR, options);
}
- var xhr = new api.XHR(api.extend({}, _fileOptions, {
+ var xhr = new api.XHR(_extend({}, _fileOptions, {
upload: _file ? function (){
// emit "fileupload" event
@@ -852,6 +1092,9 @@
progress: _file ? function (evt){
if( !_fileLoaded ){
+ // For ignore the double calls.
+ _fileLoaded = (evt.loaded === evt.total);
+
// emit "fileprogress" event
options.fileprogress({
type: 'progress'
@@ -863,35 +1106,38 @@
options.progress({
type: 'progress'
, total: _total
- , loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total))|0
+ , loaded: proxyXHR.loaded = (_loaded + data.size * (evt.loaded/evt.total)) || 0
}, _file, xhr, _fileOptions);
}
} : noop,
complete: function (err){
- // fixed throttle event
- _fileLoaded = true;
-
_each(_xhrPropsExport, function (name){
proxyXHR[name] = xhr[name];
});
if( _file ){
+ data.total = (data.total || data.size);
data.loaded = data.total;
- // emulate 100% "progress"
- this.progress(data);
+ if( !err ) {
+ // emulate 100% "progress"
+ this.progress(data);
+
+ // fixed throttle event
+ _fileLoaded = true;
- // bytes loaded
- _loaded += data.size; // data.size != data.total, it's desirable fix this
- proxyXHR.loaded = _loaded;
+ // bytes loaded
+ _loaded += data.size; // data.size != data.total, it's desirable fix this
+ proxyXHR.loaded = _loaded;
+ }
// emit "filecomplete" event
options.filecomplete(err, xhr, _file, _fileOptions);
}
// upload next file
- _nextFile.call(_this);
+ setTimeout(function () {_nextFile.call(_this);}, 0);
}
})); // xhr
@@ -908,11 +1154,17 @@
});
}
else {
- options.complete(proxyXHR.status == 200 || proxyXHR.status == 201 ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
+ var successful = proxyXHR.status == 200 || proxyXHR.status == 201 || proxyXHR.status == 204;
+ options.complete(successful ? false : (proxyXHR.statusText || 'error'), proxyXHR, options);
// Mark done state
_complete = true;
}
- }).call(this);
+ };
+
+
+ // Next tick
+ setTimeout(_nextFile, 0);
+
// Append more files to the existing request
// first - add them to the queue head/tail
@@ -931,21 +1183,23 @@
proxyXHR.statusText = "";
- if (_complete) {
+ if( _complete ){
_nextFile.call(_this);
}
- }
+ };
+
// Removes file from queue by file reference and returns it
proxyXHR.remove = function (file) {
- var idx = -1;
- _each(dataArray, function (data) {
- idx++;
- if (data.file == file) {
- return dataArray.splice(idx, 1);
+ var i = dataArray.length, _file;
+ while( i-- ){
+ if( dataArray[i].file == file ){
+ _file = dataArray.splice(i, 1);
+ _total -= _file.size;
}
- });
- }
+ }
+ return _file;
+ };
return proxyXHR;
},
@@ -958,7 +1212,7 @@
var tmp = api.getFiles(data);
oFiles[data.name || 'file'] = data.getAttribute('multiple') !== null ? tmp : tmp[0];
}
- else if( isArray(data) && isInputFile(data[0]) ){
+ else if( _isArray(data) && isInputFile(data[0]) ){
_each(data, function (input){
oFiles[input.name || 'file'] = api.getFiles(input);
});
@@ -968,12 +1222,12 @@
}
_each(oFiles, function add(file, name){
- if( isArray(file) ){
- _each(file, function (file, idx){
+ if( _isArray(file) ){
+ _each(file, function (file){
add(file, name);
});
}
- else if( file && file.name ){
+ else if( file && (file.name || file.image) ){
files.push({
name: name
, file: file
@@ -1002,15 +1256,15 @@
, trans = api.support.transform && options.imageTransform
, Form = new api.Form
, queue = api.queue(function (){ fn(Form); })
- , isOrignTrans = trans && (parseInt(trans.maxWidth || trans.minWidth || trans.width, 10) > 0 || trans.rotate)
+ , isOrignTrans = trans && _isOriginTransform(trans)
+ , postNameConcat = api.postNameConcat
;
-
// Append data
_each(options.data, function add(val, name){
if( typeof val == 'object' ){
_each(val, function (v, i){
- add(v, api.postNameConcat(name, i));
+ add(v, postNameConcat(name, i));
});
}
else {
@@ -1018,49 +1272,70 @@
}
});
+ (function _addFile(file/**Object*/){
+ if( file.image ){ // This is a FileAPI.Image
+ queue.inc();
- if( api.Image && trans && (/image/.test(file.type) || _rimgcanvas.test(file.nodeType)) ){
- queue.inc();
+ file.toData(function (err, image){
+ // @todo: требует рефакторинга и обработки ошибки
+ if (file.file) {
+ image.type = file.file.type;
+ image.quality = file.matrix.quality;
+ filename = file.file && file.file.name;
+ }
- if( isOrignTrans ){
- // Convert to array for transform function
- trans = [trans];
- }
+ filename = filename || (new Date).getTime()+'.png';
- api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
- if( isOrignTrans && !err ){
- if( !dataURLtoBlob && !api.flashEngine ){
- images[0] = api.toBinaryString(images[0]);
- Form.multipart = true;
- }
+ _addFile(image);
+ queue.next();
+ });
+ }
+ else if( api.Image && trans && (/^image/.test(file.type) || _rimgcanvas.test(file.nodeName)) ){
+ queue.inc();
- Form.append(name, images[0], filename, filetype);
+ if( isOrignTrans ){
+ // Convert to array for transform function
+ trans = [trans];
}
- else {
- if( !err ){
- _each(images, function (image, idx){
- if( !dataURLtoBlob && !api.flashEngine ){
- image = api.toBinaryString(image);
- Form.multipart = true;
- }
- Form.append(name +'['+ idx +']', image, filename, filetype);
- });
+ api.Image.transform(file, trans, options.imageAutoOrientation, function (err, images){
+ if( isOrignTrans && !err ){
+ if( !dataURLtoBlob && !api.flashEngine ){
+ // Canvas.toBlob or Flash not supported, use multipart
+ Form.multipart = true;
+ }
- name += '[original]';
+ Form.append(name, images[0], filename, trans[0].type || filetype);
}
+ else {
+ var addOrigin = 0;
- if( err || options.imageOriginal ){
- Form.append(name, file, filename, filetype);
+ if( !err ){
+ _each(images, function (image, idx){
+ if( !dataURLtoBlob && !api.flashEngine ){
+ Form.multipart = true;
+ }
+
+ if( !trans[idx].postName ){
+ addOrigin = 1;
+ }
+
+ Form.append(trans[idx].postName || postNameConcat(name, idx), image, filename, trans[idx].type || filetype);
+ });
+ }
+
+ if( err || options.imageOriginal ){
+ Form.append(postNameConcat(name, (addOrigin ? 'original' : null)), file, filename, filetype);
+ }
}
- }
- queue.next();
- });
- }
- else if( filename !== api.expando ){
- Form.append(name, file, filename);
- }
+ queue.next();
+ });
+ }
+ else if( filename !== api.expando ){
+ Form.append(name, file, filename);
+ }
+ })(file);
queue.check();
},
@@ -1087,7 +1362,7 @@
_each(fns, function (fn){
_off(inp, type, fn);
_on(clone, type, fn);
- })
+ });
});
}
@@ -1130,10 +1405,10 @@
, type: xhr.getResponseHeader('Content-Type')
};
file.dataURL = 'data:'+file.type+';base64,' + api.encode64(xhr.responseBody || xhr.responseText);
- fn({ type: 'load', result: file });
+ fn({ type: 'load', result: file }, xhr);
}
else {
- fn({ type: 'error' });
+ fn({ type: 'error' }, xhr);
}
}
};
@@ -1179,86 +1454,6 @@
} // api
;
-
-
- function _each(obj, fn, ctx){
- if( obj ){
- if( isArray(obj) ){
- for( var i = 0, n = obj.length; i < n; i++ ){ if( i in obj )
- fn.call(ctx, obj[i], i, obj);
- }
- }
- else {
- for( var key in obj ) if( obj.hasOwnProperty(key) ){
- fn.call(ctx, obj[key], key, obj);
- }
- }
- }
- }
-
-
- // @private methods
- function _on(el, type, fn){
- if( el ){
- var uid = api.uid(el);
-
- if( !_elEvents[uid] ){
- _elEvents[uid] = {};
- }
-
- _each(type.split(/\s+/), function (type){
- if( jQuery ){
- jQuery.event.add(el, type, fn);
- }
- else {
- if( !_elEvents[uid][type] ){
- _elEvents[uid][type] = []
- }
- _elEvents[uid][type].push(fn);
-
-
- if( el.addEventListener ) el.addEventListener(type, fn, false);
- else if( el.attachEvent ) el.attachEvent('on'+type, fn);
- else el['on'+type] = fn;
- }
- });
- }
- }
-
-
- function _off(el, type, fn){
- if( el ){
- var uid = api.uid(el), events = _elEvents[uid] || {};
-
- _each(type.split(/\s+/), function (type){
- if( jQuery ){
- jQuery.event.remove(el, type, fn);
- }
- else {
- var fns = events[type] || [], i = fns.length;
-
- while( i-- ){
- if( fns[i] === fn ){
- fns.splice(i, 1);
- break;
- }
- }
-
- if( el.addEventListener ) el.removeEventListener(type, fn, false);
- else if( el.detachEvent ) el.detachEvent('on'+type, fn);
- else el['on'+type] = null;
- }
- });
- }
- }
-
-
- function _one(el, type, fn){
- _on(el, type, function _(evt){
- _off(el, type, _);
- fn(evt);
- });
- }
function _emit(target, fn, name, res, ext){
@@ -1267,25 +1462,25 @@
, target: target
, result: res
};
- api.extend(evt, ext);
+ _extend(evt, ext);
fn(evt);
}
- function _hasSupportReadAs(as){
- return FileReader && !!FileReader.prototype['readAs'+as];
+ function _hasSupportReadAs(method){
+ return FileReader && !!FileReader.prototype['readAs' + method];
}
- function _readAs(file, fn, as, encoding){
- if( api.isFile(file) && _hasSupportReadAs(as) ){
+ function _readAs(file, fn, method, encoding){
+ if( api.isBlob(file) && _hasSupportReadAs(method) ){
var Reader = new FileReader;
// Add event listener
_on(Reader, _readerEvents, function _fn(evt){
var type = evt.type;
if( type == 'progress' ){
- _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total })
+ _emit(file, fn, evt, evt.target.result, { loaded: evt.loaded, total: evt.total });
}
else if( type == 'loadend' ){
_off(Reader, _readerEvents, _fn);
@@ -1300,10 +1495,10 @@
try {
// ReadAs ...
if( encoding ){
- Reader['readAs'+as](encoding, file);
+ Reader['readAs' + method](file, encoding);
}
else {
- Reader['readAs'+as](file);
+ Reader['readAs' + method](file);
}
}
catch (err){
@@ -1311,33 +1506,38 @@
}
}
else {
- _emit(file, fn, 'error', undef, { error: 'filreader_not_support_'+as });
+ _emit(file, fn, 'error', undef, { error: 'filreader_not_support_' + method });
}
}
function _isRegularFile(file, callback){
// http://stackoverflow.com/questions/8856628/detecting-folders-directories-in-javascript-filelist-objects
- if( !file.type && (file.size % 4096) == 0 && (file.size <= 102400) ){
+ if( !file.type && (safari || ((file.size % 4096) === 0 && (file.size <= 102400))) ){
if( FileReader ){
try {
- var Reader = new FileReader();
+ var reader = new FileReader();
- _one(Reader, _readerEvents, function (evt){
+ _one(reader, _readerEvents, function (evt){
var isFile = evt.type != 'error';
- callback(isFile);
if( isFile ){
- Reader.abort();
+ if ( reader.readyState == null || reader.readyState === reader.LOADING ) {
+ reader.abort();
+ }
+ callback(isFile);
+ }
+ else {
+ callback(false, reader.error);
}
});
- Reader.readAsDataURL(file);
+ reader.readAsDataURL(file);
} catch( err ){
- callback(false);
+ callback(false, err);
}
}
else {
- callback(null)
+ callback(null, new Error('FileReader is not supported'));
}
}
else {
@@ -1346,10 +1546,15 @@
}
+ function _isEntry(item){
+ return item && (item.isFile || item.isDirectory);
+ }
+
+
function _getAsEntry(item){
var entry;
- if( item.getAsEntry ) entry = item.getAsEntry();
- else if( item.webkitGetAsEntry ) entry = item.webkitGetAsEntry();
+ if( item.getAsEntry ){ entry = item.getAsEntry(); }
+ else if( item.webkitGetAsEntry ){ entry = item.webkitGetAsEntry(); }
return entry;
}
@@ -1357,42 +1562,68 @@
function _readEntryAsFiles(entry, callback){
if( !entry ){
// error
- callback('empty_entry');
+ var err = new Error('invalid entry');
+ entry = new Object(entry);
+ entry.error = err;
+ callback(err.message, [], [entry]);
}
else if( entry.isFile ){
// Read as file
- entry.file(function(file){
+ entry.file(function (file){
// success
file.fullPath = entry.fullPath;
- callback(false, [file]);
- }, function (){
+ callback(false, [file], [file]);
+ }, function (err){
// error
- callback('entry_file');
+ entry.error = err;
+ callback('FileError.code: ' + err.code, [], [entry]);
});
}
else if( entry.isDirectory ){
- var reader = entry.createReader(), result = [];
+ var
+ reader = entry.createReader()
+ , firstAttempt = true
+ , files = []
+ , all = [entry]
+ ;
+
+ var onerror = function (err){
+ // error
+ entry.error = err;
+ callback('DirectoryError.code: ' + err.code, files, all);
+ };
+ var ondone = function ondone(entries){
+ if( firstAttempt ){
+ firstAttempt = false;
+ if( !entries.length ){
+ entry.error = new Error('directory is empty');
+ }
+ }
- reader.readEntries(function(entries){
// success
- api.afor(entries, function (next, entry){
- _readEntryAsFiles(entry, function (err, files){
- if( !err ){
- result = result.concat(files);
- }
+ if( entries.length ){
+ api.afor(entries, function (next, entry){
+ _readEntryAsFiles(entry, function (err, entryFiles, allEntries){
+ if( !err ){
+ files = files.concat(entryFiles);
+ }
+ all = all.concat(allEntries);
- if( next ){
- next();
- }
- else {
- callback(false, result);
- }
+ if( next ){
+ next();
+ }
+ else {
+ reader.readEntries(ondone, onerror);
+ }
+ });
});
- });
- }, function (){
- // error
- callback('directory_reader');
- });
+ }
+ else {
+ callback(false, files, all);
+ }
+ };
+
+ reader.readEntries(ondone, onerror);
}
else {
_readEntryAsFiles(_getAsEntry(entry), callback);
@@ -1400,16 +1631,11 @@
}
- function isArray(ar) {
- return (typeof ar == 'object') && ar && ('length' in ar);
- }
-
-
function _simpleClone(obj){
var copy = {};
_each(obj, function (val, key){
- if( val && (typeof val === 'object') ){
- val = api.extend({}, val);
+ if( val && (typeof val === 'object') && (val.nodeType === void 0) ){
+ val = _extend({}, val);
}
copy[key] = val;
});
@@ -1427,10 +1653,16 @@
}
- function _fixEvent(evt){
- if( !evt.target ) evt.target = window.event && window.event.srcElement || document;
- if( evt.target.nodeType === 3 ) evt.target = event.target.parentNode;
- return evt;
+ function _isOriginTransform(trans){
+ var key;
+ for( key in trans ){
+ if( trans.hasOwnProperty(key) ){
+ if( !(trans[key] instanceof Object || key === 'overlay' || key === 'filter') ){
+ return true;
+ }
+ }
+ }
+ return false;
}
@@ -1445,6 +1677,7 @@
width: img.width
, height: img.height
});
+ img.src = api.EMPTY_PNG;
img = null;
});
}
@@ -1453,7 +1686,13 @@
});
- // Special event
+ /**
+ * Drag'n'Drop special event
+ *
+ * @param {HTMLElement} el
+ * @param {Function} onHover
+ * @param {Function} onDrop
+ */
api.event.dnd = function (el, onHover, onDrop){
var _id, _type;
@@ -1463,38 +1702,52 @@
}
if( FileReader ){
- _on(el, 'dragenter dragleave dragover', function (evt){
- var types = _getDataTransfer(evt).types, i = types && types.length;
+ // Hover
+ _on(el, 'dragenter dragleave dragover', onHover.ff = onHover.ff || function (evt){
+ var
+ types = _getDataTransfer(evt).types
+ , i = types && types.length
+ , debounceTrigger = false
+ ;
while( i-- ){
if( ~types[i].indexOf('File') ){
evt[preventDefault]();
if( _type !== evt.type ){
- _type = evt.type;
+ _type = evt.type; // Store current type of event
if( _type != 'dragleave' ){
onHover.call(evt[currentTarget], true, evt);
}
- clearTimeout(_id);
- _id = setTimeout(function (){
- onHover.call(evt[currentTarget], _type != 'dragleave', evt);
- }, 50);
+ debounceTrigger = true;
}
+
+ break; // exit from "while"
}
}
+
+ if( debounceTrigger ){
+ clearTimeout(_id);
+ _id = setTimeout(function (){
+ onHover.call(evt[currentTarget], _type != 'dragleave', evt);
+ }, 50);
+ }
});
- _on(el, 'drop', function (evt){
+
+ // Drop
+ _on(el, 'drop', onDrop.ff = onDrop.ff || function (evt){
evt[preventDefault]();
_type = 0;
- onHover.call(evt[currentTarget], false, evt);
- api.getDropFiles(evt, function (files){
- onDrop.call(evt[currentTarget], files, evt);
+ api.getDropFiles(evt, function (files, all){
+ onDrop.call(evt[currentTarget], files, all, evt);
});
+
+ onHover.call(evt[currentTarget], false, evt);
});
}
else {
@@ -1503,6 +1756,18 @@
};
+ /**
+ * Remove drag'n'drop
+ * @param {HTMLElement} el
+ * @param {Function} onHover
+ * @param {Function} onDrop
+ */
+ api.event.dnd.off = function (el, onHover, onDrop){
+ _off(el, 'dragenter dragleave dragover', onHover.ff);
+ _off(el, 'drop', onDrop.ff);
+ };
+
+
// Support jQuery
if( jQuery && !jQuery.fn.dnd ){
jQuery.fn.dnd = function (onHover, onDrop){
@@ -1510,34 +1775,40 @@
api.event.dnd(this, onHover, onDrop);
});
};
- }
+ jQuery.fn.offdnd = function (onHover, onDrop){
+ return this.each(function (){
+ api.event.dnd.off(this, onHover, onDrop);
+ });
+ };
+ }
// @export
- window.FileAPI = api.extend(api, window.FileAPI);
-
+ window.FileAPI = _extend(api, window.FileAPI);
- api.log('version: '+api.version);
- api.log('protocol: '+window.location.protocol);
-
- if( document.doctype ){
- api.log('doctype: ['+ document.doctype.name +'] '+ document.doctype.publicId +' '+ document.doctype.systemId);
- }
- else {
- api.log('doctype: unknown');
- }
+ // Debug info
+ api.log('FileAPI: ' + api.version);
+ api.log('protocol: ' + window.location.protocol);
+ api.log('doctype: [' + doctype.name + '] ' + doctype.publicId + ' ' + doctype.systemId);
// @detect 'x-ua-compatible'
_each(document.getElementsByTagName('meta'), function (meta){
if( /x-ua-compatible/i.test(meta.getAttribute('http-equiv')) ){
- api.log('meta.http-equiv: '+meta.getAttribute('content'));
+ api.log('meta.http-equiv: ' + meta.getAttribute('content'));
}
});
- // @configuration
- if( !api.flashUrl ) api.flashUrl = api.staticPath + 'FileAPI.flash.swf';
- if( !api.flashImageUrl ) api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf';
-})(window);
+ // Configuration
+ try {
+ _supportConsoleLog = !!console.log;
+ _supportConsoleLogApply = !!console.log.apply;
+ }
+ catch (err) {}
+
+ if( !api.flashUrl ){ api.flashUrl = api.staticPath + 'FileAPI.flash.swf'; }
+ if( !api.flashImageUrl ){ api.flashImageUrl = api.staticPath + 'FileAPI.flash.image.swf'; }
+ if( !api.flashWebcamUrl ){ api.flashWebcamUrl = api.staticPath + 'FileAPI.flash.camera.swf'; }
+})(window, void 0);
diff --git a/lib/canvas-to-blob.js b/lib/canvas-to-blob.js
index 2ce20b1e..024686fa 100644
--- a/lib/canvas-to-blob.js
+++ b/lib/canvas-to-blob.js
@@ -1,5 +1,5 @@
/*
- * JavaScript Canvas to Blob 2.0.3
+ * JavaScript Canvas to Blob 2.0.5
* https://github.com/blueimp/JavaScript-Canvas-to-Blob
*
* Copyright 2012, Sebastian Tschan
@@ -13,7 +13,7 @@
*/
/*jslint nomen: true, regexp: true */
-/*global window, atob, Blob, ArrayBuffer, Uint8Array, define */
+/*global window, atob, Blob, ArrayBuffer, Uint8Array */
(function (window) {
'use strict';
@@ -72,15 +72,18 @@
};
if (window.HTMLCanvasElement && !CanvasPrototype.toBlob) {
if (CanvasPrototype.mozGetAsFile) {
- CanvasPrototype.toBlob = function (callback, type) {
- callback(this.mozGetAsFile('blob', type));
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ if (quality && CanvasPrototype.toDataURL && dataURLtoBlob) {
+ callback(dataURLtoBlob(this.toDataURL(type, quality)));
+ } else {
+ callback(this.mozGetAsFile('blob', type));
+ }
};
} else if (CanvasPrototype.toDataURL && dataURLtoBlob) {
- CanvasPrototype.toBlob = function (callback, type) {
- callback(dataURLtoBlob(this.toDataURL(type)));
+ CanvasPrototype.toBlob = function (callback, type, quality) {
+ callback(dataURLtoBlob(this.toDataURL(type, quality)));
};
}
}
-
window.dataURLtoBlob = dataURLtoBlob;
-}(this));
+})(window);
diff --git a/node/file-api.js b/node/file-api.js
new file mode 100644
index 00000000..5d9fd6c5
--- /dev/null
+++ b/node/file-api.js
@@ -0,0 +1,62 @@
+var fs = require('fs');
+var qs = require('qs');
+var imageSize = require('image-size');
+
+function convertToBase64(buffer, mimetype) {
+ return 'data:' + mimetype + ';base64,' + buffer.toString('base64');
+}
+
+function fileApi() {
+ return function (req, res, next) {
+ var queryString = '';
+
+ req.files = {};
+ req.images = {};
+
+ req.busboy.on('file', function (fieldname, file, filename, encoding, mimetype) {
+ var buffersArray = [];
+
+ file.on('data', function (data) {
+ buffersArray.push(data);
+ });
+
+ file.on('end', function () {
+ var bufferResult = Buffer.concat(buffersArray);
+ var fileObj = {
+ name: filename,
+ type: mimetype,
+ mime: mimetype,
+ size: bufferResult.length,
+ dataURL: convertToBase64(bufferResult, mimetype)
+ };
+
+ req.files[fieldname] = fileObj;
+
+ if (mimetype.indexOf('image/') === 0) {
+ fs.writeFileSync(filename, bufferResult);
+
+ var size = imageSize(filename);
+
+ fileObj.width = size.width;
+ fileObj.height = size.height;
+
+ req.images[fieldname] = fileObj;
+
+ fs.unlinkSync(filename);
+ }
+ });
+ });
+
+ req.busboy.on('field', function (key, value) {
+ queryString += encodeURIComponent(key) + '=' + encodeURIComponent(value) + '&';
+ });
+
+ req.busboy.on('finish', function () {
+ req.body = qs.parse(queryString);
+
+ next();
+ });
+ };
+}
+
+module.exports = fileApi;
diff --git a/node/server.js b/node/server.js
new file mode 100644
index 00000000..119dddae
--- /dev/null
+++ b/node/server.js
@@ -0,0 +1,59 @@
+var express = require('express');
+var busboy = require('connect-busboy');
+var fileApi = require('./file-api');
+var app = express();
+
+app.use(express.static('.', {index: 'index.html'}));
+
+app.use(function (req, res, next) {
+ // Enable CORS for non static files
+ var origin = req.get('Origin');
+
+ if (origin) {
+ res.set({
+ 'Access-Control-Allow-Origin': origin,
+ 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS',
+ 'Access-Control-Allow-Headers': 'Origin, X-Requested-With, Content-Range, Content-Disposition, Content-Type, X-Foo, X-Rnd',
+ 'Access-Control-Allow-Credentials': 'true'
+ });
+ }
+ next();
+});
+
+var uploadPath = '/upload';
+
+app.options(uploadPath, function (req, res) {
+ res.end();
+});
+
+app.post(
+ uploadPath,
+ busboy({immediate: true}), // parse post data
+ fileApi(), // prepare req.body, req.files and req.images
+ function (req, res) {
+ var jsonp = req.query.callback || null;
+
+ res[jsonp ? 'jsonp' : 'json']({
+ status: 200,
+ statusText: 'OK',
+ images: req.images,
+ data: {
+ HEADERS: req.headers,
+ _REQUEST: req.body,
+ _FILES: req.files
+ }
+ });
+ }
+);
+
+// Export
+module.exports.createServer = function (port, callback) {
+ var server = app.listen(port, function () {
+ var host = server.address().address;
+ var port = server.address().port;
+
+ console.log('Test server listening at http://%s:%s', host, port);
+
+ callback(server);
+ });
+};
diff --git a/package.json b/package.json
new file mode 100644
index 00000000..8f4707c9
--- /dev/null
+++ b/package.json
@@ -0,0 +1,61 @@
+{
+ "name": "fileapi",
+ "exportName": "FileAPI",
+ "version": "2.2.0",
+ "devDependencies": {
+ "connect-busboy": "~0.0.2",
+ "eventemitter2": "~0.4.13",
+ "express": "~4.12.3",
+ "flex-sdk": "^4.6.0-0",
+ "grunt": "^0.4.5",
+ "grunt-cli": "^1.3.2",
+ "grunt-contrib-compress": "~0.9.1",
+ "grunt-contrib-concat": "~0.4.0",
+ "grunt-contrib-connect": "~0.8.0",
+ "grunt-contrib-jshint": "~0.10.0",
+ "grunt-contrib-uglify": "~0.5.0",
+ "grunt-contrib-watch": "~0.6.1",
+ "grunt-curl": "~2.0.2",
+ "grunt-mxmlc": "~0.5.2",
+ "grunt-version": "~0.3.0",
+ "image-size": "~0.3.5",
+ "phantomjs": "~1.9.7-9",
+ "qs": "~2.4.1",
+ "semver": "~5.0.0",
+ "temporary": "~0.0.8"
+ },
+ "peerDependencies": {},
+ "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.",
+ "main": "dist/FileAPI.js",
+ "files": [
+ "dist",
+ "plugins",
+ "*.js"
+ ],
+ "jam": {
+ "name": "FileAPI"
+ },
+ "scripts": {
+ "test": "grunt tests --verbose",
+ "build": "grunt build"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/mailru/FileAPI.git"
+ },
+ "keywords": [
+ "FileAPI",
+ "upload",
+ "file",
+ "html5",
+ "chunked"
+ ],
+ "author": "Konstantin Lebedev ",
+ "contributors": [
+ "Vladimir Demidov ",
+ "Ilya Lebedev ",
+ "Mikhail Bezoyan "
+ ],
+ "license": "BSD",
+ "dependencies": {}
+}
diff --git a/FileAPI.exif.js b/plugins/FileAPI.exif.js
similarity index 95%
rename from FileAPI.exif.js
rename to plugins/FileAPI.exif.js
index 7ae3f28c..0dd92d00 100644
--- a/FileAPI.exif.js
+++ b/plugins/FileAPI.exif.js
@@ -40,10 +40,20 @@ FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){
if( !file.__exif ){
var defer = file.__exif = FileAPI.defer();
- FileAPI.readAsBinaryString(file, function (evt){
+ var blob = file;
+ if (blob instanceof Blob && blob.size > 128*1024) {
+ try {
+ var size = Math.min(blob.size, 128 * 1024);
+ blob = (blob.slice || blob.mozSlice || blob.webkitSlice).call(blob, 0, size);
+ } catch (e) {
+ FileAPI.log("exception "+ e);
+ }
+ }
+
+ FileAPI.readAsBinaryString(blob, function (evt){
if( evt.type == 'load' ){
var binaryString = evt.result;
- var oFile = new BinaryFile(binaryString, 0, file.size);
+ var oFile = new BinaryFile(binaryString, 0, blob.size);
var exif = EXIF.readFromBinaryFile(oFile);
defer.resolve(false, { 'exif': exif || {} });
diff --git a/FileAPI.id3.js b/plugins/FileAPI.id3.js
similarity index 100%
rename from FileAPI.id3.js
rename to plugins/FileAPI.id3.js
diff --git a/plugins/caman.full.min.js b/plugins/caman.full.min.js
new file mode 100755
index 00000000..9cd57e9d
--- /dev/null
+++ b/plugins/caman.full.min.js
@@ -0,0 +1,217 @@
+
+(function(){var $,Analyze,Blender,Calculate,Caman,CamanParser,Canvas,Convert,Event,Fiber,Filter,IO,Image,Layer,Log,Logger,PixelInfo,Plugin,Renderer,Root,Store,Util,fs,slice,vignetteFilters,__hasProp={}.hasOwnProperty,__indexOf=[].indexOf||function(item){for(var i=0,l=this.length;i255){return 255;}
+return val;};Util.copyAttributes=function(from,to,opts){var attr,_i,_len,_ref,_ref1,_results;if(opts==null){opts={};}
+_ref=from.attributes;_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++){attr=_ref[_i];if((opts.except!=null)&&(_ref1=attr.nodeName,__indexOf.call(opts.except,_ref1)>=0)){continue;}
+_results.push(to.setAttribute(attr.nodeName,attr.nodeValue));}
+return _results;};Util.dataArray=function(length){if(length==null){length=0;}
+if(Caman.NodeJS||(window.Uint8Array!=null)){return new Uint8Array(length);}
+return new Array(length);};return Util;})();if(typeof exports!=="undefined"&&exports!==null){Root=exports;Canvas=require('canvas');Image=Canvas.Image;Fiber=require('fibers');fs=require('fs');}else{Root=window;}
+Root.Caman=Caman=(function(){Caman.version={release:"4.1.1",date:"4/8/2013"};Caman.DEBUG=false;Caman.NodeJS=typeof exports!=="undefined"&&exports!==null;Caman.autoload=!Caman.NodeJS;Caman.allowRevert=true;Caman.crossOrigin="anonymous";Caman.toString=function(){return"Version "+Caman.version.release+", Released "+Caman.version.date;};Caman.remoteProxy="";Caman.proxyParam="camanProxyUrl";Caman.getAttrId=function(canvas){if(Caman.NodeJS){return true;}
+if(typeof canvas==="string"){canvas=$(canvas);}
+if(!((canvas!=null)&&(canvas.getAttribute!=null))){return null;}
+return canvas.getAttribute('data-caman-id');};function Caman(){var args,callback,id,_this=this;if(arguments.length===0){throw"Invalid arguments";}
+if(this instanceof Caman){this.finishInit=this.finishInit.bind(this);this.imageLoaded=this.imageLoaded.bind(this);args=arguments[0];if(!Caman.NodeJS){id=parseInt(Caman.getAttrId(args[0]),10);callback=typeof args[1]==="function"?args[1]:typeof args[2]==="function"?args[2]:function(){};if(!isNaN(id)&&Store.has(id)){return Store.execute(id,callback);}}
+this.id=Util.uniqid.get();this.initializedPixelData=this.originalPixelData=null;this.cropCoordinates={x:0,y:0};this.cropped=false;this.resized=false;this.pixelStack=[];this.layerStack=[];this.canvasQueue=[];this.currentLayer=null;this.scaled=false;this.analyze=new Analyze(this);this.renderer=new Renderer(this);this.domIsLoaded(function(){_this.parseArguments(args);return _this.setup();});return this;}else{return new Caman(arguments);}}
+Caman.prototype.domIsLoaded=function(cb){var listener,_this=this;if(Caman.NodeJS){return setTimeout(function(){return cb.call(_this);},0);}else{if(document.readyState==="complete"){Log.debug("DOM initialized");return setTimeout(function(){return cb.call(_this);},0);}else{listener=function(){if(document.readyState==="complete"){Log.debug("DOM initialized");return cb.call(_this);}};return document.addEventListener("readystatechange",listener,false);}}};Caman.prototype.parseArguments=function(args){var key,val,_ref,_results;if(args.length===0){throw"Invalid arguments given";}
+this.initObj=null;this.initType=null;this.imageUrl=null;this.callback=function(){};this.setInitObject(args[0]);if(args.length===1){return;}
+switch(typeof args[1]){case"string":this.imageUrl=args[1];break;case"function":this.callback=args[1];}
+if(args.length===2){return;}
+this.callback=args[2];if(args.length===4){_ref=args[4];_results=[];for(key in _ref){if(!__hasProp.call(_ref,key))continue;val=_ref[key];_results.push(this.options[key]=val);}
+return _results;}};Caman.prototype.setInitObject=function(obj){if(Caman.NodeJS){this.initObj=obj;this.initType='node';return;}
+if(typeof obj==="object"){this.initObj=obj;}else{this.initObj=$(obj);}
+if(this.initObj==null){throw"Could not find image or canvas for initialization.";}
+return this.initType=this.initObj.nodeName.toLowerCase();};Caman.prototype.setup=function(){switch(this.initType){case"node":return this.initNode();case"img":return this.initImage();case"canvas":return this.initCanvas();}};Caman.prototype.initNode=function(){var _this=this;Log.debug("Initializing for NodeJS");this.image=new Image();this.image.onload=function(){Log.debug("Image loaded. Width = "+(_this.imageWidth())+", Height = "+(_this.imageHeight()));_this.canvas=new Canvas(_this.imageWidth(),_this.imageHeight());return _this.finishInit();};this.image.onerror=function(err){throw err;};return this.image.src=this.initObj;};Caman.prototype.initImage=function(){this.image=this.initObj;this.canvas=document.createElement('canvas');this.context=this.canvas.getContext('2d');Util.copyAttributes(this.image,this.canvas,{except:['src']});this.image.parentNode.replaceChild(this.canvas,this.image);this.imageAdjustments();return this.waitForImageLoaded();};Caman.prototype.initCanvas=function(){this.canvas=this.initObj;this.context=this.canvas.getContext('2d');if(this.imageUrl!=null){this.image=document.createElement('img');this.image.src=this.imageUrl;this.imageAdjustments();return this.waitForImageLoaded();}else{return this.finishInit();}};Caman.prototype.imageAdjustments=function(){if(this.needsHiDPISwap()){Log.debug(this.image.src,"->",this.hiDPIReplacement());this.swapped=true;this.image.src=this.hiDPIReplacement();}
+if(IO.isRemote(this.image)){this.image.src=IO.proxyUrl(this.image.src);return Log.debug("Remote image detected, using URL = "+this.image.src);}};Caman.prototype.waitForImageLoaded=function(){if(this.isImageLoaded()){return this.imageLoaded();}else{return this.image.onload=this.imageLoaded;}};Caman.prototype.isImageLoaded=function(){if(!this.image.complete){return false;}
+if((this.image.naturalWidth!=null)&&this.image.naturalWidth===0){return false;}
+return true;};Caman.prototype.imageWidth=function(){return this.image.width||this.image.naturalWidth;};Caman.prototype.imageHeight=function(){return this.image.height||this.image.naturalHeight;};Caman.prototype.imageLoaded=function(){Log.debug("Image loaded. Width = "+(this.imageWidth())+", Height = "+(this.imageHeight()));if(this.swapped){this.canvas.width=this.imageWidth()/this.hiDPIRatio();this.canvas.height=this.imageHeight()/this.hiDPIRatio();}else{this.canvas.width=this.imageWidth();this.canvas.height=this.imageHeight();}
+return this.finishInit();};Caman.prototype.finishInit=function(){var i,pixel,_i,_len,_ref;if(this.context==null){this.context=this.canvas.getContext('2d');}
+this.originalWidth=this.preScaledWidth=this.width=this.canvas.width;this.originalHeight=this.preScaledHeight=this.height=this.canvas.height;this.hiDPIAdjustments();if(!this.hasId()){this.assignId();}
+if(this.image!=null){this.context.drawImage(this.image,0,0,this.imageWidth(),this.imageHeight(),0,0,this.preScaledWidth,this.preScaledHeight);}
+this.reloadCanvasData();if(Caman.allowRevert){this.initializedPixelData=Util.dataArray(this.pixelData.length);this.originalPixelData=Util.dataArray(this.pixelData.length);_ref=this.pixelData;for(i=_i=0,_len=_ref.length;_i<_len;i=++_i){pixel=_ref[i];this.initializedPixelData[i]=pixel;this.originalPixelData[i]=pixel;}}
+this.dimensions={width:this.canvas.width,height:this.canvas.height};Store.put(this.id,this);this.callback.call(this,this);return this.callback=function(){};};Caman.prototype.reloadCanvasData=function(){this.imageData=this.context.getImageData(0,0,this.canvas.width,this.canvas.height);return this.pixelData=this.imageData.data;};Caman.prototype.resetOriginalPixelData=function(){var pixel,_i,_len,_ref,_results;if(!Caman.allowRevert){throw"Revert disabled";}
+this.originalPixelData=Util.dataArray(this.pixelData.length);_ref=this.pixelData;_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++){pixel=_ref[_i];_results.push(this.originalPixelData.push(pixel));}
+return _results;};Caman.prototype.hasId=function(){return Caman.getAttrId(this.canvas)!=null;};Caman.prototype.assignId=function(){if(Caman.NodeJS||this.canvas.getAttribute('data-caman-id')){return;}
+return this.canvas.setAttribute('data-caman-id',this.id);};Caman.prototype.hiDPIDisabled=function(){return this.canvas.getAttribute('data-caman-hidpi-disabled')!==null;};Caman.prototype.hiDPIAdjustments=function(){var ratio;if(Caman.NodeJS||this.hiDPIDisabled()){return;}
+ratio=this.hiDPIRatio();if(ratio!==1){Log.debug("HiDPI ratio = "+ratio);this.scaled=true;this.preScaledWidth=this.canvas.width;this.preScaledHeight=this.canvas.height;this.canvas.width=this.preScaledWidth*ratio;this.canvas.height=this.preScaledHeight*ratio;this.canvas.style.width=""+this.preScaledWidth+"px";this.canvas.style.height=""+this.preScaledHeight+"px";this.context.scale(ratio,ratio);this.width=this.originalWidth=this.canvas.width;return this.height=this.originalHeight=this.canvas.height;}};Caman.prototype.hiDPIRatio=function(){var backingStoreRatio,devicePixelRatio;devicePixelRatio=window.devicePixelRatio||1;backingStoreRatio=this.context.webkitBackingStorePixelRatio||this.context.mozBackingStorePixelRatio||this.context.msBackingStorePixelRatio||this.context.oBackingStorePixelRatio||this.context.backingStorePixelRatio||1;return devicePixelRatio/backingStoreRatio;};Caman.prototype.hiDPICapable=function(){return(window.devicePixelRatio!=null)&&window.devicePixelRatio!==1;};Caman.prototype.needsHiDPISwap=function(){if(this.hiDPIDisabled()||!this.hiDPICapable()){return false;}
+return this.hiDPIReplacement()!==null;};Caman.prototype.hiDPIReplacement=function(){if(this.image==null){return null;}
+return this.image.getAttribute('data-caman-hidpi');};Caman.prototype.replaceCanvas=function(newCanvas){var oldCanvas;oldCanvas=this.canvas;this.canvas=newCanvas;this.context=this.canvas.getContext('2d');oldCanvas.parentNode.replaceChild(this.canvas,oldCanvas);this.width=this.canvas.width;this.height=this.canvas.height;this.reloadCanvasData();return this.dimensions={width:this.canvas.width,height:this.canvas.height};};Caman.prototype.render=function(callback){var _this=this;if(callback==null){callback=function(){};}
+Event.trigger(this,"renderStart");return this.renderer.execute(function(){_this.context.putImageData(_this.imageData,0,0);return callback.call(_this);});};Caman.prototype.revert=function(){var i,pixel,_i,_len,_ref;if(!Caman.allowRevert){throw"Revert disabled";}
+_ref=this.originalVisiblePixels();for(i=_i=0,_len=_ref.length;_i<_len;i=++_i){pixel=_ref[i];this.pixelData[i]=pixel;}
+return this.context.putImageData(this.imageData,0,0);};Caman.prototype.reset=function(){var canvas,ctx,i,imageData,pixel,pixelData,_i,_len,_ref;canvas=document.createElement('canvas');Util.copyAttributes(this.canvas,canvas);canvas.width=this.originalWidth;canvas.height=this.originalHeight;ctx=canvas.getContext('2d');imageData=ctx.getImageData(0,0,canvas.width,canvas.height);pixelData=imageData.data;_ref=this.initializedPixelData;for(i=_i=0,_len=_ref.length;_i<_len;i=++_i){pixel=_ref[i];pixelData[i]=pixel;}
+ctx.putImageData(imageData,0,0);this.cropCoordinates={x:0,y:0};this.resized=false;return this.replaceCanvas(canvas);};Caman.prototype.originalVisiblePixels=function(){var canvas,coord,ctx,endX,endY,i,imageData,pixel,pixelData,pixels,scaledCanvas,startX,startY,width,_i,_j,_len,_ref,_ref1,_ref2,_ref3;if(!Caman.allowRevert){throw"Revert disabled";}
+pixels=[];startX=this.cropCoordinates.x;endX=startX+this.width;startY=this.cropCoordinates.y;endY=startY+this.height;if(this.resized){canvas=document.createElement('canvas');canvas.width=this.originalWidth;canvas.height=this.originalHeight;ctx=canvas.getContext('2d');imageData=ctx.getImageData(0,0,canvas.width,canvas.height);pixelData=imageData.data;_ref=this.originalPixelData;for(i=_i=0,_len=_ref.length;_i<_len;i=++_i){pixel=_ref[i];pixelData[i]=pixel;}
+ctx.putImageData(imageData,0,0);scaledCanvas=document.createElement('canvas');scaledCanvas.width=this.width;scaledCanvas.height=this.height;ctx=scaledCanvas.getContext('2d');ctx.drawImage(canvas,0,0,this.originalWidth,this.originalHeight,0,0,this.width,this.height);pixelData=ctx.getImageData(0,0,this.width,this.height).data;width=this.width;}else{pixelData=this.originalPixelData;width=this.originalWidth;}
+for(i=_j=0,_ref1=pixelData.length;_j<_ref1;i=_j+=4){coord=PixelInfo.locationToCoordinates(i,width);if(((startX<=(_ref2=coord.x)&&_ref2_ref;i=0<=_ref?++_i:--_i){divisor+=adjust[i];}}
+this.renderer.add({type:Filter.Type.Kernel,name:name,adjust:adjust,divisor:divisor,bias:bias||0});return this;};Caman.prototype.processPlugin=function(plugin,args){this.renderer.add({type:Filter.Type.Plugin,plugin:plugin,args:args});return this;};Caman.prototype.newLayer=function(callback){var layer;layer=new Layer(this);this.canvasQueue.push(layer);this.renderer.add({type:Filter.Type.LayerDequeue});callback.call(layer);this.renderer.add({type:Filter.Type.LayerFinished});return this;};Caman.prototype.executeLayer=function(layer){return this.pushContext(layer);};Caman.prototype.pushContext=function(layer){this.layerStack.push(this.currentLayer);this.pixelStack.push(this.pixelData);this.currentLayer=layer;return this.pixelData=layer.pixelData;};Caman.prototype.popContext=function(){this.pixelData=this.pixelStack.pop();return this.currentLayer=this.layerStack.pop();};Caman.prototype.applyCurrentLayer=function(){return this.currentLayer.applyToParent();};return Caman;})();Analyze=(function(){function Analyze(c){this.c=c;}
+Analyze.prototype.calculateLevels=function(){var i,levels,numPixels,_i,_j,_k,_ref;levels={r:{},g:{},b:{}};for(i=_i=0;_i<=255;i=++_i){levels.r[i]=0;levels.g[i]=0;levels.b[i]=0;}
+for(i=_j=0,_ref=this.c.pixelData.length;_j<_ref;i=_j+=4){levels.r[this.c.pixelData[i]]++;levels.g[this.c.pixelData[i+1]]++;levels.b[this.c.pixelData[i+2]]++;}
+numPixels=this.c.pixelData.length/4;for(i=_k=0;_k<=255;i=++_k){levels.r[i]/=numPixels;levels.g[i]/=numPixels;levels.b[i]/=numPixels;}
+return levels;};return Analyze;})();Caman.DOMUpdated=function(){var img,imgs,parser,_i,_len,_results;imgs=document.querySelectorAll("img[data-caman]");if(!(imgs.length>0)){return;}
+_results=[];for(_i=0,_len=imgs.length;_i<_len;_i++){img=imgs[_i];_results.push(parser=new CamanParser(img,function(){this.parse();return this.execute();}));}
+return _results;};if(Caman.autoload){(function(){if(document.readyState==="complete"){return Caman.DOMUpdated();}else{return document.addEventListener("DOMContentLoaded",Caman.DOMUpdated,false);}})();}
+CamanParser=(function(){var INST_REGEX;INST_REGEX="(\\w+)\\((.*?)\\)";function CamanParser(ele,ready){this.dataStr=ele.getAttribute('data-caman');this.caman=Caman(ele,ready.bind(this));}
+CamanParser.prototype.parse=function(){var args,filter,func,inst,instFunc,m,r,unparsedInstructions,_i,_len,_ref,_results;this.ele=this.caman.canvas;r=new RegExp(INST_REGEX,'g');unparsedInstructions=this.dataStr.match(r);if(!(unparsedInstructions.length>0)){return;}
+r=new RegExp(INST_REGEX);_results=[];for(_i=0,_len=unparsedInstructions.length;_i<_len;_i++){inst=unparsedInstructions[_i];_ref=inst.match(r),m=_ref[0],filter=_ref[1],args=_ref[2];instFunc=new Function("return function() { this."+filter+"("+args+"); };");try{func=instFunc();_results.push(func.call(this.caman));}catch(e){_results.push(Log.debug(e));}}
+return _results;};CamanParser.prototype.execute=function(){var ele;ele=this.ele;return this.caman.render(function(){return ele.parentNode.replaceChild(this.toImage(),ele);});};return CamanParser;})();Caman.Blender=Blender=(function(){function Blender(){}
+Blender.blenders={};Blender.register=function(name,func){return this.blenders[name]=func;};Blender.execute=function(name,rgbaLayer,rgbaParent){return this.blenders[name](rgbaLayer,rgbaParent);};return Blender;})();Caman.Calculate=Calculate=(function(){function Calculate(){}
+Calculate.distance=function(x1,y1,x2,y2){return Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));};Calculate.randomRange=function(min,max,getFloat){var rand;if(getFloat==null){getFloat=false;}
+rand=min+(Math.random()*(max-min));if(getFloat){return rand.toFixed(getFloat);}else{return Math.round(rand);}};Calculate.luminance=function(rgba){return(0.299*rgba.r)+(0.587*rgba.g)+(0.114*rgba.b);};Calculate.bezier=function(start,ctrl1,ctrl2,end,lowBound,highBound){var Ax,Ay,Bx,By,Cx,Cy,bezier,curveX,curveY,i,j,leftCoord,rightCoord,t,x0,x1,x2,x3,y0,y1,y2,y3,_i,_j,_k,_ref,_ref1;x0=start[0];y0=start[1];x1=ctrl1[0];y1=ctrl1[1];x2=ctrl2[0];y2=ctrl2[1];x3=end[0];y3=end[1];bezier={};Cx=parseInt(3*(x1-x0),10);Bx=3*(x2-x1)-Cx;Ax=x3-x0-Cx-Bx;Cy=3*(y1-y0);By=3*(y2-y1)-Cy;Ay=y3-y0-Cy-By;for(i=_i=0;_i<1000;i=++_i){t=i/1000;curveX=Math.round((Ax*Math.pow(t,3))+(Bx*Math.pow(t,2))+(Cx*t)+x0);curveY=Math.round((Ay*Math.pow(t,3))+(By*Math.pow(t,2))+(Cy*t)+y0);if(lowBound&&curveYhighBound){curveY=highBound;}
+bezier[curveX]=curveY;}
+if(bezier.length=_ref;i=0<=_ref?++_j:--_j){if(bezier[i]==null){leftCoord=[i-1,bezier[i-1]];for(j=_k=i,_ref1=end[0];i<=_ref1?_k<=_ref1:_k>=_ref1;j=i<=_ref1?++_k:--_k){if(bezier[j]!=null){rightCoord=[j,bezier[j]];break;}}
+bezier[i]=leftCoord[1]+((rightCoord[1]-leftCoord[1])/(rightCoord[0]-leftCoord[0]))*(i-leftCoord[0]);}}}
+if(bezier[end[0]]==null){bezier[end[0]]=bezier[end[0]-1];}
+return bezier;};return Calculate;})();Convert=(function(){function Convert(){}
+Convert.hexToRGB=function(hex){var b,g,r;if(hex.charAt(0)==="#"){hex=hex.substr(1);}
+r=parseInt(hex.substr(0,2),16);g=parseInt(hex.substr(2,2),16);b=parseInt(hex.substr(4,2),16);return{r:r,g:g,b:b};};Convert.rgbToHSL=function(r,g,b){var d,h,l,max,min,s;if(typeof r==="object"){g=r.g;b=r.b;r=r.r;}
+r/=255;g/=255;b/=255;max=Math.max(r,g,b);min=Math.min(r,g,b);l=(max+min)/2;if(max===min){h=s=0;}else{d=max-min;s=l>0.5?d/(2-max-min):d/(max+min);h=(function(){switch(max){case r:return(g-b)/d+(g1){t-=1;}
+if(t<1/6){return p+(q-p)*6*t;}
+if(t<1/2){return q;}
+if(t<2/3){return p+(q-p)*(2/3-t)*6;}
+return p;};Convert.rgbToHSV=function(r,g,b){var d,h,max,min,s,v;r/=255;g/=255;b/=255;max=Math.max(r,g,b);min=Math.min(r,g,b);v=max;d=max-min;s=max===0?0:d/max;if(max===min){h=0;}else{h=(function(){switch(max){case r:return(g-b)/d+(g0.04045){r=Math.pow((r+0.055)/1.055,2.4);}else{r/=12.92;}
+if(g>0.04045){g=Math.pow((g+0.055)/1.055,2.4);}else{g/=12.92;}
+if(b>0.04045){b=Math.pow((b+0.055)/1.055,2.4);}else{b/=12.92;}
+x=r*0.4124+g*0.3576+b*0.1805;y=r*0.2126+g*0.7152+b*0.0722;z=r*0.0193+g*0.1192+b*0.9505;return{x:x*100,y:y*100,z:z*100};};Convert.xyzToRGB=function(x,y,z){var b,g,r;x/=100;y/=100;z/=100;r=(3.2406*x)+(-1.5372*y)+(-0.4986*z);g=(-0.9689*x)+(1.8758*y)+(0.0415*z);b=(0.0557*x)+(-0.2040*y)+(1.0570*z);if(r>0.0031308){r=(1.055*Math.pow(r,0.4166666667))-0.055;}else{r*=12.92;}
+if(g>0.0031308){g=(1.055*Math.pow(g,0.4166666667))-0.055;}else{g*=12.92;}
+if(b>0.0031308){b=(1.055*Math.pow(b,0.4166666667))-0.055;}else{b*=12.92;}
+return{r:r*255,g:g*255,b:b*255};};Convert.xyzToLab=function(x,y,z){var a,b,l,whiteX,whiteY,whiteZ;if(typeof x==="object"){y=x.y;z=x.z;x=x.x;}
+whiteX=95.047;whiteY=100.0;whiteZ=108.883;x/=whiteX;y/=whiteY;z/=whiteZ;if(x>0.008856451679){x=Math.pow(x,0.3333333333);}else{x=(7.787037037*x)+0.1379310345;}
+if(y>0.008856451679){y=Math.pow(y,0.3333333333);}else{y=(7.787037037*y)+0.1379310345;}
+if(z>0.008856451679){z=Math.pow(z,0.3333333333);}else{z=(7.787037037*z)+0.1379310345;}
+l=116*y-16;a=500*(x-y);b=200*(y-z);return{l:l,a:a,b:b};};Convert.labToXYZ=function(l,a,b){var x,y,z;if(typeof l==="object"){a=l.a;b=l.b;l=l.l;}
+y=(l+16)/116;x=y+(a/500);z=y-(b/200);if(x>0.2068965517){x=x*x*x;}else{x=0.1284185493*(x-0.1379310345);}
+if(y>0.2068965517){y=y*y*y;}else{y=0.1284185493*(y-0.1379310345);}
+if(z>0.2068965517){z=z*z*z;}else{z=0.1284185493*(z-0.1379310345);}
+return{x:x*95.047,y:y*100.0,z:z*108.883};};Convert.rgbToLab=function(r,g,b){var xyz;if(typeof r==="object"){g=r.g;b=r.b;r=r.r;}
+xyz=this.rgbToXYZ(r,g,b);return this.xyzToLab(xyz);};Convert.labToRGB=function(l,a,b){};return Convert;})();Event=(function(){function Event(){}
+Event.events={};Event.types=["processStart","processComplete","renderStart","renderFinished","blockStarted","blockFinished"];Event.trigger=function(target,type,data){var event,_i,_len,_ref,_results;if(this.events[type]&&this.events[type].length){_ref=this.events[type];_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++){event=_ref[_i];if(event.target===null||target.id===event.target.id){_results.push(event.fn.call(target,data));}else{_results.push(void 0);}}
+return _results;}};Event.listen=function(target,type,fn){var _fn,_type;if(typeof target==="string"){_type=target;_fn=type;target=null;type=_type;fn=_fn;}
+if(__indexOf.call(this.types,type)<0){return false;}
+if(!this.events[type]){this.events[type]=[];}
+this.events[type].push({target:target,fn:fn});return true;};return Event;})();Caman.Event=Event;Caman.Filter=Filter=(function(){function Filter(){}
+Filter.Type={Single:1,Kernel:2,LayerDequeue:3,LayerFinished:4,LoadOverlay:5,Plugin:6};Filter.register=function(name,filterFunc){return Caman.prototype[name]=filterFunc;};return Filter;})();Caman.IO=IO=(function(){function IO(){}
+IO.domainRegex=/(?:(?:http|https):\/\/)((?:\w+)\.(?:(?:\w|\.)+))/;IO.isRemote=function(img){if(img==null){return false;}
+if(this.corsEnabled(img)){return false;}
+return this.isURLRemote(img.src);};IO.corsEnabled=function(img){var _ref;return(img.crossOrigin!=null)&&((_ref=img.crossOrigin.toLowerCase())==='anonymous'||_ref==='use-credentials');};IO.isURLRemote=function(url){var matches;matches=url.match(this.domainRegex);if(matches){return matches[1]!==document.domain;}else{return false;}};IO.remoteCheck=function(src){if(this.isURLRemote(src)){if(!Caman.remoteProxy.length){Log.info("Attempting to load a remote image without a configured proxy. URL: "+src);}else{if(Caman.isURLRemote(Caman.remoteProxy)){Log.info("Cannot use a remote proxy for loading images.");return;}
+return""+Caman.remoteProxy+"?camanProxyUrl="+(encodeURIComponent(src));}}};IO.proxyUrl=function(src){return""+Caman.remoteProxy+"?"+Caman.proxyParam+"="+(encodeURIComponent(src));};IO.useProxy=function(lang){var langToExt;langToExt={ruby:'rb',python:'py',perl:'pl',javascript:'js'};lang=lang.toLowerCase();if(langToExt[lang]!=null){lang=langToExt[lang];}
+return"proxies/caman_proxy."+lang;};return IO;})();Caman.prototype.save=function(){if(typeof exports!=="undefined"&&exports!==null){return this.nodeSave.apply(this,arguments);}else{return this.browserSave.apply(this,arguments);}};Caman.prototype.browserSave=function(type){var image;if(type==null){type="png";}
+type=type.toLowerCase();image=this.toBase64(type).replace("image/"+type,"image/octet-stream");return document.location.href=image;};Caman.prototype.nodeSave=function(file,overwrite){var stats;if(overwrite==null){overwrite=true;}
+try{stats=fs.statSync(file);if(stats.isFile()&&!overwrite){return false;}}catch(e){Log.debug("Creating output file "+file);}
+return fs.writeFile(file,this.canvas.toBuffer(),function(){return Log.debug("Finished writing to "+file);});};Caman.prototype.toImage=function(type){var img;img=document.createElement('img');img.src=this.toBase64(type);img.width=this.dimensions.width;img.height=this.dimensions.height;if(window.devicePixelRatio){img.width/=window.devicePixelRatio;img.height/=window.devicePixelRatio;}
+return img;};Caman.prototype.toBase64=function(type){if(type==null){type="png";}
+type=type.toLowerCase();return this.canvas.toDataURL("image/"+type);};Layer=(function(){function Layer(c){this.c=c;this.filter=this.c;this.options={blendingMode:'normal',opacity:1.0};this.layerID=Util.uniqid.get();this.canvas=typeof exports!=="undefined"&&exports!==null?new Canvas():document.createElement('canvas');this.canvas.width=this.c.dimensions.width;this.canvas.height=this.c.dimensions.height;this.context=this.canvas.getContext('2d');this.context.createImageData(this.canvas.width,this.canvas.height);this.imageData=this.context.getImageData(0,0,this.canvas.width,this.canvas.height);this.pixelData=this.imageData.data;}
+Layer.prototype.newLayer=function(cb){return this.c.newLayer.call(this.c,cb);};Layer.prototype.setBlendingMode=function(mode){this.options.blendingMode=mode;return this;};Layer.prototype.opacity=function(opacity){this.options.opacity=opacity/100;return this;};Layer.prototype.copyParent=function(){var i,parentData,_i,_ref;parentData=this.c.pixelData;for(i=_i=0,_ref=this.c.pixelData.length;_i<_ref;i=_i+=4){this.pixelData[i]=parentData[i];this.pixelData[i+1]=parentData[i+1];this.pixelData[i+2]=parentData[i+2];this.pixelData[i+3]=parentData[i+3];}
+return this;};Layer.prototype.fillColor=function(){return this.c.fillColor.apply(this.c,arguments);};Layer.prototype.overlayImage=function(image){if(typeof image==="object"){image=image.src;}else if(typeof image==="string"&&image[0]==="#"){image=$(image).src;}
+if(!image){return this;}
+this.c.renderer.renderQueue.push({type:Filter.Type.LoadOverlay,src:image,layer:this});return this;};Layer.prototype.applyToParent=function(){var i,layerData,parentData,result,rgbaLayer,rgbaParent,_i,_ref,_results;parentData=this.c.pixelStack[this.c.pixelStack.length-1];layerData=this.c.pixelData;_results=[];for(i=_i=0,_ref=layerData.length;_i<_ref;i=_i+=4){rgbaParent={r:parentData[i],g:parentData[i+1],b:parentData[i+2],a:parentData[i+3]};rgbaLayer={r:layerData[i],g:layerData[i+1],b:layerData[i+2],a:layerData[i+3]};result=Blender.execute(this.options.blendingMode,rgbaLayer,rgbaParent);result.r=Util.clampRGB(result.r);result.g=Util.clampRGB(result.g);result.b=Util.clampRGB(result.b);if(result.a==null){result.a=rgbaLayer.a;}
+parentData[i]=rgbaParent.r-((rgbaParent.r-result.r)*(this.options.opacity*(result.a/255)));parentData[i+1]=rgbaParent.g-((rgbaParent.g-result.g)*(this.options.opacity*(result.a/255)));_results.push(parentData[i+2]=rgbaParent.b-((rgbaParent.b-result.b)*(this.options.opacity*(result.a/255))));}
+return _results;};return Layer;})();Logger=(function(){function Logger(){var name,_i,_len,_ref;_ref=['log','info','warn','error'];for(_i=0,_len=_ref.length;_i<_len;_i++){name=_ref[_i];this[name]=(function(name){return function(){var args;args=1<=arguments.length?__slice.call(arguments,0):[];if(!Caman.DEBUG){return;}
+try{return console[name].apply(console,args);}catch(e){return console[name](args);}};})(name);}
+this.debug=this.log;}
+return Logger;})();Log=new Logger();PixelInfo=(function(){PixelInfo.coordinatesToLocation=function(x,y,width){return(y*width+x)*4;};PixelInfo.locationToCoordinates=function(loc,width){var x,y;y=Math.floor(loc/(width*4));x=(loc%(width*4))/4;return{x:x,y:y};};function PixelInfo(c){this.c=c;this.loc=0;}
+PixelInfo.prototype.locationXY=function(){var x,y;y=this.c.dimensions.height-Math.floor(this.loc/(this.c.dimensions.width*4));x=(this.loc%(this.c.dimensions.width*4))/4;return{x:x,y:y};};PixelInfo.prototype.getPixelRelative=function(horiz,vert){var newLoc;newLoc=this.loc+(this.c.dimensions.width*4*(vert*-1))+(4*horiz);if(newLoc>this.c.pixelData.length||newLoc<0){return{r:0,g:0,b:0,a:0};}
+return{r:this.c.pixelData[newLoc],g:this.c.pixelData[newLoc+1],b:this.c.pixelData[newLoc+2],a:this.c.pixelData[newLoc+3]};};PixelInfo.prototype.putPixelRelative=function(horiz,vert,rgba){var nowLoc;nowLoc=this.loc+(this.c.dimensions.width*4*(vert*-1))+(4*horiz);if(newLoc>this.c.pixelData.length||newLoc<0){return;}
+this.c.pixelData[newLoc]=rgba.r;this.c.pixelData[newLoc+1]=rgba.g;this.c.pixelData[newLoc+2]=rgba.b;this.c.pixelData[newLoc+3]=rgba.a;return true;};PixelInfo.prototype.getPixel=function(x,y){var loc;loc=this.coordinatesToLocation(x,y,this.width);return{r:this.c.pixelData[loc],g:this.c.pixelData[loc+1],b:this.c.pixelData[loc+2],a:this.c.pixelData[loc+3]};};PixelInfo.prototype.putPixel=function(x,y,rgba){var loc;loc=this.coordinatesToLocation(x,y,this.width);this.c.pixelData[loc]=rgba.r;this.c.pixelData[loc+1]=rgba.g;this.c.pixelData[loc+2]=rgba.b;return this.c.pixelData[loc+3]=rgba.a;};return PixelInfo;})();Plugin=(function(){function Plugin(){}
+Plugin.plugins={};Plugin.register=function(name,plugin){return this.plugins[name]=plugin;};Plugin.execute=function(context,name,args){return this.plugins[name].apply(context,args);};return Plugin;})();Caman.Plugin=Plugin;Caman.Renderer=Renderer=(function(){Renderer.Blocks=Caman.NodeJS?require('os').cpus().length:4;function Renderer(c){var _this=this;this.c=c;this.processNext=function(){return Renderer.prototype.processNext.apply(_this,arguments);};this.renderQueue=[];this.modPixelData=null;}
+Renderer.prototype.add=function(job){if(job==null){return;}
+return this.renderQueue.push(job);};Renderer.prototype.processNext=function(){var layer;if(this.renderQueue.length===0){Event.trigger(this,"renderFinished");if(this.finishedFn!=null){this.finishedFn.call(this.c);}
+return this;}
+this.currentJob=this.renderQueue.shift();switch(this.currentJob.type){case Filter.Type.LayerDequeue:layer=this.c.canvasQueue.shift();this.c.executeLayer(layer);return this.processNext();case Filter.Type.LayerFinished:this.c.applyCurrentLayer();this.c.popContext();return this.processNext();case Filter.Type.LoadOverlay:return this.loadOverlay(this.currentJob.layer,this.currentJob.src);case Filter.Type.Plugin:return this.executePlugin();default:return this.executeFilter();}};Renderer.prototype.execute=function(callback){this.finishedFn=callback;this.modPixelData=Util.dataArray(this.c.pixelData.length);return this.processNext();};Renderer.prototype.eachBlock=function(fn){var blockN,blockPixelLength,bnum,end,f,i,lastBlockN,n,start,_i,_ref,_results,_this=this;this.blocksDone=0;n=this.c.pixelData.length;blockPixelLength=Math.floor((n/4)/Renderer.Blocks);blockN=blockPixelLength*4;lastBlockN=blockN+((n/4)%Renderer.Blocks)*4;_results=[];for(i=_i=0,_ref=Renderer.Blocks;0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){start=i*blockN;end=start+(i===Renderer.Blocks-1?lastBlockN:blockN);if(Caman.NodeJS){f=Fiber(function(){return fn.call(_this,i,start,end);});bnum=f.run();_results.push(this.blockFinished(bnum));}else{_results.push(setTimeout((function(i,start,end){return function(){return fn.call(_this,i,start,end);};})(i,start,end),0));}}
+return _results;};Renderer.prototype.executeFilter=function(){Event.trigger(this.c,"processStart",this.currentJob);if(this.currentJob.type===Filter.Type.Single){return this.eachBlock(this.renderBlock);}else{return this.eachBlock(this.renderKernel);}};Renderer.prototype.executePlugin=function(){Log.debug("Executing plugin "+this.currentJob.plugin);Plugin.execute(this.c,this.currentJob.plugin,this.currentJob.args);Log.debug("Plugin "+this.currentJob.plugin+" finished!");return this.processNext();};Renderer.prototype.renderBlock=function(bnum,start,end){var data,i,pixelInfo,res,_i;Log.debug("Block #"+bnum+" - Filter: "+this.currentJob.name+", Start: "+start+", End: "+end);Event.trigger(this.c,"blockStarted",{blockNum:bnum,totalBlocks:Renderer.Blocks,startPixel:start,endPixel:end});data={r:0,g:0,b:0,a:0};pixelInfo=new PixelInfo(this.c);for(i=_i=start;_i=builder;j=-builder<=builder?++_j:--_j){for(k=_k=builder;builder<=-builder?_k<=-builder:_k>=-builder;k=builder<=-builder?++_k:--_k){pixel=pixelInfo.getPixelRelative(j,k);kernel[builderIndex*3]=pixel.r;kernel[builderIndex*3+1]=pixel.g;kernel[builderIndex*3+2]=pixel.b;builderIndex++;}}
+res=this.processKernel(adjust,kernel,divisor,bias);this.modPixelData[i]=Util.clampRGB(res.r);this.modPixelData[i+1]=Util.clampRGB(res.g);this.modPixelData[i+2]=Util.clampRGB(res.b);this.modPixelData[i+3]=this.c.pixelData[i+3];}
+if(Caman.NodeJS){return Fiber["yield"](bnum);}else{return this.blockFinished(bnum);}};Renderer.prototype.blockFinished=function(bnum){var i,_i,_ref;if(bnum>=0){Log.debug("Block #"+bnum+" finished! Filter: "+this.currentJob.name);}
+this.blocksDone++;Event.trigger(this.c,"blockFinished",{blockNum:bnum,blocksFinished:this.blocksDone,totalBlocks:Renderer.Blocks});if(this.blocksDone===Renderer.Blocks){if(this.currentJob.type===Filter.Type.Kernel){for(i=_i=0,_ref=this.c.pixelData.length;0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){this.c.pixelData[i]=this.modPixelData[i];}}
+if(bnum>=0){Log.debug("Filter "+this.currentJob.name+" finished!");}
+Event.trigger(this.c,"processComplete",this.currentJob);return this.processNext();}};Renderer.prototype.processKernel=function(adjust,kernel,divisor,bias){var i,val,_i,_ref;val={r:0,g:0,b:0};for(i=_i=0,_ref=adjust.length;0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){val.r+=adjust[i]*kernel[i*3];val.g+=adjust[i]*kernel[i*3+1];val.b+=adjust[i]*kernel[i*3+2];}
+val.r=(val.r/divisor)+bias;val.g=(val.g/divisor)+bias;val.b=(val.b/divisor)+bias;return val;};Renderer.prototype.loadOverlay=function(layer,src){var img,proxyUrl,_this=this;img=document.createElement('img');img.onload=function(){layer.context.drawImage(img,0,0,_this.c.dimensions.width,_this.c.dimensions.height);layer.imageData=layer.context.getImageData(0,0,_this.c.dimensions.width,_this.c.dimensions.height);layer.pixelData=layer.imageData.data;_this.c.pixelData=layer.pixelData;return _this.processNext();};proxyUrl=IO.remoteCheck(src);return img.src=proxyUrl!=null?proxyUrl:src;};return Renderer;})();Caman.Store=Store=(function(){function Store(){}
+Store.items={};Store.has=function(search){return this.items[search]!=null;};Store.get=function(search){return this.items[search];};Store.put=function(name,obj){return this.items[name]=obj;};Store.execute=function(search,callback){var _this=this;setTimeout(function(){return callback.call(_this.get(search),_this.get(search));},0);return this.get(search);};Store.flush=function(name){if(name==null){name=false;}
+if(name){return delete this.items[name];}else{return this.items={};}};return Store;})();Blender.register("normal",function(rgbaLayer,rgbaParent){return{r:rgbaLayer.r,g:rgbaLayer.g,b:rgbaLayer.b};});Blender.register("multiply",function(rgbaLayer,rgbaParent){return{r:(rgbaLayer.r*rgbaParent.r)/255,g:(rgbaLayer.g*rgbaParent.g)/255,b:(rgbaLayer.b*rgbaParent.b)/255};});Blender.register("screen",function(rgbaLayer,rgbaParent){return{r:255-(((255-rgbaLayer.r)*(255-rgbaParent.r))/255),g:255-(((255-rgbaLayer.g)*(255-rgbaParent.g))/255),b:255-(((255-rgbaLayer.b)*(255-rgbaParent.b))/255)};});Blender.register("overlay",function(rgbaLayer,rgbaParent){var result;result={};result.r=rgbaParent.r>128?255-2*(255-rgbaLayer.r)*(255-rgbaParent.r)/255:(rgbaParent.r*rgbaLayer.r*2)/255;result.g=rgbaParent.g>128?255-2*(255-rgbaLayer.g)*(255-rgbaParent.g)/255:(rgbaParent.g*rgbaLayer.g*2)/255;result.b=rgbaParent.b>128?255-2*(255-rgbaLayer.b)*(255-rgbaParent.b)/255:(rgbaParent.b*rgbaLayer.b*2)/255;return result;});Blender.register("difference",function(rgbaLayer,rgbaParent){return{r:rgbaLayer.r-rgbaParent.r,g:rgbaLayer.g-rgbaParent.g,b:rgbaLayer.b-rgbaParent.b};});Blender.register("addition",function(rgbaLayer,rgbaParent){return{r:rgbaParent.r+rgbaLayer.r,g:rgbaParent.g+rgbaLayer.g,b:rgbaParent.b+rgbaLayer.b};});Blender.register("exclusion",function(rgbaLayer,rgbaParent){return{r:128-2*(rgbaParent.r-128)*(rgbaLayer.r-128)/255,g:128-2*(rgbaParent.g-128)*(rgbaLayer.g-128)/255,b:128-2*(rgbaParent.b-128)*(rgbaLayer.b-128)/255};});Blender.register("softLight",function(rgbaLayer,rgbaParent){var result;result={};result.r=rgbaParent.r>128?255-((255-rgbaParent.r)*(255-(rgbaLayer.r-128)))/255:(rgbaParent.r*(rgbaLayer.r+128))/255;result.g=rgbaParent.g>128?255-((255-rgbaParent.g)*(255-(rgbaLayer.g-128)))/255:(rgbaParent.g*(rgbaLayer.g+128))/255;result.b=rgbaParent.b>128?255-((255-rgbaParent.b)*(255-(rgbaLayer.b-128)))/255:(rgbaParent.b*(rgbaLayer.b+128))/255;return result;});Blender.register("lighten",function(rgbaLayer,rgbaParent){return{r:rgbaParent.r>rgbaLayer.r?rgbaParent.r:rgbaLayer.r,g:rgbaParent.g>rgbaLayer.g?rgbaParent.g:rgbaLayer.g,b:rgbaParent.b>rgbaLayer.b?rgbaParent.b:rgbaLayer.b};});Blender.register("darken",function(rgbaLayer,rgbaParent){return{r:rgbaParent.r>rgbaLayer.r?rgbaLayer.r:rgbaParent.r,g:rgbaParent.g>rgbaLayer.g?rgbaLayer.g:rgbaParent.g,b:rgbaParent.b>rgbaLayer.b?rgbaLayer.b:rgbaParent.b};});Filter.register("fillColor",function(){var color;if(arguments.length===1){color=Convert.hexToRGB(arguments[0]);}else{color={r:arguments[0],g:arguments[1],b:arguments[2]};}
+return this.process("fillColor",function(rgba){rgba.r=color.r;rgba.g=color.g;rgba.b=color.b;rgba.a=255;return rgba;});});Filter.register("brightness",function(adjust){adjust=Math.floor(255*(adjust/100));return this.process("brightness",function(rgba){rgba.r+=adjust;rgba.g+=adjust;rgba.b+=adjust;return rgba;});});Filter.register("saturation",function(adjust){adjust*=-0.01;return this.process("saturation",function(rgba){var max;max=Math.max(rgba.r,rgba.g,rgba.b);if(rgba.r!==max){rgba.r+=(max-rgba.r)*adjust;}
+if(rgba.g!==max){rgba.g+=(max-rgba.g)*adjust;}
+if(rgba.b!==max){rgba.b+=(max-rgba.b)*adjust;}
+return rgba;});});Filter.register("vibrance",function(adjust){adjust*=-1;return this.process("vibrance",function(rgba){var amt,avg,max;max=Math.max(rgba.r,rgba.g,rgba.b);avg=(rgba.r+rgba.g+rgba.b)/3;amt=((Math.abs(max-avg)*2/255)*adjust)/100;if(rgba.r!==max){rgba.r+=(max-rgba.r)*amt;}
+if(rgba.g!==max){rgba.g+=(max-rgba.g)*amt;}
+if(rgba.b!==max){rgba.b+=(max-rgba.b)*amt;}
+return rgba;});});Filter.register("greyscale",function(adjust){return this.process("greyscale",function(rgba){var avg;avg=Calculate.luminance(rgba);rgba.r=avg;rgba.g=avg;rgba.b=avg;return rgba;});});Filter.register("contrast",function(adjust){adjust=Math.pow((adjust+100)/100,2);return this.process("contrast",function(rgba){rgba.r/=255;rgba.r-=0.5;rgba.r*=adjust;rgba.r+=0.5;rgba.r*=255;rgba.g/=255;rgba.g-=0.5;rgba.g*=adjust;rgba.g+=0.5;rgba.g*=255;rgba.b/=255;rgba.b-=0.5;rgba.b*=adjust;rgba.b+=0.5;rgba.b*=255;return rgba;});});Filter.register("hue",function(adjust){return this.process("hue",function(rgba){var h,hsv,rgb;hsv=Convert.rgbToHSV(rgba.r,rgba.g,rgba.b);h=hsv.h*100;h+=Math.abs(adjust);h=h%100;h/=100;hsv.h=h;rgb=Convert.hsvToRGB(hsv.h,hsv.s,hsv.v);rgb.a=rgba.a;return rgb;});});Filter.register("colorize",function(){var level,rgb;if(arguments.length===2){rgb=Convert.hexToRGB(arguments[0]);level=arguments[1];}else if(arguments.length===4){rgb={r:arguments[0],g:arguments[1],b:arguments[2]};level=arguments[3];}
+return this.process("colorize",function(rgba){rgba.r-=(rgba.r-rgb.r)*(level/100);rgba.g-=(rgba.g-rgb.g)*(level/100);rgba.b-=(rgba.b-rgb.b)*(level/100);return rgba;});});Filter.register("invert",function(){return this.process("invert",function(rgba){rgba.r=255-rgba.r;rgba.g=255-rgba.g;rgba.b=255-rgba.b;return rgba;});});Filter.register("sepia",function(adjust){if(adjust==null){adjust=100;}
+adjust/=100;return this.process("sepia",function(rgba){rgba.r=Math.min(255,(rgba.r*(1-(0.607*adjust)))+(rgba.g*(0.769*adjust))+(rgba.b*(0.189*adjust)));rgba.g=Math.min(255,(rgba.r*(0.349*adjust))+(rgba.g*(1-(0.314*adjust)))+(rgba.b*(0.168*adjust)));rgba.b=Math.min(255,(rgba.r*(0.272*adjust))+(rgba.g*(0.534*adjust))+(rgba.b*(1-(0.869*adjust))));return rgba;});});Filter.register("gamma",function(adjust){return this.process("gamma",function(rgba){rgba.r=Math.pow(rgba.r/255,adjust)*255;rgba.g=Math.pow(rgba.g/255,adjust)*255;rgba.b=Math.pow(rgba.b/255,adjust)*255;return rgba;});});Filter.register("noise",function(adjust){adjust=Math.abs(adjust)*2.55;return this.process("noise",function(rgba){var rand;rand=Calculate.randomRange(adjust*-1,adjust);rgba.r+=rand;rgba.g+=rand;rgba.b+=rand;return rgba;});});Filter.register("clip",function(adjust){adjust=Math.abs(adjust)*2.55;return this.process("clip",function(rgba){if(rgba.r>255-adjust){rgba.r=255;}else if(rgba.r255-adjust){rgba.g=255;}else if(rgba.g255-adjust){rgba.b=255;}else if(rgba.b0){rgba.r+=(255-rgba.r)*options.red;}else{rgba.r-=rgba.r*Math.abs(options.red);}}
+if(options.green!=null){if(options.green>0){rgba.g+=(255-rgba.g)*options.green;}else{rgba.g-=rgba.g*Math.abs(options.green);}}
+if(options.blue!=null){if(options.blue>0){rgba.b+=(255-rgba.b)*options.blue;}else{rgba.b-=rgba.b*Math.abs(options.blue);}}
+return rgba;});});Filter.register("curves",function(){var bezier,chans,cps,ctrl1,ctrl2,end,i,start,_i,_j,_ref,_ref1;chans=arguments[0],cps=2<=arguments.length?__slice.call(arguments,1):[];if(typeof chans==="string"){chans=chans.split("");}
+if(chans[0]==="v"){chans=['r','g','b'];}
+if(cps.length<3||cps.length>4){throw"Invalid number of arguments to curves filter";}
+start=cps[0];ctrl1=cps[1];ctrl2=cps.length===4?cps[2]:cps[1];end=cps[cps.length-1];bezier=Calculate.bezier(start,ctrl1,ctrl2,end,0,255);if(start[0]>0){for(i=_i=0,_ref=start[0];0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){bezier[i]=start[1];}}
+if(end[0]<255){for(i=_j=_ref1=end[0];_ref1<=255?_j<=255:_j>=255;i=_ref1<=255?++_j:--_j){bezier[i]=end[1];}}
+return this.process("curves",function(rgba){var _k,_ref2;for(i=_k=0,_ref2=chans.length;0<=_ref2?_k<_ref2:_k>_ref2;i=0<=_ref2?++_k:--_k){rgba[chans[i]]=bezier[rgba[chans[i]]];}
+return rgba;});});Filter.register("exposure",function(adjust){var ctrl1,ctrl2,p;p=Math.abs(adjust)/100;ctrl1=[0,255*p];ctrl2=[255-(255*p),255];if(adjust<0){ctrl1=ctrl1.reverse();ctrl2=ctrl2.reverse();}
+return this.curves('rgb',[0,0],ctrl1,ctrl2,[255,255]);});Caman.Plugin.register("crop",function(width,height,x,y){var canvas,ctx;if(x==null){x=0;}
+if(y==null){y=0;}
+if(typeof exports!=="undefined"&&exports!==null){canvas=new Canvas(width,height);}else{canvas=document.createElement('canvas');Util.copyAttributes(this.canvas,canvas);canvas.width=width;canvas.height=height;}
+ctx=canvas.getContext('2d');ctx.drawImage(this.canvas,x,y,width,height,0,0,width,height);this.cropCoordinates={x:x,y:y};this.cropped=true;return this.replaceCanvas(canvas);});Caman.Plugin.register("resize",function(newDims){var canvas,ctx;if(newDims==null){newDims=null;}
+if(newDims===null||((newDims.width==null)&&(newDims.height==null))){Log.error("Invalid or missing dimensions given for resize");return;}
+if(newDims.width==null){newDims.width=this.canvas.width*newDims.height/this.canvas.height;}else if(newDims.height==null){newDims.height=this.canvas.height*newDims.width/this.canvas.width;}
+if(typeof exports!=="undefined"&&exports!==null){canvas=new Canvas(newDims.width,newDims.height);}else{canvas=document.createElement('canvas');Util.copyAttributes(this.canvas,canvas);canvas.width=newDims.width;canvas.height=newDims.height;}
+ctx=canvas.getContext('2d');ctx.drawImage(this.canvas,0,0,this.canvas.width,this.canvas.height,0,0,newDims.width,newDims.height);this.resized=true;return this.replaceCanvas(canvas);});Caman.Filter.register("crop",function(){return this.processPlugin("crop",Array.prototype.slice.call(arguments,0));});Caman.Filter.register("resize",function(){return this.processPlugin("resize",Array.prototype.slice.call(arguments,0));});Caman.Filter.register("boxBlur",function(){return this.processKernel("Box Blur",[1,1,1,1,1,1,1,1,1]);});Caman.Filter.register("heavyRadialBlur",function(){return this.processKernel("Heavy Radial Blur",[0,0,1,0,0,0,1,1,1,0,1,1,1,1,1,0,1,1,1,0,0,0,1,0,0]);});Caman.Filter.register("gaussianBlur",function(){return this.processKernel("Gaussian Blur",[1,4,6,4,1,4,16,24,16,4,6,24,36,24,6,4,16,24,16,4,1,4,6,4,1]);});Caman.Filter.register("motionBlur",function(degrees){var kernel;if(degrees===0||degrees===180){kernel=[0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1,0,0];}else if((degrees>0&°rees<90)||(degrees>180&°rees<270)){kernel=[0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0];}else if(degrees===90||degrees===270){kernel=[0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0];}else{kernel=[1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1,0,0,0,0,0,1];}
+return this.processKernel("Motion Blur",kernel);});Caman.Filter.register("sharpen",function(amt){if(amt==null){amt=100;}
+amt/=100;return this.processKernel("Sharpen",[0,-amt,0,-amt,4*amt+1,-amt,0,-amt,0]);});vignetteFilters={brightness:function(rgba,amt,opts){rgba.r=rgba.r-(rgba.r*amt*opts.strength);rgba.g=rgba.g-(rgba.g*amt*opts.strength);rgba.b=rgba.b-(rgba.b*amt*opts.strength);return rgba;},gamma:function(rgba,amt,opts){rgba.r=Math.pow(rgba.r/255,Math.max(10*amt*opts.strength,1))*255;rgba.g=Math.pow(rgba.g/255,Math.max(10*amt*opts.strength,1))*255;rgba.b=Math.pow(rgba.b/255,Math.max(10*amt*opts.strength,1))*255;return rgba;},colorize:function(rgba,amt,opts){rgba.r-=(rgba.r-opts.color.r)*amt;rgba.g-=(rgba.g-opts.color.g)*amt;rgba.b-=(rgba.b-opts.color.b)*amt;return rgba;}};Filter.register("vignette",function(size,strength){var bezier,center,end,start;if(strength==null){strength=60;}
+if(typeof size==="string"&&size.substr(-1)==="%"){if(this.dimensions.height>this.dimensions.width){size=this.dimensions.width*(parseInt(size.substr(0,size.length-1),10)/100);}else{size=this.dimensions.height*(parseInt(size.substr(0,size.length-1),10)/100);}}
+strength/=100;center=[this.dimensions.width/2,this.dimensions.height/2];start=Math.sqrt(Math.pow(center[0],2)+Math.pow(center[1],2));end=start-size;bezier=Calculate.bezier([0,1],[30,30],[70,60],[100,80]);return this.process("vignette",function(rgba){var dist,div,loc;loc=this.locationXY();dist=Calculate.distance(loc.x,loc.y,center[0],center[1]);if(dist>end){div=Math.max(1,(bezier[Math.round(((dist-end)/size)*100)]/10)*strength);rgba.r=Math.pow(rgba.r/255,div)*255;rgba.g=Math.pow(rgba.g/255,div)*255;rgba.b=Math.pow(rgba.b/255,div)*255;}
+return rgba;});});Filter.register("rectangularVignette",function(opts){var defaults,dim,percent,size,_i,_len,_ref;defaults={strength:50,cornerRadius:0,method:'brightness',color:{r:0,g:0,b:0}};opts=Util.extend(defaults,opts);if(!opts.size){return this;}else if(typeof opts.size==="string"){percent=parseInt(opts.size,10)/100;opts.size={width:this.dimensions.width*percent,height:this.dimensions.height*percent};}else if(typeof opts.size==="object"){_ref=["width","height"];for(_i=0,_len=_ref.length;_i<_len;_i++){dim=_ref[_i];if(typeof opts.size[dim]==="string"){opts.size[dim]=this.dimensions[dim]*(parseInt(opts.size[dim],10)/100);}}}else if(opts.size==="number"){size=opts.size;opts.size={width:size,height:size};}
+if(typeof opts.cornerRadius==="string"){opts.cornerRadius=(opts.size.width/2)*(parseInt(opts.cornerRadius,10)/100);}
+opts.strength/=100;opts.size.width=Math.floor(opts.size.width);opts.size.height=Math.floor(opts.size.height);opts.image={width:this.dimensions.width,height:this.dimensions.height};if(opts.method==="colorize"&&typeof opts.color==="string"){opts.color=Convert.hexToRGB(opts.color);}
+opts.coords={left:(this.dimensions.width-opts.size.width)/2,right:this.dimensions.width-opts.coords.left,bottom:(this.dimensions.height-opts.size.height)/2,top:this.dimensions.height-opts.coords.bottom};opts.corners=[{x:opts.coords.left+opts.cornerRadius,y:opts.coords.top-opts.cornerRadius},{x:opts.coords.right-opts.cornerRadius,y:opts.coords.top-opts.cornerRadius},{x:opts.coords.right-opts.cornerRadius,y:opts.coords.bottom+opts.cornerRadius},{x:opts.coords.left+opts.cornerRadius,y:opts.coords.bottom+opts.cornerRadius}];opts.maxDist=Calculate.distance(0,0,opts.corners[3].x,opts.corners[3].y)-opts.cornerRadius;return this.process("rectangularVignette",function(rgba){var amt,loc,radialDist;loc=this.locationXY();if((loc.x>opts.corners[0].x&&loc.xopts.coords.bottom&&loc.yopts.coords.left&&loc.xopts.corners[3].y&&loc.yopts.corners[0].x&&loc.xopts.coords.top){amt=(loc.y-opts.coords.top)/opts.maxDist;}else if(loc.y>opts.corners[2].y&&loc.yopts.coords.right){amt=(loc.x-opts.coords.right)/opts.maxDist;}else if(loc.x>opts.corners[0].x&&loc.xopts.corners[2].y&&loc.y=opts.corners[0].y){radialDist=Caman.distance(loc.x,loc.y,opts.corners[0].x,opts.corners[0].y);amt=(radialDist-opts.cornerRadius)/opts.maxDist;}else if(loc.x>=opts.corners[1].x&&loc.y>=opts.corners[1].y){radialDist=Caman.distance(loc.x,loc.y,opts.corners[1].x,opts.corners[1].y);amt=(radialDist-opts.cornerRadius)/opts.maxDist;}else if(loc.x>=opts.corners[2].x&&loc.y<=opts.corners[2].y){radialDist=Caman.distance(loc.x,loc.y,opts.corners[2].x,opts.corners[2].y);amt=(radialDist-opts.cornerRadius)/opts.maxDist;}else if(loc.x<=opts.corners[3].x&&loc.y<=opts.corners[3].y){radialDist=Caman.distance(loc.x,loc.y,opts.corners[3].x,opts.corners[3].y);amt=(radialDist-opts.cornerRadius)/opts.maxDist;}
+if(amt<0){return rgba;}
+return vignetteFilters[opts.method](rgba,amt,opts);});});(function(){var BlurStack,getLinearGradientMap,getRadialGradientMap,mul_table,shg_table;mul_table=[512,512,456,512,328,456,335,512,405,328,271,456,388,335,292,512,454,405,364,328,298,271,496,456,420,388,360,335,312,292,273,512,482,454,428,405,383,364,345,328,312,298,284,271,259,496,475,456,437,420,404,388,374,360,347,335,323,312,302,292,282,273,265,512,497,482,468,454,441,428,417,405,394,383,373,364,354,345,337,328,320,312,305,298,291,284,278,271,265,259,507,496,485,475,465,456,446,437,428,420,412,404,396,388,381,374,367,360,354,347,341,335,329,323,318,312,307,302,297,292,287,282,278,273,269,265,261,512,505,497,489,482,475,468,461,454,447,441,435,428,422,417,411,405,399,394,389,383,378,373,368,364,359,354,350,345,341,337,332,328,324,320,316,312,309,305,301,298,294,291,287,284,281,278,274,271,268,265,262,259,257,507,501,496,491,485,480,475,470,465,460,456,451,446,442,437,433,428,424,420,416,412,408,404,400,396,392,388,385,381,377,374,370,367,363,360,357,354,350,347,344,341,338,335,332,329,326,323,320,318,315,312,310,307,304,302,299,297,294,292,289,287,285,282,280,278,275,273,271,269,267,265,263,261,259];shg_table=[9,11,12,13,13,14,14,15,15,15,15,16,16,16,16,17,17,17,17,17,17,17,18,18,18,18,18,18,18,18,18,19,19,19,19,19,19,19,19,19,19,19,19,19,19,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,21,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,22,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,23,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24,24];getLinearGradientMap=function(width,height,centerX,centerY,angle,length,mirrored){var cnv,context,gradient,x1,x2,y1,y2;cnv=typeof exports!=="undefined"&&exports!==null?new Canvas():document.createElement('canvas');cnv.width=width;cnv.height=height;x1=centerX+Math.cos(angle)*length*0.5;y1=centerY+Math.sin(angle)*length*0.5;x2=centerX-Math.cos(angle)*length*0.5;y2=centerY-Math.sin(angle)*length*0.5;context=cnv.getContext("2d");gradient=context.createLinearGradient(x1,y1,x2,y2);if(!mirrored){gradient.addColorStop(0,"white");gradient.addColorStop(1,"black");}else{gradient.addColorStop(0,"white");gradient.addColorStop(0.5,"black");gradient.addColorStop(1,"white");}
+context.fillStyle=gradient;context.fillRect(0,0,width,height);return context.getImageData(0,0,width,height);};getRadialGradientMap=function(width,height,centerX,centerY,radius1,radius2){var cnv,context,gradient;cnv=typeof exports!=="undefined"&&exports!==null?new Canvas():document.createElement('canvas');cnv.width=width;cnv.height=height;context=cnv.getContext("2d");gradient=context.createRadialGradient(centerX,centerY,radius1,centerX,centerY,radius2);gradient.addColorStop(1,"white");gradient.addColorStop(0,"black");context.fillStyle=gradient;context.fillRect(0,0,width,height);return context.getImageData(0,0,width,height);};BlurStack=function(){this.r=0;this.g=0;this.b=0;this.a=0;return this.next=null;};Caman.Plugin.register("compoundBlur",function(radiusData,radius,increaseFactor,blurLevels){var b_in_sum,b_out_sum,b_sum,blend,currentIndex,div,g_in_sum,g_out_sum,g_sum,height,heightMinus1,i,iblend,idx,imagePixels,index,iradius,lookupValue,mul_sum,p,pb,pg,pixels,pr,r_in_sum,r_out_sum,r_sum,radiusPixels,radiusPlus1,rbs,shg_sum,stack,stackEnd,stackIn,stackOut,stackStart,steps,sumFactor,w4,wh,wh4,width,widthMinus1,x,y,yi,yp,yw,_i,_j,_k,_l,_m,_n,_o,_p,_q,_r;width=this.dimensions.width;height=this.dimensions.height;imagePixels=this.pixelData;radiusPixels=radiusData.data;wh=width*height;wh4=wh<<2;pixels=[];for(i=_i=0;0<=wh4?_iwh4;i=0<=wh4?++_i:--_i){pixels[i]=imagePixels[i];}
+currentIndex=0;steps=blurLevels;blurLevels-=1;while(steps-->=0){iradius=(radius+0.5)|0;if(iradius===0){continue;}
+if(iradius>256){iradius=256;}
+div=iradius+iradius+1;w4=width<<2;widthMinus1=width-1;heightMinus1=height-1;radiusPlus1=iradius+1;sumFactor=radiusPlus1*(radiusPlus1+1)/2;stackStart=new BlurStack();stackEnd=void 0;stack=stackStart;for(i=_j=1;1<=div?_jdiv;i=1<=div?++_j:--_j){stack=stack.next=new BlurStack();if(i===radiusPlus1){stackEnd=stack;}}
+stack.next=stackStart;stackIn=null;stackOut=null;yw=yi=0;mul_sum=mul_table[iradius];shg_sum=shg_table[iradius];for(y=_k=0;0<=height?_kheight;y=0<=height?++_k:--_k){r_in_sum=g_in_sum=b_in_sum=r_sum=g_sum=b_sum=0;r_out_sum=radiusPlus1*(pr=pixels[yi]);g_out_sum=radiusPlus1*(pg=pixels[yi+1]);b_out_sum=radiusPlus1*(pb=pixels[yi+2]);r_sum+=sumFactor*pr;g_sum+=sumFactor*pg;b_sum+=sumFactor*pb;stack=stackStart;for(i=_l=0;0<=radiusPlus1?_lradiusPlus1;i=0<=radiusPlus1?++_l:--_l){stack.r=pr;stack.g=pg;stack.b=pb;stack=stack.next;}
+for(i=_m=1;1<=radiusPlus1?_mradiusPlus1;i=1<=radiusPlus1?++_m:--_m){p=yi+((widthMinus1width;x=0<=width?++_n:--_n){pixels[yi]=(r_sum*mul_sum)>>shg_sum;pixels[yi+1]=(g_sum*mul_sum)>>shg_sum;pixels[yi+2]=(b_sum*mul_sum)>>shg_sum;r_sum-=r_out_sum;g_sum-=g_out_sum;b_sum-=b_out_sum;r_out_sum-=stackIn.r;g_out_sum-=stackIn.g;b_out_sum-=stackIn.b;p=(yw+((p=x+radiusPlus1)width;x=0<=width?++_o:--_o){g_in_sum=b_in_sum=r_in_sum=g_sum=b_sum=r_sum=0;yi=x<<2;r_out_sum=radiusPlus1*(pr=pixels[yi]);g_out_sum=radiusPlus1*(pg=pixels[yi+1]);b_out_sum=radiusPlus1*(pb=pixels[yi+2]);r_sum+=sumFactor*pr;g_sum+=sumFactor*pg;b_sum+=sumFactor*pb;stack=stackStart;for(i=_p=0;0<=radiusPlus1?_pradiusPlus1;i=0<=radiusPlus1?++_p:--_p){stack.r=pr;stack.g=pg;stack.b=pb;stack=stack.next;}
+yp=width;for(i=_q=1;1<=radiusPlus1?_qradiusPlus1;i=1<=radiusPlus1?++_q:--_q){yi=(yp+x)<<2;r_sum+=(stack.r=(pr=pixels[yi]))*(rbs=radiusPlus1-i);g_sum+=(stack.g=(pg=pixels[yi+1]))*rbs;b_sum+=(stack.b=(pb=pixels[yi+2]))*rbs;r_in_sum+=pr;g_in_sum+=pg;b_in_sum+=pb;stack=stack.next;if(iheight;y=0<=height?++_r:--_r){p=yi<<2;pixels[p]=(r_sum*mul_sum)>>shg_sum;pixels[p+1]=(g_sum*mul_sum)>>shg_sum;pixels[p+2]=(b_sum*mul_sum)>>shg_sum;r_sum-=r_out_sum;g_sum-=g_out_sum;b_sum-=b_out_sum;r_out_sum-=stackIn.r;g_out_sum-=stackIn.g;b_out_sum-=stackIn.b;p=(x+(((p=y+radiusPlus1)-1){idx=i<<2;lookupValue=(radiusPixels[idx+2]&0xff)/255.0*blurLevels;index=lookupValue|0;if(index===currentIndex){blend=256.0*(lookupValue-(lookupValue|0));iblend=256-blend;imagePixels[idx]=(imagePixels[idx]*iblend+pixels[idx]*blend)>>8;imagePixels[idx+1]=(imagePixels[idx+1]*iblend+pixels[idx+1]*blend)>>8;imagePixels[idx+2]=(imagePixels[idx+2]*iblend+pixels[idx+2]*blend)>>8;}else if(index===currentIndex+1){imagePixels[idx]=pixels[idx];imagePixels[idx+1]=pixels[idx+1];imagePixels[idx+2]=pixels[idx+2];}}
+currentIndex++;}
+return this;});Caman.Filter.register("tiltShift",function(opts){var defaults,gradient;defaults={center:{x:this.dimensions.width/2,y:this.dimensions.height/2},angle:45,focusWidth:200,startRadius:3,radiusFactor:1.5,steps:3};opts=Util.extend(defaults,opts);opts.angle*=Math.PI/180;gradient=getLinearGradientMap(this.dimensions.width,this.dimensions.height,opts.center.x,opts.center.y,opts.angle,opts.focusWidth,true);return this.processPlugin("compoundBlur",[gradient,opts.startRadius,opts.radiusFactor,opts.steps]);});return Caman.Filter.register("radialBlur",function(opts){var defaults,gradient,radius1,radius2;defaults={size:50,center:{x:this.dimensions.width/2,y:this.dimensions.height/2},startRadius:3,radiusFactor:1.5,steps:3,radius:null};opts=Util.extend(defaults,opts);if(!opts.radius){opts.radius=this.dimensions.widthdiv;i=1<=div?++_i:--_i){stack=stack.next=new BlurStack();if(i===radiusPlus1){stackEnd=stack;}}
+stack.next=stackStart;stackIn=null;stackOut=null;yw=yi=0;mul_sum=mul_table[radius];shg_sum=shg_table[radius];for(y=_j=0;0<=height?_jheight;y=0<=height?++_j:--_j){r_in_sum=g_in_sum=b_in_sum=r_sum=g_sum=b_sum=0;r_out_sum=radiusPlus1*(pr=pixels[yi]);g_out_sum=radiusPlus1*(pg=pixels[yi+1]);b_out_sum=radiusPlus1*(pb=pixels[yi+2]);r_sum+=sumFactor*pr;g_sum+=sumFactor*pg;b_sum+=sumFactor*pb;stack=stackStart;for(i=_k=0;0<=radiusPlus1?_kradiusPlus1;i=0<=radiusPlus1?++_k:--_k){stack.r=pr;stack.g=pg;stack.b=pb;stack=stack.next;}
+for(i=_l=1;1<=radiusPlus1?_lradiusPlus1;i=1<=radiusPlus1?++_l:--_l){p=yi+((widthMinus1width;x=0<=width?++_m:--_m){pixels[yi]=(r_sum*mul_sum)>>shg_sum;pixels[yi+1]=(g_sum*mul_sum)>>shg_sum;pixels[yi+2]=(b_sum*mul_sum)>>shg_sum;r_sum-=r_out_sum;g_sum-=g_out_sum;b_sum-=b_out_sum;r_out_sum-=stackIn.r;g_out_sum-=stackIn.g;b_out_sum-=stackIn.b;p=(yw+((p=x+radius+1)width;x=0<=width?++_n:--_n){g_in_sum=b_in_sum=r_in_sum=g_sum=b_sum=r_sum=0;yi=x<<2;r_out_sum=radiusPlus1*(pr=pixels[yi]);g_out_sum=radiusPlus1*(pg=pixels[yi+1]);b_out_sum=radiusPlus1*(pb=pixels[yi+2]);r_sum+=sumFactor*pr;g_sum+=sumFactor*pg;b_sum+=sumFactor*pb;stack=stackStart;for(i=_o=0;0<=radiusPlus1?_oradiusPlus1;i=0<=radiusPlus1?++_o:--_o){stack.r=pr;stack.g=pg;stack.b=pb;stack=stack.next;}
+yp=width;for(i=_p=1;1<=radius?_p<=radius:_p>=radius;i=1<=radius?++_p:--_p){yi=(yp+x)<<2;r_sum+=(stack.r=(pr=pixels[yi]))*(rbs=radiusPlus1-i);g_sum+=(stack.g=(pg=pixels[yi+1]))*rbs;b_sum+=(stack.b=(pb=pixels[yi+2]))*rbs;r_in_sum+=pr;g_in_sum+=pg;b_in_sum+=pb;stack=stack.next;if(iheight;y=0<=height?++_q:--_q){p=yi<<2;pixels[p]=(r_sum*mul_sum)>>shg_sum;pixels[p+1]=(g_sum*mul_sum)>>shg_sum;pixels[p+2]=(b_sum*mul_sum)>>shg_sum;r_sum-=r_out_sum;g_sum-=g_out_sum;b_sum-=b_out_sum;r_out_sum-=stackIn.r;g_out_sum-=stackIn.g;b_out_sum-=stackIn.b;p=(x+(((p=y+radiusPlus1)255){return 255;}
+return val;};Util.copyAttributes=function(from,to,opts){var attr,_i,_len,_ref,_ref1,_results;if(opts==null){opts={};}
+_ref=from.attributes;_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++){attr=_ref[_i];if((opts.except!=null)&&(_ref1=attr.nodeName,__indexOf.call(opts.except,_ref1)>=0)){continue;}
+_results.push(to.setAttribute(attr.nodeName,attr.nodeValue));}
+return _results;};Util.dataArray=function(length){if(length==null){length=0;}
+if(Caman.NodeJS||(window.Uint8Array!=null)){return new Uint8Array(length);}
+return new Array(length);};return Util;})();if(typeof exports!=="undefined"&&exports!==null){Root=exports;Canvas=require('canvas');Image=Canvas.Image;Fiber=require('fibers');fs=require('fs');}else{Root=window;}
+Root.Caman=Caman=(function(){Caman.version={release:"4.1.1",date:"4/8/2013"};Caman.DEBUG=false;Caman.NodeJS=typeof exports!=="undefined"&&exports!==null;Caman.autoload=!Caman.NodeJS;Caman.allowRevert=true;Caman.crossOrigin="anonymous";Caman.toString=function(){return"Version "+Caman.version.release+", Released "+Caman.version.date;};Caman.remoteProxy="";Caman.proxyParam="camanProxyUrl";Caman.getAttrId=function(canvas){if(Caman.NodeJS){return true;}
+if(typeof canvas==="string"){canvas=$(canvas);}
+if(!((canvas!=null)&&(canvas.getAttribute!=null))){return null;}
+return canvas.getAttribute('data-caman-id');};function Caman(){var args,callback,id,_this=this;if(arguments.length===0){throw"Invalid arguments";}
+if(this instanceof Caman){this.finishInit=this.finishInit.bind(this);this.imageLoaded=this.imageLoaded.bind(this);args=arguments[0];if(!Caman.NodeJS){id=parseInt(Caman.getAttrId(args[0]),10);callback=typeof args[1]==="function"?args[1]:typeof args[2]==="function"?args[2]:function(){};if(!isNaN(id)&&Store.has(id)){return Store.execute(id,callback);}}
+this.id=Util.uniqid.get();this.initializedPixelData=this.originalPixelData=null;this.cropCoordinates={x:0,y:0};this.cropped=false;this.resized=false;this.pixelStack=[];this.layerStack=[];this.canvasQueue=[];this.currentLayer=null;this.scaled=false;this.analyze=new Analyze(this);this.renderer=new Renderer(this);this.domIsLoaded(function(){_this.parseArguments(args);return _this.setup();});return this;}else{return new Caman(arguments);}}
+Caman.prototype.domIsLoaded=function(cb){var listener,_this=this;if(Caman.NodeJS){return setTimeout(function(){return cb.call(_this);},0);}else{if(document.readyState==="complete"){Log.debug("DOM initialized");return setTimeout(function(){return cb.call(_this);},0);}else{listener=function(){if(document.readyState==="complete"){Log.debug("DOM initialized");return cb.call(_this);}};return document.addEventListener("readystatechange",listener,false);}}};Caman.prototype.parseArguments=function(args){var key,val,_ref,_results;if(args.length===0){throw"Invalid arguments given";}
+this.initObj=null;this.initType=null;this.imageUrl=null;this.callback=function(){};this.setInitObject(args[0]);if(args.length===1){return;}
+switch(typeof args[1]){case"string":this.imageUrl=args[1];break;case"function":this.callback=args[1];}
+if(args.length===2){return;}
+this.callback=args[2];if(args.length===4){_ref=args[4];_results=[];for(key in _ref){if(!__hasProp.call(_ref,key))continue;val=_ref[key];_results.push(this.options[key]=val);}
+return _results;}};Caman.prototype.setInitObject=function(obj){if(Caman.NodeJS){this.initObj=obj;this.initType='node';return;}
+if(typeof obj==="object"){this.initObj=obj;}else{this.initObj=$(obj);}
+if(this.initObj==null){throw"Could not find image or canvas for initialization.";}
+return this.initType=this.initObj.nodeName.toLowerCase();};Caman.prototype.setup=function(){switch(this.initType){case"node":return this.initNode();case"img":return this.initImage();case"canvas":return this.initCanvas();}};Caman.prototype.initNode=function(){var _this=this;Log.debug("Initializing for NodeJS");this.image=new Image();this.image.onload=function(){Log.debug("Image loaded. Width = "+(_this.imageWidth())+", Height = "+(_this.imageHeight()));_this.canvas=new Canvas(_this.imageWidth(),_this.imageHeight());return _this.finishInit();};this.image.onerror=function(err){throw err;};return this.image.src=this.initObj;};Caman.prototype.initImage=function(){this.image=this.initObj;this.canvas=document.createElement('canvas');this.context=this.canvas.getContext('2d');Util.copyAttributes(this.image,this.canvas,{except:['src']});this.image.parentNode.replaceChild(this.canvas,this.image);this.imageAdjustments();return this.waitForImageLoaded();};Caman.prototype.initCanvas=function(){this.canvas=this.initObj;this.context=this.canvas.getContext('2d');if(this.imageUrl!=null){this.image=document.createElement('img');this.image.src=this.imageUrl;this.imageAdjustments();return this.waitForImageLoaded();}else{return this.finishInit();}};Caman.prototype.imageAdjustments=function(){if(this.needsHiDPISwap()){Log.debug(this.image.src,"->",this.hiDPIReplacement());this.swapped=true;this.image.src=this.hiDPIReplacement();}
+if(IO.isRemote(this.image)){this.image.src=IO.proxyUrl(this.image.src);return Log.debug("Remote image detected, using URL = "+this.image.src);}};Caman.prototype.waitForImageLoaded=function(){if(this.isImageLoaded()){return this.imageLoaded();}else{return this.image.onload=this.imageLoaded;}};Caman.prototype.isImageLoaded=function(){if(!this.image.complete){return false;}
+if((this.image.naturalWidth!=null)&&this.image.naturalWidth===0){return false;}
+return true;};Caman.prototype.imageWidth=function(){return this.image.width||this.image.naturalWidth;};Caman.prototype.imageHeight=function(){return this.image.height||this.image.naturalHeight;};Caman.prototype.imageLoaded=function(){Log.debug("Image loaded. Width = "+(this.imageWidth())+", Height = "+(this.imageHeight()));if(this.swapped){this.canvas.width=this.imageWidth()/this.hiDPIRatio();this.canvas.height=this.imageHeight()/this.hiDPIRatio();}else{this.canvas.width=this.imageWidth();this.canvas.height=this.imageHeight();}
+return this.finishInit();};Caman.prototype.finishInit=function(){var i,pixel,_i,_len,_ref;if(this.context==null){this.context=this.canvas.getContext('2d');}
+this.originalWidth=this.preScaledWidth=this.width=this.canvas.width;this.originalHeight=this.preScaledHeight=this.height=this.canvas.height;this.hiDPIAdjustments();if(!this.hasId()){this.assignId();}
+if(this.image!=null){this.context.drawImage(this.image,0,0,this.imageWidth(),this.imageHeight(),0,0,this.preScaledWidth,this.preScaledHeight);}
+this.reloadCanvasData();if(Caman.allowRevert){this.initializedPixelData=Util.dataArray(this.pixelData.length);this.originalPixelData=Util.dataArray(this.pixelData.length);_ref=this.pixelData;for(i=_i=0,_len=_ref.length;_i<_len;i=++_i){pixel=_ref[i];this.initializedPixelData[i]=pixel;this.originalPixelData[i]=pixel;}}
+this.dimensions={width:this.canvas.width,height:this.canvas.height};Store.put(this.id,this);this.callback.call(this,this);return this.callback=function(){};};Caman.prototype.reloadCanvasData=function(){this.imageData=this.context.getImageData(0,0,this.canvas.width,this.canvas.height);return this.pixelData=this.imageData.data;};Caman.prototype.resetOriginalPixelData=function(){var pixel,_i,_len,_ref,_results;if(!Caman.allowRevert){throw"Revert disabled";}
+this.originalPixelData=Util.dataArray(this.pixelData.length);_ref=this.pixelData;_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++){pixel=_ref[_i];_results.push(this.originalPixelData.push(pixel));}
+return _results;};Caman.prototype.hasId=function(){return Caman.getAttrId(this.canvas)!=null;};Caman.prototype.assignId=function(){if(Caman.NodeJS||this.canvas.getAttribute('data-caman-id')){return;}
+return this.canvas.setAttribute('data-caman-id',this.id);};Caman.prototype.hiDPIDisabled=function(){return this.canvas.getAttribute('data-caman-hidpi-disabled')!==null;};Caman.prototype.hiDPIAdjustments=function(){var ratio;if(Caman.NodeJS||this.hiDPIDisabled()){return;}
+ratio=this.hiDPIRatio();if(ratio!==1){Log.debug("HiDPI ratio = "+ratio);this.scaled=true;this.preScaledWidth=this.canvas.width;this.preScaledHeight=this.canvas.height;this.canvas.width=this.preScaledWidth*ratio;this.canvas.height=this.preScaledHeight*ratio;this.canvas.style.width=""+this.preScaledWidth+"px";this.canvas.style.height=""+this.preScaledHeight+"px";this.context.scale(ratio,ratio);this.width=this.originalWidth=this.canvas.width;return this.height=this.originalHeight=this.canvas.height;}};Caman.prototype.hiDPIRatio=function(){var backingStoreRatio,devicePixelRatio;devicePixelRatio=window.devicePixelRatio||1;backingStoreRatio=this.context.webkitBackingStorePixelRatio||this.context.mozBackingStorePixelRatio||this.context.msBackingStorePixelRatio||this.context.oBackingStorePixelRatio||this.context.backingStorePixelRatio||1;return devicePixelRatio/backingStoreRatio;};Caman.prototype.hiDPICapable=function(){return(window.devicePixelRatio!=null)&&window.devicePixelRatio!==1;};Caman.prototype.needsHiDPISwap=function(){if(this.hiDPIDisabled()||!this.hiDPICapable()){return false;}
+return this.hiDPIReplacement()!==null;};Caman.prototype.hiDPIReplacement=function(){if(this.image==null){return null;}
+return this.image.getAttribute('data-caman-hidpi');};Caman.prototype.replaceCanvas=function(newCanvas){var oldCanvas;oldCanvas=this.canvas;this.canvas=newCanvas;this.context=this.canvas.getContext('2d');oldCanvas.parentNode.replaceChild(this.canvas,oldCanvas);this.width=this.canvas.width;this.height=this.canvas.height;this.reloadCanvasData();return this.dimensions={width:this.canvas.width,height:this.canvas.height};};Caman.prototype.render=function(callback){var _this=this;if(callback==null){callback=function(){};}
+Event.trigger(this,"renderStart");return this.renderer.execute(function(){_this.context.putImageData(_this.imageData,0,0);return callback.call(_this);});};Caman.prototype.revert=function(){var i,pixel,_i,_len,_ref;if(!Caman.allowRevert){throw"Revert disabled";}
+_ref=this.originalVisiblePixels();for(i=_i=0,_len=_ref.length;_i<_len;i=++_i){pixel=_ref[i];this.pixelData[i]=pixel;}
+return this.context.putImageData(this.imageData,0,0);};Caman.prototype.reset=function(){var canvas,ctx,i,imageData,pixel,pixelData,_i,_len,_ref;canvas=document.createElement('canvas');Util.copyAttributes(this.canvas,canvas);canvas.width=this.originalWidth;canvas.height=this.originalHeight;ctx=canvas.getContext('2d');imageData=ctx.getImageData(0,0,canvas.width,canvas.height);pixelData=imageData.data;_ref=this.initializedPixelData;for(i=_i=0,_len=_ref.length;_i<_len;i=++_i){pixel=_ref[i];pixelData[i]=pixel;}
+ctx.putImageData(imageData,0,0);this.cropCoordinates={x:0,y:0};this.resized=false;return this.replaceCanvas(canvas);};Caman.prototype.originalVisiblePixels=function(){var canvas,coord,ctx,endX,endY,i,imageData,pixel,pixelData,pixels,scaledCanvas,startX,startY,width,_i,_j,_len,_ref,_ref1,_ref2,_ref3;if(!Caman.allowRevert){throw"Revert disabled";}
+pixels=[];startX=this.cropCoordinates.x;endX=startX+this.width;startY=this.cropCoordinates.y;endY=startY+this.height;if(this.resized){canvas=document.createElement('canvas');canvas.width=this.originalWidth;canvas.height=this.originalHeight;ctx=canvas.getContext('2d');imageData=ctx.getImageData(0,0,canvas.width,canvas.height);pixelData=imageData.data;_ref=this.originalPixelData;for(i=_i=0,_len=_ref.length;_i<_len;i=++_i){pixel=_ref[i];pixelData[i]=pixel;}
+ctx.putImageData(imageData,0,0);scaledCanvas=document.createElement('canvas');scaledCanvas.width=this.width;scaledCanvas.height=this.height;ctx=scaledCanvas.getContext('2d');ctx.drawImage(canvas,0,0,this.originalWidth,this.originalHeight,0,0,this.width,this.height);pixelData=ctx.getImageData(0,0,this.width,this.height).data;width=this.width;}else{pixelData=this.originalPixelData;width=this.originalWidth;}
+for(i=_j=0,_ref1=pixelData.length;_j<_ref1;i=_j+=4){coord=PixelInfo.locationToCoordinates(i,width);if(((startX<=(_ref2=coord.x)&&_ref2_ref;i=0<=_ref?++_i:--_i){divisor+=adjust[i];}}
+this.renderer.add({type:Filter.Type.Kernel,name:name,adjust:adjust,divisor:divisor,bias:bias||0});return this;};Caman.prototype.processPlugin=function(plugin,args){this.renderer.add({type:Filter.Type.Plugin,plugin:plugin,args:args});return this;};Caman.prototype.newLayer=function(callback){var layer;layer=new Layer(this);this.canvasQueue.push(layer);this.renderer.add({type:Filter.Type.LayerDequeue});callback.call(layer);this.renderer.add({type:Filter.Type.LayerFinished});return this;};Caman.prototype.executeLayer=function(layer){return this.pushContext(layer);};Caman.prototype.pushContext=function(layer){this.layerStack.push(this.currentLayer);this.pixelStack.push(this.pixelData);this.currentLayer=layer;return this.pixelData=layer.pixelData;};Caman.prototype.popContext=function(){this.pixelData=this.pixelStack.pop();return this.currentLayer=this.layerStack.pop();};Caman.prototype.applyCurrentLayer=function(){return this.currentLayer.applyToParent();};return Caman;})();Analyze=(function(){function Analyze(c){this.c=c;}
+Analyze.prototype.calculateLevels=function(){var i,levels,numPixels,_i,_j,_k,_ref;levels={r:{},g:{},b:{}};for(i=_i=0;_i<=255;i=++_i){levels.r[i]=0;levels.g[i]=0;levels.b[i]=0;}
+for(i=_j=0,_ref=this.c.pixelData.length;_j<_ref;i=_j+=4){levels.r[this.c.pixelData[i]]++;levels.g[this.c.pixelData[i+1]]++;levels.b[this.c.pixelData[i+2]]++;}
+numPixels=this.c.pixelData.length/4;for(i=_k=0;_k<=255;i=++_k){levels.r[i]/=numPixels;levels.g[i]/=numPixels;levels.b[i]/=numPixels;}
+return levels;};return Analyze;})();Caman.DOMUpdated=function(){var img,imgs,parser,_i,_len,_results;imgs=document.querySelectorAll("img[data-caman]");if(!(imgs.length>0)){return;}
+_results=[];for(_i=0,_len=imgs.length;_i<_len;_i++){img=imgs[_i];_results.push(parser=new CamanParser(img,function(){this.parse();return this.execute();}));}
+return _results;};if(Caman.autoload){(function(){if(document.readyState==="complete"){return Caman.DOMUpdated();}else{return document.addEventListener("DOMContentLoaded",Caman.DOMUpdated,false);}})();}
+CamanParser=(function(){var INST_REGEX;INST_REGEX="(\\w+)\\((.*?)\\)";function CamanParser(ele,ready){this.dataStr=ele.getAttribute('data-caman');this.caman=Caman(ele,ready.bind(this));}
+CamanParser.prototype.parse=function(){var args,filter,func,inst,instFunc,m,r,unparsedInstructions,_i,_len,_ref,_results;this.ele=this.caman.canvas;r=new RegExp(INST_REGEX,'g');unparsedInstructions=this.dataStr.match(r);if(!(unparsedInstructions.length>0)){return;}
+r=new RegExp(INST_REGEX);_results=[];for(_i=0,_len=unparsedInstructions.length;_i<_len;_i++){inst=unparsedInstructions[_i];_ref=inst.match(r),m=_ref[0],filter=_ref[1],args=_ref[2];instFunc=new Function("return function() { this."+filter+"("+args+"); };");try{func=instFunc();_results.push(func.call(this.caman));}catch(e){_results.push(Log.debug(e));}}
+return _results;};CamanParser.prototype.execute=function(){var ele;ele=this.ele;return this.caman.render(function(){return ele.parentNode.replaceChild(this.toImage(),ele);});};return CamanParser;})();Caman.Blender=Blender=(function(){function Blender(){}
+Blender.blenders={};Blender.register=function(name,func){return this.blenders[name]=func;};Blender.execute=function(name,rgbaLayer,rgbaParent){return this.blenders[name](rgbaLayer,rgbaParent);};return Blender;})();Caman.Calculate=Calculate=(function(){function Calculate(){}
+Calculate.distance=function(x1,y1,x2,y2){return Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));};Calculate.randomRange=function(min,max,getFloat){var rand;if(getFloat==null){getFloat=false;}
+rand=min+(Math.random()*(max-min));if(getFloat){return rand.toFixed(getFloat);}else{return Math.round(rand);}};Calculate.luminance=function(rgba){return(0.299*rgba.r)+(0.587*rgba.g)+(0.114*rgba.b);};Calculate.bezier=function(start,ctrl1,ctrl2,end,lowBound,highBound){var Ax,Ay,Bx,By,Cx,Cy,bezier,curveX,curveY,i,j,leftCoord,rightCoord,t,x0,x1,x2,x3,y0,y1,y2,y3,_i,_j,_k,_ref,_ref1;x0=start[0];y0=start[1];x1=ctrl1[0];y1=ctrl1[1];x2=ctrl2[0];y2=ctrl2[1];x3=end[0];y3=end[1];bezier={};Cx=parseInt(3*(x1-x0),10);Bx=3*(x2-x1)-Cx;Ax=x3-x0-Cx-Bx;Cy=3*(y1-y0);By=3*(y2-y1)-Cy;Ay=y3-y0-Cy-By;for(i=_i=0;_i<1000;i=++_i){t=i/1000;curveX=Math.round((Ax*Math.pow(t,3))+(Bx*Math.pow(t,2))+(Cx*t)+x0);curveY=Math.round((Ay*Math.pow(t,3))+(By*Math.pow(t,2))+(Cy*t)+y0);if(lowBound&&curveYhighBound){curveY=highBound;}
+bezier[curveX]=curveY;}
+if(bezier.length=_ref;i=0<=_ref?++_j:--_j){if(bezier[i]==null){leftCoord=[i-1,bezier[i-1]];for(j=_k=i,_ref1=end[0];i<=_ref1?_k<=_ref1:_k>=_ref1;j=i<=_ref1?++_k:--_k){if(bezier[j]!=null){rightCoord=[j,bezier[j]];break;}}
+bezier[i]=leftCoord[1]+((rightCoord[1]-leftCoord[1])/(rightCoord[0]-leftCoord[0]))*(i-leftCoord[0]);}}}
+if(bezier[end[0]]==null){bezier[end[0]]=bezier[end[0]-1];}
+return bezier;};return Calculate;})();Convert=(function(){function Convert(){}
+Convert.hexToRGB=function(hex){var b,g,r;if(hex.charAt(0)==="#"){hex=hex.substr(1);}
+r=parseInt(hex.substr(0,2),16);g=parseInt(hex.substr(2,2),16);b=parseInt(hex.substr(4,2),16);return{r:r,g:g,b:b};};Convert.rgbToHSL=function(r,g,b){var d,h,l,max,min,s;if(typeof r==="object"){g=r.g;b=r.b;r=r.r;}
+r/=255;g/=255;b/=255;max=Math.max(r,g,b);min=Math.min(r,g,b);l=(max+min)/2;if(max===min){h=s=0;}else{d=max-min;s=l>0.5?d/(2-max-min):d/(max+min);h=(function(){switch(max){case r:return(g-b)/d+(g1){t-=1;}
+if(t<1/6){return p+(q-p)*6*t;}
+if(t<1/2){return q;}
+if(t<2/3){return p+(q-p)*(2/3-t)*6;}
+return p;};Convert.rgbToHSV=function(r,g,b){var d,h,max,min,s,v;r/=255;g/=255;b/=255;max=Math.max(r,g,b);min=Math.min(r,g,b);v=max;d=max-min;s=max===0?0:d/max;if(max===min){h=0;}else{h=(function(){switch(max){case r:return(g-b)/d+(g0.04045){r=Math.pow((r+0.055)/1.055,2.4);}else{r/=12.92;}
+if(g>0.04045){g=Math.pow((g+0.055)/1.055,2.4);}else{g/=12.92;}
+if(b>0.04045){b=Math.pow((b+0.055)/1.055,2.4);}else{b/=12.92;}
+x=r*0.4124+g*0.3576+b*0.1805;y=r*0.2126+g*0.7152+b*0.0722;z=r*0.0193+g*0.1192+b*0.9505;return{x:x*100,y:y*100,z:z*100};};Convert.xyzToRGB=function(x,y,z){var b,g,r;x/=100;y/=100;z/=100;r=(3.2406*x)+(-1.5372*y)+(-0.4986*z);g=(-0.9689*x)+(1.8758*y)+(0.0415*z);b=(0.0557*x)+(-0.2040*y)+(1.0570*z);if(r>0.0031308){r=(1.055*Math.pow(r,0.4166666667))-0.055;}else{r*=12.92;}
+if(g>0.0031308){g=(1.055*Math.pow(g,0.4166666667))-0.055;}else{g*=12.92;}
+if(b>0.0031308){b=(1.055*Math.pow(b,0.4166666667))-0.055;}else{b*=12.92;}
+return{r:r*255,g:g*255,b:b*255};};Convert.xyzToLab=function(x,y,z){var a,b,l,whiteX,whiteY,whiteZ;if(typeof x==="object"){y=x.y;z=x.z;x=x.x;}
+whiteX=95.047;whiteY=100.0;whiteZ=108.883;x/=whiteX;y/=whiteY;z/=whiteZ;if(x>0.008856451679){x=Math.pow(x,0.3333333333);}else{x=(7.787037037*x)+0.1379310345;}
+if(y>0.008856451679){y=Math.pow(y,0.3333333333);}else{y=(7.787037037*y)+0.1379310345;}
+if(z>0.008856451679){z=Math.pow(z,0.3333333333);}else{z=(7.787037037*z)+0.1379310345;}
+l=116*y-16;a=500*(x-y);b=200*(y-z);return{l:l,a:a,b:b};};Convert.labToXYZ=function(l,a,b){var x,y,z;if(typeof l==="object"){a=l.a;b=l.b;l=l.l;}
+y=(l+16)/116;x=y+(a/500);z=y-(b/200);if(x>0.2068965517){x=x*x*x;}else{x=0.1284185493*(x-0.1379310345);}
+if(y>0.2068965517){y=y*y*y;}else{y=0.1284185493*(y-0.1379310345);}
+if(z>0.2068965517){z=z*z*z;}else{z=0.1284185493*(z-0.1379310345);}
+return{x:x*95.047,y:y*100.0,z:z*108.883};};Convert.rgbToLab=function(r,g,b){var xyz;if(typeof r==="object"){g=r.g;b=r.b;r=r.r;}
+xyz=this.rgbToXYZ(r,g,b);return this.xyzToLab(xyz);};Convert.labToRGB=function(l,a,b){};return Convert;})();Event=(function(){function Event(){}
+Event.events={};Event.types=["processStart","processComplete","renderStart","renderFinished","blockStarted","blockFinished"];Event.trigger=function(target,type,data){var event,_i,_len,_ref,_results;if(this.events[type]&&this.events[type].length){_ref=this.events[type];_results=[];for(_i=0,_len=_ref.length;_i<_len;_i++){event=_ref[_i];if(event.target===null||target.id===event.target.id){_results.push(event.fn.call(target,data));}else{_results.push(void 0);}}
+return _results;}};Event.listen=function(target,type,fn){var _fn,_type;if(typeof target==="string"){_type=target;_fn=type;target=null;type=_type;fn=_fn;}
+if(__indexOf.call(this.types,type)<0){return false;}
+if(!this.events[type]){this.events[type]=[];}
+this.events[type].push({target:target,fn:fn});return true;};return Event;})();Caman.Event=Event;Caman.Filter=Filter=(function(){function Filter(){}
+Filter.Type={Single:1,Kernel:2,LayerDequeue:3,LayerFinished:4,LoadOverlay:5,Plugin:6};Filter.register=function(name,filterFunc){return Caman.prototype[name]=filterFunc;};return Filter;})();Caman.IO=IO=(function(){function IO(){}
+IO.domainRegex=/(?:(?:http|https):\/\/)((?:\w+)\.(?:(?:\w|\.)+))/;IO.isRemote=function(img){if(img==null){return false;}
+if(this.corsEnabled(img)){return false;}
+return this.isURLRemote(img.src);};IO.corsEnabled=function(img){var _ref;return(img.crossOrigin!=null)&&((_ref=img.crossOrigin.toLowerCase())==='anonymous'||_ref==='use-credentials');};IO.isURLRemote=function(url){var matches;matches=url.match(this.domainRegex);if(matches){return matches[1]!==document.domain;}else{return false;}};IO.remoteCheck=function(src){if(this.isURLRemote(src)){if(!Caman.remoteProxy.length){Log.info("Attempting to load a remote image without a configured proxy. URL: "+src);}else{if(Caman.isURLRemote(Caman.remoteProxy)){Log.info("Cannot use a remote proxy for loading images.");return;}
+return""+Caman.remoteProxy+"?camanProxyUrl="+(encodeURIComponent(src));}}};IO.proxyUrl=function(src){return""+Caman.remoteProxy+"?"+Caman.proxyParam+"="+(encodeURIComponent(src));};IO.useProxy=function(lang){var langToExt;langToExt={ruby:'rb',python:'py',perl:'pl',javascript:'js'};lang=lang.toLowerCase();if(langToExt[lang]!=null){lang=langToExt[lang];}
+return"proxies/caman_proxy."+lang;};return IO;})();Caman.prototype.save=function(){if(typeof exports!=="undefined"&&exports!==null){return this.nodeSave.apply(this,arguments);}else{return this.browserSave.apply(this,arguments);}};Caman.prototype.browserSave=function(type){var image;if(type==null){type="png";}
+type=type.toLowerCase();image=this.toBase64(type).replace("image/"+type,"image/octet-stream");return document.location.href=image;};Caman.prototype.nodeSave=function(file,overwrite){var stats;if(overwrite==null){overwrite=true;}
+try{stats=fs.statSync(file);if(stats.isFile()&&!overwrite){return false;}}catch(e){Log.debug("Creating output file "+file);}
+return fs.writeFile(file,this.canvas.toBuffer(),function(){return Log.debug("Finished writing to "+file);});};Caman.prototype.toImage=function(type){var img;img=document.createElement('img');img.src=this.toBase64(type);img.width=this.dimensions.width;img.height=this.dimensions.height;if(window.devicePixelRatio){img.width/=window.devicePixelRatio;img.height/=window.devicePixelRatio;}
+return img;};Caman.prototype.toBase64=function(type){if(type==null){type="png";}
+type=type.toLowerCase();return this.canvas.toDataURL("image/"+type);};Layer=(function(){function Layer(c){this.c=c;this.filter=this.c;this.options={blendingMode:'normal',opacity:1.0};this.layerID=Util.uniqid.get();this.canvas=typeof exports!=="undefined"&&exports!==null?new Canvas():document.createElement('canvas');this.canvas.width=this.c.dimensions.width;this.canvas.height=this.c.dimensions.height;this.context=this.canvas.getContext('2d');this.context.createImageData(this.canvas.width,this.canvas.height);this.imageData=this.context.getImageData(0,0,this.canvas.width,this.canvas.height);this.pixelData=this.imageData.data;}
+Layer.prototype.newLayer=function(cb){return this.c.newLayer.call(this.c,cb);};Layer.prototype.setBlendingMode=function(mode){this.options.blendingMode=mode;return this;};Layer.prototype.opacity=function(opacity){this.options.opacity=opacity/100;return this;};Layer.prototype.copyParent=function(){var i,parentData,_i,_ref;parentData=this.c.pixelData;for(i=_i=0,_ref=this.c.pixelData.length;_i<_ref;i=_i+=4){this.pixelData[i]=parentData[i];this.pixelData[i+1]=parentData[i+1];this.pixelData[i+2]=parentData[i+2];this.pixelData[i+3]=parentData[i+3];}
+return this;};Layer.prototype.fillColor=function(){return this.c.fillColor.apply(this.c,arguments);};Layer.prototype.overlayImage=function(image){if(typeof image==="object"){image=image.src;}else if(typeof image==="string"&&image[0]==="#"){image=$(image).src;}
+if(!image){return this;}
+this.c.renderer.renderQueue.push({type:Filter.Type.LoadOverlay,src:image,layer:this});return this;};Layer.prototype.applyToParent=function(){var i,layerData,parentData,result,rgbaLayer,rgbaParent,_i,_ref,_results;parentData=this.c.pixelStack[this.c.pixelStack.length-1];layerData=this.c.pixelData;_results=[];for(i=_i=0,_ref=layerData.length;_i<_ref;i=_i+=4){rgbaParent={r:parentData[i],g:parentData[i+1],b:parentData[i+2],a:parentData[i+3]};rgbaLayer={r:layerData[i],g:layerData[i+1],b:layerData[i+2],a:layerData[i+3]};result=Blender.execute(this.options.blendingMode,rgbaLayer,rgbaParent);result.r=Util.clampRGB(result.r);result.g=Util.clampRGB(result.g);result.b=Util.clampRGB(result.b);if(result.a==null){result.a=rgbaLayer.a;}
+parentData[i]=rgbaParent.r-((rgbaParent.r-result.r)*(this.options.opacity*(result.a/255)));parentData[i+1]=rgbaParent.g-((rgbaParent.g-result.g)*(this.options.opacity*(result.a/255)));_results.push(parentData[i+2]=rgbaParent.b-((rgbaParent.b-result.b)*(this.options.opacity*(result.a/255))));}
+return _results;};return Layer;})();Logger=(function(){function Logger(){var name,_i,_len,_ref;_ref=['log','info','warn','error'];for(_i=0,_len=_ref.length;_i<_len;_i++){name=_ref[_i];this[name]=(function(name){return function(){var args;args=1<=arguments.length?__slice.call(arguments,0):[];if(!Caman.DEBUG){return;}
+try{return console[name].apply(console,args);}catch(e){return console[name](args);}};})(name);}
+this.debug=this.log;}
+return Logger;})();Log=new Logger();PixelInfo=(function(){PixelInfo.coordinatesToLocation=function(x,y,width){return(y*width+x)*4;};PixelInfo.locationToCoordinates=function(loc,width){var x,y;y=Math.floor(loc/(width*4));x=(loc%(width*4))/4;return{x:x,y:y};};function PixelInfo(c){this.c=c;this.loc=0;}
+PixelInfo.prototype.locationXY=function(){var x,y;y=this.c.dimensions.height-Math.floor(this.loc/(this.c.dimensions.width*4));x=(this.loc%(this.c.dimensions.width*4))/4;return{x:x,y:y};};PixelInfo.prototype.getPixelRelative=function(horiz,vert){var newLoc;newLoc=this.loc+(this.c.dimensions.width*4*(vert*-1))+(4*horiz);if(newLoc>this.c.pixelData.length||newLoc<0){return{r:0,g:0,b:0,a:0};}
+return{r:this.c.pixelData[newLoc],g:this.c.pixelData[newLoc+1],b:this.c.pixelData[newLoc+2],a:this.c.pixelData[newLoc+3]};};PixelInfo.prototype.putPixelRelative=function(horiz,vert,rgba){var nowLoc;nowLoc=this.loc+(this.c.dimensions.width*4*(vert*-1))+(4*horiz);if(newLoc>this.c.pixelData.length||newLoc<0){return;}
+this.c.pixelData[newLoc]=rgba.r;this.c.pixelData[newLoc+1]=rgba.g;this.c.pixelData[newLoc+2]=rgba.b;this.c.pixelData[newLoc+3]=rgba.a;return true;};PixelInfo.prototype.getPixel=function(x,y){var loc;loc=this.coordinatesToLocation(x,y,this.width);return{r:this.c.pixelData[loc],g:this.c.pixelData[loc+1],b:this.c.pixelData[loc+2],a:this.c.pixelData[loc+3]};};PixelInfo.prototype.putPixel=function(x,y,rgba){var loc;loc=this.coordinatesToLocation(x,y,this.width);this.c.pixelData[loc]=rgba.r;this.c.pixelData[loc+1]=rgba.g;this.c.pixelData[loc+2]=rgba.b;return this.c.pixelData[loc+3]=rgba.a;};return PixelInfo;})();Plugin=(function(){function Plugin(){}
+Plugin.plugins={};Plugin.register=function(name,plugin){return this.plugins[name]=plugin;};Plugin.execute=function(context,name,args){return this.plugins[name].apply(context,args);};return Plugin;})();Caman.Plugin=Plugin;Caman.Renderer=Renderer=(function(){Renderer.Blocks=Caman.NodeJS?require('os').cpus().length:4;function Renderer(c){var _this=this;this.c=c;this.processNext=function(){return Renderer.prototype.processNext.apply(_this,arguments);};this.renderQueue=[];this.modPixelData=null;}
+Renderer.prototype.add=function(job){if(job==null){return;}
+return this.renderQueue.push(job);};Renderer.prototype.processNext=function(){var layer;if(this.renderQueue.length===0){Event.trigger(this,"renderFinished");if(this.finishedFn!=null){this.finishedFn.call(this.c);}
+return this;}
+this.currentJob=this.renderQueue.shift();switch(this.currentJob.type){case Filter.Type.LayerDequeue:layer=this.c.canvasQueue.shift();this.c.executeLayer(layer);return this.processNext();case Filter.Type.LayerFinished:this.c.applyCurrentLayer();this.c.popContext();return this.processNext();case Filter.Type.LoadOverlay:return this.loadOverlay(this.currentJob.layer,this.currentJob.src);case Filter.Type.Plugin:return this.executePlugin();default:return this.executeFilter();}};Renderer.prototype.execute=function(callback){this.finishedFn=callback;this.modPixelData=Util.dataArray(this.c.pixelData.length);return this.processNext();};Renderer.prototype.eachBlock=function(fn){var blockN,blockPixelLength,bnum,end,f,i,lastBlockN,n,start,_i,_ref,_results,_this=this;this.blocksDone=0;n=this.c.pixelData.length;blockPixelLength=Math.floor((n/4)/Renderer.Blocks);blockN=blockPixelLength*4;lastBlockN=blockN+((n/4)%Renderer.Blocks)*4;_results=[];for(i=_i=0,_ref=Renderer.Blocks;0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){start=i*blockN;end=start+(i===Renderer.Blocks-1?lastBlockN:blockN);if(Caman.NodeJS){f=Fiber(function(){return fn.call(_this,i,start,end);});bnum=f.run();_results.push(this.blockFinished(bnum));}else{_results.push(setTimeout((function(i,start,end){return function(){return fn.call(_this,i,start,end);};})(i,start,end),0));}}
+return _results;};Renderer.prototype.executeFilter=function(){Event.trigger(this.c,"processStart",this.currentJob);if(this.currentJob.type===Filter.Type.Single){return this.eachBlock(this.renderBlock);}else{return this.eachBlock(this.renderKernel);}};Renderer.prototype.executePlugin=function(){Log.debug("Executing plugin "+this.currentJob.plugin);Plugin.execute(this.c,this.currentJob.plugin,this.currentJob.args);Log.debug("Plugin "+this.currentJob.plugin+" finished!");return this.processNext();};Renderer.prototype.renderBlock=function(bnum,start,end){var data,i,pixelInfo,res,_i;Log.debug("Block #"+bnum+" - Filter: "+this.currentJob.name+", Start: "+start+", End: "+end);Event.trigger(this.c,"blockStarted",{blockNum:bnum,totalBlocks:Renderer.Blocks,startPixel:start,endPixel:end});data={r:0,g:0,b:0,a:0};pixelInfo=new PixelInfo(this.c);for(i=_i=start;_i=builder;j=-builder<=builder?++_j:--_j){for(k=_k=builder;builder<=-builder?_k<=-builder:_k>=-builder;k=builder<=-builder?++_k:--_k){pixel=pixelInfo.getPixelRelative(j,k);kernel[builderIndex*3]=pixel.r;kernel[builderIndex*3+1]=pixel.g;kernel[builderIndex*3+2]=pixel.b;builderIndex++;}}
+res=this.processKernel(adjust,kernel,divisor,bias);this.modPixelData[i]=Util.clampRGB(res.r);this.modPixelData[i+1]=Util.clampRGB(res.g);this.modPixelData[i+2]=Util.clampRGB(res.b);this.modPixelData[i+3]=this.c.pixelData[i+3];}
+if(Caman.NodeJS){return Fiber["yield"](bnum);}else{return this.blockFinished(bnum);}};Renderer.prototype.blockFinished=function(bnum){var i,_i,_ref;if(bnum>=0){Log.debug("Block #"+bnum+" finished! Filter: "+this.currentJob.name);}
+this.blocksDone++;Event.trigger(this.c,"blockFinished",{blockNum:bnum,blocksFinished:this.blocksDone,totalBlocks:Renderer.Blocks});if(this.blocksDone===Renderer.Blocks){if(this.currentJob.type===Filter.Type.Kernel){for(i=_i=0,_ref=this.c.pixelData.length;0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){this.c.pixelData[i]=this.modPixelData[i];}}
+if(bnum>=0){Log.debug("Filter "+this.currentJob.name+" finished!");}
+Event.trigger(this.c,"processComplete",this.currentJob);return this.processNext();}};Renderer.prototype.processKernel=function(adjust,kernel,divisor,bias){var i,val,_i,_ref;val={r:0,g:0,b:0};for(i=_i=0,_ref=adjust.length;0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){val.r+=adjust[i]*kernel[i*3];val.g+=adjust[i]*kernel[i*3+1];val.b+=adjust[i]*kernel[i*3+2];}
+val.r=(val.r/divisor)+bias;val.g=(val.g/divisor)+bias;val.b=(val.b/divisor)+bias;return val;};Renderer.prototype.loadOverlay=function(layer,src){var img,proxyUrl,_this=this;img=document.createElement('img');img.onload=function(){layer.context.drawImage(img,0,0,_this.c.dimensions.width,_this.c.dimensions.height);layer.imageData=layer.context.getImageData(0,0,_this.c.dimensions.width,_this.c.dimensions.height);layer.pixelData=layer.imageData.data;_this.c.pixelData=layer.pixelData;return _this.processNext();};proxyUrl=IO.remoteCheck(src);return img.src=proxyUrl!=null?proxyUrl:src;};return Renderer;})();Caman.Store=Store=(function(){function Store(){}
+Store.items={};Store.has=function(search){return this.items[search]!=null;};Store.get=function(search){return this.items[search];};Store.put=function(name,obj){return this.items[name]=obj;};Store.execute=function(search,callback){var _this=this;setTimeout(function(){return callback.call(_this.get(search),_this.get(search));},0);return this.get(search);};Store.flush=function(name){if(name==null){name=false;}
+if(name){return delete this.items[name];}else{return this.items={};}};return Store;})();Blender.register("normal",function(rgbaLayer,rgbaParent){return{r:rgbaLayer.r,g:rgbaLayer.g,b:rgbaLayer.b};});Blender.register("multiply",function(rgbaLayer,rgbaParent){return{r:(rgbaLayer.r*rgbaParent.r)/255,g:(rgbaLayer.g*rgbaParent.g)/255,b:(rgbaLayer.b*rgbaParent.b)/255};});Blender.register("screen",function(rgbaLayer,rgbaParent){return{r:255-(((255-rgbaLayer.r)*(255-rgbaParent.r))/255),g:255-(((255-rgbaLayer.g)*(255-rgbaParent.g))/255),b:255-(((255-rgbaLayer.b)*(255-rgbaParent.b))/255)};});Blender.register("overlay",function(rgbaLayer,rgbaParent){var result;result={};result.r=rgbaParent.r>128?255-2*(255-rgbaLayer.r)*(255-rgbaParent.r)/255:(rgbaParent.r*rgbaLayer.r*2)/255;result.g=rgbaParent.g>128?255-2*(255-rgbaLayer.g)*(255-rgbaParent.g)/255:(rgbaParent.g*rgbaLayer.g*2)/255;result.b=rgbaParent.b>128?255-2*(255-rgbaLayer.b)*(255-rgbaParent.b)/255:(rgbaParent.b*rgbaLayer.b*2)/255;return result;});Blender.register("difference",function(rgbaLayer,rgbaParent){return{r:rgbaLayer.r-rgbaParent.r,g:rgbaLayer.g-rgbaParent.g,b:rgbaLayer.b-rgbaParent.b};});Blender.register("addition",function(rgbaLayer,rgbaParent){return{r:rgbaParent.r+rgbaLayer.r,g:rgbaParent.g+rgbaLayer.g,b:rgbaParent.b+rgbaLayer.b};});Blender.register("exclusion",function(rgbaLayer,rgbaParent){return{r:128-2*(rgbaParent.r-128)*(rgbaLayer.r-128)/255,g:128-2*(rgbaParent.g-128)*(rgbaLayer.g-128)/255,b:128-2*(rgbaParent.b-128)*(rgbaLayer.b-128)/255};});Blender.register("softLight",function(rgbaLayer,rgbaParent){var result;result={};result.r=rgbaParent.r>128?255-((255-rgbaParent.r)*(255-(rgbaLayer.r-128)))/255:(rgbaParent.r*(rgbaLayer.r+128))/255;result.g=rgbaParent.g>128?255-((255-rgbaParent.g)*(255-(rgbaLayer.g-128)))/255:(rgbaParent.g*(rgbaLayer.g+128))/255;result.b=rgbaParent.b>128?255-((255-rgbaParent.b)*(255-(rgbaLayer.b-128)))/255:(rgbaParent.b*(rgbaLayer.b+128))/255;return result;});Blender.register("lighten",function(rgbaLayer,rgbaParent){return{r:rgbaParent.r>rgbaLayer.r?rgbaParent.r:rgbaLayer.r,g:rgbaParent.g>rgbaLayer.g?rgbaParent.g:rgbaLayer.g,b:rgbaParent.b>rgbaLayer.b?rgbaParent.b:rgbaLayer.b};});Blender.register("darken",function(rgbaLayer,rgbaParent){return{r:rgbaParent.r>rgbaLayer.r?rgbaLayer.r:rgbaParent.r,g:rgbaParent.g>rgbaLayer.g?rgbaLayer.g:rgbaParent.g,b:rgbaParent.b>rgbaLayer.b?rgbaLayer.b:rgbaParent.b};});Filter.register("fillColor",function(){var color;if(arguments.length===1){color=Convert.hexToRGB(arguments[0]);}else{color={r:arguments[0],g:arguments[1],b:arguments[2]};}
+return this.process("fillColor",function(rgba){rgba.r=color.r;rgba.g=color.g;rgba.b=color.b;rgba.a=255;return rgba;});});Filter.register("brightness",function(adjust){adjust=Math.floor(255*(adjust/100));return this.process("brightness",function(rgba){rgba.r+=adjust;rgba.g+=adjust;rgba.b+=adjust;return rgba;});});Filter.register("saturation",function(adjust){adjust*=-0.01;return this.process("saturation",function(rgba){var max;max=Math.max(rgba.r,rgba.g,rgba.b);if(rgba.r!==max){rgba.r+=(max-rgba.r)*adjust;}
+if(rgba.g!==max){rgba.g+=(max-rgba.g)*adjust;}
+if(rgba.b!==max){rgba.b+=(max-rgba.b)*adjust;}
+return rgba;});});Filter.register("vibrance",function(adjust){adjust*=-1;return this.process("vibrance",function(rgba){var amt,avg,max;max=Math.max(rgba.r,rgba.g,rgba.b);avg=(rgba.r+rgba.g+rgba.b)/3;amt=((Math.abs(max-avg)*2/255)*adjust)/100;if(rgba.r!==max){rgba.r+=(max-rgba.r)*amt;}
+if(rgba.g!==max){rgba.g+=(max-rgba.g)*amt;}
+if(rgba.b!==max){rgba.b+=(max-rgba.b)*amt;}
+return rgba;});});Filter.register("greyscale",function(adjust){return this.process("greyscale",function(rgba){var avg;avg=Calculate.luminance(rgba);rgba.r=avg;rgba.g=avg;rgba.b=avg;return rgba;});});Filter.register("contrast",function(adjust){adjust=Math.pow((adjust+100)/100,2);return this.process("contrast",function(rgba){rgba.r/=255;rgba.r-=0.5;rgba.r*=adjust;rgba.r+=0.5;rgba.r*=255;rgba.g/=255;rgba.g-=0.5;rgba.g*=adjust;rgba.g+=0.5;rgba.g*=255;rgba.b/=255;rgba.b-=0.5;rgba.b*=adjust;rgba.b+=0.5;rgba.b*=255;return rgba;});});Filter.register("hue",function(adjust){return this.process("hue",function(rgba){var h,hsv,rgb;hsv=Convert.rgbToHSV(rgba.r,rgba.g,rgba.b);h=hsv.h*100;h+=Math.abs(adjust);h=h%100;h/=100;hsv.h=h;rgb=Convert.hsvToRGB(hsv.h,hsv.s,hsv.v);rgb.a=rgba.a;return rgb;});});Filter.register("colorize",function(){var level,rgb;if(arguments.length===2){rgb=Convert.hexToRGB(arguments[0]);level=arguments[1];}else if(arguments.length===4){rgb={r:arguments[0],g:arguments[1],b:arguments[2]};level=arguments[3];}
+return this.process("colorize",function(rgba){rgba.r-=(rgba.r-rgb.r)*(level/100);rgba.g-=(rgba.g-rgb.g)*(level/100);rgba.b-=(rgba.b-rgb.b)*(level/100);return rgba;});});Filter.register("invert",function(){return this.process("invert",function(rgba){rgba.r=255-rgba.r;rgba.g=255-rgba.g;rgba.b=255-rgba.b;return rgba;});});Filter.register("sepia",function(adjust){if(adjust==null){adjust=100;}
+adjust/=100;return this.process("sepia",function(rgba){rgba.r=Math.min(255,(rgba.r*(1-(0.607*adjust)))+(rgba.g*(0.769*adjust))+(rgba.b*(0.189*adjust)));rgba.g=Math.min(255,(rgba.r*(0.349*adjust))+(rgba.g*(1-(0.314*adjust)))+(rgba.b*(0.168*adjust)));rgba.b=Math.min(255,(rgba.r*(0.272*adjust))+(rgba.g*(0.534*adjust))+(rgba.b*(1-(0.869*adjust))));return rgba;});});Filter.register("gamma",function(adjust){return this.process("gamma",function(rgba){rgba.r=Math.pow(rgba.r/255,adjust)*255;rgba.g=Math.pow(rgba.g/255,adjust)*255;rgba.b=Math.pow(rgba.b/255,adjust)*255;return rgba;});});Filter.register("noise",function(adjust){adjust=Math.abs(adjust)*2.55;return this.process("noise",function(rgba){var rand;rand=Calculate.randomRange(adjust*-1,adjust);rgba.r+=rand;rgba.g+=rand;rgba.b+=rand;return rgba;});});Filter.register("clip",function(adjust){adjust=Math.abs(adjust)*2.55;return this.process("clip",function(rgba){if(rgba.r>255-adjust){rgba.r=255;}else if(rgba.r255-adjust){rgba.g=255;}else if(rgba.g255-adjust){rgba.b=255;}else if(rgba.b0){rgba.r+=(255-rgba.r)*options.red;}else{rgba.r-=rgba.r*Math.abs(options.red);}}
+if(options.green!=null){if(options.green>0){rgba.g+=(255-rgba.g)*options.green;}else{rgba.g-=rgba.g*Math.abs(options.green);}}
+if(options.blue!=null){if(options.blue>0){rgba.b+=(255-rgba.b)*options.blue;}else{rgba.b-=rgba.b*Math.abs(options.blue);}}
+return rgba;});});Filter.register("curves",function(){var bezier,chans,cps,ctrl1,ctrl2,end,i,start,_i,_j,_ref,_ref1;chans=arguments[0],cps=2<=arguments.length?__slice.call(arguments,1):[];if(typeof chans==="string"){chans=chans.split("");}
+if(chans[0]==="v"){chans=['r','g','b'];}
+if(cps.length<3||cps.length>4){throw"Invalid number of arguments to curves filter";}
+start=cps[0];ctrl1=cps[1];ctrl2=cps.length===4?cps[2]:cps[1];end=cps[cps.length-1];bezier=Calculate.bezier(start,ctrl1,ctrl2,end,0,255);if(start[0]>0){for(i=_i=0,_ref=start[0];0<=_ref?_i<_ref:_i>_ref;i=0<=_ref?++_i:--_i){bezier[i]=start[1];}}
+if(end[0]<255){for(i=_j=_ref1=end[0];_ref1<=255?_j<=255:_j>=255;i=_ref1<=255?++_j:--_j){bezier[i]=end[1];}}
+return this.process("curves",function(rgba){var _k,_ref2;for(i=_k=0,_ref2=chans.length;0<=_ref2?_k<_ref2:_k>_ref2;i=0<=_ref2?++_k:--_k){rgba[chans[i]]=bezier[rgba[chans[i]]];}
+return rgba;});});Filter.register("exposure",function(adjust){var ctrl1,ctrl2,p;p=Math.abs(adjust)/100;ctrl1=[0,255*p];ctrl2=[255-(255*p),255];if(adjust<0){ctrl1=ctrl1.reverse();ctrl2=ctrl2.reverse();}
+return this.curves('rgb',[0,0],ctrl1,ctrl2,[255,255]);});Caman.Plugin.register("crop",function(width,height,x,y){var canvas,ctx;if(x==null){x=0;}
+if(y==null){y=0;}
+if(typeof exports!=="undefined"&&exports!==null){canvas=new Canvas(width,height);}else{canvas=document.createElement('canvas');Util.copyAttributes(this.canvas,canvas);canvas.width=width;canvas.height=height;}
+ctx=canvas.getContext('2d');ctx.drawImage(this.canvas,x,y,width,height,0,0,width,height);this.cropCoordinates={x:x,y:y};this.cropped=true;return this.replaceCanvas(canvas);});Caman.Plugin.register("resize",function(newDims){var canvas,ctx;if(newDims==null){newDims=null;}
+if(newDims===null||((newDims.width==null)&&(newDims.height==null))){Log.error("Invalid or missing dimensions given for resize");return;}
+if(newDims.width==null){newDims.width=this.canvas.width*newDims.height/this.canvas.height;}else if(newDims.height==null){newDims.height=this.canvas.height*newDims.width/this.canvas.width;}
+if(typeof exports!=="undefined"&&exports!==null){canvas=new Canvas(newDims.width,newDims.height);}else{canvas=document.createElement('canvas');Util.copyAttributes(this.canvas,canvas);canvas.width=newDims.width;canvas.height=newDims.height;}
+ctx=canvas.getContext('2d');ctx.drawImage(this.canvas,0,0,this.canvas.width,this.canvas.height,0,0,newDims.width,newDims.height);this.resized=true;return this.replaceCanvas(canvas);});Caman.Filter.register("crop",function(){return this.processPlugin("crop",Array.prototype.slice.call(arguments,0));});Caman.Filter.register("resize",function(){return this.processPlugin("resize",Array.prototype.slice.call(arguments,0));});}).call(this);
\ No newline at end of file
diff --git a/server/FileAPI.class.php b/server/FileAPI.class.php
new file mode 100644
index 00000000..53774e55
--- /dev/null
+++ b/server/FileAPI.class.php
@@ -0,0 +1,152 @@
+ $mixedValue ){
+ self::rRestructuringFilesArray($arrayForFill[$currentKey],
+ $nameKey,
+ $mixedValue,
+ $fileDescriptionParam);
+ }
+ } else {
+ $arrayForFill[$currentKey][$fileDescriptionParam] = $currentMixedValue;
+ }
+ }
+
+
+ private static function determineMimeType(&$file){
+ if( function_exists('mime_content_type') ){
+ if( isset($file['tmp_name']) && is_string($file['tmp_name']) ){
+ if( $file['type'] == 'application/octet-stream' ){
+ $mime = mime_content_type($file['tmp_name']);
+ if( !empty($mime) ){
+ $file['type'] = $mime;
+ }
+ }
+ }
+ else if( is_array($file) ){
+ foreach( $file as &$entry ){
+ self::determineMimeType($entry);
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Enable CORS -- http://enable-cors.org/
+ * @param array [$options]
+ */
+ public static function enableCORS($options = null){
+ if( is_null($options) ){
+ $options = array();
+ }
+
+ if( !isset($options['origin']) ){
+ $options['origin'] = $_SERVER['HTTP_ORIGIN'];
+ }
+
+ if( !isset($options['methods']) ){
+ $options['methods'] = 'POST, GET';
+ }
+
+ if( !isset($options['headers']) ){
+ $options['headers'] = array();
+ }
+
+ header('Access-Control-Allow-Origin: ' . $options['origin']);
+ header('Access-Control-Allow-Methods: ' . $options['methods']);
+ header('Access-Control-Allow-Headers: ' . implode(', ', array_merge($options['headers'], array('X-Requested-With', 'Content-Range', 'Content-Disposition'))));
+
+ if( !isset($options['cookie']) || $options['cookie'] ){
+ header('Access-Control-Allow-Credentials: true');
+ }
+ }
+
+
+ /**
+ * Request header
+ * @return array
+ */
+ public static function getRequestHeaders(){
+ $headers = array();
+
+ foreach( $_SERVER as $key => $value ){
+ if( substr($key, 0, 5) == 'HTTP_' ){
+ $header = str_replace(' ', '-', ucwords(str_replace('_', ' ', strtolower(substr($key, 5)))));
+ $headers[$header] = $value;
+ }
+ }
+
+ return $headers;
+ }
+
+
+ /**
+ * Retrieve File List
+ * @return array
+ */
+ public static function getFiles(){
+ $files = array();
+
+ // http://www.php.net/manual/ru/reserved.variables.files.php#106558
+ foreach( $_FILES as $firstNameKey => $arFileDescriptions ){
+ foreach( $arFileDescriptions as $fileDescriptionParam => $mixedValue ){
+ self::rRestructuringFilesArray($files, $firstNameKey, $_FILES[$firstNameKey][$fileDescriptionParam], $fileDescriptionParam);
+ }
+ }
+
+ self::determineMimeType($files);
+
+ return $files;
+ }
+
+
+ /**
+ * Make server response
+ * @param array $res
+ * @param string [$jsonp]
+ */
+ public static function makeResponse(array $res, $jsonp = null){
+ $body = $res['body'];
+ $json = is_array($body) ? json_encode($body) : $body;
+
+ $httpStatus = isset($res['status']) ? $res['status'] : self::OK;
+ $httpStatusText = addslashes(isset($res['statusText']) ? $res['statusText'] : 'OK');
+ $httpHeaders = isset($res['headers']) ? $res['headers'] : array();
+
+ if( empty($jsonp) ){
+ header("HTTP/1.1 $httpStatus $httpStatusText");
+ $httpHeaders['Content-Type'] = 'application/json';
+ foreach( $httpHeaders as $header => $value ){
+ header("$header: $value");
+ }
+ echo $json;
+ }
+ else {
+ $json = addslashes($json);
+
+ echo <<
+ (function (ctx, jsonp){
+ 'use strict';
+ var status = $httpStatus, statusText = "$httpStatusText", response = "$json";
+ try {
+ ctx[jsonp](status, statusText, response);
+ } catch (e){
+ var data = "{\"id\":\"$jsonp\",\"status\":"+status+",\"statusText\":\""+statusText+"\",\"response\":\""+response.replace(/\"/g, '\\\\\"')+"\"}";
+ try {
+ ctx.postMessage(data, document.referrer);
+ } catch (e){}
+ }
+ })(window.parent, '$jsonp');
+
+END;
+ }
+ }
+
+ }
diff --git a/server/ctrl.php b/server/ctrl.php
new file mode 100644
index 00000000..cba9facf
--- /dev/null
+++ b/server/ctrl.php
@@ -0,0 +1,80 @@
+ $images
+ , 'data' => array('_REQUEST' => $_REQUEST, '_FILES' => $files)
+ );
+
+
+ // Server response: "HTTP/1.1 200 OK"
+ FileAPI::makeResponse(array(
+ 'status' => FileAPI::OK
+ , 'statusText' => 'OK'
+ , 'body' => $json
+ ), $jsonp);
+ exit;
+}
+
+
+
+
+function fetchImages($files, &$images, $name = 'file'){
+ if( isset($files['tmp_name']) ){
+ $filename = $files['tmp_name'];
+ list($mime) = explode(';', @mime_content_type($filename));
+
+ if( strpos($mime, 'image') !== false ){
+ $size = getimagesize($filename);
+ $base64 = base64_encode(file_get_contents($filename));
+
+ $images[$name] = array(
+ 'width' => $size[0]
+ , 'height' => $size[1]
+ , 'mime' => $mime
+ , 'size' => filesize($filename)
+ , 'dataURL' => 'data:'. $mime .';base64,'. $base64
+ );
+ }
+ }
+ else {
+ foreach( $files as $name => $file ){
+ fetchImages($file, $images, $name);
+ }
+ }
+}
+?>
diff --git a/statics/body.png b/statics/body.png
new file mode 100644
index 00000000..5118e8dd
Binary files /dev/null and b/statics/body.png differ
diff --git a/statics/body__top.png b/statics/body__top.png
new file mode 100644
index 00000000..74983d67
Binary files /dev/null and b/statics/body__top.png differ
diff --git a/statics/content.png b/statics/content.png
new file mode 100644
index 00000000..f0d034db
Binary files /dev/null and b/statics/content.png differ
diff --git a/statics/docs.json b/statics/docs.json
new file mode 100644
index 00000000..3bdc9a26
--- /dev/null
+++ b/statics/docs.json
@@ -0,0 +1 @@
+{"FileAPI":{"label":"FileAPI","class":"FileAPI","descr":{"en":"A set of javascript tools for working with files.","ru":"Набор JavaScript инструментов для работы с файлами."},"props":{"Get started":{"name":"Get started","type":-1,"label":"started","descr":{"en":"","ru":""},"code":{"type":"html","source":{"en":" \n\n \n \n ","ru":" \n\n \n \n "}}},"Setup options":{"name":"Setup options","type":-1,"label":"FileAPI.setup","descr":{"en":"Edit the file `crossdomain.xml` and place it to the root of the domain to which files will be uploaded.","ru":"Отредактируйте файл `crossdomain.xml` и разместите его в корне домена, на который будут загружаться файлы."},"code":{"type":"html","source":{"en":" \n \n\n \n\n ","ru":" \n \n\n \n\n "}}}},"fn":{"getFiles":{"name":"getFiles","label":"FileAPI.getFiles","args":{"input":{"en":"`HTMLInputElement`, `change` and `drop` event, `jQuery` collection or `jQuery.Event`","ru":"`HTMLInputElement`, `change` и `drop` события, `jQuery` коллекция или `jQuery.Event`"}},"variants":[{"args":[{"name":"input","type":"HTMLInputElement|Event|$.Event","optional":false}],"descr":{"en":"Retrieve file list from `input` element or `event` object, also support `jQuery`.","ru":"Получить список файлов из `input` элемента, или `event`, также поддерживается `jQuery`."}}],"returns":"Array","code":{"type":"js","source":{"en":"var el = document.getElement('my-input');\nFileAPI.event.on(el, function (evt/**Event*/){\n // Retrieve file list\n var files = FileAPI.getFiles(el);\n\n // or event\n var files = FileAPI.getFiles(evt);\n});","ru":"var el = document.getElement('my-input');\nFileAPI.event.on(el, function (evt/**Event*/){\n // Получить список файлов из `input`\n var files = FileAPI.getFiles(el);\n\n // или события\n var files = FileAPI.getFiles(evt);\n});"}}},"getInfo":{"name":"getInfo","label":"FileAPI.getInfo","args":{"file":{"en":"file object (https://developer.mozilla.org/en-US/docs/DOM/File)","ru":"объект файла (https://developer.mozilla.org/en-US/docs/DOM/File)"},"callback":{"en":"function, called after collected info of file","ru":"функция, вызывается по завершению сбора информации"}},"variants":[{"args":[{"name":"file","type":"Object","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Get info of file (see also: FileAPI.addInfoReader).","ru":"Получить информацию о файле (см. FileAPI.addInfoReader)."}}],"returns":"void","code":{"type":"js","source":{"en":"// Get info of image file (FileAPI.exif.js included)\nFileAPI.getInfo(file, function (err/**String*/, info/**Object*/){\n if( !err ){\n console.log(info); // { width: 800, height: 600, exif: {..} }\n }\n});\n\n// Get info of mp3 file (FileAPI.id3.js included)\nFileAPI.getInfo(file, function (err/**String*/, info/**Object*/){\n if( !err ){\n console.log(info); // { title: \"...\", album: \"...\", artists: \"...\", ... }\n }\n});","ru":"// Получить информацию о изображении (FileAPI.exif.js подключен)\nFileAPI.getInfo(file, function (err/**String*/, info/**Object*/){\n if( !err ){\n console.log(info); // { width: 800, height: 600, exif: {..} }\n }\n});\n\n// Получить информацию о mp3 файле (FileAPI.id3.js included)\nFileAPI.getInfo(file, function (err/**String*/, info/**Object*/){\n if( !err ){\n console.log(info); // { title: \"...\", album: \"...\", artists: \"...\", ... }\n }\n});"}}},"filterFiles":{"name":"filterFiles","label":"FileAPI.filterFiles","args":{"files":{"en":"original list of files","ru":"оригинальный список файлов"},"filter":{"en":"function, takes two arguments: `file` — the file itself, `info` — additional information.","ru":"функция, принимает два аргумента: `file` — сам файл, `info` — дополнительная информация"},"callback":{"en":"function: `list` — files that match the condition, `other` — all the rest.","ru":"функция: `list` — список файлов, подошедшие под условия, `other` — все остальные."}},"variants":[{"args":[{"name":"files","type":"Array","optional":false},{"name":"filter","type":"Function","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Filtering the list of files, with additional information about files.\nSee also: FileAPI.getInfo and FileAPI.addInfoReader.","ru":"Отфильтровать список файлов, используя дополнительную информацию о них.\nсм. FileAPI.getInfo или FileAPI.addInfoReader."}}],"returns":"void","code":{"type":"js","source":{"en":"// Get list of file\nvar files = FileAPI.getFiles(input);\n\n// Filter the List\nFileAPI.filterFiles(files, function (file/**Object*/, info/**Object*/){\n if( /^image/.test(file.type) && info ){\n return info.width > 320 && info.height > 240;\n } else {\n return file.size < 20 * FileAPI.MB;\n }\n}, function (list/**Array*/, other/**Array*/){\n if( list.length ){\n // ..\n }\n});","ru":"// Получаем список файлов\nvar files = FileAPI.getFiles(input);\n\n// Фильтруем список\nFileAPI.filterFiles(files, function (file/**Object*/, info/**Object*/){\n if( /^image/.test(file.type) && info ){\n return info.width > 320 && info.height > 240;\n } else {\n return file.size < 20 * FileAPI.MB;\n }\n}, function (list/**Array*/, other/**Array*/){\n if( list.length ){\n // ..\n }\n});"}}},"getDropFiles":{"name":"getDropFiles","label":"FileAPI.getDropFiles","args":{"evt":{"en":"`drop` event","ru":"`drop` event"},"callback":{"en":"function, takes one argument, a list of files","ru":"фнукция, принимает один аргумент — список файлов"}},"variants":[{"args":[{"name":"evt","type":"Event|$.Event","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Get a list of files, including directories.","ru":"Получить весь список файлов, включая директории."}}],"returns":"void","code":{"type":"js","source":{"en":"FileAPI.event.on(document, 'drop', function (evt/**Event*/){\n evt.preventDefault();\n\n // Get a list of files\n FileAPI.getDropFiles(evt, function (files/**Array*/){\n // ...\n });\n});","ru":"FileAPI.event.on(document, 'drop', function (evt/**Event*/){\n evt.preventDefault();\n\n // Получаем все файлы\n FileAPI.getDropFiles(evt, function (files/**Array*/){\n // ...\n });\n});"}}},"upload":{"name":"upload","label":"FileAPI.upload","args":{"opts":{"en":"options object, see [Upload options](#options)","ru":"объект настрое, см. раздел [Upload options](#options)"}},"variants":[{"args":[{"name":"opts","type":"Object","optional":false}],"descr":{"en":"Uploading files to the server (successively). Returns XHR-like object.\nIt is important to remember to correctly worked flash-transport server response body must not be empty,\nfor example, you can pass, just text \"ok\".","ru":"Загрузка файлов на сервер (последовательно). Возвращает XHR-подобный объект.\nПомните, для корректной работы flash-транспорта, тело ответа сервера не должно быть пустым,\nнапример можно ответить простым текстом \"ok\"."}}],"returns":"XmlHttpRequest","code":{"type":"js","source":{"en":"var el = document.getElementById('my-input');\nFileAPI.event.on(el, 'change', function (evt/**Event*/){\n var files = FileAPI.getFiles(evt);\n var xhr = FileAPI.upload({\n url: 'http://rubaxa.org/FileAPI/server/ctrl.php',\n files: { file: files[0] },\n complete: function (err, xhr){\n if( !err ){\n var result = xhr.responseText;\n // ...\n }\n }\n });\n});","ru":"var el = document.getElementById('my-input');\nFileAPI.event.on(el, 'change', function (evt/**Event*/){\n var files = FileAPI.getFiles(evt);\n var xhr = FileAPI.upload({\n url: 'http://rubaxa.org/FileAPI/server/ctrl.php',\n files: { file: files[0] },\n complete: function (err, xhr){\n if( !err ){\n var result = xhr.responseText;\n // ...\n }\n }\n });\n});"}}},"addInfoReader":{"name":"addInfoReader","label":"FileAPI.addInfoReader","args":{"mime":{"en":"pattern of mime-type","ru":"маска mime-type"},"handler":{"en":"takes two arguments: `file` object and `complete` function callback","ru":"функция, принимает два аргумента: `file` объект и `complete` функция обратного вызова"}},"variants":[{"args":[{"name":"mime","type":"RegExp","optional":false},{"name":"handler","type":"Function","optional":false}],"descr":{"en":"Adds a handler for the collection of information about a file.\nSee also: FileAPI.getInfo and FileAPI.filterFiles.","ru":"Добавить обработчик, для сбора информации о файле.\nсм. также: FileAPI.getInfo и FileAPI.filterFiles."}}],"returns":"void","code":{"type":"js","source":{"en":"FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){\n // http://www.nihilogic.dk/labs/exif/exif.js\n // http://www.nihilogic.dk/labs/binaryajax/binaryajax.js\n FileAPI.readAsBinaryString(file, function (evt/**Object*/){\n if( evt.type == 'load' ){\n var binaryString = evt.result;\n var oFile = new BinaryFile(binaryString, 0, file.size);\n var exif = EXIF.readFromBinaryFile(oFile);\n callback(false, { 'exif': exif || {} });\n }\n else if( evt.type == 'error' ){\n callback('read_as_binary_string');\n }\n else if( evt.type == 'progress' ){\n // ...\n }\n });\n});","ru":"FileAPI.addInfoReader(/^image/, function (file/**File*/, callback/**Function*/){\n // http://www.nihilogic.dk/labs/exif/exif.js\n // http://www.nihilogic.dk/labs/binaryajax/binaryajax.js\n FileAPI.readAsBinaryString(file, function (evt/**Object*/){\n if( evt.type == 'load' ){\n var binaryString = evt.result;\n var oFile = new BinaryFile(binaryString, 0, file.size);\n var exif = EXIF.readFromBinaryFile(oFile);\n callback(false, { 'exif': exif || {} });\n }\n else if( evt.type == 'error' ){\n callback('read_as_binary_string');\n }\n else if( evt.type == 'progress' ){\n // ...\n }\n });\n});"}}},"readAsDataURL":{"name":"readAsDataURL","label":"FileAPI.readAsDataURL","args":{"file":{"en":"file object","ru":"файл для чтения"},"callback":{"en":"function, receives a result","ru":"функция обработчик"}},"variants":[{"args":[{"name":"file","type":"Object","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Reading the contents of the specified `File` as `dataURL`.","ru":"Чтение содержимого указанного файла как dataURL."}}],"returns":"void","code":{"type":"js","source":{"en":"FileAPI.readAsDataURL(file, function (evt/**Object*/){\n if( evt.type == 'load' ){\n // Success\n var dataURL = evt.result;\n } else if( evt.type =='progress' ){\n var pr = evt.loaded/evt.total * 100;\n } else {\n // Error\n }\n})","ru":"FileAPI.readAsDataURL(file, function (evt/**Object*/){\n if( evt.type == 'load' ){\n // Всё хорошо\n var dataURL = evt.result;\n } else if( evt.type =='progress' ){\n var pr = evt.loaded/evt.total * 100;\n } else {\n // Ошибка\n }\n})"}}},"readAsBinaryString":{"name":"readAsBinaryString","label":"FileAPI.readAsBinaryString","args":{"file":{"en":"file object","ru":"файл для чтения"},"callback":{"en":"function, receives a result","ru":"функция обработчик"}},"variants":[{"args":[{"name":"file","type":"Object","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Reading the contents of the specified `File` as `BinaryString`.","ru":"Чтение содержимого указанного файла как `BinaryString`."}},{"args":[{"name":"file","type":"Object","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Reading the contents of the specified `File` as `ArrayBuffer`.","ru":"Чтение содержимого указанного файла как `ArrayBuffer`."}}],"returns":"void","code":{"type":"js","source":{"en":"FileAPI.readAsArrayBuffer(file, function (evt/**Object*/){\n if( evt.type == 'load' ){\n // Success\n var arrayBuffer = evt.result;\n } else if( evt.type =='progress' ){\n var pr = evt.loaded/evt.total * 100;\n } else {\n // Error\n }\n})","ru":"FileAPI.readAsArrayBuffer(file, function (evt/**Object*/){\n if( evt.type == 'load' ){\n // Всё хорошо\n var arrayBuffer = evt.result;\n } else if( evt.type =='progress' ){\n var pr = evt.loaded/evt.total * 100;\n } else {\n // Ошибка\n }\n})"}}},"readAsText":{"name":"readAsText","label":"FileAPI.readAsText","args":{"file":{"en":"file object","ru":"файл для чтения"},"callback":{"en":"function, receives a result","ru":"функция обработчик"},"encoding":{"en":"a string indicating the encoding to use for the returned data. By default, UTF-8.","ru":"строкой с указанием кодировки. По умолчанию UTF-8."}},"variants":[{"args":[{"name":"file","type":"Object","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Reading the contents of the specified `File` as `text`.","ru":"Чтение содержимого указанного файла как `text`."}},{"args":[{"name":"file","type":"Object","optional":false},{"name":"encoding","type":"String","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Reading the contents of the specified `File` as `text`.","ru":"Чтение содержимого указанного файла как `text` в нужной кодировке."}}],"returns":"void","code":{"type":"js","source":{"en":"FileAPI.readAsText(file, \"utf-8\", function (evt/**Object*/){\n if( evt.type == 'load' ){\n // Success\n var text = evt.result;\n } else if( evt.type =='progress' ){\n var pr = evt.loaded/evt.total * 100;\n } else {\n // Error\n }\n})","ru":"FileAPI.readAsText(file, \"utf-8\", function (evt/**Object*/){\n if( evt.type == 'load' ){\n // Всё хорошо\n var text = evt.result;\n } else if( evt.type =='progress' ){\n var pr = evt.loaded/evt.total * 100;\n } else {\n // Ошибка\n }\n})"}}}}},"Upload options":{"label":"options","class":"Upload options","descr":{"en":"","ru":""},"props":{"url":{"name":"url","type":"String","label":"options.url","descr":{"en":"A string containing the URL to which the request is sent.","ru":"Строка, содержащая адрес, на который отправляется запрос."}},"data":{"name":"data","type":"Object","label":"options.data","descr":{"en":"Additional post data to be sent along with the file uploads.","ru":"Дополнительные данные, которые должны быть отправлены вместе с файлом."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n data: { 'session-id': 123 },\n files: { ... },\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n data: { 'session-id': 123 },\n files: { ... },\n});"}}},"headers":{"name":"headers","type":"Object","label":"options.headers","descr":{"en":"Additional request headers, HTML5 only.","ru":"Дополнительные заголовки запроса, только HTML5."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n headers: { 'x-upload': 'fileapi' },\n files: { .. },\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n headers: { 'x-upload': 'fileapi' },\n files: { .. },\n});"}}},"files":{"name":"files","type":"Object","label":"options.files","descr":{"en":"Key-value object, `key` — post name, `value` — File or FileAPI.Image object."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: {\n audio: files\n }\n});"}}},"chunkSize":{"name":"chunkSize","type":"Number","label":"options.chunkSize","descr":{"en":"Chunk size in bytes, HTML5 only.","ru":"Размер части файла в байта, только HTML5."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { images: fileList },\n chunkSize: 0.5 * FileAPI.MB\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { images: fileList },\n chunkSize: 0.5 * FileAPI.MB\n});"}}},"chunkUploadRetry":{"name":"chunkUploadRetry","type":"Number","label":"options.chunkUploadRetry","descr":{"en":"Number of retries during upload chunks, HTML5 only.","ru":"Количество попыток загрузки одной части, только HTML5."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { images: fileList },\n chunkSize: 0.5 * FileAPI.MB,\n chunkUploadRetry: 3\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { images: fileList },\n chunkSize: 0.5 * FileAPI.MB,\n chunkUploadRetry: 3\n});"}}},"imageTransform":{"name":"imageTransform","type":"Object","label":"options.imageTransform","descr":{"en":"Convert all images to jpeg or png.","ru":"Конвертация всех изображений в jpeg или png."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { image: imageFiles },\n imageTransform: {\n type: 'image/jpeg',\n quality: 0.86 // jpeg quality\n }\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { image: imageFiles },\n imageTransform: {\n type: 'image/jpeg',\n quality: 0.86 // качество jpeg\n }\n});"}}},"imageOriginal":{"name":"imageOriginal","type":"Boolean","label":"options.imageOriginal","descr":{"en":"Sent to the server the original image or not, if defined imageTransform option.","ru":"Отправлять исходное изображение на сервер или нет, если определен `imageTransform` вариант."}},"imageAutoOrientation":{"name":"imageAutoOrientation","type":"Boolean","label":"options.imageAutoOrientation","descr":{"en":"Auto-rotate images on the basis of EXIF.","ru":"Автоматический поворот изображения на основе EXIF."}},"prepare":{"name":"prepare","type":"Function","label":"options.prepare","descr":{"en":"Prepare options upload for a particular file.","ru":"Подготовка опций загрузки для конкретного файла."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n prepare: function (file/**Object*/, options/**Object*/){\n options.data.secret = utils.getSecretKey(file.name);\n }\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n prepare: function (file/**Object*/, options/**Object*/){\n options.data.secret = utils.getSecretKey(file.name);\n }\n});"}}},"upload":{"name":"upload","type":"Function","label":"options.upload","descr":{"en":"Start uploading.","ru":"Начало загрузки"},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n upload: function (xhr/**Object*/, options/**Object*/){\n // ...\n }\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n upload: function (xhr/**Object*/, options/**Object*/){\n // ...\n }\n});"}}},"fileupload":{"name":"fileupload","type":"Function","label":"options.fileupload","descr":{"en":"Start file uploading.","ru":"Начало загрузки файла"},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n fileupload: function (file/**Object*/, xhr/**Object*/, options/**Object*/){\n // ...\n }\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n fileupload: function (file/**Object*/, xhr/**Object*/, options/**Object*/){\n // ...\n }\n});"}}},"progress":{"name":"progress","type":"Function","label":"options.progress","descr":{"en":"Callback for upload progress events.","ru":"Общий прогресс загрузки файлов."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n progress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){\n var pr = evt.loaded/evt.total * 100;\n }\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n progress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){\n var pr = evt.loaded/evt.total * 100;\n }\n});"}}},"fileprogress":{"name":"fileprogress","type":"Function","label":"options.fileprogress","descr":{"en":"Callback for upload file progress events.","ru":"Прогресс загрузки файла."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n fileprogress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){\n var pr = evt.loaded/evt.total * 100;\n }\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n fileprogress: function (evt/**Object*/, file/**Object*/, xhr/**Object*/, options/**Object*/){\n var pr = evt.loaded/evt.total * 100;\n }\n});"}}},"complete":{"name":"complete","type":"Function","label":"options.complete","descr":{"en":"Callback for end upload requests.","ru":"Завершение загрузки всех файлов."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n complete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){\n if( !err ){\n // All files successfully uploaded.\n }\n }\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n complete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){\n if( !err ){\n // Все файлы загружены успешно\n }\n }\n});"}}},"filecomplete":{"name":"filecomplete","type":"Function","label":"options.filecomplete","descr":{"en":"Callback for end upload requests.","ru":"Конец загрузки файла."},"code":{"type":"js","source":{"en":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n filecomplete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){\n if( !err ){\n // File successfully uploaded\n var result = xhr.responseText;\n }\n }\n});","ru":"var xhr = FileAPI.upload({\n url: '...',\n files: { .. }\n filecomplete: function (err/**String*/, xhr/**Object*/, file/**Object/, options/**Object*/){\n if( !err ){\n // Файл загружен успешно\n var result = xhr.responseText;\n }\n }\n});"}}}},"fn":{}},"File object":{"label":"File","class":"File object","descr":{"en":"","ru":""},"props":{"name":{"name":"name","type":-1,"label":"File.name","descr":{"en":"The name of the file referenced by the File object.","ru":"Имя файла."}},"type":{"name":"type","type":-1,"label":"File.type","descr":{"en":"The type (MIME type) of the file referenced by the File object.","ru":"MIME type"}},"size":{"name":"size","type":-1,"label":"File.size","descr":{"en":"The size (in bytes) of the file referenced by the File object.","ru":"Размер файла в байтах."}}},"fn":{}},"FileAPI.event":{"label":"FileAPI.event","class":"FileAPI.event","descr":{"en":"","ru":""},"props":{},"fn":{"on":{"name":"on","label":"FileAPI.event.on","args":{"el":{"en":"DOM element","ru":"DOM элемент."},"events":{"en":"one or more space-separated event types.","ru":"одно или нескольких разделенных пробелами типов событий."},"handler":{"en":"A function to execute when the event is triggered.","ru":"функция обработчик события."}},"variants":[{"args":[{"name":"el","type":"HTMLElement","optional":false},{"name":"events","type":"String","optional":false},{"name":"handler","type":"Function","optional":false}],"descr":{"en":"Attach an event handler function.","ru":"Добавить функцию обработки события."}}],"returns":"void"},"off":{"name":"off","label":"FileAPI.event.off","args":{"el":{"en":"DOM element","ru":"DOM элемент"},"events":{"en":"one or more space-separated event types.","ru":"одно или нескольких разделенных пробелами типов событий."},"handler":{"en":"a handler function previously attached for the event(s).","ru":"функции обработчика ранее назначения на `event`."}},"variants":[{"args":[{"name":"el","type":"HTMLElement","optional":false},{"name":"events","type":"String","optional":false},{"name":"handler","type":"Function","optional":false}],"descr":{"en":"Remove an event handler.","ru":"Удалить обработчик события."}}],"returns":"void"},"one":{"name":"one","label":"FileAPI.event.one","args":{"el":{"en":"DOM element","ru":"DOM элемент."},"events":{"en":"one or more space-separated event types.","ru":"одно или нескольких разделенных пробелами типов событий."},"handler":{"en":"a function to execute when the event is triggered.","ru":"функция обработчик события."}},"variants":[{"args":[{"name":"el","type":"HTMLElement","optional":false},{"name":"events","type":"String","optional":false},{"name":"handler","type":"Function","optional":false}],"descr":{"en":"Attach an event handler function. The handler is executed at most once.","ru":"Добавить функцию обработки события. Обработчик выполняется не более одного раза."}}],"returns":"void"},"dnd":{"name":"dnd","label":"FileAPI.event.dnd","args":{"el":{"en":"drop zone","ru":"DOM элемент"},"hover":{"en":"`dragenter` and `dragleave` listener","ru":"`dragenter` и `dragleave` слушатель"},"handler":{"en":"`drop` event handler.","ru":"обработчик события `drop`"}},"variants":[{"args":[{"name":"el","type":"HTMLElement","optional":false},{"name":"hover","type":"Function","optional":false},{"name":"handler","type":"Function","optional":false}],"descr":{"en":"Attach an drag and drop event handler function.","ru":"Добавить функцию обработки событий `drag` и `drop`."}}],"returns":"void","code":{"type":"js","source":{"en":"var el = document.getElementById('dropzone');\nFileAPI.event.dnd(el, function (over){\n el.style.backgroundColor = over ? '#f60': '';\n}, function (files){\n if( files.length ){\n // Upload their.\n }\n});\n\n// or jQuery\n$('#dropzone').dnd(hoverFn, dropFn);","ru":"var el = document.getElementById('dropzone');\nFileAPI.event.dnd(el, function (over){\n el.style.backgroundColor = over ? '#f60': '';\n}, function (files){\n if( files.length ){\n // Загружаем их.\n }\n});\n\n// или jQuery\n$('#dropzone').dnd(hoverFn, dropFn);"}}},"dnd.off":{"name":"dnd.off","label":"FileAPI.event.dnd.off","args":{"el":{"en":"drop zone","ru":"DOM элемент"},"hover":{"en":"`dragenter` and `dragleave` listener","ru":"`dragenter` и `dragleave` слушатель"},"handler":{"en":"`drop` event handler.","ru":"обработчик события `drop`"}},"variants":[{"args":[{"name":"el","type":"HTMLElement","optional":false},{"name":"hover","type":"Function","optional":false},{"name":"handler","type":"Function","optional":false}],"descr":{"en":"Remove an drag and drop event handler function.","ru":"Удалить функцию обработки событий `drag` и `drop`."}}],"returns":"void","code":{"type":"js","source":{"en":"// Native\nFileAPI.event.dnd.off(el, hoverFn, dropFn);\n\n// jQuery\n$('#dropzone').dndoff(hoverFn, dropFn);","ru":"// Native\nFileAPI.event.dnd.off(el, hoverFn, dropFn);\n\n// jQuery\n$('#dropzone').dndoff(hoverFn, dropFn);"}}}}},"FileAPI.Image":{"label":"FileAPI.Image","class":"FileAPI.Image","descr":{"en":"Class for working with images","ru":"Класс для работы с изображениями"},"props":{},"fn":{"constructor":{"name":"constructor","label":"FileAPI.Image","args":{"file":{"en":"the `File` object","ru":"файл изображения"}},"variants":[{"args":[{"name":"file","type":"Object","optional":false}],"descr":{"en":"The constructor takes a single argument, the `File` object.","ru":"Конструктор получает только один параметр, файл."}}],"returns":"void","code":{"type":"js","source":{"en":"FileAPI.Image(imageFile).get(function (err/**String*/, img/**HTMLElement*/){\n if( !err ){\n document.body.appendChild( img );\n }\n});","ru":"FileAPI.Image(imageFile).get(function (err/**String*/, img/**HTMLElement*/){\n if( !err ){\n document.body.appendChild( img );\n }\n});"}}},"crop":{"name":"crop","label":"FileAPI.Image.crop","args":{"width":{"en":"new image width","ru":"новая ширина изображения"},"height":{"en":"new image height","ru":"новая высота изображения"},"x":{"en":"offset from the top corner","ru":"смещение относительно по x левого угла"},"y":{"en":"offset from the left corner","ru":"смещение относительно по y левого угла"}},"variants":[{"args":[{"name":"width","type":"Number","optional":false},{"name":"height","type":"Number","optional":false}],"descr":{"en":"Crop image by width and height.","ru":"Кроп изображения по ширине и высоте."}},{"args":[{"name":"x","type":"Number","optional":false},{"name":"y","type":"Number","optional":false},{"name":"width","type":"Number","optional":false},{"name":"height","type":"Number","optional":false}],"descr":{"en":"Crop image by x, y, width and height.","ru":"Кроп изображения по ширине и высоте, а также смещению по x и y."}}],"returns":"FileAPI.Image","code":{"type":"js","source":{"en":"FileAPI.Image(imageFile)\n .crop(100, 50, 320, 240)\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;","ru":"FileAPI.Image(imageFile)\n .crop(100, 50, 320, 240)\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;"}}},"resize":{"name":"resize","label":"FileAPI.Image.resize","args":{"width":{"en":"new image width","ru":"новая ширина"},"height":{"en":"new image height","ru":"новая высота"},"type":{"en":"enum: `min`, `max`, `preview`. By default `undefined`.","ru":"enum: `min`, `max`, `preview`. По умолчанию `undefined`."}},"variants":[{"args":[{"name":"width","type":"Number","optional":false},{"name":"height","type":"Number","optional":false},{"name":"type","type":"String","optional":true}],"descr":{"en":"Resize image.","ru":"Ресайз."}}],"returns":"FileAPI.Image","code":{"type":"js","source":{"en":"FileAPI.Image(imageFile)\n .resize(320, 240)\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;\n\n// Resize image on by max side.\nFileAPI.Image(imageFile)\n .resize(320, 240, 'max')\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;","ru":"FileAPI.Image(imageFile)\n .resize(320, 240)\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;\n\n// По большей стороне\nFileAPI.Image(imageFile)\n .resize(320, 240, 'max')\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;"}}},"preview":{"name":"preview","label":"FileAPI.Image.preview","args":{"width":{"en":"new image width","ru":"новая ширина"},"height":{"en":"new image height","ru":"новая высота"}},"variants":[{"args":[{"name":"width","type":"Number","optional":false},{"name":"height","type":"Number","optional":true}],"descr":{"en":"Crop and resize image.","ru":"Кроп и ресайз изображения."}}],"returns":"FileAPI.Image","code":{"type":"js","source":{"en":"FileAPI.Image(imageFile)\n .preview(100, 100)\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;","ru":"FileAPI.Image(imageFile)\n .preview(100, 100)\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;"}}},"rotate":{"name":"rotate","label":"FileAPI.Image.rotate","args":{"deg":{"en":"rotation angle in degrees","ru":"угол поворота в градусах"}},"variants":[{"args":[{"name":"deg","type":"Number","optional":false}],"descr":{"en":"Rotate image.","ru":"Поворот."}}],"returns":"FileAPI.Image","code":{"type":"js","source":{"en":"FileAPI.Image(imageFile)\n .rotate(90)\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;","ru":"FileAPI.Image(imageFile)\n .rotate(90)\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;"}}},"filter":{"name":"filter","label":"FileAPI.Image.filter","args":{"callback":{"en":"takes two arguments, `canvas` element and `done` method.","ru":"принимает два рагумента, `canvas` элемент и метод `done`."},"name":{"en":"CamanJS filter name (custom or preset)","ru":"название CamanJS фильтра (произвольный, либо предустановленный)"}},"variants":[{"args":[{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Apply filter function. Only `HTML5`.","ru":"Применить фильтр функцию. Только `HTML5`."}},{"args":[{"name":"name","type":"String","optional":false}],"descr":{"en":"Uses [CamanJS](http://camanjs.com/), include it before FileAPI library.","ru":"Используется [CamanJS](http://camanjs.com/), подключите его перед библиотекой FileAPI."}}],"returns":"FileAPI.Image","code":{"type":"js","source":{"en":"Caman.Filter.register(\"my-funky-filter\", function () {\n // http://camanjs.com/guides/#Extending\n});\n\nFileAPI.Image(imageFile)\n .filter(\"my-funky-filter\") // or .filter(\"vintage\")\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;","ru":"Caman.Filter.register(\"my-funky-filter\", function () {\n // http://camanjs.com/guides/#Extending\n});\n\nFileAPI.Image(imageFile)\n .filter(\"my-funky-filter\") // или .filter(\"vintage\")\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;"}}},"overlay":{"name":"overlay","label":"FileAPI.Image.overlay","args":{"images":{"en":"array of overlays","ru":"массив наложений"}},"variants":[{"args":[{"name":"images","type":"Array","optional":false}],"descr":{"en":"Add overlay images, eg: watermark.","ru":"Добавить наложение, например: водяной знак."}}],"returns":"FileAPI.Image","code":{"type":"js","source":{"en":"FileAPI.Image(imageFile)\n .overlay([\n // Left corner.\n { x: 10, y: 10, w: 100, h: 10, src: '/i/watermark.png' },\n\n // Right bottom corner.\n { x: 10, y: 10, src: '/i/watermark.png', rel: FileAPI.Image.RIGHT_BOTTOM }\n ])\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;","ru":"FileAPI.Image(imageFile)\n .overlay([\n // Левый угл.\n { x: 10, y: 10, w: 100, h: 10, src: '/i/watermark.png' },\n\n // Правый нижний угл.\n { x: 10, y: 10, src: '/i/watermark.png', rel: FileAPI.Image.RIGHT_BOTTOM }\n ])\n .get(function (err/**String*/, img/**HTMLElement*/){\n\n })\n;"}}},"get":{"name":"get","label":"FileAPI.Image.get","args":{"fn":{"en":"complete callback","ru":"функция обратного вызова"}},"variants":[{"args":[{"name":"fn","type":"Function","optional":false}],"descr":{"en":"Get the final image.","ru":"Получить итоговое изображение."}}],"returns":"FileAPI.Image"}}},"FileAPI.Camera":{"label":"FileAPI.Camera","class":"FileAPI.Camera","descr":{"en":"To work with a webcam, be sure to set `FileAPI.media: true`.","ru":"Для работы с веб-камерой, обязательно установить параметр `FileAPI.media: true`."},"props":{},"fn":{"publish":{"name":"publish","label":"FileAPI.Camera.publish","args":{"el":{"en":"target","ru":"куда публикуем"},"options":{"en":"{ `width: 100%`, `height: 100%`, `start: true` }","ru":"{ `width: 100%`, `height: 100%`, `start: true` }"},"callback":{"en":"the first parameter is a possible error, the second instance of FileAPI.Camera","ru":"первый параметр возможная ошибка, второй экземпляр FileAPI.Camera"}},"variants":[{"args":[{"name":"el","type":"HTMLElement","optional":false},{"name":"options","type":"Object","optional":false},{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Publication of the camera.","ru":"Публикация камеры."}}],"returns":"void","code":{"type":"js","source":{"en":"var el = document.getElementById('cam');\nFileAPI.Camera.publish(el, { width: 320, height: 240 }, function (err, cam/**FileAPI.Camera*/){\n if( !err ){\n // The webcam is ready, you can use it.\n }\n});","ru":"var el = document.getElementById('cam');\nFileAPI.Camera.publish(el, { width: 320, height: 240 }, function (err, cam/**FileAPI.Camera*/){\n if( !err ){\n // Камера готова, можно использовать\n }\n});"}}},"start":{"name":"start","label":"FileAPI.Camera.start","args":{"callback":{"en":"will be called when the camera ready","ru":"будет вызван в момент готовности камеры"}},"variants":[{"args":[{"name":"callback","type":"Function","optional":false}],"descr":{"en":"Turn on the camera.","ru":"Включить камеру"}}],"returns":"void","code":{"type":"js","source":{"en":"var el = document.getElementById('cam');\nFileAPI.Camera.publish(el, { start: false }, function (err, cam/**FileAPI.Camera*/){\n if( !err ){\n // Turn on\n cam.start(function (err){\n if( !err ){\n // The camera is ready for use.\n }\n });\n }\n});","ru":"var el = document.getElementById('cam');\nFileAPI.Camera.publish(el, { start: false }, function (err, cam/**FileAPI.Camera*/){\n if( !err ){\n // Включаем камеру\n cam.start(function (err){\n if( !err ){\n // камера готова к использованию\n }\n });\n }\n});"}}},"stop":{"name":"stop","label":"FileAPI.Camera.stop","args":{},"variants":[{"args":0,"descr":{"en":"Turn off the camera.","ru":"Выключить камеру"}}],"returns":"void"},"shot":{"name":"shot","label":"FileAPI.Camera.shot","args":{},"variants":[{"args":0,"descr":{"en":"Take a picture with the camera.","ru":"Сделать снимок с камеры"}}],"returns":"FileAPI.Image","code":{"type":"js","source":{"en":"var el = document.getElementById('cam');\nFileAPI.Camera.publish(el, function (err, cam/**FileAPI.Camera*/){\n if( !err ){\n var shot = cam.shot(); // take a picture\n\n // create thumbnail 100x100\n shot.preview(100).get(function (err, img){\n previews.appendChild(img);\n });\n\n // and/or\n FileAPI.upload({\n url: '...',\n files: { cam: shot\n });\n }\n});","ru":"var el = document.getElementById('cam');\nFileAPI.Camera.publish(el, function (err, cam/**FileAPI.Camera*/){\n if( !err ){\n var shot = cam.shot(); // делаем снимок\n\n // создаем предпросмотр 100x100\n shot.preview(100).get(function (err, img){\n previews.appendChild(img);\n });\n\n // и/или загружаем\n FileAPI.upload({\n url: '...',\n files: { cam: shot\n });\n }\n});"}}}}},"Сonst":{"label":"const","class":"Сonst","descr":{"en":"","ru":""},"props":{"FileAPI.KB":{"name":"FileAPI.KB","type":"Number","label":"FileAPI.KB","descr":{"en":"1024 bytes","ru":"1024 байт"}},"FileAPI.MB":{"name":"FileAPI.MB","type":"Number","label":"FileAPI.MB","descr":{"en":"1048576 bytes","ru":"1048576 байт"}},"FileAPI.GB":{"name":"FileAPI.GB","type":"Number","label":"FileAPI.GB","descr":{"en":"1073741824 bytes","ru":"1073741824 байт"}},"FileAPI.TB":{"name":"FileAPI.TB","type":"Number","label":"FileAPI.TB","descr":{"en":"1.0995116e+12 bytes","ru":"1.0995116e+12 байт"}}},"fn":{}},"Utils":{"label":"FileAPI.utils","class":"Utils","descr":{"en":"","ru":""},"props":{},"fn":{"FileAPI.each":{"name":"FileAPI.each","label":"FileAPI.each","args":{"obj":{"en":"array or object","ru":"массив или объект"},"callback":{"en":"a function to execute for each element.","ru":"функция, выполняется для каждого элемента."},"thisObject":{"en":"object to use as `this` when executing `callback`.","ru":"объект для использования в качестве `this` при выполнении `callback`."}},"variants":[{"args":[{"name":"obj","type":"Object|Array","optional":false},{"name":"callback","type":"Function","optional":false},{"name":"thisObject","type":"Mixed","optional":true}],"descr":{"en":"Iterate over a object or array, executing a function for each matched element.","ru":"Перебор объект или массив, выполняя функцию для каждого элемента."}}],"returns":"void"},"FileAPI.extend":{"name":"FileAPI.extend","label":"FileAPI.extend","args":{"dst":{"en":"an object that will receive the new properties","ru":"объект, который получит новые свойства"},"src":{"en":"an object containing additional properties to merge in.","ru":"объект, содержащий дополнительные свойства для объединения"}},"variants":[{"args":[{"name":"dst","type":"Object","optional":false},{"name":"src","type":"Object","optional":false}],"descr":{"en":"Merge the contents of two objects together into the first object.","ru":"Объединить содержимое двух объектов вместе."}}],"returns":"Object"},"FileAPI.filter":{"name":"FileAPI.filter","label":"FileAPI.filter","args":{"array":{"en":"original Array","ru":"оригинальный массив"},"callback":{"en":"Function to test each element of the array.","ru":"функция для проверки каждого элемента массива."},"thisObject":{"en":"object to use as `this` when executing `callback`.","ru":"объект для использования в качестве `this` при выполнении `callback`."}},"variants":[{"args":[{"name":"array","type":"Array","optional":false},{"name":"callback","type":"Function","optional":false},{"name":"thisObject","type":"Mixed","optional":false}],"descr":{"en":"Creates a new array with all elements that pass the test implemented by the provided function.","ru":"Создает новый массив со всеми элементами, которые соответствуют условиям."}}],"returns":"Object"}}},"Support":{"label":"support","class":"Support","descr":{"en":"\n\tMultiupload: all browsers that support HTML5 or Flash \n\tDrag'n'Drop upload: files (HTML5) & directories (Chrome 21+) \n\tChunked file upload (HTML5) \n\tUpload one file: all browsers \n\t\n\t\tWorking with Images: IE6+, FF 3.6+, Chrome 10+, Opera 11.1+, Safari 5.4+\n\t\t\n\t\t\tcrop, resize, preview & rotate (HTML5 or Flash) \n\t\t\tauto orientation by exif (HTML5, if include FileAPI.exif.js or Flash) \n\t\t \n\t \n ","ru":"\n\tMultiupload: все браузеры поддерживающие HTML5 или Flash \n\tDrag'n'Drop загрузка: файлы (HTML5) и директории (Chrome 21+) \n\tЗагрузка файлов по частям, только HTML5 \n\tЗагрузка одно файла: все браузеры, даже очень старые \n\t\n\t\tРабота с изображениями: IE6+, FF 3.6+, Chrome 10+, Opera 11.1+, Safari 5.4+\n\t\t\n\t\t\tcrop, resize, preview & rotate (HTML5 или Flash) \n\t\t\tавто ориентация на основе EXIF (HTML5, если подключен FileAPI.exif.js или Flash) \n\t\t \n\t \n "},"props":{"FileAPI.support.html5":{"name":"FileAPI.support.html5","type":"Boolean","label":"FileAPI.support.html5","descr":{"en":"HTML5 borwser support","ru":"Поддержка HTML5."}},"FileAPI.support.cors":{"name":"FileAPI.support.cors","type":"Boolean","label":"FileAPI.support.cors","descr":{"en":"This cross-origin resource sharing is used to enable cross-site HTTP requests.","ru":"Поддержка кроссдоменных запросов."}},"FileAPI.support.dnd":{"name":"FileAPI.support.dnd","type":"Boolean","label":"FileAPI.support.dnd","descr":{"en":"Drag'n'drop events support.","ru":"Поддержка Drag'n'drop событий."}},"FileAPI.support.flash":{"name":"FileAPI.support.flash","type":"Boolean","label":"FileAPI.support.flash","descr":{"en":"Availability Flash plugin.","ru":"Наличие Flash плагина."}},"FileAPI.support.canvas":{"name":"FileAPI.support.canvas","type":"Boolean","label":"FileAPI.support.canvas","descr":{"en":"Canvas support.","ru":"Поддержка canvas."}},"FileAPI.support.dataURI":{"name":"FileAPI.support.dataURI","type":"Boolean","label":"FileAPI.support.dataURI","descr":{"en":"Support dataURI as src for image.","ru":"Поддержка dataURI в качестве src для изображений."}},"FileAPI.support.chunked":{"name":"FileAPI.support.chunked","type":"Boolean","label":"FileAPI.support.chunked","descr":{"en":"Support chuncked upload.","ru":"Возможность загрузки по частям."}}},"fn":{}},"Flash":{"label":"flash","class":"Flash","descr":{"en":"Flash is very \"buggy\" thing :]\nThe server response can not be empty.\nTherefore, in the event of a successful uploading `http status` should be only `200 OK`.","ru":"Флеш очень \"глючная\" штука :]\nПоэтому в случае успешной загрузки http status должен быть только `200 OK`."},"props":{"Settings":{"name":"Settings","type":-1,"label":"flash.settings","descr":{"en":"Flash settings.\nIt is advisable to place flash on the same server where the files will be uploaded.","ru":"Настройки для flash части.\nЖелательно, разместить flash на том же сервере, куда будут загружаться файлы."},"code":{"type":"html","source":{"en":"\n","ru":"\n"}}},"crossdomain.xml":{"name":"crossdomain.xml","type":-1,"label":"crossdomain.xml","descr":{"en":"Necessarily make this file on the server.\nDo not forget to replace `youdomain.com` on the name of your domain.","ru":"Обязательно создайте этот файл на сервере, куда будут загружаться файлы.\nНе забудьте заменить `youdomain.com` на имя вашего домена."},"code":{"type":"xml","source":{"en":"\n\n\n \n \n \n \n ","ru":"\n\n\n \n \n \n \n "}}},"request":{"name":"request","type":-1,"label":"flash.request","descr":{"en":"The following sample HTTP POST request is sent from Flash Player to a server-side script if no parameters are specified:","ru":"Пример запроса, который отправляет flash player."},"code":{"type":"xml","source":{"en":"POST /server/ctrl.php HTTP/1.1\nAccept: text/*\nContent-Type: multipart/form-data;\nboundary=----------Ij5ae0ae0KM7GI3KM7\nUser-Agent: Shockwave Flash\nHost: www.youdomain.com\nContent-Length: 421\nConnection: Keep-Alive\nCache-Control: no-cache\n\n------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7\nContent-Disposition: form-data; name=\"Filename\"\n\nMyFile.jpg\n------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7\nContent-Disposition: form-data; name=\"Filedata\"; filename=\"MyFile.jpg\"\nContent-Type: application/octet-stream\n\n[[..FILE_DATA_HERE..]]\n------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7\nContent-Disposition: form-data; name=\"Upload\"\n\nSubmit Query\n------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7--","ru":"POST /server/ctrl.php HTTP/1.1\nAccept: text/*\nContent-Type: multipart/form-data;\nboundary=----------Ij5ae0ae0KM7GI3KM7\nUser-Agent: Shockwave Flash\nHost: www.youdomain.com\nContent-Length: 421\nConnection: Keep-Alive\nCache-Control: no-cache\n\n------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7\nContent-Disposition: form-data; name=\"Filename\"\n\nMyFile.jpg\n------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7\nContent-Disposition: form-data; name=\"Filedata\"; filename=\"MyFile.jpg\"\nContent-Type: application/octet-stream\n\n[[..FILE_DATA_HERE..]]\n------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7\nContent-Disposition: form-data; name=\"Upload\"\n\nSubmit Query\n------------Ij5GI3GI3ei4GI3ei4KM7GI3KM7KM7--"}}}},"fn":{}},"Server settings":{"label":"server","class":"Server settings","descr":{"en":"","ru":""},"props":{"IFrame/JSONP":{"name":"IFrame/JSONP","type":-1,"label":"server.iframe","descr":{"en":"","ru":""},"code":{"type":"php","source":{"en":"\n\n\n\n FileAPI::OK\n , 'statusText' => 'OK'\n , 'body' => array('count' => sizeof($files)\n ), $jsonp);\n exit;\n }\n?>","ru":"\n\n\n\n FileAPI::OK\n , 'statusText' => 'OK'\n , 'body' => array('count' => sizeof($files)\n ), $jsonp);\n exit;\n }\n?>"}}},"CORS":{"name":"CORS","type":-1,"label":"server.CORS","descr":{"en":"Enable CORS.","ru":"Включение CORS."},"code":{"type":"php","source":{"en":"","ru":"\nClient explicitly sets the following headers: \n\n\tContent-Range: bytes <start-offset>-<end-offset>/<total> \n\tContent-Disposition: attachment; filename=<file-name> \n \nAny other headers are set by a target browser and are not used by client. Library does not provide any facilities to track a file uniqueness across requests, it's left on developer's consideration. \nResponse codes:\n\n\t200 - last chunk is uploaded \n\t201 - chunk is successfully saved \n\t416 - range is not acceptable error, recoverable \n\t500 - server error, recoverable \n \nFor recoverable errors server tries to resend chunk `chunkUploadRetry` times then fails. \n\tX-Last-Known-Byte: int, library tries to resend chunk from the given offset. Applicable to response codes 200 and 416 \n\nAll the other codes - fatal error, user's involvement is recommend.","ru":"Всё общение между клиентом и сервером ведётся на уровне HTTP заголовков. \nДля передачи отдельного chunk'а клиент устанавливает заголовки: \n\n\tContent-Range: bytes <start-offset>-<end-offset>/<total> \n\tContent-Disposition: attachment; filename=<file-name> \n \nДругие заголовки не используются, отслеживание уникальности имени передаваемого файла не реализуется и оставлено на усмотрение разработчика. \nВ ответ на передаваемый chunk сервер может отвечать следующими кодами: \n\n\t200, 201 — chunk сохранён успешно \n\t416, 500 — восстановимая ошибка \n \nОстальные коды — фатальная ошибка, требуется вмешательство пользователя."}}},"fn":{}},"Buttons examples":{"label":"buttons.examples","class":"Buttons examples","descr":{"en":"","ru":""},"props":{"Base":{"name":"Base","type":-1,"label":"buttons.examples.base","descr":{"en":"Simple input[type=\"file\"]","ru":"Простой input[type=\"file\"]"},"code":{"type":"html","source":{"en":"\n \n ","ru":"\n \n "}}},"Button":{"name":"Button","type":-1,"label":"buttons.examples.button","descr":{"en":"Stylized button.","ru":"Стилизованная кнопка."},"code":{"type":"html","source":{"en":"\n","ru":"\n"}}},"Link":{"name":"Link","type":-1,"label":"buttons.examples.link","descr":{"en":"Button like link.","ru":"Кнопка в виде ссылки"},"code":{"type":"html","source":{"en":"\n\n Upload photo \n \n ","ru":"\n\n Upload photo \n \n "}}}},"fn":{}},"Installation":{"label":"install","class":"Installation","descr":{"en":"`npm install fileapi` \n`cd fileapi` \n`npm install` \n`grunt`"},"props":{},"fn":{}},"Changelog":{"label":"Changelog","class":"Changelog","descr":{"en":""},"props":{"2.0.0":{"name":"2.0.0","type":-1,"label":"Changelog","descr":{"en":"\n\t+ FileAPI.Camera (HTML5 and Flash fallback) \n\t+ jquery.fileapi.js, see demo \n\t+ npm support \n\t+ grunt support \n\t+ requirejs support \n\t+ [#80](https://https://github.com/mailru/FileAPI/issues/80): FileAPI.Image.fn.overlay \n \t`imageTransform` — now supports: `crop`, `type`, `quality` and `overlay` properties. \n\tImproved the documentation \n\t+iOS fix (https://github.com/blueimp/JavaScript-Load-Image) \n\t[#121](https://github.com/mailru/FileAPI/issues/121): + FileAPI.`postNameConcat:Function(name, idx)` \n\t[#116](https://github.com/mailru/FileAPI/issues/116): + `cache:false` option for FileAPI.upload \n "}},"1.2.6":{"name":"1.2.6","type":-1,"label":"Changelog","descr":{"en":"\n\t[#91](https://github.com/mailru/FileAPI/issues/91): replace `new Image` to `FileAPI.newImage` \n\t+ FileAPI.withCredentials: true \n\t[#90](https://github.com/mailru/FileAPI/issues/90): Fixed `progress` event \n\t[#105](https://github.com/mailru/FileAPI/issues/105): Fixed `image/jpg` -> `image/jpeg` \n\t[#108](https://github.com/mailru/FileAPI/issues/108): Check width/height before resize by type(min/max) \n "}},"1.2.5":{"name":"1.2.5","type":-1,"label":"Changelog","descr":{"en":"\n\t[#86](https://github.com/mailru/FileAPI/issues/86): Smarter upload recovery \n\t[#87](https://github.com/mailru/FileAPI/issues/87): Fixed upload files into browsers that do not support FormData \n\tFixed support \"accept\" attribute for Flash. \n\tFixed detection of HTML5 support for FireFox 3.6 \n\t + FileAPI.html5 option, default \"true\" \n "}},"1.2.4":{"name":"1.2.4","type":-1,"label":"Changelog","descr":{"en":"\n\tFixed auto orientation image by EXIF (Flash) \n\tFixed image dimensions after rotate (Flash) \n\t[#82](https://github.com/mailru/FileAPI/issues/82): \"undefined\" data-fields cause exceptions \n\t[#83](https://github.com/mailru/FileAPI/issues/83): Allow requests without files \n\t[#84](https://github.com/mailru/FileAPI/pull/84): Fixed connection abort when waiting for connection recovery \n "}},"1.2.3":{"name":"1.2.3","type":-1,"label":"Changelog","descr":{"en":"\n\t[#77](https://github.com/mailru/FileAPI/pull/77): Fixed flash.abort(), [#75](https://github.com/mailru/FileAPI/issues/75) \n\t- `FileAPI.addMime` \n\t+ `FileAPI.accept` — fallback for flash. \n "}},"1.2.2":{"name":"1.2.2","type":-1,"label":"Changelog","descr":{"en":"\n\t[#67](https://github.com/mailru/FileAPI/pull/67): Added correct httpStatus for upload fail, [#62](https://github.com/mailru/FileAPI/pull/68) \n\t[#68](https://github.com/mailru/FileAPI/pull/68) Added \"Content-Type\" for chunked upload, [#65](https://github.com/mailru/FileAPI/pull/65) \n\t[#69](https://github.com/mailru/FileAPI/issues/69): Fixed network down recovery \n\tFixed progress event, [#66](https://github.com/mailru/FileAPI/issues/66) \n\tIncrease flash stage size, [#73](https://github.com/mailru/FileAPI/pull/73) \n\t- array index from POST-param \"name\", [#72](https://github.com/mailru/FileAPI/issues/72) \n\t- dependency on FileAPI.Image for FileAPI.Flash \n "}},"1.2.1":{"name":"1.2.1","type":-1,"label":"Changelog","descr":{"en":"\n\t[#64](https://github.com/mailru/FileAPI/issues/64): Bufixed for [#63](https://github.com/mailru/FileAPI/issues/63) \n "}},"1.2.0":{"name":"1.2.0","type":-1,"label":"Changelog","descr":{"en":"\n\t[#57](https://github.com/mailru/FileAPI/issues/57): Chunked file upload \n "}},"1.1.0":{"name":"1.1.0","type":-1,"label":"Changelog","descr":{"en":"\n\t[#54](https://github.com/mailru/FileAPI/issues/54): added `FileAPI.flashUrl` and `FileAPI.flashImageUrl` \n "}},"1.0.1":{"name":"1.0.1","type":-1,"label":"Changelog","descr":{"en":"\n\t[#51](https://github.com/mailru/FileAPI/issues/51): remove circular references from `file-objects` (Flash transport) \n\tadded `changelog` \n "}},"1.0.0":{"name":"1.0.0","type":-1,"label":"Changelog","descr":{"en":""}}},"fn":{}}}
diff --git a/statics/logo.png b/statics/logo.png
new file mode 100644
index 00000000..84ca8236
Binary files /dev/null and b/statics/logo.png differ
diff --git a/statics/main.css b/statics/main.css
new file mode 100644
index 00000000..0cd14c3f
--- /dev/null
+++ b/statics/main.css
@@ -0,0 +1,359 @@
+html, body {
+ margin: 0;
+ padding: 0;
+}
+
+img {
+ border: 0;
+ vertical-align: middle;
+}
+
+body {
+ color: #ddd;
+ font-size: 14px;
+ font-family: 'Arail', sans-serif;
+ line-height: 140%;
+ background: #202227 url('body.png');
+}
+ .body__top {
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 321px;
+ z-index: -1;
+ position: absolute;
+ background-image: url('body__top.png');
+ }
+
+a {
+ color: #ddd;
+}
+ a:hover {
+ color: #fff;
+ }
+
+.logo {
+ height: 210px;
+ background: url('logo.png') center center no-repeat;
+}
+
+
+.sidebar {
+ top: 0;
+ left: 0;
+ height: 100%;
+ position: fixed;
+ overflow: auto;
+ overflow-x: hidden;
+ width: 250px;
+ background: url('content.png');
+ box-shadow: 0 0 20px rgba(0,0,0,.9)
+}
+ .sidebar ul {
+ list-style: none;
+ padding: 0;
+ margin: 5px 0 5px 15px;
+ }
+
+ .sidebar a {
+ color: #333;
+ text-decoration: none;
+ }
+ .sidebar a:hover {
+ color: #f60;
+ border-bottom: 1px dotted #f60;
+ }
+
+.menu {
+ margin: 20px 15px;
+ font-size: 16px;
+ line-height: 130%;
+}
+ .menu__name {
+ border-bottom: 1px dotted #333;
+ }
+
+
+
+h2 {
+ margin: 0 0 10px;
+ font-size: 40px;
+}
+
+h3 {
+ color: #f1f1f1;
+ font-size: 30px;
+ font-weight: 400;
+}
+
+h2, h3, h4 {
+ font-weight: 300;
+ font-family: 'Roboto', sans-serif;
+}
+
+.descr {
+ color: #AAA;
+ margin-top: 5px;
+ margin-bottom: 10px;
+}
+
+.type {
+ color: #ffcc66;
+ font-weight: normal;
+}
+
+.fn {
+ margin: 10px 0 0;
+ font-size: 16px;
+ font-weight: 400;
+}
+ .fn__args {
+ font-weight: 300;
+ }
+
+.args b {
+ font-weight: 400;
+}
+
+h2, h3, h4, .fn, .descr, .args {
+ text-shadow: 0 1px 1px #000;
+}
+
+
+.code {
+ color: #DDD;
+ font-size: 90%;
+ font-family: monospace;
+ border: 1px solid #666;
+ border-radius: 3px;
+ background-color: rgba(255,255,255,.05);
+ *background-color: #f3f3f3;
+ padding: 0 3px;
+}
+
+
+.content {
+ margin-left: 300px;
+ margin-right: 50px;
+ position: relative;
+}
+
+
+
+.example {
+ margin: 25px;
+}
+ .example__left {
+ float: left;
+ width: 290px;
+ text-align: center;
+ }
+
+ .example__right {
+ margin-left: 320px;
+ }
+
+ .example__code {
+ padding: 1px 20px 0 20px;
+ overflow: auto;
+ max-height: 400px;
+ border-right: 4px solid #DDE0DA;
+ border-bottom: 4px solid #DDE0DA;
+ background-color: #fff;
+ }
+
+ .example h2 {
+ overflow: hidden;
+ }
+ .example h2 span {
+ float: left;
+ display: block;
+ }
+
+ .example__tabs {
+ display: block;
+ font-size: 14px;
+ vertical-align: top;
+ }
+ .example__tabs a {
+ color: #36c;
+ cursor: pointer;
+ margin: 0 5px 2px;
+ border-bottom: 1px dotted #36c;
+ }
+ .example__tabs a.active {
+ color: #333;
+ cursor: default;
+ margin: 0;
+ padding: 0 5px 2px;
+ border-bottom: 0;
+ background-color: rgba(0,0,0,.1);
+ *background-color: #eee;
+ }
+
+
+code {
+ font-family: monospace;
+ font-size: 12px;
+ border-radius: 5px;
+ max-width: 800px;
+ max-height: 400px;
+ overflow: auto;
+ background-image: url('content.png');
+ color: black;
+}
+
+
+
+/**
+ * XCode style (c) Angel Garcia
+ */
+
+pre code {
+ display: block; padding: 0.5em;
+ color: black;
+}
+
+pre .comment,
+pre .template_comment,
+pre .javadoc,
+pre .comment * {
+ color: rgb(0,106,0);
+}
+
+pre .keyword,
+pre .literal,
+pre .nginx .title {
+ color: rgb(170,13,145);
+}
+pre .method,
+pre .list .title,
+pre .tag .title,
+pre .setting .value,
+pre .winutils,
+pre .tex .command,
+pre .http .title,
+pre .request,
+pre .status {
+ color: #008;
+}
+
+pre .envvar,
+pre .tex .special {
+ color: #660;
+}
+
+pre .string {
+ color: rgb(196,26,22);
+}
+pre .tag .value,
+pre .cdata,
+pre .filter .argument,
+pre .attr_selector,
+pre .apache .cbracket,
+pre .date,
+pre .regexp {
+ color: #080;
+}
+
+pre .sub .identifier,
+pre .pi,
+pre .tag,
+pre .tag .keyword,
+pre .decorator,
+pre .ini .title,
+pre .shebang,
+pre .prompt,
+pre .hexcolor,
+pre .rules .value,
+pre .css .value .number,
+pre .symbol,
+pre .symbol .string,
+pre .number,
+pre .css .function,
+pre .clojure .title,
+pre .clojure .built_in {
+ color: rgb(28,0,207);
+}
+
+pre .class .title,
+pre .haskell .type,
+pre .smalltalk .class,
+pre .javadoctag,
+pre .yardoctag,
+pre .phpdoc,
+pre .typename,
+pre .tag .attribute,
+pre .doctype,
+pre .class .id,
+pre .built_in,
+pre .setting,
+pre .params,
+pre .clojure .attribute {
+ color: rgb(92,38,153);
+}
+
+pre .variable {
+ color: rgb(63,110,116);
+}
+pre .css .tag,
+pre .rules .property,
+pre .pseudo,
+pre .subst {
+ color: #000;
+}
+
+pre .css .class, pre .css .id {
+ color: #9B703F;
+}
+
+pre .value .important {
+ color: #ff7700;
+ font-weight: bold;
+}
+
+pre .rules .keyword {
+ color: #C5AF75;
+}
+
+pre .annotation,
+pre .apache .sqbracket,
+pre .nginx .built_in {
+ color: #9B859D;
+}
+
+pre .preprocessor,
+pre .preprocessor * {
+ color: rgb(100,56,32);
+}
+
+pre .tex .formula {
+ background-color: #EEE;
+ font-style: italic;
+}
+
+pre .diff .header,
+pre .chunk {
+ color: #808080;
+ font-weight: bold;
+}
+
+pre .diff .change {
+ background-color: #BCCFF9;
+}
+
+pre .addition {
+ background-color: #BAEEBA;
+}
+
+pre .deletion {
+ background-color: #FFC8BD;
+}
+
+pre .comment .yardoctag {
+ font-weight: bold;
+}
+
+pre .method .id {
+ color: #000;
+}
diff --git a/statics/watermark.png b/statics/watermark.png
new file mode 100644
index 00000000..9d357b17
Binary files /dev/null and b/statics/watermark.png differ
diff --git a/statics/xtpl.min.js b/statics/xtpl.min.js
new file mode 100644
index 00000000..870967d1
--- /dev/null
+++ b/statics/xtpl.min.js
@@ -0,0 +1,3 @@
+/*! xtpl 0.1.0 - MIT | git://github.com/rubaxa/xtpl.git */
+"use strict";(function(t){"function"==typeof define&&define.amd?define("xtpl",[],t):"undefined"!=typeof module&&module.exports!==void 0?module.exports=t():window.xtpl=t()})(function(){function t(n,r){var i={exports:{}};r(i,i.exports,e),t[n]=i.exports}function e(e){return t[e]}return t("utils",function(t){var e,n=/[&<>"]/,r=/&/g,i=/>/g,a=/=0&&0 in t)for(;i>r;r++)e.call(n,t[r],r,t);else for(r in t)t.hasOwnProperty(r)&&e.call(n,t[r],r,t)}},map:Array.map||function(t,e,n){for(var r=[],i=0,a=t.length;a>i;i++)r.push(e.call(n,t[i],i,t));return r},filter:Array.filter||function(t,e,n){for(var r=[],i=0,a=t.length;a>i;i++)e.call(n,t[i],i,t)&&r.push(t[i]);return r},inArray:Array.indexOf||function(t,e){for(var n=t.length;n--;)if(t[n]===e)return n;return-1},isEmptyObject:function(t){for(var e in t)return!1;return!0},escapeHTML:function(t){return"string"==typeof t?n.test(t)&&(~t.indexOf("&")&&(t=t.replace(r,"&")),~t.indexOf("<")&&(t=t.replace(a,"<")),~t.indexOf(">")&&(t=t.replace(i,">")),~t.indexOf('"')&&(t=t.replace(o,"""))):void 0===t&&(t=""),t},notEqual:function l(t,e){var n,r=typeof e;if(typeof t!==r&&"object"===r)return!0;if(e instanceof Array){if(n=e.length,t.length!==n)return!0;for(;n--;)if(l(t[n],e[n]))return!0}else{if("object"!==r||null===t||null===e)return t!=e;for(n in e)if(e.hasOwnProperty(n)&&l(t[n],e[n]))return!0;for(n in t)if(t.hasOwnProperty(n)&&l(t[n],e[n]))return!0}return!1},simpleNotEqual:function l(t,e){var n,r=typeof e;if(typeof t!==r&&"object"===r)return!0;if(e instanceof Array){if(n=e.length,t.length!==n)return!0;for(;n--;)if(t[n]!==e[n])return!0}else{if("object"!==r)return t!=e;for(n in e)if(e.hasOwnProperty(n)&&l(t[n],e[n]))return!0;for(n in t)if(t.hasOwnProperty(n)&&l(t[n],e[n]))return!0}return!1},simpleClone:function(t){var e,n=t;if(t instanceof Array)for(e=t.length,n=[];e--;)n[e]=t[e];else if(t instanceof Function)n=t;else if(t instanceof Object){n={};for(e in t)t.hasOwnProperty(e)&&(n[e]=t[e])}return n},print:function(){console.log(e.map(arguments,function(t){return t?(""+JSON.stringify(t)).replace(/^"|"$/g,""):t}))},error:function(t,e,n){console.log(t.message+"\nline: "+(n||t.line||t.lineNumber)+"\nfile: "+(e||t.file||t.filename))},matchAll:function(t,e){for(var n,r=[];n=e.exec(t);)void 0!==n[1]&&r.push(n.slice(1));return r},throttle:function(t,e,n){var r,i,a,o,s=function(){t.apply(n,o)};return function(){a=(new Date).getTime(),o=arguments,void 0===r?(i=a,r=setTimeout(s,e)):a-i>=e&&(clearTimeout(r),r=void 0,s())}},readFile:function(t){var e=new XMLHttpRequest;return e.open("GET",t,!1),e.send(null),e.responseText}},e.isClient&&(e.support.touch=function(){var t=document.createElement("div");return"ontouchstart"in t}(),window.jQuery)){var c=e.support.touch?"toucstart":"click";jQuery.event.special.tap={delegateType:c,bindType:c}}}),t("xmlparser",function(t,e,n){function r(t,e){return new i(t,e)}function i(t,e){this.name=t||"#",this.shorty=void 0!==$[t],this.value="",this.attrs=e||{},this.length=0,this.children=[],this.binding=[]}var a=function(t){return t.charCodeAt(0)},o=/^[\r\n\s\t]+$/,s=a("\n"),l=a("<"),c=a(">"),u=a("="),f=a("a"),d=a("z"),p=a("0"),h=a("9"),x=a("-"),v=a(":"),m=a('"'),_=a("/"),g=(a("\\"),33),y=1,b=2,w=n("utils"),C=w.each,$=w.shortTags,E=function(t,e,n){function i(t){var e,r=n[t.name];void 0!==r&&(e="string"==typeof r?r:r(t,t.attrs),"string"==typeof e&&t.replace(e))}function a(t){var r,i=n["#text"],a=t.value;if(void 0!==i&&(r=i(a,t),r!==a)){var o=e.line;r=E(r,e,n),e.line=o,t.replace(r.children)}}function w(e){var n=t.indexOf("\n",S),r=S-P+1;throw 0>=n&&(n=t.length),{file:F,line:z+":"+r,message:e+"\n"+t.substring(P,n).replace(/\t/g," ")+"\n"+Array(r).join("-")+"^"}}function C(){var e=t.charCodeAt(++S);for(e!==m&&w('"'+j+'" expected value of the attribute in quotes'),L=++S;M>S;){if($=t.charCodeAt(S),e===$)return t.substring(L,S);S++}S-=0|(S-L)/2+1,w('Unclosed string literal in "'+j+'" attribute')}n=n||{},e=e||{};for(var $,k,A,T,O,j,N,S=0,L=0,M=t.length,D=r("#root"),z=e.line||1,F=e.file,P=0,q=0,B=null==e.trim?1:e.trim;M>S;)$=t.charCodeAt(S),g>$&&$===s&&(P=S+1,z++),T===y?$===c||$===_&&t.charCodeAt(++S)===c?(O=t.substring(k,q+1),T=void 0,q=S+1,"#"===D.name?(D.name=O,$===_&&(D.shorty=!0,i(D),D=D.parent)):D.name===O?(i(D),D=D.parent):w('Wrong close "'+O+'" tag should be "'+D.name+'"')):g>$?(L=S,T=b,A=void 0):$>=f&&d>=$||$>=p&&h>=$||$===x||$===v?q=S:w("Invalid character in node name"):T===b?$===_||$===c?(void 0!==A&&D.attr(t.substring(A,S).trim(),!0),S--,T=y):$===u||g>$?void 0!==A&&(j=t.substring(A,S).trim(),D.attr(j,g>$?!0:C()),A=void 0):(f>$||$>d)&&$!==x&&$!==v?w("Invalid character in attrbiute name"):void 0===A&&(A=S):$===l&&(S-q>0&&(N=t.substring(q,S),(0===B||o.test(N)===!1)&&(D.appendText(N),a(D.last))),t.charCodeAt(S+1)===_?S++:(D=D.newChild(),D.file=F,D.line=z),k=S+1,T=y),S++;return T===y?w('"'+t.substring(k,S)+'" tag should be closed'):"#root"!==D.name&&w('"'+D.name+'" tag should be closed'),S-q>0&&(N=t.substring(q,S),(0===B||o.test(N)===!1)&&(D.appendText(N),a(D.last))),D};i.prototype={create:function(t,e){return r(t,e)},attr:function(t,e){return this.attrs[t]=e,this},bind:function(t){t.binded=this.binded=!0,this.binding.push(t)},on:function(t,e,n){void 0===this.events&&(this.events=[]),this.events.push(t.replace(/^.+[:-]+/,""),e,n)},bindDecl:function(t,e){void 0===this.decl&&(this.decl=[]),this.decl.push(t,e)},closest:function(t){var e=this.parent;if(e.name!==t)for(;(e=e.parent)&&e.name!==t;);return e},index:function(){for(var t=this.parent.children,e=t.length;e--;)if(t[e]===this)return e;return-1},append:function(t,e){var n=this.children;return void 0===e?n.push(t):n.splice(e.index(),0,t),this.first=n[0],this.last=n[(this.length=n.length)-1],t.parent=this,this},appendArray:function(t){return C(t,function(t){this.append(t)},this),this},empty:function(){return this.children=[],this.length,delete this.first,delete this.last,this},remove:function(){var t=this.parent,e=t.children,n=this.index();return-1!==n&&e.splice(n,1),t.first=e[0],t.last=e[(t.length=e.length)-1],this},find:function(t){var e=[],n=">"!=t.charAt(0);return t=t.replace(">",""),C(this.children,function r(i){(i.name===t||"*"===t)&&e.push(i),n&&C(i.children,r)}),e},wrap:function(t,e){var n=r(t,e);return n.file=this.file,n.line=this.line,this.parent.append(n,this),n.append(this.remove()),n},innerWrap:function(t,e){var n=r(t,e),i=this.children,a=0,o=i.length;for(this.children=[];o>a;a++)n.append(i[a]);return this.append(n)},replace:function(t){var e=this.parent,n=[].concat(t),r=0,i=n.length;for(this.remove();i>r;r++)t=n[r],"string"==typeof t?e.appendText(t):e.append(t);return this},appendText:function(t){if(""!==t){var e=this.last;void 0===e||"#text"!==e.name?this.newChild("#text").value=t:e.value+=t}return this},newChild:function(t,e){var n=r(t,e);return n.file=this.file,n.line=this.line,this.append(n),n},exception:function(t){throw{message:'Tag "'+this.name+'", '+t,line:this.line,file:this.file}},each:function(t){t(this,this.attrs),C(this.children,function(e){e.each(t)})},clone:function(){var t=r();return C(this,function(e,n){"children"===n?C(e,function(e){t.append(e.clone())}):/first|last/.test(n)||(t[n]=w.simpleClone(e))}),t},toString:function(t,e){e=t&&e||"",t=t?"\n":"";var n=e+this.value;if(C(this.children,function(r){n+=r.toString(t," "+e)}),"#"!=this.name.charAt(0)){var r,i,a=t+e+"<"+this.name,o=[];for(r in this.attrs)i=this.attrs[r],i===!0?o.push(r):o.push(r+'="'+i+'"');o.length&&(a+=" "+o.join(" ")),n=a+(this.shorty&&!this.length?"/>":">"+n+t+e+""+this.name+">")}return n}},E.newNode=r,t.exports=E}),t("xparser",function(t,e,n){(function(){var e=n("xmlparser"),r=e.newNode,i=function(t){return t.charCodeAt(0)},a=function(t){return String.fromCharCode(t)},o=0,s=i("\n"),l=i("{"),c=i("}"),u=i("("),f=(i(")"),i("[")),d=i("]"),p=i("="),h=i("a"),x=(i("z"),i("0")),v=i("9"),m=i("\n"),_=i(">"),g=i("&"),y=i("."),b=i("#"),w=i("|"),C=i(","),$=i(":"),E=i("-"),k=i('"'),A=i("'"),T=i("/"),O=i("\\"),j=32,N=++o,S=++o,L=++o,M=++o,D=++o,z=(++o,++o),F=++o,P=++o,q=++o,B=(++o,n("utils").each),H=/(width|height|left|right|top|bottom|font-size|text-indent)$/,I="a abbr acronym address applet area article aside audio b base basefont bdi bdo big blockquote body br canvas caption center cite code col colgroup command datalist dd del details dfn dialog dir div dl dt em embed fieldset figcaption figure font footer form frame frameset h1 to h6 head header hgroup hr html i iframe img input ins kbd keygen label legend li link map mark menu meta meter nav noframes noscript object ol optgroup option output p param pre progress q rp rt ruby s samp script section select small source span strike strong style sub summary sup table tbody td textarea tfoot th thead time title tr track tt u ul var video wbr".split(" "),W={"if":function(t){return r("x:if",{test:t})},"else":function(t,e){var n=r("x:else");return n.shorty=!0,/^if\b/.test(t)&&(n.attrs["if"]=t.substr(2).trim()),e.last.append(n)},elseif:function(t,e){return W["else"]("if "+t,e)},"for":function(t,e){return W.each(t,e)},each:function(t){return t=t.match(/^\(?(\w+)(?:\s|,)\s*(\w+)?\)?\s*in\s*([^\|]+)(\|.+)?/i),r("x:each",{data:t[3],as:t[2]||t[1],key:t[2]?t[1]:"$index",filter:/^\|/.test(t[t.length-1])?t.pop().substr(1):void 0})}};B(I,function(t){I[t]=1});var J=function(t,n,i){function o(e,n){var r=t.indexOf("\n",oe),i=oe-de+1;0>=r&&(r=t.length);try{r()}catch(a){console.log(a.stack.split("\n").slice(1).join("\n"))}throw{file:fe,line:ue+":"+i,message:e+"\n"+t.substring(de,r).replace(/\t/g," ")+"\n"+Array(i+(0|n)).join("-")+"^"}}function I(t,e){var n;try{t="string"==typeof t?t.replace(/(\w)->(\w)/g,"$1.xxx.$2"):t,n=Function("ctx,Soul","return "+t)()}catch(r){if(t=t.replace(/(\w)\.xxx\.(\w)/g,"$1->$2"),r instanceof ReferenceError||r instanceof TypeError)return e?{expr:t,str:t}:"{{"+t+"}}";o(r.message+"\n---\n"+t+"\n---")}return e?{value:t,str:t,result:n}:n}function J(t){var r,a=ce.newChild("#text"),o=i["#text"],s=a.value=t;if(void 0!==o&&(r=o(s,a),r!==s)){var l=n.line;r=e(r,n,i),n.line=l,a.replace(r.children)}return a}function Q(){var e,n;for(se=oe;le>oe;){if(Y=t.charCodeAt(oe),Y===k||Y===A||Y===O&&!/["')]/.test(a(Z))){for(oe++,Z=Y;le>oe;){if(Y=t.charCodeAt(oe),Y===O)e=!e;else{if(Y===Z&&!e)break;e=0}oe++}oe>=le&&o("Unterminated string")}else{if(Y===C||Y===c||Y===m)return n=t.substring(se,oe).trim().replace(/[;,]$/,""),se=oe+1,oe--,n;if(Y===l)break}Y>j&&(Z=Y),oe++}oe=se}function R(e){for(se=oe+(0|e);le>oe&&(Y=t.charCodeAt(oe),Y!==s);)oe++;return t.substring(se,oe)}function U(t){var e,n=i[t.name];void 0!==n&&(e="string"==typeof n?n:n(t,t.attrs),"string"==typeof e&&t.replace(e))}function G(t){var e={};return B(t.attrs,function(n,r){var i=n,a=t.name;"string"==typeof n?/^x[:-](if|each)/.test(a)||/^x-/.test(r)||(i=I(i)):n instanceof Object&&(i="",B(n,function(t,n){t=I(t,!0),"class"===r?void 0!==t.expr?(n=n.replace(/^([^'"])/,'"$1').replace(/([^'"])$/,'$1"'),i+=" {{"+t.expr+" ? "+n+' : ""}}'):t.result&&(i+=" "+n.trim().replace(/(^["']|["']$)/g,"")):"style"===r?i+=n+":"+(void 0!==t.expr?"{{"+t.expr+"}}":t.result)+(H.test(n)&&(t.expr?!/(\+\s*["'](%|[a-z]{2,3})['"]|%)$/.test(t.str):/^\d+(\.\d+)?$/.test(t.str))?"px":"")+";":(i=void 0,e[r+"-"+n]=void 0!==t.expr?"{{"+t.expr+"}}":t.result)})),null!=i&&(e[r]=i)}),t.attrs=e,t.parent||o("Too many close"),U(t),"x:decl"==t.parent.name&&(t=t.parent,U(t)),t.parent.single?G(t.parent):t.parent}function V(e){var n=t.substring(se+(0|e),oe);return se=oe,n}function X(e){for(;le>oe;){if(Y=t.charCodeAt(oe),Y===l){var n=W[e](V().trim(),ce);return n.line=ue,n.file=fe,n.parent||ce.append(n),n}oe++}o("Syntax error")}i=i||{},n=n||{};for(var K,Y,Z,te,ee,ne,re,ie,ae,oe=0,se=0,le=t.length,ce=r("#root"),ue=n.line||1,fe=n.file,de=0;le>oe;){if(Y=t.charCodeAt(oe),Y===s&&(ue++,de=oe),void 0===te)Y===g?(se=oe,te=q):Y===w?(J(R(1).trim().replace(/\|$/,"")),ee&&(ee=!1,ce=G(ce),se=oe)):Y===T?R():Y===b?(se=oe,ce=ce.newChild("div"),te=N):Y===y?(ce=ce.newChild("div"),te=D):Y===_||Y===C?se=oe:Y===l||(Y===c?(ce=G(ce),se=oe):Y>j&&(se=oe,te=S)),ce.file=fe,ce.line=ue;else if(te===S){if((Y===u||h>Z)&&(ne=t.substring(se,oe).trim(),W[ne])){se=oe,ce=X(ne),te=void 0;continue}if(Y===y)te=D;else if(Y===f)te=L;else if(Y===b)te=N;else if(Y===w)te=void 0,ee=!0;else{if(Y===$){te=z;continue}Y===_?te=M:Y===l?te=M:Y>j&&h>Y&&Y!==E&&(x>Y||Y>v)&&o("Syntax error")}te!==S&&(ce=ce.newChild(V().trim()),ne=ce.name,oe--,/^[a-z_-][a-z0-9_-]*$/i.test(ne)||o('Invalid node name "'+ne+'"',-ne.length/2))}else te===N?(Y===y||j>=Y||Y===f)&&(te=Y===y?D:Y===f?L:M,ce.attrs.id='"'+V(1).trim()+'"',oe--):te===D?(j>=Y||Y===w||Y===l||Y===f)&&(ce.attrs["class"]='"'+V().trim().split(".").slice(1).join(" ")+'"',oe--,re=void 0,te=Y===f?L:M):te===L?(Y===p||Y===d)&&(void 0===re&&(re=V(1)),Y===d&&(ae=V(1),se=oe+1,ce.attrs[re]="]"===ae?"true":ae,re=void 0,t.charCodeAt(oe+1)!==f&&(te=M))):te===M?Y===w?(J(R(1).trim().replace(/\|$/,"")),se=oe,ce=G(ce),te=void 0):Y===_?(se=oe+1,te=void 0,ce.single=!0):Y===l&&(te=void 0):te===z||te===P?Y===$?(ae=V().trim(),te===z?(re=ae,K=ce.attrs[re],ce.attrs[re]="",void 0!==K&&"class"===re&&(ce.attrs[re]=K+" ")):(ie=ae,ce.attrs[re][ie]=""),te=F):Y===c&&(te=void 0,void 0===re&&(ce=G(ce))):te===F?Y===l?(K=ce.attrs[re],ce.attrs[re]={},""!==K&&"class"===re&&(ce.attrs[re][K]=1),se=oe+1,te=P):Y>j&&(ae=Q(),void 0===ie?ce.attrs[re]="class"===re?(ce.attrs[re]+ae).replace(/"\s"/," "):ae:(te=P,ce.attrs[re][ie]=ae,ie=void 0),Y===c?(te===P&&oe++,re=void 0,ie=void 0,te=void 0):te!==P&&(re=void 0,ie=void 0,te=void 0)):te===q&&(Y===p?(ce=ce.newChild("x:decl",{name:V(1).trim()}),te=void 0):Y===l?(ce=ce.newChild("x:"+V(1).trim()),se=++oe,te=z):Y>j&&j>=Z&&(ce.newChild("x:"+V(1).trim(),{"x:context":R()}),te=void 0));Z=Y,oe++}return"#root"!==ce.name&&(console.log(ce),ce.exception("not closed")),ce};t.exports=J})()}),t("compile",function(t){function e(t,n,r){return e.pre(t,n),e.code(t,r)}e.pre=function n(t,e){var r,i=t.name,a=e[i],o=t.attrs;if(!t.noPre){if(void 0===a||(r=a(t,t.attrs)),r!==!0&&(r=e["**"](t,t.attrs)),r===!0)return n(t.parent,e),void 0;for(i in o)if(a=e["@"+i],void 0!==a&&a.call(e,t,i,o[i])===!0||e["@"].call(e,t,i,o[i])===!0)return delete o[i],n(t.parent,e),void 0}for(var s=t.children.slice(0),l=0,c=s.length;c>l;l++)n(s[l],e)},e.code=function r(t,n){var i,a,o,s,l,c=t.name,u=n[c],f=t.children,d="",p=t.binding,h=t.decl,x=t.events,v=(p.length||h||x)&&e.xpath(t),m="",_="",g="";if(i=void 0===u?n["**"](c,t,t.attrs):""===u?["",""]:u(t,t.attrs),t.xctrl&&(i[0]=e.tryCatch(t,'xtpl.ctrl["_'+t.xctrl+'"](ctx)')+i[0]),a=p.length){var y,b=0;for(o=0;a>o;o++){if(l=p[o],m+=",__xbind"+o,"x:if"==l.name)s=e.getExpr(l,"test"),b=1,l.attrs.test="__xbind"+o;else if("x:else"==l.name)s=e.getExpr(l,"if"),b=1,l.attrs.test="__xbind"+o;else if("x:value"==l.name)s=e.getExpr(l,"data"),l.attrs.data="__xbind"+o;else if("x:var"==l.name){var w=l.key;if(l.expr){var C=e.getExpr(l,w,l.value);s=e.tryCatch(l,"__xval="+C),"context"==w?(_=s+"if( __xval instanceof Object ) for( var key in __xval ){"+" __xval0 = __xval[key];"+" __xargs[key] = __xval0;"+' __xargs["__"+key] = __xclone(__xval0);'+" __xc = 1;"+"}"+_,g='for( var key in __xargs ){ if( !/^__/.test(key) ){ __xval = __xargs[key]; if( __xnotEq(__xval, __xargs["__"+key]) ){'+e.tryCatch(l,C+"[key] = __xval;")+" __xc = 1;"+" }"+" }"+"}"+g):(_+=s+' __xargs["'+w+'"] = __xval;',l.sync!==!1&&(_+=' __xargs["__'+w+'"] = __xclone(__xval);'+" __xc = 1;"),l.sync===!1||/[\*\-\+\\!%\/=><]/.test(C)||(g+='__xval=__xargs["'+w+'"];'+'if( __xnotEq(__xval, __xargs["__'+w+'"]) ){'+e.tryCatch(l,C+" = __xval;")+" __xc = 1;"+"}"))}else _+='__xargs["'+w+'"]='+e.lex(l.value)+";";continue}_+=e.tryCatch(l,"__xargs["+o+"]="+s),l.attrs.safe=!0}"x:attr"==c?(b=3,y=e.lex(t.attrs.name)):"x:decl"===c&&(b=5,y=e.lex(t.declName),m=" ctx,__xsync",_="var __xc;if(apply===void 0){"+_+"}else{"+g+"}return __xc"),i[0]+="__buf.b("+b+", ["+v+"],function (__xargs,apply){"+_+"},function "+(t.fnName||"")+"("+m.substr(1)+"){",i[1]="}"+(y?","+y:"")+");"+i[1],"x:decl"===c&&(i[0]+="var ___$apply=__buf.$apply, __$apply=__buf.$apply=function(force){if( __xsync(ctx,true) || force === true ){ ___$apply(true) }};",i[1]="__buf.$apply=___$apply;"+i[1])}if(x)for(o=0,a=x.length;a>o;o+=3)i[1]+="__buf.b(4,["+v+"],"+e.lex(x[o])+",function(evt){evt.preventDefault();"+"var el = evt.currentTarget;"+x[o+1]+"\n __$apply()"+"},"+e.lex(x[o+2]||0)+");";if(h)for(o=0,a=h.length;a>o;o+=2)s=h[o+1],s instanceof Object&&(s=JSON.stringify(s)),i[1]+="__buf.b(5,["+v+"],function(__xargs){"+e.tryCatch(t,"__xargs["+o+"]="+s)+"\nreturn __xargs},"+e.lex(h[o])+");";for(a=f.length,o=0;a>o;o++)d+=r(f[o],n);return(i[0]+d+i[1]).replace(/"\)\n__buf\.s\("/g,"").replace(/{;_/g,"{_")},e.lex=function(t){return JSON.stringify(t)},e.str=function(t){return"__buf.s("+e.lex(t)+")\n"},e.variable=function(t,n,r){return e.expr(t,"__buf.w($"+n+(r?',"'+r+'"':"")+");")},e.tryCatch=function(t,e,n){return"try{"+e+"}catch(e){"+(n||"")+(t.file?';__xerr(e,"'+t.file+'",'+t.line+")}":"}")},e.expr=function(t,n){var r="",i=0;return n=n.replace(/([\$@&])([\w-]+)/g,function(n,a,o){var s=t.attrs[o];return"$"!=a||t.attrs.safe?"@"==a&&(s=e.lex(s)):(s=e.getExpr(t,o),o="__xval"+i++,r+=e.tryCatch(t,o+"="+s,o+"=void 0"),s=o),void 0===s?"undefined":s}),r+n},e.getExpr=function(t,e,n){var r=(void 0===n?t.attrs[e]:n).replace(/(\w|\))->(\w)/g,"$1.attributes.$2");try{Function('"use strict";\n'+r)}catch(i){try{Function('"use strict";\n('+r+")")}catch(a){t.exception(' attribute "'+e+'": '+i.message+"\n---\n"+r+"\n---")}}return r},e.wrap=function(t,e){if(!t.binded){do{var n=t.parent,r=n.children,i=r.length,a=i,o=/#text|value/,s=0;if(e=e||t.attrs.bind,i>1){for(;a--;)r[a].hidden&&t!==r[a]&&i--;a=i}if("both"==e&&(1!=t.length||o.test(t.first.name)))return t.innerWrap("x"),!0;if(1===i)"both"==n.bindMod&&n.innerWrap("x"),t.sys&&n.sys&&(n=t.wrap("#scope"),n.sys=1,n.hidden=1),t.manualBind||n.manualBind||n.bind(t);else if(o.test(t.name)){for(;a--;)if(!o.test(r[a].name)&&!r[a].hidden)return t.wrap("x"),!0;t.manualBind||n.manualBind||n.bind(t)}else("wrap"==e||"both"==e)&&(t.wrap("x"),s=1)}while(s);t.bindMod=e,delete t.attrs.bind}},e.xpath=function(t,e){var n=t.xpath;if(e=t.attrs.x||e,void 0!==n)return n;if(n=[],e&&console.group(t.name+": "+JSON.stringify(t.attrs)),t.parent)do for(var r=t.parent.children,i=0,a=r.length,o=0;a>i;i++)if(e&&console.log(r[i].hidden,o,""+r[i]),r[i].pseudoRoot)o=0;else if(!r[i].hidden){if(r[i]===t){n.push(o);break}o++}while((t=t.parent)&&t.parent&&!t.sys);return e&&(console.log("xpath:",n),console.groupEnd()),n},t.exports=e}),t("buffer",function(t,e,n){function r(t){var e,n=t.min,i=n,a=t.max;if(void 0!==n)for(;a>=i;i++)e=t[i],void 0!==e&&(e.zmb=1,r(e))}function i(t,e,n,r,i,s){var l=0,u=e.childNodes,f=r.extra,d=document.createElement("div"),p=r.render,h=[];x(i,function(i,c){if(void 0===f||f(i)){var x,v=s[l],m=u[l];i!==v&&(n.remove(l),t.idx=l,t.xpath=r.xpath,t.clear(),p(i,c),x=a(d,""+t),void 0===m?e.appendChild(x):o(m,x,e)),h[l]=i,l++}});for(var v=u.length-1;v>=l;v--)c(u[v],t._decl,!0),n.remove(v),e.removeChild(u[v]);return d=null,h}function a(t,e){return/^"+e+"",t=t.firstChild):/^"+e+" ",t=t.firstChild.firstChild):t.innerHTML=e,t.firstChild}function o(t,e,n){void 0===n&&(n=t.parentNode),n.insertBefore(e,t),n.removeChild(t)}function s(t,e){if(void 0!==e.el)return e.el;for(var n=e.xpath,r=n.length;r--&&(t=t.childNodes[n[r]]););return e.el=t}function l(t,e){for(var n=e.length;n--&&(t=t.childNodes[e[n]],null!=t););return t}function c(t,e,n){var r,i=t.getElementsByTagName("*"),a=e[t.xdecl];for(n===!0&&(void 0!==t.xevents&&h(t),void 0!==a&&void 0!==a.remove&&a.remove(t,t.parentNode)),r=i.length;r--;)t=i[r],a=e[t.xdecl],void 0!==t.xevents&&h(t),void 0!==a&&void 0!==a.remove&&a.remove(t,t.parentNode)}function u(t,e){var n,r=t;if(t instanceof Array)for(n=t.length,r=[];n--;)r[n]=e?t[n]:u(t[n]);else if(t instanceof Function)r=t;else if(t instanceof Object){r={};for(n in t)t.hasOwnProperty(n)&&(r[n]=t[n])}return r}function f(t){return"tap"===t&&(t=E.support.touch?"touchstart":"click"),t}function d(t,e,n,r){var i,a,o,s=t.xevents=t.xevents||m++;n=f(n),r.gid=r.gid||"x"+m++,$[s]||($[s]={}),$[s][n]||($[s][n]=[]),o=$[s][n],i=o[r.gid]=o[r.gid]||p(t,r),t.addEventListener?t[e?"addEventListener":"removeEventListener"](n,i,!1):t[e?"attachEvent":"detachEvent"]("on"+n,i),e===!0?o.push(r):(a=T(o,r),a>-1&&o.splice(a,1),o.length||(delete o[r.gid],delete $[s][n]),O($[s])&&delete $[s])}function p(t,e){return function(n){n=n||window.event,n.target||(n.target=n.srcElement||document),3===n.target.nodeType&&(n.target=n.target.parentNode),n.currentTarget||(n.currentTarget=t),!n.relatedTarget&&n.fromElement&&(n.relatedTarget=n.fromElement===n.target?n.toElement:n.fromElement),n.stopPropagation||(n.stopPropagation=function(){this.cancelBubble=!0}),n.preventDefault||(n.preventDefault=function(){this.returnValue=!1}),e.call(t,n)===!1&&(n.preventDefault(),n.stopPropagation())}}function h(t){var e,n,r,i=t.xevents,a=$[i];if(a)for(e in a)for(r=a[e],n=r.length;n--;)d(t,0,e,r[n])}function x(t,e){if(t){var n,n=0,r=t.length;if(void 0!==r&&n in t)for(;r>n;n++)e(t[n],n);else for(n in t)t.hasOwnProperty(n)&&e(t[n],n)}}var v=0,m=1,_=1,g=2,y=3,b=4,w=5,C={autofocus:1,autoplay:1,async:1,checked:1,controls:1,defer:1,disabled:1,hidden:1,loop:1,multiple:1,open:1,readonly:1,required:1,scoped:1,selected:1},$={},E=n("utils"),k=E.isClient,A=k&&(window.opera||/MSIE/.test(navigator.userAgent)),T=E.inArray,O=E.isEmptyObject,j=E.notEqual,N=E.escapeHTML,S=k&&"cssText"in document.createElement("div").style,L=function(t){this.id=++v,this._=[],t&&(this.ids={},this.index=[])};L.prototype={constructor:L,get:function(t){for(var e,n=t.length,r=this;n--;)e=t[n],r=r[e]||L.Null;return r},push:function(t,e){for(var n,r,i=t.xpath,a=i.length,o=this;a--;)n=i[a],r=o[n],(void 0===r||void 0!==r.zmb)&&(o[n]=new L,o.min=void 0===o.min?n:Math.min(o.min,n),o.max=Math.max(0|o.max,n)),o=o[n];o.idx=i[0],o.label=e,1===o._.push(t)&&(o.type=t.type);var s=this.ids;return void 0===s[o.id]&&(s[o.id]=1,this.index.push(o)),t},removeItem:function(t){for(var e=this.get(t.xpath),n=e._,r=n.length;r--;)if(n[r]===t){n.splice(r,1);break}},remove:function(t){var e,n=this,i=t.length;if("object"==typeof t)for(;i--;)void 0!==n&&(e=t[i],n=n[e]);else n=n[t];n&&(n.zmb=1,r(n))},empty:function(t){r(this),t&&this._.length>1&&this._.splice(1,10)},each:function(t){for(var e,n,r=this,i=r.index,a=0,o=i.length,s={};o>a;)if(r=i[a],1!==r.zmb){a++,e=r._;for(var l,c=0;void 0!==e[c];c++)l=e[c],n=l.label,void 0===s[n]&&t(r,l)===!1&&(s[n]=1)}else o--,i.splice(a,1)}},L.Null=new L;var M=function(t,e){this.el=t,this.data=A?[]:"",this.xpath=[],this.events=[],this.idxQueue=[],this.bindDecl=[],this.bindData=new L(!0),this.applyFns=[],this._decl=e,this.$apply=this.applyChanges.bind(this)};M.prototype={s:A?function(t){this.data.push(t)}:function(t){this.data+=t},a:function(t){void 0!==t&&this.s(t)},w:function(t,e){void 0!==t&&("text"===e?this.s(t):this.s(N(t)))},save:function(){this.__data=this.data,this.data=A?[]:""},revert:function(){this.data=this.__data},each:function(t,e,n){if(t){var r,i=0,a=t.length,o=0,s=this.idx;if(void 0!==a&&0 in t)for(;a>i;i++)r=t[i],(void 0===n||n(r,i))&&(this.idx=o++,e(r,i));else for(i in t)r=t[i],t.hasOwnProperty(i)&&(void 0===n||n(r,i))&&(this.idx=o++,e(r,i));this.idx=s}},b:function(t,e,n,r,i){var a,o=this.idx,s=this.xpath,l=this.xlabel,c=e.length,f=[];if(void 0!==o&&(0===c?e[0]=o:e[c-1]=o,delete this.idx),e=e.concat(s),t===b)this.events.push({name:n,fn:r,xpath:e,sel:i});else if(n(f),t===g?f[0]=u(f[0],!0):t===w&&void 0!==i?this.xlabel=m++:f=u(f),this.bindData.push(a={type:t,xpath:this.xpath=e,args:f,getter:n,render:r,extra:i,label:this.xlabel}),t===g)this.each(f[0],r,i);else if(t===w)this.bindDecl.push(a),void 0!==i&&(r(f,n),a.$apply=this.$apply);else if(t===y){this.save(),r.apply(this,f);var d=""+this;this.revert(),""!==d&&"false"!==d&&"0"!==d&&"null"!==d&&this.s(" "+i+'="'+d+'"')}else r.apply(this,f);this.idx=o,this.xpath=s,this.xlabel=l},clear:function(){this.data=A?[]:""},toString:A?function(){return this.data.join("")}:function(){return this.data},applyChanges:function(t){var e=this,n=e.el;this.__freeze||(this.__freeze=!0,console.time("apply"),"function"==typeof t&&this.applyFns.push(t),this.bindData.each(function(t,r){var a,o,l,f=[],d=r.args,p=r.type,h=r.extra,x=r.getter,v=e.idx,m=e.xpath,b=e.$apply;if(p===w&&void 0!==h){if(o=!0,void 0===x(f=d))return!1;e.$apply=r.$apply}else if(x(f),p===g)o=!0;else for(a=f.length;a--;)j(d[a],f[a])&&(f[a]=u(f[a]),o=!0);if(o){if(e.xpath=r.xpath,l=s(n,r),p===g)f[0]=i(e,l,t,r,f[0],d[0]);else if(p===w){var $=e._decl[h||r.render];void 0!==$.update&&(void 0===h?$.update(l,f[0],d[0]):$.update(l,d))}else{e.clear(),p===y?e.w=e.a:t.empty(p===_),r.render.apply(e,f);var E=""+e;p===y?(e.w=e._w,"value"===h?l.value!==E&&(l.value=E):"class"===h?l.className=E:void 0!==C[h]?l[h]=""!==E&&"false"!==E&&"0"!==E:"style"===h&&S?l.style.cssText=E:"null"===E?l.removeAttribute(h):l.setAttribute(h,E)):(c(l,e._decl),l.innerHTML=E)}e.idx=v,e.xpath=m,e.$apply=b,r.args=f}}),this.applyEnd(),this.__freeze=!1,console.timeEnd("apply"))},applyEnd:function(){var t,e,n,r,i,a=this.bindDecl,o=this.events,c=this.applyFns,u=this.ctx;for(t=o.length;t--;)r=o[t],n=l(this.el,r.xpath,!0),r.sel?jQuery(n).on(r.name,r.sel,r.fn):d(n,!0,r.name,r.fn);for(t=a.length;t--;)i=a[t],n=s(this.el,i,!0),void 0===n?console.log("decl.error:",i.xpath,i):(r=this._decl[n.xdecl=i.extra||i.render],void 0!==r.init&&(r.ctx=u,r.init(n,i.extra?i.args:i.args[0])));for(this.events=[],this.bindDecl=[],this.applyFns=[],t=0,e=c.length;e>t;t++)c[t]()},setModel:function(t,e,n){if(!t.xmodel){t.xmodel=!0;var r,i=e.attributes,a=/text|pass/i.test(t.type+t.tagName),o=i[n],s=this.$apply,l=function(){o=i[n],s()},c=function(l){a?(r=t.value,o===r||l!==!0&&13!==l.keyCode||(i[n]=o,e.set(n,r)),i[n]=r):e.set(n,t.checked),s()},u=function(){t.xmodel=!1,c(!0),e.off("all",l),d(t,!1,"keyup",c),d(t,!1,"input",c),d(t,!1,"change",c),d(t,!1,"blur",u)};e.on("all",l),a?(d(t,!0,"keyup",c),d(t,!0,"input",c)):d(t,!0,"change",c),d(t,!0,"blur",u)}},destory:function(){c(this.el,this._decl,!0)}},M.prototype._w=M.prototype.w,t.exports=M}),t("xtpl",function(t,e,n){var r=n("utils"),i=n("xparser"),a=n("xmlparser"),o=n("compile"),s=n("buffer"),l=r.isClient,c=l&&window.xtpl,u=l?location.pathname:__dirname,f={},d={},p={},h={},x={"#text":function(t){return t.replace(/\{\{(.*?)\}\}/g,"$1 ").replace(/\{\*(.*?)\*\}/g,'$1 ')},"x:text":function(t,e){var n=t.first?t.first.value:e.value;return n},"x:space":" ","x:enter":"\n","x-space":" ","x-enter":"\n"},v=r.each,m=r.extend,_=function(t,e){var n={},r=this;"object"==typeof t?n=t:n[t]=e,v(n,function(t,e){if(t.pre||t.code){t.pre&&_.call(d,e,t.pre),t.code&&_.call(p,e,t.code);var n=t.children;n&&(x[e]=function(t){var e=t.children,r=t.length;if(n)for(0===r&&t.exception("can not be empty");r--;)-1===n.indexOf(e[r].name)&&t.exception("can contain only: "+n.replace(/\s/g,", "))})}else r[e]=r[e.replace(":","-")]=t})},g=function(t){return t=y(t),t.replace(/(\w)->(\w)/g,"$1.attributes.$2")},y=function(t){var e=["",""];return v(r.matchAll(t,/(?:\|\|)|(\|\s*([a-z]+[^|]+))/g),function(n){if(n[0]){var r=n[0].trim().split(":");e[0]="__xmods."+r[0].replace(/\|\s*/,"")+"("+e[0],e[1]=e[1]+(r.length>1?","+r.slice(1).join(","):"")+")",t=t.replace(n[0],"")}}),e[0]+t+e[1]},b=function(t,e){var n={__len:0};v(e,function(e,r){var i=r.replace(/^x[:-]/,"");/->/.test(e)&&(t.bind({name:"x:var",key:"__x$"+i,expr:!0,value:e.split("->")[0],sync:!1}),n.__len++,n["ctx."+i]="ctx.__x$"+i+"->"+e.split("->")[1]),t.bind({name:"x:var",key:i,expr:r!=i,value:g(e)})}),n.__len>0&&t.each(function(t,e,r){(r=n[e["x:model"]])?e["x:model"]=r:(r=n[e["x-model"]])&&(e["x:model"]=r)})},w={xml:!1,autoInit:!0,each:v,cache:f,mods:{},utils:r,parse:function(t){return a(t,{file:u},x)},xparse:function(t){return i(t,{file:u},x)},compile:function(t,e,n){var r,s=f[e];if(void 0===s){try{"string"==typeof t&&(t=n===!0?a(t,{file:e||u},x):i(t,{file:e||u},x)),r=o(t,d,p),s=Function("return "+r)()}catch(l){s=Function("return function(__buf){"+o.str(""+l.message+"\nline: "+l.line+"\nfile: "+l.file+" ")+"return __buf.toString()}")(),console.log(l.message),l.stack&&console.log(l.stack)}void 0!==e&&(f[e]=s)}return s},ctrl:function(t,e){w.ctrl["_"+t]=e},mod:function(t,e){w.mods[t]=e},bind:function(t,e,n,r){var i=w.xml;void 0===r&&(r=i),w.xml=r;var a="string"==typeof e?w.compile(e,void 0,r):e,o=new s(t,h);return n=n||{},n.xel=t,n.xfn=a,n.xbuffer=o,n.$set=function(t,e){n[t]=e,n.$apply()},n.$delay=function(t,e){return setTimeout(function(){t(),n.$apply()},e)},n.$loop=function(t,e){return setInterval(function(){t(),n.$apply()},e)},n.$clear=function(t){clearTimeout(t)},n.$apply=o.$apply,t.innerHTML=a(o,n),o.applyEnd(),w.xml=i,n},pre:_.bind(d),tag:_.bind(p),decl:function(t,e){var n,r;"object"==typeof t?n=t:(n={},n[t]=e),v(n,function(t,e){"function"==typeof t&&(t={init:t,update:t}),t=h[e]=m(h[e],t),r=e.replace("x:","x-"),h[r]=t,t.tpl?d["x:"+e]=function(n,r){var i="string"==typeof t.tpl?t.tpl=w[w.xml?"parse":"xparse"](t.tpl).first:t.tpl.clone(),a=i.find("x:content")[0]||i.find("x-content")[0];return a&&(a.hidden=1,a.appendArray(n.children)),v(i.find("x:"+e),function(t){t.sys=1,t.name="x:decl",t.declName=e,b(t,t.attrs),t.newChild("x:decl-call",{name:e})}),i.hidden=1,n.sys=1,n.name="x:decl",n.fnName="__x"+e.replace(/[^a-z0-9]/,""),n.declName=e,n.empty().append(i),b(n,r),!0}:d[r]=d["@"+r]=d["@"+e]=function(e,n,r){var i;return e.__xdecl||(e.__xdecl=!0,delete e.attrs[n],t.compile&&(t.compile(e,r),i=!0),(t.init||t.update||t.remove)&&("string"==typeof n?e.bindDecl(n,g(r)):e.bindDecl(e.name,n))),i}})},noConflict:function(){return window.xtpl=c,w}};w.pre({"@":function(t,e,n){if("string"==typeof n&&-1!==n.indexOf("{{")){var r=t.create("x:attr",{name:e});return t.append(r,t.first),v(n.match(/\{\{[^}]*\}\}|\{[^{]+|[^{]+/g),function(n){if(""!==n){var i=r.newChild("#text");i.file=t.file,i.line=t.line,"{{"===n.substr(0,2)?(i.name="x:value",i.attrs.data=o.getExpr(t,e,g(n.slice(2,-2)))):i.value=n}}),!0}},"@x:ctrl":function(t,e,n){t.xctrl=n.replace(/"/g,""),delete t.attrs[e]},"@x:if":function(t,e,n){return t.wrap(e,{test:n}),!0},"@x:each":function(t,e,n,r){return n=n.match(/^\(?(\w+)(?:\s|,)\s*(\w+)?\)?\s*in\s*([^\|]+)(\|.+)?/i),t[r||"innerWrap"]("x:each",{data:n[3],as:n[2]||n[1],key:n[2]?n[1]:"$index",filter:/^\|/.test(n[n.length-1])?n.pop().substr(1):void 0}),!0},"@x:eval":function(t,e,n){t.newChild("x:eval",{source:n}),delete t.attrs[e]},"@x:repeat":function(t,e,n){return this["@x:each"](t,e,n,"wrap")},"@x:model":function(t,e,n){var r=t.attrs.type||"text",i="change",a=g(n),s="value";return n=n.split("->"),"text"==r?(i="input",a+=" = evt.target.value"):(a=a+" =! "+a,s="checked"),n[1]&&(i="focus",a="__buf.setModel(el, "+n[0]+", "+o.lex(n[1])+")",/check|radio/.test(r)&&t.on("mousedown",a+";")),t.on(i,a+";"),"textarea"==t.name?t.newChild("x:value",{bind:!1,data:g(n.join("->"))}):t.newChild("x:attr",{name:s}).newChild("x:value",{data:g(n.join("->"))}),!0}}),v("change select submit reset tap click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave input keydown keypress keyup".split(" "),function(t){w.pre("@x:"+t,function(t,e,n){return n=n.split("=>"),t.on(e,g(n.pop()),n[0]),!0})}),x["x:decl"]=x["x-decl"]=function(t,e){e.name&&(t.remove(),w.decl(t.attrs.name,{tpl:t.first}))
+},w.tag({"#root":function(){return["function _(__buf,ctx){var __xerr=xtpl.utils.error,__xlog=xtpl.utils.print,__xnotEq=xtpl.utils.notEqual,__xclone=xtpl.utils.simpleClone,__xmods=xtpl.mods,__xval,__xval0,__xval1,__xval2,__xval3,__xval4,__$apply=__buf.$apply,__ctx=ctx,model=ctx.model,collection=ctx.collection;__buf.ctx=ctx;","__buf.$apply=__$apply;return __buf.toString()}"]},"#text":function(t){return[o.str(t.value),""]},"#scope":function(){return["",""]},"x:ctrl":{pre:function(t,e){t.xctrl=e.name,t.hidden=1},code:function(){return["",""]}},"x:decl":function(){return["",""]},"x:decl-call":function(t,e){return["__x"+e.name+"(ctx, __xsync);",""]},"x:content":function(){return["",""]},"x:value":{children:"#cdata #text",pre:function(t,e){e.bind="false"!==e.bind,e.data=g(t.first?t.first.remove().value:e.data)},code:function(t,e){return[o.variable(t,"data",e.output),""]}},"x:if":{pre:function(t,e){e.bind="false"!==e.bind&&"wrap",t.sys=e.bind,t.hidden=1,t.braces=1},code:function(t){return[o.expr(t,"if($test){"),Array(t.braces+1).join("}")]}},"x:else":{pre:function(t,e){t.hidden=1,t.pseudoRoot=!0,e["if"]&&(t.sys=1,t.parent.braces++,t.parent.parent.bind(t))},code:function(t,e){var n="";return e["if"]&&(n="if($if){"),["} else {\n"+o.expr(t,n),""]}},"x:each":{pre:function(t,e){t.sys=1,t.hidden=1,t.manualBind=!0,e.bind="both"},code:function(t,e){return t.binded=!0,["__buf.b(2, ["+o.xpath(t)+"],function(__xargs){"+o.tryCatch(t,"__xargs[0]="+e.data)+o.expr(t,"},function(&as,&key){"),o.expr(t,"},&filter);")]}},"x:eval":{pre:function(t,e){t.sys=1,t.hidden=1,t.manualBind=!0,e.bind=!0},code:function(t,e){return t.binded=!0,["__buf.b(0, ["+o.xpath(t)+"],function(__xargs){"+o.tryCatch(t,"__xargs[0]="+e.source)+"},function(__xsource){"+" var fn = xtpl.compile(__xsource, void 0, "+w.xml+");"+" fn(__buf, ctx);","});"]}},"x:attrs":function(){return["",""]},"x:attr":{pre:function(t){var e=t.parent,n=e.xattrs;if(void 0===n){if("x:attrs"===e.name)return;n=e.xattrs=t.create("x:attrs"),n.allow=e.attrs["x:attrs-bind"],n.hidden=1,e.append(n,e.first),delete e.attrs["x:attrs-bind"]}t.hidden=1,t.manualBind=null!=n.allow&&-1==n.allow.indexOf(t.attrs.name),n.append(t.remove())},code:function(t){var e=t.manualBind;return[e?o.str(" "+t.attrs.name+'="'):"",e?o.str('"'):""]}},"**":{pre:function(t,e){return void 0===e.bind||e.bind||(t.hidden=1),e.bind&&o.wrap(t)},code:function(t,e,n){var r,i,a=[""],s="",l=e.shorty;for(r in n)i=n[r],a.push(r+(i===!0?"":"="+o.lex(i)));return a=a.length>1?a.join(" "):"",e.xattrs&&(s+=o(e.xattrs,d,p),e.xattrs.remove()),a+=l?"/>":">",[o.str("<"+t)+s+o.str(a),l?"":o.str(""+t+">")]}}}),w.decl({"x:show":{compile:function(t,e){var n=t.attrs;n.style=(n.style||"")+";{{"+e+" ? '' : 'display: none;'}}"}},"x:hide":{compile:function(t,e){var n=t.attrs;n.style=(n.style||"")+";{{"+e+" ? 'display: none;' : ''}}"}},"x:fade-to":function(t,e){t.style.opacity=0|e},"x:autofocus":function(t,e){e&&setTimeout(function(){t.focus(),t.selectionStart=t.selectionEnd=t.value?t.value.length:0},1)}}),w.mod("css",function(t,e){return e?t:""}),r.isClient&&function(t,e){t.console=t.console||{},v(["log","time","timeEnd"],function(t){console[t]||(console[t]=function(){})}),e.createElement("x");var n=!1,r="load",i="onload",a="complete",o="DOMContentLoaded",s="onreadystatechange",l=function(){if(t.addEventListener)t.removeEventListener(r,l,!1),e.removeEventListener(o,l,!1);else{if(e.readyState!==a)return;t.detachEvent(i,l),e.detachEvent(s,l)}c()},c=function(){if(w.autoInit)for(var t,n,r,i,a,o,s=e.scripts,l=s.length;l--;)t=s[l],n=t.getAttribute("type"),r=t.getAttribute("x-ctrl")||t.getAttribute("x:ctrl")||t.getAttribute("ctrl"),(r||/x(ml|html)/.test(n))&&(i=e.createElement("div"),o=t.innerHTML,a=t.parentNode,/x(ml|html)/.test(n)?(o=""+o+"
",w.bind(i,o,{},!0)):(o="div"+(r?"[x-ctrl="+r+"]":"")+" {\n"+o+"\n}",w.bind(i,o)),a.insertBefore(i,t),a.removeChild(t))};if(e.readyState===a)setTimeout(l,1);else if(t.addEventListener)t.addEventListener(r,l,!1),e.addEventListener(o,l,!1);else{t.attachEvent(i,l),e.attachEvent(s,l);try{n=null==t.frameElement&&e.documentElement}catch(u){}n&&n.doScroll&&function f(){try{n.doScroll("left"),l()}catch(t){setTimeout(f,50)}}()}}(window,document),t.exports=w}),e("xtpl")});
\ No newline at end of file
diff --git a/support.html b/support.html
index cea84fe9..ba46f172 100644
--- a/support.html
+++ b/support.html
@@ -99,7 +99,8 @@
var files = FileAPI.getFiles(multipleFiles);
_uploadFiles(files);
});
-//
+
+
FileAPI.each(FileAPI.support, function (val, key){
_log('FileAPI.support.'+key+': '+val);
});
diff --git a/tests/files/1px.gif b/tests/files/1px.gif
new file mode 100644
index 00000000..ce1adc81
Binary files /dev/null and b/tests/files/1px.gif differ
diff --git a/tests/files/big.jpg b/tests/files/big.jpg
new file mode 100644
index 00000000..d0dc3b17
Binary files /dev/null and b/tests/files/big.jpg differ
diff --git a/tests/files/dino.png b/tests/files/dino.png
new file mode 100644
index 00000000..d435cfdb
Binary files /dev/null and b/tests/files/dino.png differ
diff --git a/tests/files/hello.txt b/tests/files/hello.txt
new file mode 100644
index 00000000..c056d7dd
--- /dev/null
+++ b/tests/files/hello.txt
@@ -0,0 +1 @@
+Hello FileAPI!
diff --git a/tests/files/image.jpg b/tests/files/image.jpg
new file mode 100644
index 00000000..a0876902
Binary files /dev/null and b/tests/files/image.jpg differ
diff --git a/tests/files/lebowski.json b/tests/files/lebowski.json
new file mode 100644
index 00000000..2c55e142
--- /dev/null
+++ b/tests/files/lebowski.json
@@ -0,0 +1,139 @@
+{
+ "adverts":[
+ {
+ "title":"Walter",
+ "text":"You see what happens, Larry?",
+ "url":"http://www.imdb.com/title/tt0118715/quotes"
+ },
+ {
+ "title":"Walter",
+ "text":"I don't roll on Shabbos!",
+ "url":"http://www.imdb.com/title/tt0118715/quotes"
+ },
+ {
+ "title":"Blond Thug",
+ "text":"Where's the money, Lebowski?",
+ "url":"http://www.imdb.com/title/tt0118715/quotes"
+ },
+ {
+ "title":"Nihilist",
+ "text":"We believe in nothing, Lebowski.",
+ "url":"http://www.imdb.com/title/tt0118715/quotes"
+ },
+ {
+ "title":"Walter",
+ "text":"Is this your homework, Larry?",
+ "url":"http://www.imdb.com/title/tt0118715/quotes"
+ },
+ {
+ "title":"Nihilist",
+ "text":"Ve vant ze money, Lebowski",
+ "url":"http://www.imdb.com/title/tt0118715/quotes"
+ }
+ ],
+
+ "sections":[
+ {
+ "id":1234,
+ "title":"The Dude",
+ "rip":0
+ },
+ {
+ "id":2345,
+ "title":"Walter Sobchak",
+ "rip":0
+ },
+ {
+ "id":3456,
+ "title":"Donny",
+ "rip":1
+ },
+ {
+ "id":4567,
+ "title":"Maude Lebowski",
+ "rip":0
+ },
+ {
+ "id":5678,
+ "title":"The Big Lebowski",
+ "rip":0
+ },
+ {
+ "id":6789,
+ "title":"Brandt",
+ "rip":0
+ },
+ {
+ "id":7890,
+ "title":"Jesus Quintana",
+ "rip":0
+ }
+ ],
+
+ "total":654329,
+ "online":[
+ { "name":"true" },
+ { "name":"false" },
+ { "name":"short" },
+ { "name":"long" },
+ { "name":"apha" },
+ { "name":"omega" },
+ { "name":"drag" },
+ { "name":"drop" },
+ { "name":"make" },
+ { "name":"clean" },
+ { "name":"east" },
+ { "name":"west" },
+ { "name":"up" },
+ { "name":"down" },
+ { "name":"sun" },
+ { "name":"rain" },
+ { "name":"secondary" },
+ { "name":"main" }
+ ],
+
+ "news":[
+ {
+ "time":"03:45",
+ "id":987,
+ "title":"The Stranger",
+ "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..."
+ },
+ {
+ "time":"03:48",
+ "id":876,
+ "title":"The Stranger",
+ "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."
+ },
+ {
+ "time":"03:50",
+ "id":765,
+ "title":"Walter Sobchak",
+ "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."
+ },
+ {
+ "time":"03:52",
+ "id":654,
+ "title":"The Dude",
+ "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?"
+ },
+ {
+ "time":"03:57",
+ "id":543,
+ "title":"Jesus Quintana",
+ "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!"
+ },
+ {
+ "time":"03:59",
+ "id":432,
+ "title":"Jesus Quintana",
+ "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.\""
+ },
+ {
+ "time":"04:01",
+ "id":321,
+ "title":"The Dude",
+ "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."
+ }
+ ]
+}
diff --git a/tests/files/samples/chrome-dino-180deg-50x50.png b/tests/files/samples/chrome-dino-180deg-50x50.png
new file mode 100644
index 00000000..a3a40676
Binary files /dev/null and b/tests/files/samples/chrome-dino-180deg-50x50.png differ
diff --git a/tests/files/samples/chrome-dino-50x50.jpeg b/tests/files/samples/chrome-dino-50x50.jpeg
new file mode 100644
index 00000000..a47fff6c
Binary files /dev/null and b/tests/files/samples/chrome-dino-50x50.jpeg differ
diff --git a/tests/files/samples/chrome-dino-90deg-100x100.png b/tests/files/samples/chrome-dino-90deg-100x100.png
new file mode 100644
index 00000000..5e1875ed
Binary files /dev/null and b/tests/files/samples/chrome-dino-90deg-100x100.png differ
diff --git a/tests/files/samples/chrome-dino-custom.png b/tests/files/samples/chrome-dino-custom.png
new file mode 100644
index 00000000..8b842fa2
Binary files /dev/null and b/tests/files/samples/chrome-dino-custom.png differ
diff --git a/tests/files/samples/chrome-image-auto-100x100.jpeg b/tests/files/samples/chrome-image-auto-100x100.jpeg
new file mode 100644
index 00000000..730f0418
Binary files /dev/null and b/tests/files/samples/chrome-image-auto-100x100.jpeg differ
diff --git a/tests/files/samples/firefox-dino-180deg-50x50.png b/tests/files/samples/firefox-dino-180deg-50x50.png
new file mode 100644
index 00000000..ab19c5f0
Binary files /dev/null and b/tests/files/samples/firefox-dino-180deg-50x50.png differ
diff --git a/tests/files/samples/firefox-dino-50x50.jpeg b/tests/files/samples/firefox-dino-50x50.jpeg
new file mode 100644
index 00000000..3cdbdd01
Binary files /dev/null and b/tests/files/samples/firefox-dino-50x50.jpeg differ
diff --git a/tests/files/samples/firefox-dino-90deg-100x100.png b/tests/files/samples/firefox-dino-90deg-100x100.png
new file mode 100644
index 00000000..de0e2d8b
Binary files /dev/null and b/tests/files/samples/firefox-dino-90deg-100x100.png differ
diff --git a/tests/files/samples/firefox-dino-custom.png b/tests/files/samples/firefox-dino-custom.png
new file mode 100644
index 00000000..3db115c7
Binary files /dev/null and b/tests/files/samples/firefox-dino-custom.png differ
diff --git a/tests/files/samples/firefox-image-auto-100x100.jpeg b/tests/files/samples/firefox-image-auto-100x100.jpeg
new file mode 100644
index 00000000..9044b945
Binary files /dev/null and b/tests/files/samples/firefox-image-auto-100x100.jpeg differ
diff --git a/tests/files/samples/firefox-vintage.png b/tests/files/samples/firefox-vintage.png
new file mode 100644
index 00000000..ac85e950
Binary files /dev/null and b/tests/files/samples/firefox-vintage.png differ
diff --git a/tests/files/samples/phantomjs-dino-180deg-50x50.png b/tests/files/samples/phantomjs-dino-180deg-50x50.png
new file mode 100644
index 00000000..ea77be04
Binary files /dev/null and b/tests/files/samples/phantomjs-dino-180deg-50x50.png differ
diff --git a/tests/files/samples/phantomjs-dino-50x50.jpeg b/tests/files/samples/phantomjs-dino-50x50.jpeg
new file mode 100644
index 00000000..64896540
Binary files /dev/null and b/tests/files/samples/phantomjs-dino-50x50.jpeg differ
diff --git a/tests/files/samples/phantomjs-dino-90deg-100x100.png b/tests/files/samples/phantomjs-dino-90deg-100x100.png
new file mode 100644
index 00000000..b8bf3b18
Binary files /dev/null and b/tests/files/samples/phantomjs-dino-90deg-100x100.png differ
diff --git a/tests/files/samples/phantomjs-dino-custom.png b/tests/files/samples/phantomjs-dino-custom.png
new file mode 100644
index 00000000..a61c4a36
Binary files /dev/null and b/tests/files/samples/phantomjs-dino-custom.png differ
diff --git a/tests/files/samples/phantomjs-image-auto-100x100.jpeg b/tests/files/samples/phantomjs-image-auto-100x100.jpeg
new file mode 100644
index 00000000..5b12df3a
Binary files /dev/null and b/tests/files/samples/phantomjs-image-auto-100x100.jpeg differ
diff --git a/tests/files/samples/phantomjs-vintage.png b/tests/files/samples/phantomjs-vintage.png
new file mode 100644
index 00000000..ac85e950
Binary files /dev/null and b/tests/files/samples/phantomjs-vintage.png differ
diff --git a/tests/files/samples/safari-dino-90deg-100x100.png b/tests/files/samples/safari-dino-90deg-100x100.png
new file mode 100644
index 00000000..de0e2d8b
Binary files /dev/null and b/tests/files/samples/safari-dino-90deg-100x100.png differ
diff --git a/tests/flash.html b/tests/flash.html
new file mode 100644
index 00000000..057ec35b
--- /dev/null
+++ b/tests/flash.html
@@ -0,0 +1,114 @@
+
+
+
+
+
+
+ FileAPI :: Tests
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+