From fdc34c4ef711f711b661971e476ad37ff2e1ee93 Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Tue, 18 Oct 2016 11:10:01 -0400 Subject: [PATCH] Box modifications --- .vs/config/applicationhost.config | 10 +- ...ntoryTraker.sln => InventoryTraker-Box.sln | 0 .../InventoryTraker.Web.Tests.csproj | 17 +- .../Models/InventoryAddForm.cs | 9 +- .../Services/InventoryImporterTests.cs | 63 + .../DistributionReportWriterTests.cs | 69 - .../Documents/InventoryData-BadDate.xlsx | Bin 0 -> 9340 bytes .../Documents/InventoryData-BadHeader.xlsx | Bin 0 -> 9326 bytes .../Documents/InventoryData-NoExtension | Bin 0 -> 9343 bytes .../Utilities/Documents/InventoryData.xlsx | Bin 0 -> 9343 bytes .../Documents/InventoryTypeData.xlsx | Bin 15257 -> 0 bytes .../Utilities/InventoryParserTests.cs | 44 + .../Utilities/InventoryReportWriterTests.cs | 11 +- .../Utilities/InventoryTypeParserTests.cs | 29 - .../Utilities/MovementReportWriterTests.cs | 76 - InventoryTraker.Web/App_Start/BundleConfig.cs | 3 + InventoryTraker.Web/App_Start/EFConfig.cs | 3 +- InventoryTraker.Web/App_Start/WebApiConfig.cs | 17 + .../Attributes/ExceptionHandlingAttribute.cs | 37 + .../Controllers/AuthenticationController.cs | 2 +- .../Controllers/HomeController.cs | 12 - .../Controllers/ImportController.cs | 103 + .../Controllers/InventoryController.cs | 87 +- .../Controllers/InventoryTypeController.cs | 86 - .../Controllers/ReportController.cs | 81 - .../Controllers/TransactionController.cs | 8 +- InventoryTraker.Web/Core/Inventory.cs | 20 +- InventoryTraker.Web/Core/InventoryType.cs | 25 - InventoryTraker.Web/Core/TransactionType.cs | 4 +- InventoryTraker.Web/Data/AppDbContext.cs | 2 - InventoryTraker.Web/Global.asax.cs | 4 +- .../InventoryTraker.Web.csproj | 65 +- .../201609201242047_Initial.Designer.cs | 29 - .../Migrations/201609201242047_Initial.cs | 156 - .../Migrations/201609201242047_Initial.resx | 126 - InventoryTraker.Web/Migrations/SeedData.cs | 173 +- .../Models/DistributionReport.cs | 13 - .../Models/InventoryAddForm.cs | 19 +- .../Models/InventoryDistributeForm.cs | 19 - .../Models/InventoryQuantityForm.cs | 2 +- .../Models/InventoryRemoveForm.cs | 2 +- .../Models/InventoryTypeViewModel.cs | 46 - .../Models/InventoryViewModel.cs | 35 +- InventoryTraker.Web/Models/MovementReport.cs | 11 - .../Models/MovementReportItem.cs | 13 - .../Models/TransactionViewModel.cs | 37 +- .../Properties/PublishProfiles/ETHRA.pubxml | 8 +- InventoryTraker.Web/Scripts/FileAPI.flash.swf | Bin 0 -> 71214 bytes InventoryTraker.Web/Scripts/FileAPI.js | 4313 +++++++++++++++++ InventoryTraker.Web/Scripts/FileAPI.min.js | 6 + .../Scripts/ng-file-upload-all.js | 2904 +++++++++++ .../Scripts/ng-file-upload-all.min.js | 4 + .../Scripts/ng-file-upload-shim.js | 421 ++ .../Scripts/ng-file-upload-shim.min.js | 2 + InventoryTraker.Web/Scripts/ng-file-upload.js | 2482 ++++++++++ .../Scripts/ng-file-upload.min.js | 3 + .../Services/ImportException.cs | 24 + .../Services/InventoryImporter.cs | 83 + InventoryTraker.Web/Services/ReportService.cs | 176 - .../Utilities/ControllerContextExtensions.cs | 10 +- .../Utilities/DistributionReportWriter.cs | 97 - .../Utilities/InventoryParser.cs | 44 + .../Utilities/InventoryReportWriter.cs | 14 +- .../Utilities/InventoryTypeParser.cs | 43 - .../Utilities/InventoryTypeReportWriter.cs | 50 - InventoryTraker.Web/Utilities/ModelHelper.cs | 44 + .../Utilities/MovementReportWriter.cs | 125 - InventoryTraker.Web/Views/Home/Index.cshtml | 14 - .../Views/Inventory/Index.cshtml | 19 +- .../Views/InventoryType/Index.cshtml | 17 - .../Views/Shared/_Layout.cshtml | 2 +- .../Views/Shared/_Navigation.cshtml | 13 +- .../Views/Transaction/Index.cshtml | 6 +- InventoryTraker.Web/Web.config | 2 +- InventoryTraker.Web/css/layout.css | 4 +- InventoryTraker.Web/js/app.js | 2 +- .../js/inventory/InventoryAddDirective.js | 43 +- .../inventory/InventoryDistributeDirective.js | 51 - .../js/inventory/InventoryEditDirective.js | 15 +- .../js/inventory/InventoryImportDirective.js | 54 + .../js/inventory/InventoryListController.js | 7 + .../js/inventory/InventoryListDirective.js | 6 +- .../js/inventory/inventoryInfoDirective.js | 8 +- .../js/inventory/inventoryRemoveDirective.js | 2 +- .../js/inventory/inventorySvc.js | 35 +- .../templates/inventoryAdd.tmpl.cshtml | 72 +- .../templates/inventoryDistribute.tmpl.cshtml | 63 - .../templates/inventoryEdit.tmpl.cshtml | 36 +- .../templates/inventoryImport.tmpl.cshtml | 32 + .../templates/inventoryInfo.tmpl.cshtml | 24 +- .../templates/inventoryList.tmpl.cshtml | 22 +- .../templates/inventoryRemove.tmpl.cshtml | 7 +- .../InventoryTypeAddDirective.js | 40 - .../inventoryType/InventoryTypeController.js | 24 - .../InventoryTypeEditDirective.js | 44 - .../InventoryTypeListDirective.js | 27 - .../js/inventoryType/inventoryTypeSvc.js | 45 - .../templates/inventoryTypeAdd.tmpl.cshtml | 27 - .../templates/inventoryTypeEdit.tmpl.cshtml | 27 - .../templates/inventoryTypeList.tmpl.cshtml | 24 - .../js/report/DistributionReportController.js | 18 - .../js/report/DistributionReportDirective.js | 19 - .../js/report/MovementReportController.js | 17 - .../js/report/MovementReportDirective.js | 29 - InventoryTraker.Web/js/report/reportSvc.js | 46 - .../templates/distributionReport.tmpl.cshtml | 26 - .../templates/movementReport.tmpl.cshtml | 47 - .../js/transaction/TransactionController.js | 37 +- InventoryTraker.Web/packages.config | 1 + 109 files changed, 11033 insertions(+), 2442 deletions(-) rename InventoryTraker.sln => InventoryTraker-Box.sln (100%) create mode 100644 InventoryTraker.Web.Tests/Services/InventoryImporterTests.cs delete mode 100644 InventoryTraker.Web.Tests/Utilities/DistributionReportWriterTests.cs create mode 100644 InventoryTraker.Web.Tests/Utilities/Documents/InventoryData-BadDate.xlsx create mode 100644 InventoryTraker.Web.Tests/Utilities/Documents/InventoryData-BadHeader.xlsx create mode 100644 InventoryTraker.Web.Tests/Utilities/Documents/InventoryData-NoExtension create mode 100644 InventoryTraker.Web.Tests/Utilities/Documents/InventoryData.xlsx delete mode 100644 InventoryTraker.Web.Tests/Utilities/Documents/InventoryTypeData.xlsx create mode 100644 InventoryTraker.Web.Tests/Utilities/InventoryParserTests.cs delete mode 100644 InventoryTraker.Web.Tests/Utilities/InventoryTypeParserTests.cs delete mode 100644 InventoryTraker.Web.Tests/Utilities/MovementReportWriterTests.cs create mode 100644 InventoryTraker.Web/App_Start/WebApiConfig.cs create mode 100644 InventoryTraker.Web/Attributes/ExceptionHandlingAttribute.cs delete mode 100644 InventoryTraker.Web/Controllers/HomeController.cs create mode 100644 InventoryTraker.Web/Controllers/ImportController.cs delete mode 100644 InventoryTraker.Web/Controllers/InventoryTypeController.cs delete mode 100644 InventoryTraker.Web/Controllers/ReportController.cs delete mode 100644 InventoryTraker.Web/Core/InventoryType.cs delete mode 100644 InventoryTraker.Web/Migrations/201609201242047_Initial.Designer.cs delete mode 100644 InventoryTraker.Web/Migrations/201609201242047_Initial.cs delete mode 100644 InventoryTraker.Web/Migrations/201609201242047_Initial.resx delete mode 100644 InventoryTraker.Web/Models/DistributionReport.cs delete mode 100644 InventoryTraker.Web/Models/InventoryDistributeForm.cs delete mode 100644 InventoryTraker.Web/Models/InventoryTypeViewModel.cs delete mode 100644 InventoryTraker.Web/Models/MovementReport.cs delete mode 100644 InventoryTraker.Web/Models/MovementReportItem.cs create mode 100644 InventoryTraker.Web/Scripts/FileAPI.flash.swf create mode 100644 InventoryTraker.Web/Scripts/FileAPI.js create mode 100644 InventoryTraker.Web/Scripts/FileAPI.min.js create mode 100644 InventoryTraker.Web/Scripts/ng-file-upload-all.js create mode 100644 InventoryTraker.Web/Scripts/ng-file-upload-all.min.js create mode 100644 InventoryTraker.Web/Scripts/ng-file-upload-shim.js create mode 100644 InventoryTraker.Web/Scripts/ng-file-upload-shim.min.js create mode 100644 InventoryTraker.Web/Scripts/ng-file-upload.js create mode 100644 InventoryTraker.Web/Scripts/ng-file-upload.min.js create mode 100644 InventoryTraker.Web/Services/ImportException.cs create mode 100644 InventoryTraker.Web/Services/InventoryImporter.cs delete mode 100644 InventoryTraker.Web/Services/ReportService.cs delete mode 100644 InventoryTraker.Web/Utilities/DistributionReportWriter.cs create mode 100644 InventoryTraker.Web/Utilities/InventoryParser.cs delete mode 100644 InventoryTraker.Web/Utilities/InventoryTypeParser.cs delete mode 100644 InventoryTraker.Web/Utilities/InventoryTypeReportWriter.cs create mode 100644 InventoryTraker.Web/Utilities/ModelHelper.cs delete mode 100644 InventoryTraker.Web/Utilities/MovementReportWriter.cs delete mode 100644 InventoryTraker.Web/Views/Home/Index.cshtml delete mode 100644 InventoryTraker.Web/Views/InventoryType/Index.cshtml delete mode 100644 InventoryTraker.Web/js/inventory/InventoryDistributeDirective.js create mode 100644 InventoryTraker.Web/js/inventory/InventoryImportDirective.js delete mode 100644 InventoryTraker.Web/js/inventory/templates/inventoryDistribute.tmpl.cshtml create mode 100644 InventoryTraker.Web/js/inventory/templates/inventoryImport.tmpl.cshtml delete mode 100644 InventoryTraker.Web/js/inventoryType/InventoryTypeAddDirective.js delete mode 100644 InventoryTraker.Web/js/inventoryType/InventoryTypeController.js delete mode 100644 InventoryTraker.Web/js/inventoryType/InventoryTypeEditDirective.js delete mode 100644 InventoryTraker.Web/js/inventoryType/InventoryTypeListDirective.js delete mode 100644 InventoryTraker.Web/js/inventoryType/inventoryTypeSvc.js delete mode 100644 InventoryTraker.Web/js/inventoryType/templates/inventoryTypeAdd.tmpl.cshtml delete mode 100644 InventoryTraker.Web/js/inventoryType/templates/inventoryTypeEdit.tmpl.cshtml delete mode 100644 InventoryTraker.Web/js/inventoryType/templates/inventoryTypeList.tmpl.cshtml delete mode 100644 InventoryTraker.Web/js/report/DistributionReportController.js delete mode 100644 InventoryTraker.Web/js/report/DistributionReportDirective.js delete mode 100644 InventoryTraker.Web/js/report/MovementReportController.js delete mode 100644 InventoryTraker.Web/js/report/MovementReportDirective.js delete mode 100644 InventoryTraker.Web/js/report/reportSvc.js delete mode 100644 InventoryTraker.Web/js/report/templates/distributionReport.tmpl.cshtml delete mode 100644 InventoryTraker.Web/js/report/templates/movementReport.tmpl.cshtml diff --git a/.vs/config/applicationhost.config b/.vs/config/applicationhost.config index 3ef54eb..f6b0b9d 100644 --- a/.vs/config/applicationhost.config +++ b/.vs/config/applicationhost.config @@ -163,12 +163,20 @@ - + + + + + + + + + diff --git a/InventoryTraker.sln b/InventoryTraker-Box.sln similarity index 100% rename from InventoryTraker.sln rename to InventoryTraker-Box.sln diff --git a/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj b/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj index 826cfed..67c0ee0 100644 --- a/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj +++ b/InventoryTraker.Web.Tests/InventoryTraker.Web.Tests.csproj @@ -59,10 +59,9 @@ + + - - - @@ -73,10 +72,20 @@ - + PreserveNewest + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - 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 - - - dbo - - \ No newline at end of file diff --git a/InventoryTraker.Web/Migrations/SeedData.cs b/InventoryTraker.Web/Migrations/SeedData.cs index c120034..826a056 100644 --- a/InventoryTraker.Web/Migrations/SeedData.cs +++ b/InventoryTraker.Web/Migrations/SeedData.cs @@ -1,12 +1,9 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Web; using InventoryTraker.Web.Core; using InventoryTraker.Web.Data; using InventoryTraker.Web.Identity; -using InventoryTraker.Web.Utilities; using Microsoft.AspNet.Identity; using Microsoft.AspNet.Identity.EntityFramework; @@ -14,7 +11,6 @@ namespace InventoryTraker.Web.Migrations { public static class SeedData { - public static void AddAdminRole() { using (var context = new AppDbContext()) @@ -80,32 +76,16 @@ namespace InventoryTraker.Web.Migrations if (!context.Inventories.Any()) { - AddInventoryTypes(context); + //AddInventory(context); context.SaveChanges(); - - AddInventory(context); - - context.SaveChanges(); - } - } - - private static void AddInventoryTypes(AppDbContext context) - { - var folder = HttpContext.Current.Server.MapPath("~/App_Data"); - var inventoryTypeFile = Path.Combine(folder, "InventoryTypeSeedData.xlsx"); - var parser = new InventoryTypeParser(new FileInfo(inventoryTypeFile)); - foreach (var inventoryType in parser.Parse()) - { - context.InventoryTypes.Add(inventoryType); } } private static void AddInventory(AppDbContext context) { var r = new Random(1); - var inventoryTypes = context.InventoryTypes.ToList(); - var counties = new List {"Blount County", "Morgan County", "Knox County", "Anderson County", "Claiborne County", "Campbell County"}; + var programs = new List { "Aging", "Corrections", "Employment", "Family", "Health", "Housing" }; var colors = new List {"Red", "Purple", "Blue", "Yellow", "White"}; for (var dd = DateTime.Today.AddYears(-4); dd < DateTime.Today.AddMonths(-2); dd = dd.AddMonths(3)) @@ -114,31 +94,33 @@ namespace InventoryTraker.Web.Migrations // add some inventory for (int i = 0; i < r.Next(5,10); i++) { - var inventoryType = inventoryTypes.ElementAt(r.Next(0, context.InventoryTypes.Count())); var addedDate = dd.AddDays(r.Next(-10, 10)); var expiration = addedDate.AddMonths(r.Next(2, 48)); var memo = r.Next(0, 3) > 0 - ? colors.ElementAt(r.Next(0, colors.Count)) + $" {inventoryType.ContainerType}" + ? colors.ElementAt(r.Next(0, colors.Count)) + " box" : string.Empty; - var quantity = r.Next(5, 112); + var quantity = 1; + var id = r.Next(10000000, 99999999).ToString(); + var program = programs.ElementAt(r.Next(0, programs.Count)); var addedTransaction = new Transaction { TransactionType = TransactionType.Added, AddedQuantity = quantity, - Memo = "Arrival", + Memo = "Added", CurrentQuantity = quantity, TransactionDate = addedDate, Timestamp = addedDate }; var inventory = new Inventory { - InventoryType = inventoryType, - ExpirationDate = expiration, + ShredReadyDate = expiration, AddedDate = addedDate, Memo = memo, Quantity = quantity, + ProgramName = program, + Id = id, Transactions = new List {addedTransaction} }; context.Inventories.Add(inventory); @@ -148,135 +130,8 @@ namespace InventoryTraker.Web.Migrations ExprireLossInventory(context, dd, r); dd = dd.AddDays(14); - // distribute some inventory - foreach (var destination in counties) - { - var availableInventories = - context.Inventories.Local.Where(i => i.Quantity > 0).ToList(); - - for ( - var i = r.Next(0, availableInventories.Count); - i < availableInventories.Count; - i += r.Next(1, availableInventories.Count / 2)) - { - var inventory = availableInventories.ElementAt(i); - if (inventory.ExpirationDate <= dd) - continue; - - var quantityRemoved = r.Next(1, inventory.Quantity + 1); - - var transMemo = $"Distributed to {destination}"; - var transType = TransactionType.Distributed; - - var distributeTransaction = new Transaction - { - TransactionType = transType, - RemovedQuantity = quantityRemoved, - CurrentQuantity = inventory.Quantity - quantityRemoved, - Inventory = inventory, - Memo = transMemo, - TransactionDate = dd, - Timestamp = dd, - Destination = destination - }; - inventory.Quantity = distributeTransaction.CurrentQuantity; - - inventory.Transactions.Add(distributeTransaction); - } - } ExprireLossInventory(context, dd, r); } - - //for (int i = 0; i < 200; i++) - //{ - // var inventoryType = inventoryTypes.ElementAt(r.Next(0, context.InventoryTypes.Count())); - // var addedDate = DateTime.Today.AddMonths(-r.Next(1, 24)); - // var expiration = addedDate.AddMonths(r.Next(2, 48)); - // var memo = - // r.Next(0,3) > 0 - // ? colors.ElementAt(r.Next(0, colors.Count)) + $" {inventoryType.ContainerType}" - // : string.Empty; - // var quantity = r.Next(5, 112); - - // var previousTransaction = new Transaction - // { - // TransactionType = TransactionType.Added, - // AddedQuantity = quantity, - // Memo = "Arrival", - // CurrentQuantity = quantity, - // TransactionDate = addedDate, - // Timestamp = addedDate - // }; - // var inventory = new Inventory - // { - // InventoryType = inventoryType, - // ExpirationDate = expiration, - // AddedDate = addedDate, - // Memo = memo, - // Quantity = quantity, - // Transactions = new List { previousTransaction} - // }; - // context.Inventories.Add(inventory); - - // for (int j = 0; j < 5 && previousTransaction.CurrentQuantity > 0; j++) - // { - // var transactionDate = previousTransaction.TransactionDate.AddDays(r.Next(1, 100)); - // if (transactionDate >= DateTime.Today) - // break; - - // Transaction transaction; - // if (transactionDate >= expiration) - // { - // if (r.Next(1, 3) == 1) - // break; - - //transaction = new Transaction - //{ - // TransactionType = TransactionType.Expired, - // RemovedQuantity = previousTransaction.CurrentQuantity, - // CurrentQuantity = 0, - // Inventory = inventory, - // Memo = $"Expired on {expiration.ToShortDateString()}", - // TransactionDate = transactionDate, - // Timestamp = transactionDate - //}; - // } - // else - // { - // var quantityRemoved = r.Next(1, 100); - // if (quantityRemoved > previousTransaction.CurrentQuantity) - // quantityRemoved = previousTransaction.CurrentQuantity; - - // var destination = counties.ElementAt(r.Next(0, counties.Count)); - // var transMemo = $"Distributed to {destination}"; - // var transType = TransactionType.Distributed; - - // if (r.Next(1, 15) == 1) - // { - // transMemo = "Loss"; - // transType = TransactionType.Loss; - // } - - // transaction = new Transaction - // { - // TransactionType = transType, - // RemovedQuantity = quantityRemoved, - // CurrentQuantity = previousTransaction.CurrentQuantity - quantityRemoved, - // Inventory = inventory, - // Memo = transMemo, - // TransactionDate = transactionDate, - // Timestamp = transactionDate, - // Destination = destination - // }; - // } - - // inventory.Quantity = transaction.CurrentQuantity; - - // inventory.Transactions.Add(transaction); - - // previousTransaction = transaction; - // } - //} } private static void ExprireLossInventory(AppDbContext context, DateTime dd, Random r) @@ -286,15 +141,15 @@ namespace InventoryTraker.Web.Migrations foreach (var inventory in availableInventories) { - if (inventory.ExpirationDate <= dd && r.Next(0, 4) > 0) + if (inventory.ShredReadyDate <= dd && r.Next(0, 4) > 0) { var expiredTransaction = new Transaction { - TransactionType = TransactionType.Expired, + TransactionType = TransactionType.Shreded, RemovedQuantity = inventory.Quantity, CurrentQuantity = 0, Inventory = inventory, - Memo = $"Expired on {inventory.ExpirationDate.ToShortDateString()}", + Memo = $"Expired on {inventory.ShredReadyDate.ToShortDateString()}", TransactionDate = dd, Timestamp = dd }; @@ -303,7 +158,7 @@ namespace InventoryTraker.Web.Migrations } else if (r.Next(0, 40) == 0) { - var lossQty = r.Next(1, inventory.Quantity + 1); + var lossQty = 1; var lossTransaction = new Transaction { TransactionType = TransactionType.Loss, diff --git a/InventoryTraker.Web/Models/DistributionReport.cs b/InventoryTraker.Web/Models/DistributionReport.cs deleted file mode 100644 index 7eb067a..0000000 --- a/InventoryTraker.Web/Models/DistributionReport.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; - -namespace InventoryTraker.Web.Models -{ - public class DistributionReport - { - public TransactionViewModel[] Transactions; - - public string Destination { get; set; } - - public DateTime Date { get; set; } - } -} \ No newline at end of file diff --git a/InventoryTraker.Web/Models/InventoryAddForm.cs b/InventoryTraker.Web/Models/InventoryAddForm.cs index 04f7bd3..65e25ce 100644 --- a/InventoryTraker.Web/Models/InventoryAddForm.cs +++ b/InventoryTraker.Web/Models/InventoryAddForm.cs @@ -1,6 +1,5 @@ using System; using System.ComponentModel.DataAnnotations; -using System.Web.Mvc; using AutoMapper; using InventoryTraker.Web.Core; @@ -8,17 +7,20 @@ namespace InventoryTraker.Web.Models { public class InventoryAddForm { - [HiddenInput(DisplayValue = false)] [Required] - public int InventoryTypeId { get; set; } + public string Id { get; set; } [Required] - public DateTime ExpirationDate { get; set; } + public string ProgramName { get; set; } - [Required, Range(1, int.MaxValue, ErrorMessage = "Quantity must be greater than 0")] - public int Quantity { get; set; } + public string ProgramSubtype { get; set; } - [Required, Display(Name = "Arrival Date")] + public string Description { get; set; } + + [Required] + public DateTime ShredReadyDate { get; set; } + + [Required] public DateTime AddedDate { get; set; } public string Memo { get; set; } @@ -27,7 +29,8 @@ namespace InventoryTraker.Web.Models { public AutoMapperProfile() { - CreateMap(); + CreateMap() + .AfterMap((form, inventory) => { inventory.Quantity = 1; }); } } } diff --git a/InventoryTraker.Web/Models/InventoryDistributeForm.cs b/InventoryTraker.Web/Models/InventoryDistributeForm.cs deleted file mode 100644 index 7ff4240..0000000 --- a/InventoryTraker.Web/Models/InventoryDistributeForm.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; - -namespace InventoryTraker.Web.Models -{ - public class InventoryDistributeForm - { - public IList InventoryQuantities { get; set; } - - [Required] - public string Destination { get; set; } - - [Required, Display(Name = "Distributed Date")] - public DateTime DistributedDate { get; set; } - - public string Memo { get; set; } - } -} \ No newline at end of file diff --git a/InventoryTraker.Web/Models/InventoryQuantityForm.cs b/InventoryTraker.Web/Models/InventoryQuantityForm.cs index 2afa59a..0208b64 100644 --- a/InventoryTraker.Web/Models/InventoryQuantityForm.cs +++ b/InventoryTraker.Web/Models/InventoryQuantityForm.cs @@ -5,7 +5,7 @@ namespace InventoryTraker.Web.Models public class InventoryQuantityForm { [Required] - public int InventoryId { get; set; } + public string InventoryId { get; set; } [Required, Range(1, int.MaxValue, ErrorMessage = "Quantity must be greater than 0")] public int Quantity { get; set; } diff --git a/InventoryTraker.Web/Models/InventoryRemoveForm.cs b/InventoryTraker.Web/Models/InventoryRemoveForm.cs index e76efe7..e38e57b 100644 --- a/InventoryTraker.Web/Models/InventoryRemoveForm.cs +++ b/InventoryTraker.Web/Models/InventoryRemoveForm.cs @@ -7,7 +7,7 @@ namespace InventoryTraker.Web.Models public class InventoryRemoveForm { [Required] - public int InventoryId { get; set; } + public string InventoryId { get; set; } [Required, Range(1, int.MaxValue, ErrorMessage = "Quantity must be greater than 0")] public int Quantity { get; set; } diff --git a/InventoryTraker.Web/Models/InventoryTypeViewModel.cs b/InventoryTraker.Web/Models/InventoryTypeViewModel.cs deleted file mode 100644 index 6d61376..0000000 --- a/InventoryTraker.Web/Models/InventoryTypeViewModel.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Web.Mvc; -using AutoMapper; -using InventoryTraker.Web.Core; - -namespace InventoryTraker.Web.Models -{ - public class InventoryTypeViewModel - { - [HiddenInput] - public int Id { get; set; } - - [Required] - public string Identifier { get; set; } - - [Required] - public string Name { get; set; } - - [Required] - public int UnitsPerCase { get; set; } - - [Required] - public string ContainerType { get; set; } - - [HiddenInput] - public string UnitsPerCaseContainerType - { - get { return $"{UnitsPerCase} / {ContainerType}"; } - } - - [Required] - public double WeightPerCase { get; set; } - - [Required] - public decimal PricePerCase { get; set; } - - public class AutoMapperProfile : Profile - { - public AutoMapperProfile() - { - CreateMap(); - CreateMap(); - } - } - } -} \ No newline at end of file diff --git a/InventoryTraker.Web/Models/InventoryViewModel.cs b/InventoryTraker.Web/Models/InventoryViewModel.cs index 71cac11..122913a 100644 --- a/InventoryTraker.Web/Models/InventoryViewModel.cs +++ b/InventoryTraker.Web/Models/InventoryViewModel.cs @@ -6,42 +6,27 @@ namespace InventoryTraker.Web.Models { public class InventoryViewModel { - public int Id { get; set; } + public string Id { get; set; } - public int InventoryTypeId { get; set; } - - public string Name { get; set; } - - public int UnitsPerCase { get; set; } - - public string ContainerType { get; set; } - - public double WeightPerCase { get; set; } - - public decimal PricePerCase { get; set; } - - public int Quantity { get; set; } - - public DateTime ExpirationDate { get; set; } + public DateTime ShredReadyDate { get; set; } public DateTime AddedDate { get; set; } + public int Quantity { get; set; } + public string Memo { get; set; } - public bool IsExpired => ExpirationDate < DateTime.Today; + public string ProgramName { get; set; } + + public string ProgramSubtype { get; set; } + + public string Description { get; set; } public class AutoMapperProfile : Profile { public AutoMapperProfile() { - CreateMap() - .ForMember(d => d.InventoryTypeId, opt => opt.MapFrom(s => s.InventoryType.Id)) - .ForMember(d => d.Name, opt => opt.MapFrom(s => s.InventoryType.Name)) - .ForMember(d => d.UnitsPerCase, opt => opt.MapFrom(s => s.InventoryType.UnitsPerCase)) - .ForMember(d => d.ContainerType, opt => opt.MapFrom(s => s.InventoryType.ContainerType)) - .ForMember(d => d.WeightPerCase, opt => opt.MapFrom(s => s.InventoryType.WeightPerCase)) - .ForMember(d => d.PricePerCase, opt => opt.MapFrom(s => s.InventoryType.PricePerCase)) - .ForMember(d => d.AddedDate, opt => opt.MapFrom(s => s.AddedDate)); + CreateMap(); } } } diff --git a/InventoryTraker.Web/Models/MovementReport.cs b/InventoryTraker.Web/Models/MovementReport.cs deleted file mode 100644 index 24c8ca3..0000000 --- a/InventoryTraker.Web/Models/MovementReport.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; - -namespace InventoryTraker.Web.Models -{ - public class MovementReport - { - public DateTime Month { get; set; } - public IEnumerable Items { get; set; } - } -} \ No newline at end of file diff --git a/InventoryTraker.Web/Models/MovementReportItem.cs b/InventoryTraker.Web/Models/MovementReportItem.cs deleted file mode 100644 index 629dae4..0000000 --- a/InventoryTraker.Web/Models/MovementReportItem.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace InventoryTraker.Web.Models -{ - public class MovementReportItem - { - public InventoryTypeViewModel InventoryType { get; set; } - public int BeginningQuantity { get; set; } - public int AddedQuantity { get; set; } - public int TotalAvailableQuantity { get; set; } - public int DistributedQuantity { get; set; } - public int AdjustmentQuantity { get; set; } - public int EndingQuantity { get; set; } - } -} \ No newline at end of file diff --git a/InventoryTraker.Web/Models/TransactionViewModel.cs b/InventoryTraker.Web/Models/TransactionViewModel.cs index 8475b85..4b7a341 100644 --- a/InventoryTraker.Web/Models/TransactionViewModel.cs +++ b/InventoryTraker.Web/Models/TransactionViewModel.cs @@ -7,16 +7,19 @@ namespace InventoryTraker.Web.Models { public class TransactionViewModel { - [Required] public int Id { get; set; } - public int InventoryId { get; set; } - public string Name { get; set; } - public int UnitsPerCase { get; set; } - public string ContainerType { get; set; } - public DateTime ExpirationDate { get; set; } + public string InventoryId { get; set; } + + public string ProgramName { get; set; } + + public string ProgramSubtype { get; set; } + + public string Description { get; set; } + + public DateTime ShredReadyDate { get; set; } + public DateTime AddedDate { get; set; } - public double WeightPerCase { get; set; } public string TransactionType { get; set; } @@ -32,8 +35,6 @@ namespace InventoryTraker.Web.Models public string Memo { get; set; } - public string Destination { get; set; } - public DateTime Timestamp { get; set; } public class AutoMapperProfile : Profile @@ -43,18 +44,16 @@ namespace InventoryTraker.Web.Models CreateMap() .ForMember(d => d.InventoryId, opt => opt.MapFrom(s => s.Inventory.Id)) - .ForMember(d => d.Name, - opt => opt.MapFrom(s => s.Inventory.InventoryType.Name)) - .ForMember(d => d.ExpirationDate, - opt => opt.MapFrom(s => s.Inventory.ExpirationDate)) + .ForMember(d => d.ProgramName, + opt => opt.MapFrom(s => s.Inventory.ProgramName)) + .ForMember(d => d.ProgramSubtype, + opt => opt.MapFrom(s => s.Inventory.ProgramSubtype)) + .ForMember(d => d.Description, + opt => opt.MapFrom(s => s.Inventory.Description)) + .ForMember(d => d.ShredReadyDate, + opt => opt.MapFrom(s => s.Inventory.ShredReadyDate)) .ForMember(d => d.AddedDate, opt => opt.MapFrom(s => s.Inventory.AddedDate)) - .ForMember(d => d.UnitsPerCase, - opt => opt.MapFrom(s => s.Inventory.InventoryType.UnitsPerCase)) - .ForMember(d => d.ContainerType, - opt => opt.MapFrom(s => s.Inventory.InventoryType.ContainerType)) - .ForMember(d => d.WeightPerCase, - opt => opt.MapFrom(s => s.Inventory.InventoryType.WeightPerCase)) .ForMember(d => d.TransactionType, opt => opt.MapFrom(s => s.TransactionType.ToString())) .ForMember(d => d.PreviousQuantity, diff --git a/InventoryTraker.Web/Properties/PublishProfiles/ETHRA.pubxml b/InventoryTraker.Web/Properties/PublishProfiles/ETHRA.pubxml index 530d421..48c9ce9 100644 --- a/InventoryTraker.Web/Properties/PublishProfiles/ETHRA.pubxml +++ b/InventoryTraker.Web/Properties/PublishProfiles/ETHRA.pubxml @@ -11,13 +11,13 @@ by editing this MSBuild file. In order to learn more about this please visit htt True False - C:\Users\poprhythm\Documents\code\PublishPackages\InventoryTraker.Web.zip + C:\Users\poprhythm\Documents\code\PublishPackages\InventoryTraker-Box.Web.zip true - Default Web Site + InventoryTraker-Box - + @@ -27,7 +27,7 @@ by editing this MSBuild file. In order to learn more about this please visit htt - Data Source=localhost;Initial Catalog=InventoryTraker;User Id=InventoryTrakerUser;Password=QcXxvpztGp1;Connect Timeout=60 + Data Source=localhost;Initial Catalog=InventoryTraker-Box;User Id=InventoryTraker-BoxUser;Password=QcXxvpztGp3;Connect Timeout=60 \ No newline at end of file diff --git a/InventoryTraker.Web/Scripts/FileAPI.flash.swf b/InventoryTraker.Web/Scripts/FileAPI.flash.swf new file mode 100644 index 0000000000000000000000000000000000000000..65de396c797dcf7a63cc5b05f8424f4f985ba242 GIT binary patch literal 71214 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 EeqW}N^ literal 0 HcmV?d00001 diff --git a/InventoryTraker.Web/Scripts/FileAPI.js b/InventoryTraker.Web/Scripts/FileAPI.js new file mode 100644 index 0000000..5caa110 --- /dev/null +++ b/InventoryTraker.Web/Scripts/FileAPI.js @@ -0,0 +1,4313 @@ +/*! FileAPI 2.0.7 - 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, + + // 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\//i.test(userAgent) && !/chrome\//i.test(userAgent) && /windows/i.test(userAgent)), // 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), + + // 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, + + + 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.0.7', + + cors: false, + html5: true, + media: false, + formData: true, + multiPassResize: true, + + 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 (){ + // ngf fix for IE8 #1071 + if( api.debug && api._supportConsoleLog ){ + if( api._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.isFile(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 = [] + , dataTransfer = _getDataTransfer(evt) + , entrySupport = _isArray(dataTransfer.items) && dataTransfer.items[0] && _getAsEntry(dataTransfer.items[0]) + , queue = api.queue(function (){ callback(files); }) + ; + + _each((entrySupport ? dataTransfer.items : dataTransfer.files) || [], function (item){ + queue.inc(); + + try { + if( entrySupport ){ + _readEntryAsFiles(item, function (err, entryFiles){ + if( err ){ + api.log('[err] getDropFiles:', err); + } else { + files.push.apply(files, entryFiles); + } + queue.next(); + }); + } + else { + _isRegularFile(item, function (yes){ + yes && files.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.isFile(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: error + 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(as){ + return FileReader && !!FileReader.prototype['readAs'+as]; + } + + + function _readAs(file, fn, as, encoding){ + if( api.isBlob(file) && _hasSupportReadAs(as) ){ + 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'+as](file, encoding); + } + else { + Reader['readAs'+as](file); + } + } + catch (err){ + _emit(file, fn, 'error', undef, { error: err.toString() }); + } + } + else { + _emit(file, fn, 'error', undef, { error: 'FileReader_not_support_'+as }); + } + } + + + 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( FileReader ){ + try { + var Reader = new FileReader(); + + _one(Reader, _readerEvents, function (evt){ + var isFile = evt.type != 'error'; + callback(isFile); + if( isFile ){ + Reader.abort(); + } + }); + + Reader.readAsDataURL(file); + } catch( err ){ + callback(false); + } + } + else { + callback(null); + } + } + else { + callback(true); + } + } + + + 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 + callback('invalid entry'); + } + else if( entry.isFile ){ + // Read as file + entry.file(function(file){ + // success + file.fullPath = entry.fullPath; + callback(false, [file]); + }, function (err){ + // error + callback('FileError.code: '+err.code); + }); + } + else if( entry.isDirectory ){ + var reader = entry.createReader(), result = []; + + reader.readEntries(function(entries){ + // success + api.afor(entries, function (next, entry){ + _readEntryAsFiles(entry, function (err, files){ + if( err ){ + api.log(err); + } + else { + result = result.concat(files); + } + + if( next ){ + next(); + } + else { + callback(false, result); + } + }); + }); + }, function (err){ + // error + callback('directory_reader: ' + err); + }); + } + 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; + onHover.call(evt[currentTarget], false, evt); + + api.getDropFiles(evt, function (files){ + onDrop.call(evt[currentTarget], files, 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 { + api._supportConsoleLog = !!console.log; + api._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/.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 + ); + 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 ){ + 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 mexapixel 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){ + next(file, data, queue, arg); + }); + + 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){ + // Start uploading + options.upload(options, _this); + _this._send.call(_this, options, data); + }, options); + }, + + _send: function (options, data){ + var _this = this, xhr, uid = _this.uid, onloadFuncName = _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[onloadFuncName] = 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[onloadFuncName] = 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 = '

' + + '' + + (jsonp && (options.url.indexOf('=?') < 0) ? '' : '') + + '
' + ; + + // 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 + form.submit(); + 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('POST', url, true); + + 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 ){ + try { + for( var k in _xhrResponsePostfix ){ + _this['response'+k] = xhr['response'+k]; + } + }catch(_){} + 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 + video.src = URL.createObjectURL(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(); + this.stream.stop(); + } catch( 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 ){ + // 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'); + }; + + + /** + * @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( e ){} + 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) + ) + && (function (){ + var + _attr = api.uid() + , _retry = 0 + , _files = {} + , _rhttp = /^https?:/i + + , flash = { + _fn: {}, + + + /** + * 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); + }, + + + /** + * 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: 1e6+'' // 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); + } + }, + + + 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) ); + }, + + disableMouseover: false, + + mouseover: function (evt){ + if (!flash.disableMouseover) { + 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: + // i — 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: 1e6+'' // 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); + } + }, + mouseDown: function(evt) { + flash.disableMouseover = true; + }, + cancel: function(evt) { + flash.disableMouseover = false; + }, + 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){ + try { + 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); + } + } finally { + flash.disableMouseover = false; + } + }, + + interval: null, + cmd: function (id, name, data, last) { + if (flash.uploadInProgress && flash.readInProgress) { + setTimeout(function() { + flash.cmd(id, name, data, last); + }, 100); + } else { + this.cmdFn(id, name, data, last); + } + }, + + cmdFn: function(id, name, data, last) { + try { + api.log('(js -> flash).'+name+':', data); + return flash.get(id.flashId || id).cmd(name, data); + } catch (e){ + api.log('(js -> flash).onError:', e); + if( !last ){ + // try again + setTimeout(function (){ flash.cmd(id, name, data, true); }, 50); + } + } + }, + + patch: function (){ + api.flashEngine = true; + + // FileAPI + _inherit(api, { + readAsDataURL: function (file, callback){ + if( _isHtmlFile(file) ){ + this.parent.apply(this, arguments); + } + else { + api.log('FlashAPI.readAsBase64'); + flash.readInProgress = true; + flash.cmd(file, 'readAsBase64', { + id: file.id, + callback: _wrap(function _(err, base64){ + flash.readInProgress = false; + _unwrap(_); + + api.log('FlashAPI.readAsBase64:', err); + + callback({ + type: err ? 'error' : 'load' + , error: err + , result: 'data:'+ file.type +';base64,'+ base64 + }); + }) + }); + } + }, + + readAsText: function (file, encoding, callback){ + if( callback ){ + api.log('[warn] FlashAPI.readAsText not supported `encoding` param'); + } else { + callback = encoding; + } + + api.readAsDataURL(file, function (evt){ + if( evt.type == 'load' ){ + try { + evt.result = window.atob(evt.result.split(';base64,')[1]); + } catch( err ){ + evt.type = 'error'; + evt.error = err.toString(); + } + } + callback(evt); + }); + }, + + 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); +// }) +// }); + defer.resolve(null, file.info = null); + + } + + 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.uploadInProgress = false; flash.cmd(flashId, 'abort', { id: fileId }); }, + getResponseHeader: function (name){ return this.headers[name]; }, + getAllResponseHeaders: function (){ return this.headers; } + }; + + var queue = api.queue(function (){ + flash.uploadInProgress = true; + 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' ){ + flash.uploadInProgress = false; + _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' ){ + flash.uploadInProgress = false; + _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 + ; + + function getOffset(obj) { + var left, top; + left = top = 0; + if (obj.offsetParent) { + do { + left += obj.offsetLeft; + top += obj.offsetTop; + } while (obj = obj.offsetParent); + } + return { + left : left, + top : top + }; + }; + + return { + top: getOffset(el).top + , left: getOffset(el).left + , width: el.offsetWidth + , height: el.offsetHeight + }; + } + + // @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)) { + (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/InventoryTraker.Web/Scripts/FileAPI.min.js b/InventoryTraker.Web/Scripts/FileAPI.min.js new file mode 100644 index 0000000..222d3fc --- /dev/null +++ b/InventoryTraker.Web/Scripts/FileAPI.min.js @@ -0,0 +1,6 @@ +/*! 12.2.12 */ +/*! FileAPI 2.0.7 - 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. + */ +!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;hd;d++)d in a&&b.call(c,a[d],d,a);else for(var f in a)a.hasOwnProperty(f)&&b.call(c,a[f],f,a)},S=function(a){for(var b=arguments,c=1,d=function(b,c){a[c]=b};c=c&&!d&&f.end()},isFail:function(){return d},fail:function(){!d&&a(d=!0)},end:function(){e||(e=!0,a())}};return f},each:R,afor:function(a,b){var c=0,d=a.length;Q(a)&&d--?!function e(){b(d!=c&&e,a[c],c++)}():b(!1)},extend:S,isFile:function(a){return"[object File]"===H.call(a)},isBlob:function(a){return this.isFile(a)||"[object Blob]"===H.call(a)},isCanvas:function(a){return a&&D.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){Y.isCanvas(a)?c(a,b,"load",Y.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=Y.toBinaryString(a.result)}catch(c){a.type="error",a.message=c.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(Y.toDataURL(b).replace(G,""))},readAsImage:function(a,d,e){if(Y.isFile(a))if(r){var f=r.createObjectURL(a);f===b?c(a,d,"error"):Y.readAsImage(f,d,e)}else Y.readAsDataURL(a,function(b){"load"==b.type?Y.readAsImage(b.result,d,e):(e||"error"==b.type)&&c(a,d,b,null,{loaded:b.loaded,total:b.total})});else if(Y.isCanvas(a))c(a,d,"load",a);else if(C.test(a.nodeName))if(a.complete)c(a,d,"load",a);else{var g="error abort load";V(a,g,function i(b){"load"==b.type&&r&&r.revokeObjectURL(a.src),U(a,g,i),c(a,d,b,a)})}else if(a.iframe)c(a,d,{type:"error"});else{var h=Y.newImage(a.dataURL||a);Y.readAsImage(h,d,e)}},checkFileObj:function(a){var b={},c=Y.accept;return"object"==typeof a?b=a:b.name=(a+"").split(/\\|\//g).pop(),null==b.type&&(b.type=b.name.split(".").pop()),R(c,function(a,c){a=new RegExp(a.replace(/\s/g,"|"),"i"),(a.test(b.type)||Y.ext2mime[b.type])&&(b.type=Y.ext2mime[b.type]||c.split("/")[0]+"/"+b.type)}),b},getDropFiles:function(a,b){var c=[],d=k(a),e=Q(d.items)&&d.items[0]&&g(d.items[0]),i=Y.queue(function(){b(c)});R((e?d.items:d.files)||[],function(a){i.inc();try{e?h(a,function(a,b){a?Y.log("[err] getDropFiles:",a):c.push.apply(c,b),i.next()}):f(a,function(b){b&&c.push(a),i.next()})}catch(b){i.next(),Y.log("[err] getDropFiles: ",b)}}),i.check()},getFiles:function(a,b,c){var d=[];return c?(Y.filterFiles(Y.getFiles(a),b,c),null):(a.jquery&&(a.each(function(){d=d.concat(Y.getFiles(this))}),a=d,d=[]),"string"==typeof b&&(b=Y.getFilesFilter(b)),a.originalEvent?a=W(a.originalEvent):a.srcElement&&(a=W(a)),a.dataTransfer?a=a.dataTransfer:a.target&&(a=a.target),a.files?(d=a.files,y||(d[0].blob=a,d[0].iframe=!0)):!y&&j(a)?Y.trim(a.value)&&(d=[Y.checkFileObj(a.value)],d[0].blob=a,d[0].iframe=!0):Q(a)&&(d=a),Y.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=L.concat();Y.isFile(a)?!function e(){var f=d.shift();f?f.test(a.type)?f(a,function(a,d){a?b(a):(S(c,d),e())}):e():b(!1,c)}():b("not_support_info",c)},addInfoReader:function(a,b){b.test=function(b){return a.test(b)},L.push(b)},filter:function(a,b){for(var c,d=[],e=0,f=a.length;f>e;e++)e in a&&(c=a[e],b.call(c,c,e,a)&&d.push(c));return d},filterFiles:function(a,b,c){if(a.length){var d,e=a.concat(),f=[],g=[];!function h(){e.length?(d=e.shift(),Y.getInfo(d,function(a,c){(b(d,a?!1:c)?f:g).push(d),h()})):c(f,g)}()}else c([],a)},upload:function(a){a=S({jsonp:"callback",prepare:Y.F,beforeupload:Y.F,upload:Y.F,fileupload:Y.F,fileprogress:Y.F,filecomplete:Y.F,progress:Y.F,complete:Y.F,pause:Y.F,imageOriginal:!0,chunkSize:Y.chunkSize,chunkUploadRetry:Y.chunkUploadRetry,uploadRetry:Y.uploadRetry},a),a.imageAutoOrientation&&!a.imageTransform&&(a.imageTransform={rotate:"auto"});var b,c=new Y.XHR(a),d=this._getFilesDataArray(a.files),e=this,f=0,g=0,h=!1;return R(d,function(a){f+=a.size}),c.files=[],R(d,function(a){c.files.push(a.file)}),c.total=f,c.loaded=0,c.filesLeft=d.length,a.beforeupload(c,a),b=function(){var j=d.shift(),k=j&&j.file,l=!1,m=i(a);if(c.filesLeft=d.length,k&&k.name===Y.expando&&(k=null,Y.log("[warn] FileAPI.upload() — called without files")),("abort"!=c.statusText||c.current)&&j){if(h=!1,c.currentFile=k,k&&a.prepare(k,m)===!1)return void b.call(e);m.file=k,e._getFormData(m,j,function(h){g||a.upload(c,a);var i=new Y.XHR(S({},m,{upload:k?function(){a.fileupload(k,i,m)}:n,progress:k?function(b){l||(l=b.loaded===b.total,a.fileprogress({type:"progress",total:j.total=b.total,loaded:j.loaded=b.loaded},k,i,m),a.progress({type:"progress",total:f,loaded:c.loaded=g+j.size*(b.loaded/b.total)|0},k,i,m))}:n,complete:function(d){R(N,function(a){c[a]=i[a]}),k&&(j.total=j.total||j.size,j.loaded=j.total,d||(this.progress(j),l=!0,g+=j.size,c.loaded=g),a.filecomplete(d,i,k,m)),setTimeout(function(){b.call(e)},0)}}));c.abort=function(a){a||(d.length=0),this.current=a,i.abort()},i.send(h)})}else{var o=200==c.status||201==c.status||204==c.status;a.complete(o?!1:c.statusText||"error",c,a),h=!0}},setTimeout(b,0),c.append=function(a,g){a=Y._getFilesDataArray([].concat(a)),R(a,function(a){f+=a.size,c.files.push(a.file),g?d.unshift(a):d.push(a)}),c.statusText="",h&&b.call(e)},c.remove=function(a){for(var b,c=d.length;c--;)d[c].file==a&&(b=d.splice(c,1),f-=b.size);return b},c},_getFilesDataArray:function(a){var b=[],c={};if(j(a)){var d=Y.getFiles(a);c[a.name||"file"]=null!==a.getAttribute("multiple")?d:d[0]}else Q(a)&&j(a[0])?R(a,function(a){c[a.name||"file"]=Y.getFiles(a)}):c=a;return R(c,function e(a,c){Q(a)?R(a,function(a){e(a,c)}):a&&(a.name||a.image)&&b.push({name:c,file:a,size:a.size,total:a.size,loaded:0})}),b.length||b.push({file:{name:Y.expando}}),b},_getFormData:function(a,b,c){var d=b.file,e=b.name,f=d.name,g=d.type,h=Y.support.transform&&a.imageTransform,i=new Y.Form,j=Y.queue(function(){c(i)}),k=h&&l(h),m=Y.postNameConcat;R(a.data,function n(a,b){"object"==typeof a?R(a,function(a,c){n(a,m(b,c))}):i.append(b,a)}),function o(b){b.image?(j.inc(),b.toData(function(a,b){f=f||(new Date).getTime()+".png",o(b),j.next()})):Y.Image&&h&&(/^image/.test(b.type)||E.test(b.nodeName))?(j.inc(),k&&(h=[h]),Y.Image.transform(b,h,a.imageAutoOrientation,function(c,d){if(k&&!c)B||Y.flashEngine||(i.multipart=!0),i.append(e,d[0],f,h[0].type||g);else{var l=0;c||R(d,function(a,b){B||Y.flashEngine||(i.multipart=!0),h[b].postName||(l=1),i.append(h[b].postName||m(e,b),a,f,h[b].type||g)}),(c||a.imageOriginal)&&i.append(m(e,l?"original":null),b,f,g)}j.next()})):f!==Y.expando&&i.append(e,b,f)}(d),j.check()},reset:function(a,b){var c,d;return x?(d=x(a).clone(!0).insertBefore(a).val("")[0],b||x(a).remove()):(c=a.parentNode,d=c.insertBefore(a.cloneNode(!0),a),d.value="",b||c.removeChild(a),R(K[Y.uid(a)],function(b,c){R(b,function(b){U(a,c,b),T(d,c,b)})})),d},load:function(a,b){var c=Y.getXHR();return c?(c.open("GET",a,!0),c.overrideMimeType&&c.overrideMimeType("text/plain; charset=x-user-defined"),T(c,"progress",function(a){a.lengthComputable&&b({type:a.type,loaded:a.loaded,total:a.total},c)}),c.onreadystatechange=function(){if(4==c.readyState)if(c.onreadystatechange=null,200==c.status){a=a.split("/");var d={name:a[a.length-1],size:c.getResponseHeader("Content-Length"),type:c.getResponseHeader("Content-Type")};d.dataURL="data:"+d.type+";base64,"+Y.encode64(c.responseBody||c.responseText),b({type:"load",result:d},c)}else b({type:"error"},c)},c.send(null)):b({type:"error"}),c},encode64:function(a){var b="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",c="",d=0;for("string"!=typeof a&&(a=String(a));d>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}};Y.addInfoReader(/^image/,function(a,b){if(!a.__dimensions){var c=a.__dimensions=Y.defer();Y.readAsImage(a,function(a){var b=a.target;c.resolve("load"==a.type?!1:"error",{width:b.width,height:b.height}),b.src=Y.EMPTY_PNG,b=null})}a.__dimensions.then(b)}),Y.event.dnd=function(a,b,c){var d,e;c||(c=b,b=Y.F),u?(T(a,"dragenter dragleave dragover",b.ff=b.ff||function(a){for(var c=k(a).types,f=c&&c.length,g=!1;f--;)if(~c[f].indexOf("File")){a[P](),e!==a.type&&(e=a.type,"dragleave"!=e&&b.call(a[O],!0,a),g=!0);break}g&&(clearTimeout(d),d=setTimeout(function(){b.call(a[O],"dragleave"!=e,a)},50))}),T(a,"drop",c.ff=c.ff||function(a){a[P](),e=0,b.call(a[O],!1,a),Y.getDropFiles(a,function(b){c.call(a[O],b,a)})})):Y.log("Drag'n'Drop -- not supported")},Y.event.dnd.off=function(a,b,c){U(a,"dragenter dragleave dragover",b.ff),U(a,"drop",c.ff)},x&&!x.fn.dnd&&(x.fn.dnd=function(a,b){return this.each(function(){Y.event.dnd(this,a,b)})},x.fn.offdnd=function(a,b){return this.each(function(){Y.event.dnd.off(this,a,b)})}),a.FileAPI=S(Y,a.FileAPI),Y.log("FileAPI: "+Y.version),Y.log("protocol: "+a.location.protocol),Y.log("doctype: ["+p.name+"] "+p.publicId+" "+p.systemId),R(o.getElementsByTagName("meta"),function(a){/x-ua-compatible/i.test(a.getAttribute("http-equiv"))&&Y.log("meta.http-equiv: "+a.getAttribute("content"))});try{Y._supportConsoleLog=!!console.log,Y._supportConsoleLogApply=!!console.log.apply}catch(Z){}Y.flashUrl||(Y.flashUrl=Y.staticPath+"FileAPI.flash.swf"),Y.flashImageUrl||(Y.flashImageUrl=Y.staticPath+"FileAPI.flash.image.swf"),Y.flashWebcamUrl||(Y.flashWebcamUrl=Y.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}return this instanceof d?(this.file=b,this.size=b.size||100,void(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})):new d(b)}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(j){}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/.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(k){}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 l&&(d>h||g>i?"min"==l?(h=f(k>j?e(d,h):i*j),i=f(k>j?h/j:e(g,i))):(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(l){throw a.log("renderImageToCanvas failed"),l}},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,-a.width+1,0),0===c.getImageData(0,0,1,1).data[3]):!1},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;h>p;){for(i=0,o=0;g>o;)t.clearRect(0,0,s,s),t.drawImage(d,e,f,g,h,-o,-p,g,h),q.drawImage(r,0,0,s,s,i,j,k,l),o+=s,i+=k;p+=s,j+=l}return q.restore(),c}}return b(c,d,e,f,g,h,i,j,k,l)}}}),function(a,b){"use strict";function c(b,c,d){var e=b.blob,f=b.file;if(f){if(!e.toDataURL)return void a.readAsBinaryString(e,function(a){"load"==a.type&&c(b,a.result)});var g={"image/jpeg":".jpe?g","image/png":".png"},h=g[b.type]?b.type:"image/png",i=g[h]||".png",j=e.quality||1;f.match(new RegExp(i+"$","i"))||(f+=i.replace("?","")),b.file=f,b.type=h,!d&&e.toBlob?e.toBlob(function(a){c(b,a)},h,j):c(b,a.toBinaryString(e.toDataURL(h,j)))}else c(b,e)}var d=b.document,e=b.FormData,f=function(){this.items=[]},g=b.encodeURIComponent;f.prototype={append:function(a,b,c,d){this.items.push({name:a,blob:b&&b.blob||(void 0==b?"":b),file:b&&(c||b.name),type:b&&(d||b.type)})},each:function(a){for(var b=0,c=this.items.length;c>b;b++)a.call(this,this.items[b])},toData:function(b,c){c._chunked=a.support.chunked&&c.chunkSize>0&&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(a){d(a,b,f,e)}),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?!1:f.statusText||"unknown",f),f.xhr&&f.xhr.node&&setTimeout(function(){var b=f.xhr.node;try{b.parentNode.removeChild(b)}catch(c){}try{delete a[f.uid]}catch(c){}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){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(d){n(0,d.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(e){b.log("[transport.onload]",e)}},g=d.createElement("div"),g.innerHTML='
'+(l&&c.url.indexOf("=?")<0?'':"")+"
";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,o.submit(),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("POST",k,!0),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){try{for(var d in f)h["response"+d]=g["response"+d]}catch(i){}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 j=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)},j)}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 b,c=f.createElement("canvas"),d=!1;try{b=c.getContext("2d"),b.drawImage(a,0,0,1,1),d=255!=b.getImageData(0,0,1,1).data[4]}catch(e){}return d}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,i.src=e.createObjectURL(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(),this.stream.stop()}catch(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){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")};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(g){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&&(0||!c.html5||!c.support.html5||c.cors&&!c.support.cors||c.media&&!c.support.media)&&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(e){}}}}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(b){}}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{var a=v.get(j);a.setImage(b)}catch(d){c.log('[err] FlashAPI.Preview.setImage -- can not set "base64":',d)}}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 p(){return m(p),--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(a){function b(a){var b,c;if(b=c=0,a.offsetParent)do b+=a.offsetLeft,c+=a.offsetTop;while(a=a.offsetParent);return{left:b,top:c}}a.getBoundingClientRect(),d.body,(a&&a.ownerDocument).documentElement;return{top:b(a).top,left:b(a).left,width:a.offsetWidth,height:a.offsetHeight}}var r=c.uid(),s=0,t={},u=/^https?:/i,v={_fn:{},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)},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:1e6+""}),a.parentNode.insertBefore(b,a),void v.publish(b,r)}while(a=a.nextSibling);10>s&&setTimeout(v.init,50*++s)},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)},disableMouseover:!1,mouseover:function(a){if(!v.disableMouseover){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:1e6+"",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(d){}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)},mouseDown:function(){v.disableMouseover=!0},cancel:function(){v.disableMouseover=!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(d){c.log('[err] Can not find "input" by flashId:',a,d)}},select:function(a){try{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))}finally{v.disableMouseover=!1}},interval:null,cmd:function(a,b,c,d){v.uploadInProgress&&v.readInProgress?setTimeout(function(){v.cmd(a,b,c,d)},100):this.cmdFn(a,b,c,d)},cmdFn: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),e||setTimeout(function(){v.cmd(a,b,d,!0)},50)}},patch:function(){c.flashEngine=!0,j(c,{readAsDataURL:function(a,b){k(a)?this.parent.apply(this,arguments):(c.log("FlashAPI.readAsBase64"),v.readInProgress=!0,v.cmd(a,"readAsBase64",{id:a.id,callback:l(function d(e,f){v.readInProgress=!1,m(d),c.log("FlashAPI.readAsBase64:",e),b({type:e?"error":"load",error:e,result:"data:"+a.type+";base64,"+f})})}))},readAsText:function(b,d,e){e?c.log("[warn] FlashAPI.readAsText not supported `encoding` param"):e=d,c.readAsDataURL(b,function(b){if("load"==b.type)try{b.result=a.atob(b.result.split(";base64,")[1])}catch(c){b.type="error",b.error=c.toString()}e(b)})},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();d.resolve(null,a.info=null)}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.uploadInProgress=!1,v.cmd(d,"abort",{id:e})},getResponseHeader:function(a){return this.headers[a]},getAllResponseHeaders:function(){return this.headers}};var j=c.queue(function(){v.uploadInProgress=!0,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?(v.uploadInProgress=!1,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)&&(v.uploadInProgress=!1,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&&!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(b){}}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 i(a){"camera"===a.type&&(b(i),a.error?(c.log("FlashAPI.Camera.publish.error: "+a.error),g(a.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 g(a){b(g),a.error?(c.log("FlashAPI.camera.on.error: "+a.error),d(a.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/InventoryTraker.Web/Scripts/ng-file-upload-all.js b/InventoryTraker.Web/Scripts/ng-file-upload-all.js new file mode 100644 index 0000000..99b8855 --- /dev/null +++ b/InventoryTraker.Web/Scripts/ng-file-upload-all.js @@ -0,0 +1,2904 @@ +/**! + * AngularJS file upload directives and services. Supports: file upload/drop/paste, resume, cancel/abort, + * progress, resize, thumbnail, preview, validation and CORS + * FileAPI Flash shim for old browsers not supporting FormData + * @author Danial + * @version 12.2.12 + */ + +(function () { + /** @namespace FileAPI.noContentTimeout */ + + function patchXHR(fnName, newFn) { + window.XMLHttpRequest.prototype[fnName] = newFn(window.XMLHttpRequest.prototype[fnName]); + } + + function redefineProp(xhr, prop, fn) { + try { + Object.defineProperty(xhr, prop, {get: fn}); + } catch (e) {/*ignore*/ + } + } + + if (!window.FileAPI) { + window.FileAPI = {}; + } + + if (!window.XMLHttpRequest) { + throw 'AJAX is not supported. XMLHttpRequest is not defined.'; + } + + FileAPI.shouldLoad = !window.FormData || FileAPI.forceLoad; + if (FileAPI.shouldLoad) { + var initializeUploadListener = function (xhr) { + if (!xhr.__listeners) { + if (!xhr.upload) xhr.upload = {}; + xhr.__listeners = []; + var origAddEventListener = xhr.upload.addEventListener; + xhr.upload.addEventListener = function (t, fn) { + xhr.__listeners[t] = fn; + if (origAddEventListener) origAddEventListener.apply(this, arguments); + }; + } + }; + + patchXHR('open', function (orig) { + return function (m, url, b) { + initializeUploadListener(this); + this.__url = url; + try { + orig.apply(this, [m, url, b]); + } catch (e) { + if (e.message.indexOf('Access is denied') > -1) { + this.__origError = e; + orig.apply(this, [m, '_fix_for_ie_crossdomain__', b]); + } + } + }; + }); + + patchXHR('getResponseHeader', function (orig) { + return function (h) { + return this.__fileApiXHR && this.__fileApiXHR.getResponseHeader ? this.__fileApiXHR.getResponseHeader(h) : (orig == null ? null : orig.apply(this, [h])); + }; + }); + + patchXHR('getAllResponseHeaders', function (orig) { + return function () { + return this.__fileApiXHR && this.__fileApiXHR.getAllResponseHeaders ? this.__fileApiXHR.getAllResponseHeaders() : (orig == null ? null : orig.apply(this)); + }; + }); + + patchXHR('abort', function (orig) { + return function () { + return this.__fileApiXHR && this.__fileApiXHR.abort ? this.__fileApiXHR.abort() : (orig == null ? null : orig.apply(this)); + }; + }); + + patchXHR('setRequestHeader', function (orig) { + return function (header, value) { + if (header === '__setXHR_') { + initializeUploadListener(this); + var val = value(this); + // fix for angular < 1.2.0 + if (val instanceof Function) { + val(this); + } + } else { + this.__requestHeaders = this.__requestHeaders || {}; + this.__requestHeaders[header] = value; + orig.apply(this, arguments); + } + }; + }); + + patchXHR('send', function (orig) { + return function () { + var xhr = this; + if (arguments[0] && arguments[0].__isFileAPIShim) { + var formData = arguments[0]; + var config = { + url: xhr.__url, + jsonp: false, //removes the callback form param + cache: true, //removes the ?fileapiXXX in the url + complete: function (err, fileApiXHR) { + if (err && angular.isString(err) && err.indexOf('#2174') !== -1) { + // this error seems to be fine the file is being uploaded properly. + err = null; + } + xhr.__completed = true; + if (!err && xhr.__listeners.load) + xhr.__listeners.load({ + type: 'load', + loaded: xhr.__loaded, + total: xhr.__total, + target: xhr, + lengthComputable: true + }); + if (!err && xhr.__listeners.loadend) + xhr.__listeners.loadend({ + type: 'loadend', + loaded: xhr.__loaded, + total: xhr.__total, + target: xhr, + lengthComputable: true + }); + if (err === 'abort' && xhr.__listeners.abort) + xhr.__listeners.abort({ + type: 'abort', + loaded: xhr.__loaded, + total: xhr.__total, + target: xhr, + lengthComputable: true + }); + if (fileApiXHR.status !== undefined) redefineProp(xhr, 'status', function () { + return (fileApiXHR.status === 0 && err && err !== 'abort') ? 500 : fileApiXHR.status; + }); + if (fileApiXHR.statusText !== undefined) redefineProp(xhr, 'statusText', function () { + return fileApiXHR.statusText; + }); + redefineProp(xhr, 'readyState', function () { + return 4; + }); + if (fileApiXHR.response !== undefined) redefineProp(xhr, 'response', function () { + return fileApiXHR.response; + }); + var resp = fileApiXHR.responseText || (err && fileApiXHR.status === 0 && err !== 'abort' ? err : undefined); + redefineProp(xhr, 'responseText', function () { + return resp; + }); + redefineProp(xhr, 'response', function () { + return resp; + }); + if (err) redefineProp(xhr, 'err', function () { + return err; + }); + xhr.__fileApiXHR = fileApiXHR; + if (xhr.onreadystatechange) xhr.onreadystatechange(); + if (xhr.onload) xhr.onload(); + }, + progress: function (e) { + e.target = xhr; + if (xhr.__listeners.progress) xhr.__listeners.progress(e); + xhr.__total = e.total; + xhr.__loaded = e.loaded; + if (e.total === e.loaded) { + // fix flash issue that doesn't call complete if there is no response text from the server + var _this = this; + setTimeout(function () { + if (!xhr.__completed) { + xhr.getAllResponseHeaders = function () { + }; + _this.complete(null, {status: 204, statusText: 'No Content'}); + } + }, FileAPI.noContentTimeout || 10000); + } + }, + headers: xhr.__requestHeaders + }; + config.data = {}; + config.files = {}; + for (var i = 0; i < formData.data.length; i++) { + var item = formData.data[i]; + if (item.val != null && item.val.name != null && item.val.size != null && item.val.type != null) { + config.files[item.key] = item.val; + } else { + config.data[item.key] = item.val; + } + } + + setTimeout(function () { + if (!FileAPI.hasFlash) { + throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"'; + } + xhr.__fileApiXHR = FileAPI.upload(config); + }, 1); + } else { + if (this.__origError) { + throw this.__origError; + } + orig.apply(xhr, arguments); + } + }; + }); + window.XMLHttpRequest.__isFileAPIShim = true; + window.FormData = FormData = function () { + return { + append: function (key, val, name) { + if (val.__isFileAPIBlobShim) { + val = val.data[0]; + } + this.data.push({ + key: key, + val: val, + name: name + }); + }, + data: [], + __isFileAPIShim: true + }; + }; + + window.Blob = Blob = function (b) { + return { + data: b, + __isFileAPIBlobShim: true + }; + }; + } + +})(); + +(function () { + /** @namespace FileAPI.forceLoad */ + /** @namespace window.FileAPI.jsUrl */ + /** @namespace window.FileAPI.jsPath */ + + function isInputTypeFile(elem) { + return elem[0].tagName.toLowerCase() === 'input' && elem.attr('type') && elem.attr('type').toLowerCase() === 'file'; + } + + function hasFlash() { + try { + var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); + if (fo) return true; + } catch (e) { + if (navigator.mimeTypes['application/x-shockwave-flash'] !== undefined) return true; + } + return false; + } + + function getOffset(obj) { + var left = 0, top = 0; + + if (window.jQuery) { + return jQuery(obj).offset(); + } + + if (obj.offsetParent) { + do { + left += (obj.offsetLeft - obj.scrollLeft); + top += (obj.offsetTop - obj.scrollTop); + obj = obj.offsetParent; + } while (obj); + } + return { + left: left, + top: top + }; + } + + if (FileAPI.shouldLoad) { + FileAPI.hasFlash = hasFlash(); + + //load FileAPI + if (FileAPI.forceLoad) { + FileAPI.html5 = false; + } + + if (!FileAPI.upload) { + var jsUrl, basePath, script = document.createElement('script'), allScripts = document.getElementsByTagName('script'), i, index, src; + if (window.FileAPI.jsUrl) { + jsUrl = window.FileAPI.jsUrl; + } else if (window.FileAPI.jsPath) { + basePath = window.FileAPI.jsPath; + } else { + for (i = 0; i < allScripts.length; i++) { + src = allScripts[i].src; + index = src.search(/\/ng\-file\-upload[\-a-zA-z0-9\.]*\.js/); + if (index > -1) { + basePath = src.substring(0, index + 1); + break; + } + } + } + + if (FileAPI.staticPath == null) FileAPI.staticPath = basePath; + script.setAttribute('src', jsUrl || basePath + 'FileAPI.min.js'); + document.getElementsByTagName('head')[0].appendChild(script); + } + + FileAPI.ngfFixIE = function (elem, fileElem, changeFn) { + if (!hasFlash()) { + throw 'Adode Flash Player need to be installed. To check ahead use "FileAPI.hasFlash"'; + } + var fixInputStyle = function () { + var label = fileElem.parent(); + if (elem.attr('disabled')) { + if (label) label.removeClass('js-fileapi-wrapper'); + } else { + if (!fileElem.attr('__ngf_flash_')) { + fileElem.unbind('change'); + fileElem.unbind('click'); + fileElem.bind('change', function (evt) { + fileApiChangeFn.apply(this, [evt]); + changeFn.apply(this, [evt]); + }); + fileElem.attr('__ngf_flash_', 'true'); + } + label.addClass('js-fileapi-wrapper'); + if (!isInputTypeFile(elem)) { + label.css('position', 'absolute') + .css('top', getOffset(elem[0]).top + 'px').css('left', getOffset(elem[0]).left + 'px') + .css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px') + .css('filter', 'alpha(opacity=0)').css('display', elem.css('display')) + .css('overflow', 'hidden').css('z-index', '900000') + .css('visibility', 'visible'); + fileElem.css('width', elem[0].offsetWidth + 'px').css('height', elem[0].offsetHeight + 'px') + .css('position', 'absolute').css('top', '0px').css('left', '0px'); + } + } + }; + + elem.bind('mouseenter', fixInputStyle); + + var fileApiChangeFn = function (evt) { + var files = FileAPI.getFiles(evt); + //just a double check for #233 + for (var i = 0; i < files.length; i++) { + if (files[i].size === undefined) files[i].size = 0; + if (files[i].name === undefined) files[i].name = 'file'; + if (files[i].type === undefined) files[i].type = 'undefined'; + } + if (!evt.target) { + evt.target = {}; + } + evt.target.files = files; + // if evt.target.files is not writable use helper field + if (evt.target.files !== files) { + evt.__files_ = files; + } + (evt.__files_ || evt.target.files).item = function (i) { + return (evt.__files_ || evt.target.files)[i] || null; + }; + }; + }; + + FileAPI.disableFileInput = function (elem, disable) { + if (disable) { + elem.removeClass('js-fileapi-wrapper'); + } else { + elem.addClass('js-fileapi-wrapper'); + } + }; + } +})(); + +if (!window.FileReader) { + window.FileReader = function () { + var _this = this, loadStarted = false; + this.listeners = {}; + this.addEventListener = function (type, fn) { + _this.listeners[type] = _this.listeners[type] || []; + _this.listeners[type].push(fn); + }; + this.removeEventListener = function (type, fn) { + if (_this.listeners[type]) _this.listeners[type].splice(_this.listeners[type].indexOf(fn), 1); + }; + this.dispatchEvent = function (evt) { + var list = _this.listeners[evt.type]; + if (list) { + for (var i = 0; i < list.length; i++) { + list[i].call(_this, evt); + } + } + }; + this.onabort = this.onerror = this.onload = this.onloadstart = this.onloadend = this.onprogress = null; + + var constructEvent = function (type, evt) { + var e = {type: type, target: _this, loaded: evt.loaded, total: evt.total, error: evt.error}; + if (evt.result != null) e.target.result = evt.result; + return e; + }; + var listener = function (evt) { + if (!loadStarted) { + loadStarted = true; + if (_this.onloadstart) _this.onloadstart(constructEvent('loadstart', evt)); + } + var e; + if (evt.type === 'load') { + if (_this.onloadend) _this.onloadend(constructEvent('loadend', evt)); + e = constructEvent('load', evt); + if (_this.onload) _this.onload(e); + _this.dispatchEvent(e); + } else if (evt.type === 'progress') { + e = constructEvent('progress', evt); + if (_this.onprogress) _this.onprogress(e); + _this.dispatchEvent(e); + } else { + e = constructEvent('error', evt); + if (_this.onerror) _this.onerror(e); + _this.dispatchEvent(e); + } + }; + this.readAsDataURL = function (file) { + FileAPI.readAsDataURL(file, listener); + }; + this.readAsText = function (file) { + FileAPI.readAsText(file, listener); + }; + }; +} + +/**! + * AngularJS file upload directives and services. Supoorts: file upload/drop/paste, resume, cancel/abort, + * progress, resize, thumbnail, preview, validation and CORS + * @author Danial + * @version 12.2.12 + */ + +if (window.XMLHttpRequest && !(window.FileAPI && FileAPI.shouldLoad)) { + window.XMLHttpRequest.prototype.setRequestHeader = (function (orig) { + return function (header, value) { + if (header === '__setXHR_') { + var val = value(this); + // fix for angular < 1.2.0 + if (val instanceof Function) { + val(this); + } + } else { + orig.apply(this, arguments); + } + }; + })(window.XMLHttpRequest.prototype.setRequestHeader); +} + +var ngFileUpload = angular.module('ngFileUpload', []); + +ngFileUpload.version = '12.2.12'; + +ngFileUpload.service('UploadBase', ['$http', '$q', '$timeout', function ($http, $q, $timeout) { + var upload = this; + upload.promisesCount = 0; + + this.isResumeSupported = function () { + return window.Blob && window.Blob.prototype.slice; + }; + + var resumeSupported = this.isResumeSupported(); + + function sendHttp(config) { + config.method = config.method || 'POST'; + config.headers = config.headers || {}; + + var deferred = config._deferred = config._deferred || $q.defer(); + var promise = deferred.promise; + + function notifyProgress(e) { + if (deferred.notify) { + deferred.notify(e); + } + if (promise.progressFunc) { + $timeout(function () { + promise.progressFunc(e); + }); + } + } + + function getNotifyEvent(n) { + if (config._start != null && resumeSupported) { + return { + loaded: n.loaded + config._start, + total: (config._file && config._file.size) || n.total, + type: n.type, config: config, + lengthComputable: true, target: n.target + }; + } else { + return n; + } + } + + if (!config.disableProgress) { + config.headers.__setXHR_ = function () { + return function (xhr) { + if (!xhr || !xhr.upload || !xhr.upload.addEventListener) return; + config.__XHR = xhr; + if (config.xhrFn) config.xhrFn(xhr); + xhr.upload.addEventListener('progress', function (e) { + e.config = config; + notifyProgress(getNotifyEvent(e)); + }, false); + //fix for firefox not firing upload progress end, also IE8-9 + xhr.upload.addEventListener('load', function (e) { + if (e.lengthComputable) { + e.config = config; + notifyProgress(getNotifyEvent(e)); + } + }, false); + }; + }; + } + + function uploadWithAngular() { + $http(config).then(function (r) { + if (resumeSupported && config._chunkSize && !config._finished && config._file) { + var fileSize = config._file && config._file.size || 0; + notifyProgress({ + loaded: Math.min(config._end, fileSize), + total: fileSize, + config: config, + type: 'progress' + } + ); + upload.upload(config, true); + } else { + if (config._finished) delete config._finished; + deferred.resolve(r); + } + }, function (e) { + deferred.reject(e); + }, function (n) { + deferred.notify(n); + } + ); + } + + if (!resumeSupported) { + uploadWithAngular(); + } else if (config._chunkSize && config._end && !config._finished) { + config._start = config._end; + config._end += config._chunkSize; + uploadWithAngular(); + } else if (config.resumeSizeUrl) { + $http.get(config.resumeSizeUrl).then(function (resp) { + if (config.resumeSizeResponseReader) { + config._start = config.resumeSizeResponseReader(resp.data); + } else { + config._start = parseInt((resp.data.size == null ? resp.data : resp.data.size).toString()); + } + if (config._chunkSize) { + config._end = config._start + config._chunkSize; + } + uploadWithAngular(); + }, function (e) { + throw e; + }); + } else if (config.resumeSize) { + config.resumeSize().then(function (size) { + config._start = size; + if (config._chunkSize) { + config._end = config._start + config._chunkSize; + } + uploadWithAngular(); + }, function (e) { + throw e; + }); + } else { + if (config._chunkSize) { + config._start = 0; + config._end = config._start + config._chunkSize; + } + uploadWithAngular(); + } + + + promise.success = function (fn) { + promise.then(function (response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.error = function (fn) { + promise.then(null, function (response) { + fn(response.data, response.status, response.headers, config); + }); + return promise; + }; + + promise.progress = function (fn) { + promise.progressFunc = fn; + promise.then(null, null, function (n) { + fn(n); + }); + return promise; + }; + promise.abort = promise.pause = function () { + if (config.__XHR) { + $timeout(function () { + config.__XHR.abort(); + }); + } + return promise; + }; + promise.xhr = function (fn) { + config.xhrFn = (function (origXhrFn) { + return function () { + if (origXhrFn) origXhrFn.apply(promise, arguments); + fn.apply(promise, arguments); + }; + })(config.xhrFn); + return promise; + }; + + upload.promisesCount++; + if (promise['finally'] && promise['finally'] instanceof Function) { + promise['finally'](function () { + upload.promisesCount--; + }); + } + return promise; + } + + this.isUploadInProgress = function () { + return upload.promisesCount > 0; + }; + + this.rename = function (file, name) { + file.ngfName = name; + return file; + }; + + this.jsonBlob = function (val) { + if (val != null && !angular.isString(val)) { + val = JSON.stringify(val); + } + var blob = new window.Blob([val], {type: 'application/json'}); + blob._ngfBlob = true; + return blob; + }; + + this.json = function (val) { + return angular.toJson(val); + }; + + function copy(obj) { + var clone = {}; + for (var key in obj) { + if (obj.hasOwnProperty(key)) { + clone[key] = obj[key]; + } + } + return clone; + } + + this.isFile = function (file) { + return file != null && (file instanceof window.Blob || (file.flashId && file.name && file.size)); + }; + + this.upload = function (config, internal) { + function toResumeFile(file, formData) { + if (file._ngfBlob) return file; + config._file = config._file || file; + if (config._start != null && resumeSupported) { + if (config._end && config._end >= file.size) { + config._finished = true; + config._end = file.size; + } + var slice = file.slice(config._start, config._end || file.size); + slice.name = file.name; + slice.ngfName = file.ngfName; + if (config._chunkSize) { + formData.append('_chunkSize', config._chunkSize); + formData.append('_currentChunkSize', config._end - config._start); + formData.append('_chunkNumber', Math.floor(config._start / config._chunkSize)); + formData.append('_totalSize', config._file.size); + } + return slice; + } + return file; + } + + function addFieldToFormData(formData, val, key) { + if (val !== undefined) { + if (angular.isDate(val)) { + val = val.toISOString(); + } + if (angular.isString(val)) { + formData.append(key, val); + } else if (upload.isFile(val)) { + var file = toResumeFile(val, formData); + var split = key.split(','); + if (split[1]) { + file.ngfName = split[1].replace(/^\s+|\s+$/g, ''); + key = split[0]; + } + config._fileKey = config._fileKey || key; + formData.append(key, file, file.ngfName || file.name); + } else { + if (angular.isObject(val)) { + if (val.$$ngfCircularDetection) throw 'ngFileUpload: Circular reference in config.data. Make sure specified data for Upload.upload() has no circular reference: ' + key; + + val.$$ngfCircularDetection = true; + try { + for (var k in val) { + if (val.hasOwnProperty(k) && k !== '$$ngfCircularDetection') { + var objectKey = config.objectKey == null ? '[i]' : config.objectKey; + if (val.length && parseInt(k) > -1) { + objectKey = config.arrayKey == null ? objectKey : config.arrayKey; + } + addFieldToFormData(formData, val[k], key + objectKey.replace(/[ik]/g, k)); + } + } + } finally { + delete val.$$ngfCircularDetection; + } + } else { + formData.append(key, val); + } + } + } + } + + function digestConfig() { + config._chunkSize = upload.translateScalars(config.resumeChunkSize); + config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; + + config.headers = config.headers || {}; + config.headers['Content-Type'] = undefined; + config.transformRequest = config.transformRequest ? + (angular.isArray(config.transformRequest) ? + config.transformRequest : [config.transformRequest]) : []; + config.transformRequest.push(function (data) { + var formData = new window.FormData(), key; + data = data || config.fields || {}; + if (config.file) { + data.file = config.file; + } + for (key in data) { + if (data.hasOwnProperty(key)) { + var val = data[key]; + if (config.formDataAppender) { + config.formDataAppender(formData, key, val); + } else { + addFieldToFormData(formData, val, key); + } + } + } + + return formData; + }); + } + + if (!internal) config = copy(config); + if (!config._isDigested) { + config._isDigested = true; + digestConfig(); + } + + return sendHttp(config); + }; + + this.http = function (config) { + config = copy(config); + config.transformRequest = config.transformRequest || function (data) { + if ((window.ArrayBuffer && data instanceof window.ArrayBuffer) || data instanceof window.Blob) { + return data; + } + return $http.defaults.transformRequest[0].apply(this, arguments); + }; + config._chunkSize = upload.translateScalars(config.resumeChunkSize); + config._chunkSize = config._chunkSize ? parseInt(config._chunkSize.toString()) : null; + + return sendHttp(config); + }; + + this.translateScalars = function (str) { + if (angular.isString(str)) { + if (str.search(/kb/i) === str.length - 2) { + return parseFloat(str.substring(0, str.length - 2) * 1024); + } else if (str.search(/mb/i) === str.length - 2) { + return parseFloat(str.substring(0, str.length - 2) * 1048576); + } else if (str.search(/gb/i) === str.length - 2) { + return parseFloat(str.substring(0, str.length - 2) * 1073741824); + } else if (str.search(/b/i) === str.length - 1) { + return parseFloat(str.substring(0, str.length - 1)); + } else if (str.search(/s/i) === str.length - 1) { + return parseFloat(str.substring(0, str.length - 1)); + } else if (str.search(/m/i) === str.length - 1) { + return parseFloat(str.substring(0, str.length - 1) * 60); + } else if (str.search(/h/i) === str.length - 1) { + return parseFloat(str.substring(0, str.length - 1) * 3600); + } + } + return str; + }; + + this.urlToBlob = function(url) { + var defer = $q.defer(); + $http({url: url, method: 'get', responseType: 'arraybuffer'}).then(function (resp) { + var arrayBufferView = new Uint8Array(resp.data); + var type = resp.headers('content-type') || 'image/WebP'; + var blob = new window.Blob([arrayBufferView], {type: type}); + var matches = url.match(/.*\/(.+?)(\?.*)?$/); + if (matches.length > 1) { + blob.name = matches[1]; + } + defer.resolve(blob); + }, function (e) { + defer.reject(e); + }); + return defer.promise; + }; + + this.setDefaults = function (defaults) { + this.defaults = defaults || {}; + }; + + this.defaults = {}; + this.version = ngFileUpload.version; +} + +]); + +ngFileUpload.service('Upload', ['$parse', '$timeout', '$compile', '$q', 'UploadExif', function ($parse, $timeout, $compile, $q, UploadExif) { + var upload = UploadExif; + upload.getAttrWithDefaults = function (attr, name) { + if (attr[name] != null) return attr[name]; + var def = upload.defaults[name]; + return (def == null ? def : (angular.isString(def) ? def : JSON.stringify(def))); + }; + + upload.attrGetter = function (name, attr, scope, params) { + var attrVal = this.getAttrWithDefaults(attr, name); + if (scope) { + try { + if (params) { + return $parse(attrVal)(scope, params); + } else { + return $parse(attrVal)(scope); + } + } catch (e) { + // hangle string value without single qoute + if (name.search(/min|max|pattern/i)) { + return attrVal; + } else { + throw e; + } + } + } else { + return attrVal; + } + }; + + upload.shouldUpdateOn = function (type, attr, scope) { + var modelOptions = upload.attrGetter('ngfModelOptions', attr, scope); + if (modelOptions && modelOptions.updateOn) { + return modelOptions.updateOn.split(' ').indexOf(type) > -1; + } + return true; + }; + + upload.emptyPromise = function () { + var d = $q.defer(); + var args = arguments; + $timeout(function () { + d.resolve.apply(d, args); + }); + return d.promise; + }; + + upload.rejectPromise = function () { + var d = $q.defer(); + var args = arguments; + $timeout(function () { + d.reject.apply(d, args); + }); + return d.promise; + }; + + upload.happyPromise = function (promise, data) { + var d = $q.defer(); + promise.then(function (result) { + d.resolve(result); + }, function (error) { + $timeout(function () { + throw error; + }); + d.resolve(data); + }); + return d.promise; + }; + + function applyExifRotations(files, attr, scope) { + var promises = [upload.emptyPromise()]; + angular.forEach(files, function (f, i) { + if (f.type.indexOf('image/jpeg') === 0 && upload.attrGetter('ngfFixOrientation', attr, scope, {$file: f})) { + promises.push(upload.happyPromise(upload.applyExifRotation(f), f).then(function (fixedFile) { + files.splice(i, 1, fixedFile); + })); + } + }); + return $q.all(promises); + } + + function resizeFile(files, attr, scope, ngModel) { + var resizeVal = upload.attrGetter('ngfResize', attr, scope); + if (!resizeVal || !upload.isResizeSupported() || !files.length) return upload.emptyPromise(); + if (resizeVal instanceof Function) { + var defer = $q.defer(); + return resizeVal(files).then(function (p) { + resizeWithParams(p, files, attr, scope, ngModel).then(function (r) { + defer.resolve(r); + }, function (e) { + defer.reject(e); + }); + }, function (e) { + defer.reject(e); + }); + } else { + return resizeWithParams(resizeVal, files, attr, scope, ngModel); + } + } + + function resizeWithParams(params, files, attr, scope, ngModel) { + var promises = [upload.emptyPromise()]; + + function handleFile(f, i) { + if (f.type.indexOf('image') === 0) { + if (params.pattern && !upload.validatePattern(f, params.pattern)) return; + params.resizeIf = function (width, height) { + return upload.attrGetter('ngfResizeIf', attr, scope, + {$width: width, $height: height, $file: f}); + }; + var promise = upload.resize(f, params); + promises.push(promise); + promise.then(function (resizedFile) { + files.splice(i, 1, resizedFile); + }, function (e) { + f.$error = 'resize'; + (f.$errorMessages = (f.$errorMessages || {})).resize = true; + f.$errorParam = (e ? (e.message ? e.message : e) + ': ' : '') + (f && f.name); + ngModel.$ngfValidations.push({name: 'resize', valid: false}); + upload.applyModelValidation(ngModel, files); + }); + } + } + + for (var i = 0; i < files.length; i++) { + handleFile(files[i], i); + } + return $q.all(promises); + } + + upload.updateModel = function (ngModel, attr, scope, fileChange, files, evt, noDelay) { + function update(files, invalidFiles, newFiles, dupFiles, isSingleModel) { + attr.$$ngfPrevValidFiles = files; + attr.$$ngfPrevInvalidFiles = invalidFiles; + var file = files && files.length ? files[0] : null; + var invalidFile = invalidFiles && invalidFiles.length ? invalidFiles[0] : null; + + if (ngModel) { + upload.applyModelValidation(ngModel, files); + ngModel.$setViewValue(isSingleModel ? file : files); + } + + if (fileChange) { + $parse(fileChange)(scope, { + $files: files, + $file: file, + $newFiles: newFiles, + $duplicateFiles: dupFiles, + $invalidFiles: invalidFiles, + $invalidFile: invalidFile, + $event: evt + }); + } + + var invalidModel = upload.attrGetter('ngfModelInvalid', attr); + if (invalidModel) { + $timeout(function () { + $parse(invalidModel).assign(scope, isSingleModel ? invalidFile : invalidFiles); + }); + } + $timeout(function () { + // scope apply changes + }); + } + + var allNewFiles, dupFiles = [], prevValidFiles, prevInvalidFiles, + invalids = [], valids = []; + + function removeDuplicates() { + function equals(f1, f2) { + return f1.name === f2.name && (f1.$ngfOrigSize || f1.size) === (f2.$ngfOrigSize || f2.size) && + f1.type === f2.type; + } + + function isInPrevFiles(f) { + var j; + for (j = 0; j < prevValidFiles.length; j++) { + if (equals(f, prevValidFiles[j])) { + return true; + } + } + for (j = 0; j < prevInvalidFiles.length; j++) { + if (equals(f, prevInvalidFiles[j])) { + return true; + } + } + return false; + } + + if (files) { + allNewFiles = []; + dupFiles = []; + for (var i = 0; i < files.length; i++) { + if (isInPrevFiles(files[i])) { + dupFiles.push(files[i]); + } else { + allNewFiles.push(files[i]); + } + } + } + } + + function toArray(v) { + return angular.isArray(v) ? v : [v]; + } + + function resizeAndUpdate() { + function updateModel() { + $timeout(function () { + update(keep ? prevValidFiles.concat(valids) : valids, + keep ? prevInvalidFiles.concat(invalids) : invalids, + files, dupFiles, isSingleModel); + }, options && options.debounce ? options.debounce.change || options.debounce : 0); + } + + var resizingFiles = validateAfterResize ? allNewFiles : valids; + resizeFile(resizingFiles, attr, scope, ngModel).then(function () { + if (validateAfterResize) { + upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope) + .then(function (validationResult) { + valids = validationResult.validsFiles; + invalids = validationResult.invalidsFiles; + updateModel(); + }); + } else { + updateModel(); + } + }, function () { + for (var i = 0; i < resizingFiles.length; i++) { + var f = resizingFiles[i]; + if (f.$error === 'resize') { + var index = valids.indexOf(f); + if (index > -1) { + valids.splice(index, 1); + invalids.push(f); + } + updateModel(); + } + } + }); + } + + prevValidFiles = attr.$$ngfPrevValidFiles || []; + prevInvalidFiles = attr.$$ngfPrevInvalidFiles || []; + if (ngModel && ngModel.$modelValue) { + prevValidFiles = toArray(ngModel.$modelValue); + } + + var keep = upload.attrGetter('ngfKeep', attr, scope); + allNewFiles = (files || []).slice(0); + if (keep === 'distinct' || upload.attrGetter('ngfKeepDistinct', attr, scope) === true) { + removeDuplicates(attr, scope); + } + + var isSingleModel = !keep && !upload.attrGetter('ngfMultiple', attr, scope) && !upload.attrGetter('multiple', attr); + + if (keep && !allNewFiles.length) return; + + upload.attrGetter('ngfBeforeModelChange', attr, scope, { + $files: files, + $file: files && files.length ? files[0] : null, + $newFiles: allNewFiles, + $duplicateFiles: dupFiles, + $event: evt + }); + + var validateAfterResize = upload.attrGetter('ngfValidateAfterResize', attr, scope); + + var options = upload.attrGetter('ngfModelOptions', attr, scope); + upload.validate(allNewFiles, keep ? prevValidFiles.length : 0, ngModel, attr, scope) + .then(function (validationResult) { + if (noDelay) { + update(allNewFiles, [], files, dupFiles, isSingleModel); + } else { + if ((!options || !options.allowInvalid) && !validateAfterResize) { + valids = validationResult.validFiles; + invalids = validationResult.invalidFiles; + } else { + valids = allNewFiles; + } + if (upload.attrGetter('ngfFixOrientation', attr, scope) && upload.isExifSupported()) { + applyExifRotations(valids, attr, scope).then(function () { + resizeAndUpdate(); + }); + } else { + resizeAndUpdate(); + } + } + }); + }; + + return upload; +}]); + +ngFileUpload.directive('ngfSelect', ['$parse', '$timeout', '$compile', 'Upload', function ($parse, $timeout, $compile, Upload) { + var generatedElems = []; + + function isDelayedClickSupported(ua) { + // fix for android native browser < 4.4 and safari windows + var m = ua.match(/Android[^\d]*(\d+)\.(\d+)/); + if (m && m.length > 2) { + var v = Upload.defaults.androidFixMinorVersion || 4; + return parseInt(m[1]) < 4 || (parseInt(m[1]) === v && parseInt(m[2]) < v); + } + + // safari on windows + return ua.indexOf('Chrome') === -1 && /.*Windows.*Safari.*/.test(ua); + } + + function linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, upload) { + /** @namespace attr.ngfSelect */ + /** @namespace attr.ngfChange */ + /** @namespace attr.ngModel */ + /** @namespace attr.ngfModelOptions */ + /** @namespace attr.ngfMultiple */ + /** @namespace attr.ngfCapture */ + /** @namespace attr.ngfValidate */ + /** @namespace attr.ngfKeep */ + var attrGetter = function (name, scope) { + return upload.attrGetter(name, attr, scope); + }; + + function isInputTypeFile() { + return elem[0].tagName.toLowerCase() === 'input' && attr.type && attr.type.toLowerCase() === 'file'; + } + + function fileChangeAttr() { + return attrGetter('ngfChange') || attrGetter('ngfSelect'); + } + + function changeFn(evt) { + if (upload.shouldUpdateOn('change', attr, scope)) { + var fileList = evt.__files_ || (evt.target && evt.target.files), files = []; + /* Handle duplicate call in IE11 */ + if (!fileList) return; + for (var i = 0; i < fileList.length; i++) { + files.push(fileList[i]); + } + upload.updateModel(ngModel, attr, scope, fileChangeAttr(), + files.length ? files : null, evt); + } + } + + upload.registerModelChangeValidator(ngModel, attr, scope); + + var unwatches = []; + if (attrGetter('ngfMultiple')) { + unwatches.push(scope.$watch(attrGetter('ngfMultiple'), function () { + fileElem.attr('multiple', attrGetter('ngfMultiple', scope)); + })); + } + if (attrGetter('ngfCapture')) { + unwatches.push(scope.$watch(attrGetter('ngfCapture'), function () { + fileElem.attr('capture', attrGetter('ngfCapture', scope)); + })); + } + if (attrGetter('ngfAccept')) { + unwatches.push(scope.$watch(attrGetter('ngfAccept'), function () { + fileElem.attr('accept', attrGetter('ngfAccept', scope)); + })); + } + unwatches.push(attr.$observe('accept', function () { + fileElem.attr('accept', attrGetter('accept')); + })); + function bindAttrToFileInput(fileElem, label) { + function updateId(val) { + fileElem.attr('id', 'ngf-' + val); + label.attr('id', 'ngf-label-' + val); + } + + for (var i = 0; i < elem[0].attributes.length; i++) { + var attribute = elem[0].attributes[i]; + if (attribute.name !== 'type' && attribute.name !== 'class' && attribute.name !== 'style') { + if (attribute.name === 'id') { + updateId(attribute.value); + unwatches.push(attr.$observe('id', updateId)); + } else { + fileElem.attr(attribute.name, (!attribute.value && (attribute.name === 'required' || + attribute.name === 'multiple')) ? attribute.name : attribute.value); + } + } + } + } + + function createFileInput() { + if (isInputTypeFile()) { + return elem; + } + + var fileElem = angular.element(''); + + var label = angular.element(''); + label.css('visibility', 'hidden').css('position', 'absolute').css('overflow', 'hidden') + .css('width', '0px').css('height', '0px').css('border', 'none') + .css('margin', '0px').css('padding', '0px').attr('tabindex', '-1'); + bindAttrToFileInput(fileElem, label); + + generatedElems.push({el: elem, ref: label}); + + document.body.appendChild(label.append(fileElem)[0]); + + return fileElem; + } + + function clickHandler(evt) { + if (elem.attr('disabled')) return false; + if (attrGetter('ngfSelectDisabled', scope)) return; + + var r = detectSwipe(evt); + // prevent the click if it is a swipe + if (r != null) return r; + + resetModel(evt); + + // fix for md when the element is removed from the DOM and added back #460 + try { + if (!isInputTypeFile() && !document.body.contains(fileElem[0])) { + generatedElems.push({el: elem, ref: fileElem.parent()}); + document.body.appendChild(fileElem.parent()[0]); + fileElem.bind('change', changeFn); + } + } catch (e) {/*ignore*/ + } + + if (isDelayedClickSupported(navigator.userAgent)) { + setTimeout(function () { + fileElem[0].click(); + }, 0); + } else { + fileElem[0].click(); + } + + return false; + } + + + var initialTouchStartY = 0; + var initialTouchStartX = 0; + + function detectSwipe(evt) { + var touches = evt.changedTouches || (evt.originalEvent && evt.originalEvent.changedTouches); + if (touches) { + if (evt.type === 'touchstart') { + initialTouchStartX = touches[0].clientX; + initialTouchStartY = touches[0].clientY; + return true; // don't block event default + } else { + // prevent scroll from triggering event + if (evt.type === 'touchend') { + var currentX = touches[0].clientX; + var currentY = touches[0].clientY; + if ((Math.abs(currentX - initialTouchStartX) > 20) || + (Math.abs(currentY - initialTouchStartY) > 20)) { + evt.stopPropagation(); + evt.preventDefault(); + return false; + } + } + return true; + } + } + } + + var fileElem = elem; + + function resetModel(evt) { + if (upload.shouldUpdateOn('click', attr, scope) && fileElem.val()) { + fileElem.val(null); + upload.updateModel(ngModel, attr, scope, fileChangeAttr(), null, evt, true); + } + } + + if (!isInputTypeFile()) { + fileElem = createFileInput(); + } + fileElem.bind('change', changeFn); + + if (!isInputTypeFile()) { + elem.bind('click touchstart touchend', clickHandler); + } else { + elem.bind('click', resetModel); + } + + function ie10SameFileSelectFix(evt) { + if (fileElem && !fileElem.attr('__ngf_ie10_Fix_')) { + if (!fileElem[0].parentNode) { + fileElem = null; + return; + } + evt.preventDefault(); + evt.stopPropagation(); + fileElem.unbind('click'); + var clone = fileElem.clone(); + fileElem.replaceWith(clone); + fileElem = clone; + fileElem.attr('__ngf_ie10_Fix_', 'true'); + fileElem.bind('change', changeFn); + fileElem.bind('click', ie10SameFileSelectFix); + fileElem[0].click(); + return false; + } else { + fileElem.removeAttr('__ngf_ie10_Fix_'); + } + } + + if (navigator.appVersion.indexOf('MSIE 10') !== -1) { + fileElem.bind('click', ie10SameFileSelectFix); + } + + if (ngModel) ngModel.$formatters.push(function (val) { + if (val == null || val.length === 0) { + if (fileElem.val()) { + fileElem.val(null); + } + } + return val; + }); + + scope.$on('$destroy', function () { + if (!isInputTypeFile()) fileElem.parent().remove(); + angular.forEach(unwatches, function (unwatch) { + unwatch(); + }); + }); + + $timeout(function () { + for (var i = 0; i < generatedElems.length; i++) { + var g = generatedElems[i]; + if (!document.body.contains(g.el[0])) { + generatedElems.splice(i, 1); + g.ref.remove(); + } + } + }); + + if (window.FileAPI && window.FileAPI.ngfFixIE) { + window.FileAPI.ngfFixIE(elem, fileElem, changeFn); + } + } + + return { + restrict: 'AEC', + require: '?ngModel', + link: function (scope, elem, attr, ngModel) { + linkFileSelect(scope, elem, attr, ngModel, $parse, $timeout, $compile, Upload); + } + }; +}]); + +(function () { + + ngFileUpload.service('UploadDataUrl', ['UploadBase', '$timeout', '$q', function (UploadBase, $timeout, $q) { + var upload = UploadBase; + upload.base64DataUrl = function (file) { + if (angular.isArray(file)) { + var d = $q.defer(), count = 0; + angular.forEach(file, function (f) { + upload.dataUrl(f, true)['finally'](function () { + count++; + if (count === file.length) { + var urls = []; + angular.forEach(file, function (ff) { + urls.push(ff.$ngfDataUrl); + }); + d.resolve(urls, file); + } + }); + }); + return d.promise; + } else { + return upload.dataUrl(file, true); + } + }; + upload.dataUrl = function (file, disallowObjectUrl) { + if (!file) return upload.emptyPromise(file, file); + if ((disallowObjectUrl && file.$ngfDataUrl != null) || (!disallowObjectUrl && file.$ngfBlobUrl != null)) { + return upload.emptyPromise(disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl, file); + } + var p = disallowObjectUrl ? file.$$ngfDataUrlPromise : file.$$ngfBlobUrlPromise; + if (p) return p; + + var deferred = $q.defer(); + $timeout(function () { + if (window.FileReader && file && + (!window.FileAPI || navigator.userAgent.indexOf('MSIE 8') === -1 || file.size < 20000) && + (!window.FileAPI || navigator.userAgent.indexOf('MSIE 9') === -1 || file.size < 4000000)) { + //prefer URL.createObjectURL for handling refrences to files of all sizes + //since it doesn´t build a large string in memory + var URL = window.URL || window.webkitURL; + if (URL && URL.createObjectURL && !disallowObjectUrl) { + var url; + try { + url = URL.createObjectURL(file); + } catch (e) { + $timeout(function () { + file.$ngfBlobUrl = ''; + deferred.reject(); + }); + return; + } + $timeout(function () { + file.$ngfBlobUrl = url; + if (url) { + deferred.resolve(url, file); + upload.blobUrls = upload.blobUrls || []; + upload.blobUrlsTotalSize = upload.blobUrlsTotalSize || 0; + upload.blobUrls.push({url: url, size: file.size}); + upload.blobUrlsTotalSize += file.size || 0; + var maxMemory = upload.defaults.blobUrlsMaxMemory || 268435456; + var maxLength = upload.defaults.blobUrlsMaxQueueSize || 200; + while ((upload.blobUrlsTotalSize > maxMemory || upload.blobUrls.length > maxLength) && upload.blobUrls.length > 1) { + var obj = upload.blobUrls.splice(0, 1)[0]; + URL.revokeObjectURL(obj.url); + upload.blobUrlsTotalSize -= obj.size; + } + } + }); + } else { + var fileReader = new FileReader(); + fileReader.onload = function (e) { + $timeout(function () { + file.$ngfDataUrl = e.target.result; + deferred.resolve(e.target.result, file); + $timeout(function () { + delete file.$ngfDataUrl; + }, 1000); + }); + }; + fileReader.onerror = function () { + $timeout(function () { + file.$ngfDataUrl = ''; + deferred.reject(); + }); + }; + fileReader.readAsDataURL(file); + } + } else { + $timeout(function () { + file[disallowObjectUrl ? '$ngfDataUrl' : '$ngfBlobUrl'] = ''; + deferred.reject(); + }); + } + }); + + if (disallowObjectUrl) { + p = file.$$ngfDataUrlPromise = deferred.promise; + } else { + p = file.$$ngfBlobUrlPromise = deferred.promise; + } + p['finally'](function () { + delete file[disallowObjectUrl ? '$$ngfDataUrlPromise' : '$$ngfBlobUrlPromise']; + }); + return p; + }; + return upload; + }]); + + function getTagType(el) { + if (el.tagName.toLowerCase() === 'img') return 'image'; + if (el.tagName.toLowerCase() === 'audio') return 'audio'; + if (el.tagName.toLowerCase() === 'video') return 'video'; + return /./; + } + + function linkFileDirective(Upload, $timeout, scope, elem, attr, directiveName, resizeParams, isBackground) { + function constructDataUrl(file) { + var disallowObjectUrl = Upload.attrGetter('ngfNoObjectUrl', attr, scope); + Upload.dataUrl(file, disallowObjectUrl)['finally'](function () { + $timeout(function () { + var src = (disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl; + if (isBackground) { + elem.css('background-image', 'url(\'' + (src || '') + '\')'); + } else { + elem.attr('src', src); + } + if (src) { + elem.removeClass('ng-hide'); + } else { + elem.addClass('ng-hide'); + } + }); + }); + } + + $timeout(function () { + var unwatch = scope.$watch(attr[directiveName], function (file) { + var size = resizeParams; + if (directiveName === 'ngfThumbnail') { + if (!size) { + size = {width: elem[0].naturalWidth || elem[0].clientWidth, + height: elem[0].naturalHeight || elem[0].clientHeight}; + } + if (size.width === 0 && window.getComputedStyle) { + var style = getComputedStyle(elem[0]); + size = { + width: parseInt(style.width.slice(0, -2)), + height: parseInt(style.height.slice(0, -2)) + }; + } + } + + if (angular.isString(file)) { + elem.removeClass('ng-hide'); + if (isBackground) { + return elem.css('background-image', 'url(\'' + file + '\')'); + } else { + return elem.attr('src', file); + } + } + if (file && file.type && file.type.search(getTagType(elem[0])) === 0 && + (!isBackground || file.type.indexOf('image') === 0)) { + if (size && Upload.isResizeSupported()) { + size.resizeIf = function (width, height) { + return Upload.attrGetter('ngfResizeIf', attr, scope, + {$width: width, $height: height, $file: file}); + }; + Upload.resize(file, size).then( + function (f) { + constructDataUrl(f); + }, function (e) { + throw e; + } + ); + } else { + constructDataUrl(file); + } + } else { + elem.addClass('ng-hide'); + } + }); + + scope.$on('$destroy', function () { + unwatch(); + }); + }); + } + + + /** @namespace attr.ngfSrc */ + /** @namespace attr.ngfNoObjectUrl */ + ngFileUpload.directive('ngfSrc', ['Upload', '$timeout', function (Upload, $timeout) { + return { + restrict: 'AE', + link: function (scope, elem, attr) { + linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfSrc', + Upload.attrGetter('ngfResize', attr, scope), false); + } + }; + }]); + + /** @namespace attr.ngfBackground */ + /** @namespace attr.ngfNoObjectUrl */ + ngFileUpload.directive('ngfBackground', ['Upload', '$timeout', function (Upload, $timeout) { + return { + restrict: 'AE', + link: function (scope, elem, attr) { + linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfBackground', + Upload.attrGetter('ngfResize', attr, scope), true); + } + }; + }]); + + /** @namespace attr.ngfThumbnail */ + /** @namespace attr.ngfAsBackground */ + /** @namespace attr.ngfSize */ + /** @namespace attr.ngfNoObjectUrl */ + ngFileUpload.directive('ngfThumbnail', ['Upload', '$timeout', function (Upload, $timeout) { + return { + restrict: 'AE', + link: function (scope, elem, attr) { + var size = Upload.attrGetter('ngfSize', attr, scope); + linkFileDirective(Upload, $timeout, scope, elem, attr, 'ngfThumbnail', size, + Upload.attrGetter('ngfAsBackground', attr, scope)); + } + }; + }]); + + ngFileUpload.config(['$compileProvider', function ($compileProvider) { + if ($compileProvider.imgSrcSanitizationWhitelist) $compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/); + if ($compileProvider.aHrefSanitizationWhitelist) $compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|webcal|local|file|data|blob):/); + }]); + + ngFileUpload.filter('ngfDataUrl', ['UploadDataUrl', '$sce', function (UploadDataUrl, $sce) { + return function (file, disallowObjectUrl, trustedUrl) { + if (angular.isString(file)) { + return $sce.trustAsResourceUrl(file); + } + var src = file && ((disallowObjectUrl ? file.$ngfDataUrl : file.$ngfBlobUrl) || file.$ngfDataUrl); + if (file && !src) { + if (!file.$ngfDataUrlFilterInProgress && angular.isObject(file)) { + file.$ngfDataUrlFilterInProgress = true; + UploadDataUrl.dataUrl(file, disallowObjectUrl); + } + return ''; + } + if (file) delete file.$ngfDataUrlFilterInProgress; + return (file && src ? (trustedUrl ? $sce.trustAsResourceUrl(src) : src) : file) || ''; + }; + }]); + +})(); + +ngFileUpload.service('UploadValidate', ['UploadDataUrl', '$q', '$timeout', function (UploadDataUrl, $q, $timeout) { + var upload = UploadDataUrl; + + function globStringToRegex(str) { + var regexp = '', excludes = []; + if (str.length > 2 && str[0] === '/' && str[str.length - 1] === '/') { + regexp = str.substring(1, str.length - 1); + } else { + var split = str.split(','); + if (split.length > 1) { + for (var i = 0; i < split.length; i++) { + var r = globStringToRegex(split[i]); + if (r.regexp) { + regexp += '(' + r.regexp + ')'; + if (i < split.length - 1) { + regexp += '|'; + } + } else { + excludes = excludes.concat(r.excludes); + } + } + } else { + if (str.indexOf('!') === 0) { + excludes.push('^((?!' + globStringToRegex(str.substring(1)).regexp + ').)*$'); + } else { + if (str.indexOf('.') === 0) { + str = '*' + str; + } + regexp = '^' + str.replace(new RegExp('[.\\\\+*?\\[\\^\\]$(){}=!<>|:\\-]', 'g'), '\\$&') + '$'; + regexp = regexp.replace(/\\\*/g, '.*').replace(/\\\?/g, '.'); + } + } + } + return {regexp: regexp, excludes: excludes}; + } + + upload.validatePattern = function (file, val) { + if (!val) { + return true; + } + var pattern = globStringToRegex(val), valid = true; + if (pattern.regexp && pattern.regexp.length) { + var regexp = new RegExp(pattern.regexp, 'i'); + valid = (file.type != null && regexp.test(file.type)) || + (file.name != null && regexp.test(file.name)); + } + var len = pattern.excludes.length; + while (len--) { + var exclude = new RegExp(pattern.excludes[len], 'i'); + valid = valid && (file.type == null || exclude.test(file.type)) && + (file.name == null || exclude.test(file.name)); + } + return valid; + }; + + upload.ratioToFloat = function (val) { + var r = val.toString(), xIndex = r.search(/[x:]/i); + if (xIndex > -1) { + r = parseFloat(r.substring(0, xIndex)) / parseFloat(r.substring(xIndex + 1)); + } else { + r = parseFloat(r); + } + return r; + }; + + upload.registerModelChangeValidator = function (ngModel, attr, scope) { + if (ngModel) { + ngModel.$formatters.push(function (files) { + if (ngModel.$dirty) { + var filesArray = files; + if (files && !angular.isArray(files)) { + filesArray = [files]; + } + upload.validate(filesArray, 0, ngModel, attr, scope).then(function () { + upload.applyModelValidation(ngModel, filesArray); + }); + } + return files; + }); + } + }; + + function markModelAsDirty(ngModel, files) { + if (files != null && !ngModel.$dirty) { + if (ngModel.$setDirty) { + ngModel.$setDirty(); + } else { + ngModel.$dirty = true; + } + } + } + + upload.applyModelValidation = function (ngModel, files) { + markModelAsDirty(ngModel, files); + angular.forEach(ngModel.$ngfValidations, function (validation) { + ngModel.$setValidity(validation.name, validation.valid); + }); + }; + + upload.getValidationAttr = function (attr, scope, name, validationName, file) { + var dName = 'ngf' + name[0].toUpperCase() + name.substr(1); + var val = upload.attrGetter(dName, attr, scope, {$file: file}); + if (val == null) { + val = upload.attrGetter('ngfValidate', attr, scope, {$file: file}); + if (val) { + var split = (validationName || name).split('.'); + val = val[split[0]]; + if (split.length > 1) { + val = val && val[split[1]]; + } + } + } + return val; + }; + + upload.validate = function (files, prevLength, ngModel, attr, scope) { + ngModel = ngModel || {}; + ngModel.$ngfValidations = ngModel.$ngfValidations || []; + + angular.forEach(ngModel.$ngfValidations, function (v) { + v.valid = true; + }); + + var attrGetter = function (name, params) { + return upload.attrGetter(name, attr, scope, params); + }; + + var ignoredErrors = (upload.attrGetter('ngfIgnoreInvalid', attr, scope) || '').split(' '); + var runAllValidation = upload.attrGetter('ngfRunAllValidations', attr, scope); + + if (files == null || files.length === 0) { + return upload.emptyPromise({'validFiles': files, 'invalidFiles': []}); + } + + files = files.length === undefined ? [files] : files.slice(0); + var invalidFiles = []; + + function validateSync(name, validationName, fn) { + if (files) { + var i = files.length, valid = null; + while (i--) { + var file = files[i]; + if (file) { + var val = upload.getValidationAttr(attr, scope, name, validationName, file); + if (val != null) { + if (!fn(file, val, i)) { + if (ignoredErrors.indexOf(name) === -1) { + file.$error = name; + (file.$errorMessages = (file.$errorMessages || {}))[name] = true; + file.$errorParam = val; + if (invalidFiles.indexOf(file) === -1) { + invalidFiles.push(file); + } + if (!runAllValidation) { + files.splice(i, 1); + } + valid = false; + } else { + files.splice(i, 1); + } + } + } + } + } + if (valid !== null) { + ngModel.$ngfValidations.push({name: name, valid: valid}); + } + } + } + + validateSync('pattern', null, upload.validatePattern); + validateSync('minSize', 'size.min', function (file, val) { + return file.size + 0.1 >= upload.translateScalars(val); + }); + validateSync('maxSize', 'size.max', function (file, val) { + return file.size - 0.1 <= upload.translateScalars(val); + }); + var totalSize = 0; + validateSync('maxTotalSize', null, function (file, val) { + totalSize += file.size; + if (totalSize > upload.translateScalars(val)) { + files.splice(0, files.length); + return false; + } + return true; + }); + + validateSync('validateFn', null, function (file, r) { + return r === true || r === null || r === ''; + }); + + if (!files.length) { + return upload.emptyPromise({'validFiles': [], 'invalidFiles': invalidFiles}); + } + + function validateAsync(name, validationName, type, asyncFn, fn) { + function resolveResult(defer, file, val) { + function resolveInternal(fn) { + if (fn()) { + if (ignoredErrors.indexOf(name) === -1) { + file.$error = name; + (file.$errorMessages = (file.$errorMessages || {}))[name] = true; + file.$errorParam = val; + if (invalidFiles.indexOf(file) === -1) { + invalidFiles.push(file); + } + if (!runAllValidation) { + var i = files.indexOf(file); + if (i > -1) files.splice(i, 1); + } + defer.resolve(false); + } else { + var j = files.indexOf(file); + if (j > -1) files.splice(j, 1); + defer.resolve(true); + } + } else { + defer.resolve(true); + } + } + + if (val != null) { + asyncFn(file, val).then(function (d) { + resolveInternal(function () { + return !fn(d, val); + }); + }, function () { + resolveInternal(function () { + return attrGetter('ngfValidateForce', {$file: file}); + }); + }); + } else { + defer.resolve(true); + } + } + + var promises = [upload.emptyPromise(true)]; + if (files) { + files = files.length === undefined ? [files] : files; + angular.forEach(files, function (file) { + var defer = $q.defer(); + promises.push(defer.promise); + if (type && (file.type == null || file.type.search(type) !== 0)) { + defer.resolve(true); + return; + } + if (name === 'dimensions' && upload.attrGetter('ngfDimensions', attr) != null) { + upload.imageDimensions(file).then(function (d) { + resolveResult(defer, file, + attrGetter('ngfDimensions', {$file: file, $width: d.width, $height: d.height})); + }, function () { + defer.resolve(false); + }); + } else if (name === 'duration' && upload.attrGetter('ngfDuration', attr) != null) { + upload.mediaDuration(file).then(function (d) { + resolveResult(defer, file, + attrGetter('ngfDuration', {$file: file, $duration: d})); + }, function () { + defer.resolve(false); + }); + } else { + resolveResult(defer, file, + upload.getValidationAttr(attr, scope, name, validationName, file)); + } + }); + } + var deffer = $q.defer(); + $q.all(promises).then(function (values) { + var isValid = true; + for (var i = 0; i < values.length; i++) { + if (!values[i]) { + isValid = false; + break; + } + } + ngModel.$ngfValidations.push({name: name, valid: isValid}); + deffer.resolve(isValid); + }); + return deffer.promise; + } + + var deffer = $q.defer(); + var promises = []; + + promises.push(validateAsync('maxHeight', 'height.max', /image/, + this.imageDimensions, function (d, val) { + return d.height <= val; + })); + promises.push(validateAsync('minHeight', 'height.min', /image/, + this.imageDimensions, function (d, val) { + return d.height >= val; + })); + promises.push(validateAsync('maxWidth', 'width.max', /image/, + this.imageDimensions, function (d, val) { + return d.width <= val; + })); + promises.push(validateAsync('minWidth', 'width.min', /image/, + this.imageDimensions, function (d, val) { + return d.width >= val; + })); + promises.push(validateAsync('dimensions', null, /image/, + function (file, val) { + return upload.emptyPromise(val); + }, function (r) { + return r; + })); + promises.push(validateAsync('ratio', null, /image/, + this.imageDimensions, function (d, val) { + var split = val.toString().split(','), valid = false; + for (var i = 0; i < split.length; i++) { + if (Math.abs((d.width / d.height) - upload.ratioToFloat(split[i])) < 0.01) { + valid = true; + } + } + return valid; + })); + promises.push(validateAsync('maxRatio', 'ratio.max', /image/, + this.imageDimensions, function (d, val) { + return (d.width / d.height) - upload.ratioToFloat(val) < 0.0001; + })); + promises.push(validateAsync('minRatio', 'ratio.min', /image/, + this.imageDimensions, function (d, val) { + return (d.width / d.height) - upload.ratioToFloat(val) > -0.0001; + })); + promises.push(validateAsync('maxDuration', 'duration.max', /audio|video/, + this.mediaDuration, function (d, val) { + return d <= upload.translateScalars(val); + })); + promises.push(validateAsync('minDuration', 'duration.min', /audio|video/, + this.mediaDuration, function (d, val) { + return d >= upload.translateScalars(val); + })); + promises.push(validateAsync('duration', null, /audio|video/, + function (file, val) { + return upload.emptyPromise(val); + }, function (r) { + return r; + })); + + promises.push(validateAsync('validateAsyncFn', null, null, + function (file, val) { + return val; + }, function (r) { + return r === true || r === null || r === ''; + })); + + $q.all(promises).then(function () { + + if (runAllValidation) { + for (var i = 0; i < files.length; i++) { + var file = files[i]; + if (file.$error) { + files.splice(i--, 1); + } + } + } + + runAllValidation = false; + validateSync('maxFiles', null, function (file, val, i) { + return prevLength + i < val; + }); + + deffer.resolve({'validFiles': files, 'invalidFiles': invalidFiles}); + }); + return deffer.promise; + }; + + upload.imageDimensions = function (file) { + if (file.$ngfWidth && file.$ngfHeight) { + var d = $q.defer(); + $timeout(function () { + d.resolve({width: file.$ngfWidth, height: file.$ngfHeight}); + }); + return d.promise; + } + if (file.$ngfDimensionPromise) return file.$ngfDimensionPromise; + + var deferred = $q.defer(); + $timeout(function () { + if (file.type.indexOf('image') !== 0) { + deferred.reject('not image'); + return; + } + upload.dataUrl(file).then(function (dataUrl) { + var img = angular.element('').attr('src', dataUrl) + .css('visibility', 'hidden').css('position', 'fixed') + .css('max-width', 'none !important').css('max-height', 'none !important'); + + function success() { + var width = img[0].naturalWidth || img[0].clientWidth; + var height = img[0].naturalHeight || img[0].clientHeight; + img.remove(); + file.$ngfWidth = width; + file.$ngfHeight = height; + deferred.resolve({width: width, height: height}); + } + + function error() { + img.remove(); + deferred.reject('load error'); + } + + img.on('load', success); + img.on('error', error); + + var secondsCounter = 0; + function checkLoadErrorInCaseOfNoCallback() { + $timeout(function () { + if (img[0].parentNode) { + if (img[0].clientWidth) { + success(); + } else if (secondsCounter++ > 10) { + error(); + } else { + checkLoadErrorInCaseOfNoCallback(); + } + } + }, 1000); + } + + checkLoadErrorInCaseOfNoCallback(); + + angular.element(document.getElementsByTagName('body')[0]).append(img); + }, function () { + deferred.reject('load error'); + }); + }); + + file.$ngfDimensionPromise = deferred.promise; + file.$ngfDimensionPromise['finally'](function () { + delete file.$ngfDimensionPromise; + }); + return file.$ngfDimensionPromise; + }; + + upload.mediaDuration = function (file) { + if (file.$ngfDuration) { + var d = $q.defer(); + $timeout(function () { + d.resolve(file.$ngfDuration); + }); + return d.promise; + } + if (file.$ngfDurationPromise) return file.$ngfDurationPromise; + + var deferred = $q.defer(); + $timeout(function () { + if (file.type.indexOf('audio') !== 0 && file.type.indexOf('video') !== 0) { + deferred.reject('not media'); + return; + } + upload.dataUrl(file).then(function (dataUrl) { + var el = angular.element(file.type.indexOf('audio') === 0 ? '

Current inventory

- - \ No newline at end of file diff --git a/InventoryTraker.Web/Views/InventoryType/Index.cshtml b/InventoryTraker.Web/Views/InventoryType/Index.cshtml deleted file mode 100644 index d7ac58a..0000000 --- a/InventoryTraker.Web/Views/InventoryType/Index.cshtml +++ /dev/null @@ -1,17 +0,0 @@ -@model dynamic - -@{ - ViewBag.Title = "Commodity Types"; -} - -
-

- @ViewBag.Title -

- - - -
\ No newline at end of file diff --git a/InventoryTraker.Web/Views/Shared/_Layout.cshtml b/InventoryTraker.Web/Views/Shared/_Layout.cshtml index fb2dbcc..63074e8 100644 --- a/InventoryTraker.Web/Views/Shared/_Layout.cshtml +++ b/InventoryTraker.Web/Views/Shared/_Layout.cshtml @@ -27,7 +27,7 @@ {
-

Inventory Traker © 2016 Kolpack Software Consulting LLC

+

Inventory Traker © ETHRA, 2016 Kolpack Software Consulting LLC

} diff --git a/InventoryTraker.Web/Views/Shared/_Navigation.cshtml b/InventoryTraker.Web/Views/Shared/_Navigation.cshtml index cca34ae..da76d19 100644 --- a/InventoryTraker.Web/Views/Shared/_Navigation.cshtml +++ b/InventoryTraker.Web/Views/Shared/_Navigation.cshtml @@ -8,7 +8,7 @@ - Inventory Traker + Inventory Traker (Box) diff --git a/InventoryTraker.Web/Views/Transaction/Index.cshtml b/InventoryTraker.Web/Views/Transaction/Index.cshtml index 7efe498..e77b6b1 100644 --- a/InventoryTraker.Web/Views/Transaction/Index.cshtml +++ b/InventoryTraker.Web/Views/Transaction/Index.cshtml @@ -1,6 +1,4 @@ -@using InventoryTraker.Web.Helpers -@using InventoryTraker.Web.Models -@model dynamic +@model dynamic @{ ViewBag.Title = "Inventory"; @@ -8,7 +6,7 @@

- Transaction History + Transaction History

diff --git a/InventoryTraker.Web/Web.config b/InventoryTraker.Web/Web.config index 449d6b5..b57e1a2 100644 --- a/InventoryTraker.Web/Web.config +++ b/InventoryTraker.Web/Web.config @@ -17,7 +17,7 @@ - + diff --git a/InventoryTraker.Web/css/layout.css b/InventoryTraker.Web/css/layout.css index b08031f..bebd1f0 100644 --- a/InventoryTraker.Web/css/layout.css +++ b/InventoryTraker.Web/css/layout.css @@ -26,7 +26,7 @@ body { } .navbar-brand i { - color:palegreen + color:palevioletred } .navbar-brand:link, @@ -34,7 +34,7 @@ body { .navbar-brand:hover, .navbar-brand:active { font-weight: bolder; - color: #92B9BD; + color: papayawhip; } .navbar.navbar-fixed-top { diff --git a/InventoryTraker.Web/js/app.js b/InventoryTraker.Web/js/app.js index ea8de6e..80d0187 100644 --- a/InventoryTraker.Web/js/app.js +++ b/InventoryTraker.Web/js/app.js @@ -3,7 +3,7 @@ window.app = angular - .module('InventoryTraker', ['ngAnimate', 'ui.bootstrap', 'ui.grid', 'ui.grid.pagination', 'mgcrea.ngStrap']) + .module('InventoryTraker', ['ngAnimate', 'ui.bootstrap', 'ui.grid', 'ui.grid.pagination', 'mgcrea.ngStrap', 'ngFileUpload']) .config(function($datepickerProvider) { angular.extend($datepickerProvider.defaults, { diff --git a/InventoryTraker.Web/js/inventory/InventoryAddDirective.js b/InventoryTraker.Web/js/inventory/InventoryAddDirective.js index cb01a8e..1bad14d 100644 --- a/InventoryTraker.Web/js/inventory/InventoryAddDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryAddDirective.js @@ -10,38 +10,33 @@ } }); - controller.$inject = ['$scope', 'inventorySvc', 'inventoryTypeSvc']; - function controller($scope, inventorySvc, inventoryTypeSvc) { + controller.$inject = ['$scope', 'inventorySvc']; + function controller($scope, inventorySvc) { var vm = this; vm.add = add; vm.inventory = { }; - vm.inventoryTypes = inventoryTypeSvc.inventoryTypes; + vm.programNames = [ + "Admin", + "Aging", + "CC", + "CCFP", + "CIS", + "CSBG", + "Homemaker", + "HUD", + "Misdemeanor", + "Nutrition", + "Title V", + "Transportation", + "WAP", + "Workforce" + ]; vm.saving = false; - vm.statusMessage = "Enter details for the inventory arrival below."; + vm.statusMessage = "Enter details for the inventory addition below."; vm.errorMessages = []; - vm.quantity = quantity; - - function zeroNaN(v) { - return isNaN(v) ? 0 : v; - } - - function quantity() { - vm.inventory.quantity = - zeroNaN($scope.palletCount) - * zeroNaN($scope.casesPerPallet) - + zeroNaN($scope.caseCount); - - return vm.inventory.quantity > 0 ? vm.inventory.quantity : ""; - } - - $scope.$watch('vm.commodity', function (newValue) { - if (newValue) - vm.inventory.inventoryTypeId = newValue.id; - }); - function add() { vm.statusMessage = null; vm.saving = true; diff --git a/InventoryTraker.Web/js/inventory/InventoryDistributeDirective.js b/InventoryTraker.Web/js/inventory/InventoryDistributeDirective.js deleted file mode 100644 index 2124cd8..0000000 --- a/InventoryTraker.Web/js/inventory/InventoryDistributeDirective.js +++ /dev/null @@ -1,51 +0,0 @@ -(function() { - "use strict"; - - window.app.directive('inventoryDistribute', - function() { - return { - templateUrl: '/inventory/template/inventoryDistribute.tmpl.cshtml', - controller: controller, - controllerAs: 'vm' - } - }); - - controller.$inject = ['$scope', 'inventorySvc']; - function controller($scope, inventorySvc) { - var vm = this; - - vm.save = save; - vm.quantities = angular.copy(inventorySvc.inventories); - vm.distribution = {}; - - vm.saving = false; - vm.statusMessage = "Enter details for the inventory distribution below."; - vm.errorMessages = []; - - function getInventoryDistributeQuantities(inventory) { - var invQty = []; - inventory.forEach(function (i) { - if (i.distributeQuantity > 0) - invQty.push({ inventoryId: i.id, quantity: i.distributeQuantity }); - }); - return invQty; - } - - function save() { - vm.statusMessage = null; - vm.saving = true; - vm.distribution.inventoryQuantities = getInventoryDistributeQuantities(vm.quantities); - inventorySvc.distribute(vm.distribution) - .success(function () { - //Close the modal - $scope.$close(); - }) - .error(function(data) { - vm.errorMessages = angular.copy(data.errorMessages, vm.errorMessages); - }) - .finally(function() { - vm.saving = false; - }); - } - } -})(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/InventoryEditDirective.js b/InventoryTraker.Web/js/inventory/InventoryEditDirective.js index cc65ba9..7869192 100644 --- a/InventoryTraker.Web/js/inventory/InventoryEditDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryEditDirective.js @@ -17,6 +17,7 @@ function controller($scope, $uibModal, inventorySvc, transactionSvc) { var vm = this; vm.inventory = $scope.inventory; + vm.inventoryOriginal = angular.copy(vm.inventory); vm.transactions = []; function refreshTransactions() { @@ -31,11 +32,15 @@ angular.copy(data.transactions, vm.transactions); } }) - .finally(function(){vm.loadingTransactions = false;}); + .finally(function() { + vm.loadingTransactions = false; + vm.inventoryOriginal = angular.copy(vm.inventory); + }); } refreshTransactions(); // initial call vm.save = save; + vm.cancel = cancel; vm.deleteTransaction = deleteTransaction; vm.removeInventory = removeInventory; vm.saving = false; @@ -43,6 +48,9 @@ vm.confirmDeleteTransaction = false; vm.loadingTransactions = false; + vm.isShredReady = function () { return inventorySvc.isShredReady(vm.inventory); }; + vm.isInInventory = function() { return vm.inventory.quantity > 0; } + function deleteTransaction(transactionId) { vm.confirmDeleteTransaction = false; transactionSvc @@ -71,5 +79,10 @@ vm.saving = false; }); } + + function cancel() { + angular.copy(vm.inventoryOriginal, $scope.inventory); + $scope.$parent.$dismiss(); + } } })(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/InventoryImportDirective.js b/InventoryTraker.Web/js/inventory/InventoryImportDirective.js new file mode 100644 index 0000000..cb4c9a7 --- /dev/null +++ b/InventoryTraker.Web/js/inventory/InventoryImportDirective.js @@ -0,0 +1,54 @@ +(function() { + "use strict"; + + window.app.directive('inventoryImport', + function() { + return { + templateUrl: '/inventory/template/inventoryImport.tmpl.cshtml', + controller: controller, + controllerAs: 'vm' + }; + }); + + controller.$inject = ['$scope', 'inventorySvc']; + function controller($scope, inventorySvc) { + var vm = this; + + vm.uploading = false; + vm.file = ""; + + vm.statusMessage = "Import inventory items below."; + vm.errorMessages = []; + + // upload later on form submit or something similar + vm.submit = function () { + if (vm.form.file.$valid && vm.file) { + vm.upload(vm.file); + } + }; + + // upload on file select or drop + vm.upload = function(file) { + vm.uploading = true; + inventorySvc + .importFile(file) + .then(function(resp) { + console.log("Success " + resp.config.data.file.name + "uploaded. Response: " + resp.data); + vm.uploading = false; + }, + function (resp) { + if (angular.isArray(resp.data)) + angular.copy(resp.data, vm.errorMessages); + else + angular.copy([resp.data], vm.errorMessages); + console.log("Error status: " + resp.status + " message: " + resp.data); + vm.uploading = false; + }, + function(evt) { + var progressPercentage = parseInt(100.0 * evt.loaded / evt.total); + console.log("progress: " + progressPercentage + "% " + evt.config.data.file.name); + }); + }; + + } +})(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/InventoryListController.js b/InventoryTraker.Web/js/inventory/InventoryListController.js index 5aeb493..2cadd65 100644 --- a/InventoryTraker.Web/js/inventory/InventoryListController.js +++ b/InventoryTraker.Web/js/inventory/InventoryListController.js @@ -27,6 +27,13 @@ inventorySvc.exportInventory() .success(downloadSvc.success); }; + + vm.import = function() { + $uibModal.open({ + template: "", + backdrop: "static" + }); + }; } ]); })(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/InventoryListDirective.js b/InventoryTraker.Web/js/inventory/InventoryListDirective.js index a4845bf..97670ad 100644 --- a/InventoryTraker.Web/js/inventory/InventoryListDirective.js +++ b/InventoryTraker.Web/js/inventory/InventoryListDirective.js @@ -11,8 +11,8 @@ } }); - controller.$inject = ['$scope', '$uibModal']; - function controller($scope, $uibModal) { + controller.$inject = ['$scope', '$uibModal', 'inventorySvc']; + function controller($scope, $uibModal, inventorySvc) { var vm = this; vm.inventories = $scope.inventories; @@ -32,5 +32,7 @@ scope: angular.extend($scope.$new(true), { inventory: inventory }) }); } + + vm.isShredReady = inventorySvc.isShredReady; } })(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/inventoryInfoDirective.js b/InventoryTraker.Web/js/inventory/inventoryInfoDirective.js index 2d8f867..a2fbacd 100644 --- a/InventoryTraker.Web/js/inventory/inventoryInfoDirective.js +++ b/InventoryTraker.Web/js/inventory/inventoryInfoDirective.js @@ -11,9 +11,13 @@ } }); - controller.$inject = ['$scope']; - function controller($scope) { + controller.$inject = ['$scope', 'inventorySvc']; + function controller($scope, inventorySvc) { var vm = this; vm.inventory = $scope.inventory; + + vm.isShredReady = function () { return inventorySvc.isShredReady(vm.inventory); }; + + vm.isInInventory = function() { return vm.inventory.quantity > 0; } } })(); \ No newline at end of file diff --git a/InventoryTraker.Web/js/inventory/inventoryRemoveDirective.js b/InventoryTraker.Web/js/inventory/inventoryRemoveDirective.js index ba8dfdf..3d5a339 100644 --- a/InventoryTraker.Web/js/inventory/inventoryRemoveDirective.js +++ b/InventoryTraker.Web/js/inventory/inventoryRemoveDirective.js @@ -21,7 +21,7 @@ vm.removeForm = { inventoryId: vm.inventory.id, quantity: vm.inventory.quantity, - transactionType: vm.inventory.isExpired ? "Expired" : null + transactionType: vm.inventory.shredReadyDate ? "Shreded" : null }; vm.statusMessage = "Enter details for the inventory removal below."; diff --git a/InventoryTraker.Web/js/inventory/inventorySvc.js b/InventoryTraker.Web/js/inventory/inventorySvc.js index 6127937..ea8dd79 100644 --- a/InventoryTraker.Web/js/inventory/inventorySvc.js +++ b/InventoryTraker.Web/js/inventory/inventorySvc.js @@ -1,8 +1,8 @@ (function() { window.app.factory("inventorySvc", [ - "$http", "$filter", - function($http, $filter) { + "$http", "$filter", 'Upload', + function($http, $filter, Upload) { var inventories = []; loadInventories(); @@ -15,8 +15,10 @@ inventories: inventories, get: get, refresh: refresh, - find: find, - exportInventory: exportInventory + load: load, + exportInventory: exportInventory, + isShredReady: isShredReady, + importFile: importFile }; return svc; @@ -73,15 +75,34 @@ function refresh(id) { var existingInventory = get(id); if (existingInventory) { - find(id) + load(id) .success(function(inventory) { angular.copy(inventory, existingInventory); }); } } - function find(id) { - return $http.post("/Inventory/Find", { id: id }); + function load(id) { + return $http.post("/Inventory/Find", { id: id }) + .success(function (inventory) { + var existingInventory = get(id); + if (existingInventory != null) + angular.copy(inventory, existingInventory); + else + inventories.unshift(inventory); + }); + } + + function importFile(file) { + return Upload.upload({ + url: "/api/Import", + method: 'POST', + data: { file: file} + }); + } + + function isShredReady(inventory) { + return new Date(inventory.shredReadyDate) <= new Date(); } } ]); diff --git a/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml b/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml index 89e4962..f270246 100644 --- a/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml +++ b/InventoryTraker.Web/js/inventory/templates/inventoryAdd.tmpl.cshtml @@ -9,78 +9,40 @@