Added new mix upload page

This commit is contained in:
Fergal Moran
2013-08-09 17:43:33 +01:00
parent dbf5e610b9
commit d9d06a8eb0
44 changed files with 7273 additions and 140 deletions

3
.gitignore vendored
View File

@@ -19,4 +19,5 @@ test.py
mysql2pgsql.yml
downloads.txt
plays.txt
dss_ah
dss_ah
fixtures.json

View File

@@ -1,12 +0,0 @@
from unittest import TestCase
class MixTestItem(object):
def get_mix_at(self, hour):
pass
class MixTest(TestCase):
def test_simple(self):
m = MixTestItem()

View File

@@ -1,4 +1,3 @@
import uuid
import os
def generate_save_file_name(uid, prefix, filename):

View File

@@ -5,6 +5,7 @@ import os
from django.core.urlresolvers import reverse_lazy
import djcelery
from django.conf import global_settings
import sys
from dss import localsettings
from dss import logsettings
@@ -48,15 +49,12 @@ MEDIA_ROOT = localsettings.MEDIA_ROOT
STATIC_ROOT = here('static')
CACHE_ROOT = localsettings.CACHE_ROOT
if DEBUG:
STATIC_URL = '/static/'
else:
STATIC_URL = localsettings.STATIC_URL if hasattr(localsettings, 'STATIC_URL') else 'http://static.deepsouthsounds.com/'
STATIC_URL = localsettings.STATIC_URL if hasattr(localsettings, 'STATIC_URL') else '/static/'
if DEBUG:
MEDIA_URL = '/media/'
else:
MEDIA_URL = localsettings.MEDIA_URL if hasattr(localsettings, 'MEDIA_URL') else 'http://media.deepsouthsounds.com/'
MEDIA_URL = localsettings.MEDIA_URL if hasattr(localsettings, 'MEDIA_URL') else '/static/'
ADMIN_MEDIA_PREFIX = STATIC_URL + "grappelli/"
@@ -131,7 +129,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'spa.middleware.uploadify.SWFUploadMiddleware',
#'spa.middleware.uploadify.SWFUploadMiddleware',
#'spa.middleware.sqlprinter.SqlPrintingMiddleware' if DEBUG else None,
#'debug_toolbar.middleware.DebugToolbarMiddleware',
)
@@ -170,6 +168,7 @@ INSTALLED_APPS = (
'debug_toolbar',
'django_jenkins',
'dbbackup',
'jfu',
#'backbone_tastypie',
)
@@ -271,3 +270,11 @@ DBBACKUP_TOKENS_FILEPATH = localsettings.DBBACKUP_TOKENS_FILEPATH
DBBACKUP_DROPBOX_APP_KEY = localsettings.DBBACKUP_DROPBOX_APP_KEY
DBBACKUP_DROPBOX_APP_SECRET = localsettings.DBBACKUP_DROPBOX_APP_SECRET
DBBACKUP_CLEANUP_KEEP = 5
if 'test' in sys.argv:
try:
from test_settings import *
except ImportError:
pass

View File

@@ -7,11 +7,11 @@ django-allauth>=0.7.0
django-annoying>=0.7.6
django-celery>=3.0.4
django-gravatar2>=1.0.6
django-avatar>=1.0.5
django-notification>=0.2
django-socialauth>=0.1.2c
django-tastypie>=0.9.11
django-tinymce>=1.5.1b2
git+git://github.com/jezdez/django-avatar.git#egg=django-avatar
git+git://github.com/PaulUithol/backbone-tastypie.git#egg=backbone-tastypie
git+git://github.com/tschellenbach/Django-facebook.git#django-facebook
git+git://github.com/hpoul/sct-communitytools.git#django-sct
@@ -38,3 +38,4 @@ django-jenkins
gunicorn
django-dbbackup
dropbox
django-jfu

View File

@@ -5,12 +5,15 @@ import logging
from django.conf.urls import url
from django.contrib.auth.decorators import login_required
from django.core.exceptions import ObjectDoesNotExist
from django.core.files.base import ContentFile
from django.core.files.storage import default_storage
from django.db.models import get_model
from django.http import HttpResponse, HttpResponseNotFound
from annoying.decorators import render_to
from django.shortcuts import render_to_response
from django.utils import simplejson
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from core.utils import live
from dss import localsettings, settings
@@ -21,7 +24,7 @@ from core.serialisers import json
from core.tasks import create_waveform_task
from core.utils.audio.mp3 import mp3_length
from spa.models.notification import Notification
from jfu.http import upload_receive, UploadResponse, JFUResponse
logger = logging.getLogger(__name__)
@@ -49,7 +52,6 @@ class AjaxHandler(object):
url(r'^upload_release_image/(?P<release_id>\d+)/$', 'spa.ajax.upload_release_image',
name='ajax_upload_release_image'),
url(r'^upload_avatar_image/$', 'spa.ajax.upload_avatar_image', name='ajax_upload_avatar_image'),
url(r'^upload_mix_file_handler/$', 'spa.ajax.upload_mix_file_handler', name='ajax_upload_mix_file_handler'),
url(r'^lookup/search/$', 'spa.ajax.lookup_search', name='ajax_lookup'),
url(r'^lookup/(?P<source>\w+)/$', 'spa.ajax.lookup', name='ajax_lookup'),
]
@@ -225,33 +227,40 @@ def upload_avatar_image(request):
return HttpResponse(_get_json("Failed"))
@csrf_exempt
def upload_mix_file_handler(request):
@require_POST
@login_required
def upload(request):
# The assumption here is that jQuery File Upload
# has been configured to send files one at a time.
# If multiple files can be uploaded simultaneously,
# 'file' may be a list of files.
try:
if 'Filedata' in request.FILES and 'upload-hash' in request.POST:
f = request.FILES['Filedata']
fileName, extension = os.path.splitext(f.name)
uid = request.POST['upload-hash']
in_file = os.path.join(settings.CACHE_ROOT, "mixes", "%s%s" % (uid, extension))
with open(in_file, 'wb+') as destination:
for chunk in f.chunks():
destination.write(chunk)
try:
#update mix with duration
try:
mix = Mix.objects.get(uid=uid)
mix.duration = mp3_length(mix.get_absolute_path())
mix.save()
except ObjectDoesNotExist:
pass
uid = request.POST['upload-hash']
in_file = upload_receive(request)
fileName, extension = os.path.splitext(in_file.name)
path = os.path.join(settings.CACHE_ROOT, "mixes", "%s%s" % (uid, extension))
cache_file = default_storage.save(path, ContentFile(in_file.read()))
create_waveform_task.delay(in_file=in_file, mix_uid=uid)
except Exception, ex:
logger.exception("Error starting waveform generation task: %s" % ex.message)
return HttpResponse(_get_json("Success"), mimetype='application/json')
try:
mix = Mix.objects.get(uid=uid)
mix.duration = mp3_length(mix.get_absolute_path())
mix.save()
except ObjectDoesNotExist:
#Form hasn't been posted yet
pass
#create_waveform_task.delay(in_file=file, mix_uid=uid)
create_waveform_task(in_file=cache_file, mix_uid=uid)
file_dict = {
'size': in_file.size,
'uid': uid
}
return UploadResponse(request, file_dict)
except Exception, ex:
logger.exception("Error uploading mix")
return HttpResponse(_get_json("Failed"), mimetype='application/json')
logger.error(ex.message)
return HttpResponse(content="Error occurred uploading file", status=503)
@csrf_exempt

View File

@@ -4,6 +4,6 @@ from django.core.urlresolvers import reverse
class SWFUploadMiddleware(object):
def process_request(self, request):
if (request.method == 'POST') and\
(request.path == reverse('ajax_upload_mix_file_handler')) and\
(request.path == reverse('jfu_upload')) and\
request.POST.has_key(settings.SESSION_COOKIE_NAME):
request.COOKIES[settings.SESSION_COOKIE_NAME] = request.POST[settings.SESSION_COOKIE_NAME]

View File

@@ -1,5 +1,6 @@
import threading
from django.core.exceptions import ObjectDoesNotExist
from django.core.exceptions import ObjectDoesNotExist, ImproperlyConfigured
from django.db.models import get_app
from django.db.models.signals import post_save
from django.dispatch import Signal
from django.contrib.auth.models import User
@@ -24,6 +25,7 @@ def waveform_generated_callback(sender, **kwargs):
mix.duration = mp3_length(mix.get_absolute_path())
mix.save()
except ObjectDoesNotExist:
print "Mix has still not been uploaded"
pass
@@ -39,17 +41,6 @@ def create_profile(sender, **kw):
post_save.connect(create_profile, sender=User)
if "notification" in settings.INSTALLED_APPS:
from notification import models as notification
def create_notice_types(app, created_models, verbosity, **kwargs):
notification.create_notice_type("new_follower", _("You have a new follower on deepsouthsounds.com"),
_("You have a new follower."))
signals.post_migrate.connect(create_notice_types, sender=notification)
else:
print "Skipping creation of NoticeTypes as notification app not found"
"""
Doing signals for notifications here.
I like this method because I have a single signal

1
core/tests/__init__.py → spa/tests/__init__.py Executable file → Normal file
View File

@@ -1 +1,2 @@
__author__ = 'fergalm'
from mix import *

21
spa/tests/mix.py Normal file
View File

@@ -0,0 +1,21 @@
import uuid
from django.core.urlresolvers import reverse
from django.test.client import Client
import unittest
from utils import here
class TestPostMix(unittest.TestCase):
fixtures = ['full.json']
def testPost(self):
print "Running TestPostMix"
try:
c = Client()
uid = uuid.uuid1()
with open(here('spa/tests/data/test.mp3')) as fp:
response = c.post(reverse('jfu_upload'), {'upload-hash': uid, 'files[]': [fp]}, HTTP_ACCEPT_ENCODING="application/json")
self.assertEquals(response.status_code, 200)
except Exception, ex:
self.fail(ex.message)

View File

@@ -42,6 +42,7 @@ urlpatterns = patterns(
url(r'^podcasts', 'spa.podcast.get_default_podcast'),
url(r'^social/', include('spa.social.urls')),
url(r'^embed/', include('spa.embedding.urls')),
url(r'_upload/', 'spa.ajax.upload', name='jfu_upload'),
url(r'^ajax/', include(ajax.urls)),
url(r'^audio/', include(audio.urls)),
url(r'^api/', include(v1_api.urls)),

View File

@@ -0,0 +1,67 @@
@charset "UTF-8";
/*
* jQuery File Upload UI Plugin CSS 8.1
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
.fileinput-button {
position: relative;
overflow: hidden;
}
.fileinput-button input {
position: absolute;
top: 0;
right: 0;
margin: 0;
opacity: 0;
filter: alpha(opacity=0);
transform: translate(-300px, 0) scale(4);
font-size: 23px;
direction: ltr;
cursor: pointer;
}
.fileupload-buttonbar .btn,
.fileupload-buttonbar .toggle {
margin-bottom: 5px;
}
.progress-animated .bar {
background: url(../img/progressbar.gif) !important;
filter: none;
}
.fileupload-loading {
float: right;
width: 32px;
height: 32px;
background: url(../img/loading.gif) center no-repeat;
background-size: contain;
display: none;
}
.fileupload-processing .fileupload-loading {
display: block;
}
.files audio,
.files video {
max-width: 300px;
}
@media (max-width: 767px) {
.fileupload-buttonbar .toggle,
.files .toggle,
.files .btn span {
display: none;
}
.files .name {
width: 80px;
word-wrap: break-word;
}
.files audio,
.files video {
max-width: 80px;
}
}

BIN
static/img/loading.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
static/img/progressbar.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -4,6 +4,7 @@ requirejs.config({
waitSeconds: 200,
paths: {
jquery: 'libs/jquery',
'jquery.ui.widget': 'libs/jquery.ui.widget',
backbone: 'libs/backbone/backbone',
'backbone.babysitter': 'libs/backbone/backbone.babysitter',
'backbone.relational': 'libs/backbone/backbone.relational',
@@ -23,6 +24,22 @@ requirejs.config({
models: 'app/models',
'app.lib': 'app/lib',
moment: 'libs/moment',
'tmpl': 'libs/upload/tmpl',
'jquery.fileupload': 'libs/upload/jquery.fileupload',
'jquery.fileupload-ui': 'libs/upload/jquery.fileupload-ui',
'jquery.fileupload-image': 'libs/upload/jquery.fileupload-image',
'jquery.fileupload-video': 'libs/upload/jquery.fileupload-video',
'jquery.fileupload-validate': 'libs/upload/jquery.fileupload-validate',
'jquery.fileupload-process': 'libs/upload/jquery.fileupload-process',
'jquery.fileupload-audio': 'libs/upload/jquery.fileupload-audio',
'jquery.iframe-transport': 'libs/jquery.iframe-transport',
'load-image': 'libs/upload/load-image',
'load-image-meta': 'libs/upload/load-image-meta',
'load-image-exif': 'libs/upload/load-image-exif',
'load-image-ios': 'libs/upload/load-image-ios',
'canvas-to-blob': 'libs/canvas-to-blob',
toastr: 'libs/toastr',
'socket.io': [
com.podnoms.settings.SOCKET_IO_JS_URL,
@@ -33,6 +50,9 @@ requirejs.config({
jquery: {
exports: '$'
},
'jquery.ui.widget': {
deps: ['jquery']
},
backbone: {
exports: 'Backbone',
deps: ['jquery', 'underscore']
@@ -41,6 +61,12 @@ requirejs.config({
exports: 'bootstrap',
deps: ['jquery']
},
/*
fileupload:{
deps: ['iframe-transport', 'fileupload-process', 'fileupload-audio', 'fileupload-video', 'fileupload-validate', 'fileupload-ui'],
deps: ['jquery.iframe-transport', 'jquery.ui.widget', 'fileupload-ui'],
exports: '$.fileupload'
},*/
typeahead: {
deps: ['jquery', 'bootstrap']
},

View File

@@ -1,4 +1,6 @@
define ['app.lib/editableView', 'moment', 'utils', 'libs/backbone/backbone.syphon', 'text!/tpl/MixEditView'],
define ['app.lib/editableView', 'moment', 'utils', 'libs/backbone/backbone.syphon', 'text!/tpl/MixEditView'
'jquery.fileupload', 'jquery.fileupload-process', 'jquery.fileupload-audio', 'jquery.iframe-transport', 'jquery.ui.widget', 'jquery.fileupload-ui',
'libs/ajaxfileupload', 'libs/jasny/bootstrap-fileupload'],
(EditableView, moment, utils, Syphon, Template) ->
class MixEditView extends EditableView
template: _.template(Template)
@@ -15,40 +17,29 @@ define ['app.lib/editableView', 'moment', 'utils', 'libs/backbone/backbone.sypho
@guid = utils.generateGuid()
@state = 0
onDomRefresh: ->
if not @model.id
$("#fileupload", @el).fileupload
downloadTemplateId: undefined
url: "/_upload/"
start: ->
$("#mix-details", @el).show()
done: ->
$("#div-upload-mix", @el).hide()
onRender: ->
console.log("MixEditView: onRender")
@sendImage = false
parent = this
if not @model.id
$("#mix-upload", @el).uploadifive(
uploadScript: "/ajax/upload_mix_file_handler/"
buttonText: "Select audio file (mp3 for now please)"
formData:
"upload-hash": @guid
sessionid: $.cookie("sessionid")
onUploadFile: (file) ->
$(window).on "beforeunload", ->
alert "Go on outta that.."
onAddQueueItem: (file) ->
$("#upload-extension", @el).val file.name.split(".").pop()
$("#mix-details", @el).show()
onProgress: (file, e) ->
onUploadComplete: (file, data) ->
parent.state++
parent.checkRedirect()
)
$(".fileupload", @el).fileupload uploadtype: "image"
$("#mix-details", @el).hide()
$(".upload-hash", @el).val @guid
$("#upload-hash", @el).val @guid
else
$("#div-upload-mix", @el).hide()
@state = 1
$("#mix-imageupload", @el).jas_fileupload uploadtype: "image"
$("#image-form-proxy", @el).ajaxForm
beforeSubmit: ->
$("#results").html "Submitting..."

View File

@@ -4,7 +4,7 @@
__hasProp = {}.hasOwnProperty,
__extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; };
define(['app.lib/editableView', 'moment', 'utils', 'libs/backbone/backbone.syphon', 'text!/tpl/MixEditView'], function(EditableView, moment, utils, Syphon, Template) {
define(['app.lib/editableView', 'moment', 'utils', 'libs/backbone/backbone.syphon', 'text!/tpl/MixEditView', 'jquery.fileupload', 'jquery.fileupload-process', 'jquery.fileupload-audio', 'jquery.iframe-transport', 'jquery.ui.widget', 'jquery.fileupload-ui', 'libs/ajaxfileupload', 'libs/jasny/bootstrap-fileupload'], function(EditableView, moment, utils, Syphon, Template) {
var MixEditView, _ref;
return MixEditView = (function(_super) {
@@ -35,6 +35,21 @@
return this.state = 0;
};
MixEditView.prototype.onDomRefresh = function() {
if (!this.model.id) {
return $("#fileupload", this.el).fileupload({
downloadTemplateId: void 0,
url: "/_upload/",
start: function() {
return $("#mix-details", this.el).show();
},
done: function() {
return $("#div-upload-mix", this.el).hide();
}
});
}
};
MixEditView.prototype.onRender = function() {
var parent;
@@ -42,37 +57,15 @@
this.sendImage = false;
parent = this;
if (!this.model.id) {
$("#mix-upload", this.el).uploadifive({
uploadScript: "/ajax/upload_mix_file_handler/",
buttonText: "Select audio file (mp3 for now please)",
formData: {
"upload-hash": this.guid,
sessionid: $.cookie("sessionid")
},
onUploadFile: function(file) {
return $(window).on("beforeunload", function() {
return alert("Go on outta that..");
});
},
onAddQueueItem: function(file) {
$("#upload-extension", this.el).val(file.name.split(".").pop());
return $("#mix-details", this.el).show();
},
onProgress: function(file, e) {},
onUploadComplete: function(file, data) {
parent.state++;
return parent.checkRedirect();
}
});
$(".fileupload", this.el).fileupload({
uploadtype: "image"
});
$("#mix-details", this.el).hide();
$(".upload-hash", this.el).val(this.guid);
$("#upload-hash", this.el).val(this.guid);
} else {
$("#div-upload-mix", this.el).hide();
this.state = 1;
}
$("#mix-imageupload", this.el).jas_fileupload({
uploadtype: "image"
});
$("#image-form-proxy", this.el).ajaxForm({
beforeSubmit: function() {
return $("#results").html("Submitting...");

View File

@@ -0,0 +1,95 @@
/*
* 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, define */
(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)));
};
}
}
if (typeof define === 'function' && define.amd) {
define(function () {
return dataURLtoBlob;
});
} else {
window.dataURLtoBlob = dataURLtoBlob;
}
}(this));

View File

@@ -98,7 +98,7 @@
/* INPUTMASK PLUGIN DEFINITION
* =========================== */
$.fn.fileupload = function (options) {
$.fn.jas_fileupload = function (options) {
return this.each(function () {
var $this = $(this)
, data = $this.data('fileupload')
@@ -106,7 +106,7 @@
})
}
$.fn.fileupload.Constructor = Fileupload
$.fn.jas_fileupload.Constructor = Fileupload
/* INPUTMASK DATA-API
@@ -116,7 +116,7 @@
$('body').on('click.fileupload.data-api', '[data-provides="fileupload"]', function (e) {
var $this = $(this)
if ($this.data('fileupload')) return
$this.fileupload($this.data())
$this.jas_fileupload($this.data())
if ($(e.target).data('dismiss') == 'fileupload') $(e.target).trigger('click.fileupload')
})

View File

@@ -0,0 +1,205 @@
/*
* jQuery Iframe Transport Plugin 1.7
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint unparam: true, nomen: true */
/*global define, window, document */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['jquery'], factory);
} else {
// Browser globals:
factory(window.jQuery);
}
}(function ($) {
'use strict';
// Helper variable to create unique names for the transport iframes:
var counter = 0;
// The iframe transport accepts three additional options:
// options.fileInput: a jQuery collection of file input fields
// options.paramName: the parameter name for the file form data,
// overrides the name property of the file input field(s),
// can be a string or an array of strings.
// options.formData: an array of objects with name and value properties,
// equivalent to the return data of .serializeArray(), e.g.:
// [{name: 'a', value: 1}, {name: 'b', value: 2}]
$.ajaxTransport('iframe', function (options) {
if (options.async) {
var form,
iframe,
addParamChar;
return {
send: function (_, completeCallback) {
form = $('<form style="display:none;"></form>');
form.attr('accept-charset', options.formAcceptCharset);
addParamChar = /\?/.test(options.url) ? '&' : '?';
// XDomainRequest only supports GET and POST:
if (options.type === 'DELETE') {
options.url = options.url + addParamChar + '_method=DELETE';
options.type = 'POST';
} else if (options.type === 'PUT') {
options.url = options.url + addParamChar + '_method=PUT';
options.type = 'POST';
} else if (options.type === 'PATCH') {
options.url = options.url + addParamChar + '_method=PATCH';
options.type = 'POST';
}
// javascript:false as initial iframe src
// prevents warning popups on HTTPS in IE6.
// IE versions below IE8 cannot set the name property of
// elements that have already been added to the DOM,
// so we set the name along with the iframe HTML markup:
counter += 1;
iframe = $(
'<iframe src="javascript:false;" name="iframe-transport-' +
counter + '"></iframe>'
).bind('load', function () {
var fileInputClones,
paramNames = $.isArray(options.paramName) ?
options.paramName : [options.paramName];
iframe
.unbind('load')
.bind('load', function () {
var response;
// Wrap in a try/catch block to catch exceptions thrown
// when trying to access cross-domain iframe contents:
try {
response = iframe.contents();
// Google Chrome and Firefox do not throw an
// exception when calling iframe.contents() on
// cross-domain requests, so we unify the response:
if (!response.length || !response[0].firstChild) {
throw new Error();
}
} catch (e) {
response = undefined;
}
// The complete callback returns the
// iframe content document as response object:
completeCallback(
200,
'success',
{'iframe': response}
);
// Fix for IE endless progress bar activity bug
// (happens on form submits to iframe targets):
$('<iframe src="javascript:false;"></iframe>')
.appendTo(form);
window.setTimeout(function () {
// Removing the form in a setTimeout call
// allows Chrome's developer tools to display
// the response result
form.remove();
}, 0);
});
form
.prop('target', iframe.prop('name'))
.prop('action', options.url)
.prop('method', options.type);
if (options.formData) {
$.each(options.formData, function (index, field) {
$('<input type="hidden"/>')
.prop('name', field.name)
.val(field.value)
.appendTo(form);
});
}
if (options.fileInput && options.fileInput.length &&
options.type === 'POST') {
fileInputClones = options.fileInput.clone();
// Insert a clone for each file input field:
options.fileInput.after(function (index) {
return fileInputClones[index];
});
if (options.paramName) {
options.fileInput.each(function (index) {
$(this).prop(
'name',
paramNames[index] || options.paramName
);
});
}
// Appending the file input fields to the hidden form
// removes them from their original location:
form
.append(options.fileInput)
.prop('enctype', 'multipart/form-data')
// enctype must be set as encoding for IE:
.prop('encoding', 'multipart/form-data');
}
form.submit();
// Insert the file input fields at their original location
// by replacing the clones with the originals:
if (fileInputClones && fileInputClones.length) {
options.fileInput.each(function (index, input) {
var clone = $(fileInputClones[index]);
$(input).prop('name', clone.prop('name'));
clone.replaceWith(input);
});
}
});
form.append(iframe).appendTo(document.body);
},
abort: function () {
if (iframe) {
// javascript:false as iframe src aborts the request
// and prevents warning popups on HTTPS in IE6.
// concat is used to avoid the "Script URL" JSLint error:
iframe
.unbind('load')
.prop('src', 'javascript'.concat(':false;'));
}
if (form) {
form.remove();
}
}
};
}
});
// The iframe transport returns the iframe content document as response.
// The following adds converters from iframe to text, json, html, xml
// and script.
// Please note that the Content-Type for JSON responses has to be text/plain
// or text/html, if the browser doesn't include application/json in the
// Accept header, else IE will show a download dialog.
// The Content-Type for XML responses on the other hand has to be always
// application/xml or text/xml, so IE properly parses the XML response.
// See also
// https://github.com/blueimp/jQuery-File-Upload/wiki/Setup#content-type-negotiation
$.ajaxSetup({
converters: {
'iframe text': function (iframe) {
return iframe && $(iframe[0].body).text();
},
'iframe json': function (iframe) {
return iframe && $.parseJSON($(iframe[0].body).text());
},
'iframe html': function (iframe) {
return iframe && $(iframe[0].body).html();
},
'iframe xml': function (iframe) {
var xmlDoc = iframe && iframe[0];
return xmlDoc && $.isXMLDoc(xmlDoc) ? xmlDoc :
$.parseXML((xmlDoc.XMLDocument && xmlDoc.XMLDocument.xml) ||
$(xmlDoc.body).html());
},
'iframe script': function (iframe) {
return iframe && $.globalEval($(iframe[0].body).text());
}
}
});
}));

View File

@@ -0,0 +1,489 @@
/*!
* jQuery Templates Plugin 1.0.0pre
* http://github.com/jquery/jquery-tmpl
* Requires jQuery 1.4.2
*
* Copyright 2011, Software Freedom Conservancy, Inc.
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
(function (jQuery, undefined) {
var oldManip = jQuery.fn.domManip, tmplItmAtt = "_tmplitem", htmlExpr = /^[^<]*(<[\w\W]+>)[^>]*$|\{\{\! /,
newTmplItems = {}, wrappedItems = {}, appendToTmplItems, topTmplItem = { key: 0, data: {} }, itemKey = 0, cloneIndex = 0, stack = [];
function newTmplItem(options, parentItem, fn, data) {
// Returns a template item data structure for a new rendered instance of a template (a 'template item').
// The content field is a hierarchical array of strings and nested items (to be
// removed and replaced by nodes field of dom elements, once inserted in DOM).
var newItem = {
data: data || (data === 0 || data === false) ? data : (parentItem ? parentItem.data : {}),
_wrap: parentItem ? parentItem._wrap : null,
tmpl: null,
parent: parentItem || null,
nodes: [],
calls: tiCalls,
nest: tiNest,
wrap: tiWrap,
html: tiHtml,
update: tiUpdate
};
if (options) {
jQuery.extend(newItem, options, { nodes: [], parent: parentItem });
}
if (fn) {
// Build the hierarchical content to be used during insertion into DOM
newItem.tmpl = fn;
newItem._ctnt = newItem._ctnt || newItem.tmpl(jQuery, newItem);
newItem.key = ++itemKey;
// Keep track of new template item, until it is stored as jQuery Data on DOM element
(stack.length ? wrappedItems : newTmplItems)[itemKey] = newItem;
}
return newItem;
}
// Override appendTo etc., in order to provide support for targeting multiple elements. (This code would disappear if integrated in jquery core).
jQuery.each({
appendTo: "append",
prependTo: "prepend",
insertBefore: "before",
insertAfter: "after",
replaceAll: "replaceWith"
}, function (name, original) {
jQuery.fn[ name ] = function (selector) {
var ret = [], insert = jQuery(selector), elems, i, l, tmplItems,
parent = this.length === 1 && this[0].parentNode;
appendToTmplItems = newTmplItems || {};
if (parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1) {
insert[ original ](this[0]);
ret = this;
} else {
for (i = 0, l = insert.length; i < l; i++) {
cloneIndex = i;
elems = (i > 0 ? this.clone(true) : this).get();
jQuery(insert[i])[ original ](elems);
ret = ret.concat(elems);
}
cloneIndex = 0;
ret = this.pushStack(ret, name, insert.selector);
}
tmplItems = appendToTmplItems;
appendToTmplItems = null;
jQuery.tmpl.complete(tmplItems);
return ret;
};
});
jQuery.fn.extend({
// Use first wrapped element as template markup.
// Return wrapped set of template items, obtained by rendering template against data.
tmpl: function (data, options, parentItem) {
return jQuery.tmpl(this[0], data, options, parentItem);
},
// Find which rendered template item the first wrapped DOM element belongs to
tmplItem: function () {
return jQuery.tmplItem(this[0]);
},
// Consider the first wrapped element as a template declaration, and get the compiled template or store it as a named template.
template: function (name) {
return jQuery.template(name, this[0]);
},
domManip: function (args, table, callback, options) {
if (args[0] && jQuery.isArray(args[0])) {
var dmArgs = jQuery.makeArray(arguments), elems = args[0], elemsLength = elems.length, i = 0, tmplItem;
while (i < elemsLength && !(tmplItem = jQuery.data(elems[i++], "tmplItem"))) {
}
if (tmplItem && cloneIndex) {
dmArgs[2] = function (fragClone) {
// Handler called by oldManip when rendered template has been inserted into DOM.
jQuery.tmpl.afterManip(this, fragClone, callback);
};
}
oldManip.apply(this, dmArgs);
} else {
oldManip.apply(this, arguments);
}
cloneIndex = 0;
if (!appendToTmplItems) {
jQuery.tmpl.complete(newTmplItems);
}
return this;
}
});
jQuery.extend({
// Return wrapped set of template items, obtained by rendering template against data.
tmpl: function (tmpl, data, options, parentItem) {
var ret, topLevel = !parentItem;
if (topLevel) {
// This is a top-level tmpl call (not from a nested template using {{tmpl}})
parentItem = topTmplItem;
tmpl = jQuery.template[tmpl] || jQuery.template(null, tmpl);
wrappedItems = {}; // Any wrapped items will be rebuilt, since this is top level
} else if (!tmpl) {
// The template item is already associated with DOM - this is a refresh.
// Re-evaluate rendered template for the parentItem
tmpl = parentItem.tmpl;
newTmplItems[parentItem.key] = parentItem;
parentItem.nodes = [];
if (parentItem.wrapped) {
updateWrapped(parentItem, parentItem.wrapped);
}
// Rebuild, without creating a new template item
return jQuery(build(parentItem, null, parentItem.tmpl(jQuery, parentItem)));
}
if (!tmpl) {
return []; // Could throw...
}
if (typeof data === "function") {
data = data.call(parentItem || {});
}
if (options && options.wrapped) {
updateWrapped(options, options.wrapped);
}
ret = jQuery.isArray(data) ?
jQuery.map(data, function (dataItem) {
return dataItem ? newTmplItem(options, parentItem, tmpl, dataItem) : null;
}) :
[ newTmplItem(options, parentItem, tmpl, data) ];
return topLevel ? jQuery(build(parentItem, null, ret)) : ret;
},
// Return rendered template item for an element.
tmplItem: function (elem) {
var tmplItem;
if (elem instanceof jQuery) {
elem = elem[0];
}
while (elem && elem.nodeType === 1 && !(tmplItem = jQuery.data(elem, "tmplItem")) && (elem = elem.parentNode)) {
}
return tmplItem || topTmplItem;
},
// Set:
// Use $.template( name, tmpl ) to cache a named template,
// where tmpl is a template string, a script element or a jQuery instance wrapping a script element, etc.
// Use $( "selector" ).template( name ) to provide access by name to a script block template declaration.
// Get:
// Use $.template( name ) to access a cached template.
// Also $( selectorToScriptBlock ).template(), or $.template( null, templateString )
// will return the compiled template, without adding a name reference.
// If templateString includes at least one HTML tag, $.template( templateString ) is equivalent
// to $.template( null, templateString )
template: function (name, tmpl) {
if (tmpl) {
// Compile template and associate with name
if (typeof tmpl === "string") {
// This is an HTML string being passed directly in.
tmpl = buildTmplFn(tmpl);
} else if (tmpl instanceof jQuery) {
tmpl = tmpl[0] || {};
}
if (tmpl.nodeType) {
// If this is a template block, use cached copy, or generate tmpl function and cache.
tmpl = jQuery.data(tmpl, "tmpl") || jQuery.data(tmpl, "tmpl", buildTmplFn(tmpl.innerHTML));
// Issue: In IE, if the container element is not a script block, the innerHTML will remove quotes from attribute values whenever the value does not include white space.
// This means that foo="${x}" will not work if the value of x includes white space: foo="${x}" -> foo=value of x.
// To correct this, include space in tag: foo="${ x }" -> foo="value of x"
}
return typeof name === "string" ? (jQuery.template[name] = tmpl) : tmpl;
}
// Return named compiled template
return name ? (typeof name !== "string" ? jQuery.template(null, name) :
(jQuery.template[name] ||
// If not in map, and not containing at least on HTML tag, treat as a selector.
// (If integrated with core, use quickExpr.exec)
jQuery.template(null, htmlExpr.test(name) ? name : jQuery(name)))) : null;
},
encode: function (text) {
// Do HTML encoding replacing < > & and ' and " by corresponding entities.
return ("" + text).split("<").join("&lt;").split(">").join("&gt;").split('"').join("&#34;").split("'").join("&#39;");
}
});
jQuery.extend(jQuery.tmpl, {
tag: {
"tmpl": {
_default: { $2: "null" },
open: "if($notnull_1){__=__.concat($item.nest($1,$2));}"
// tmpl target parameter can be of type function, so use $1, not $1a (so not auto detection of functions)
// This means that {{tmpl foo}} treats foo as a template (which IS a function).
// Explicit parens can be used if foo is a function that returns a template: {{tmpl foo()}}.
},
"wrap": {
_default: { $2: "null" },
open: "$item.calls(__,$1,$2);__=[];",
close: "call=$item.calls();__=call._.concat($item.wrap(call,__));"
},
"each": {
_default: { $2: "$index, $value" },
open: "if($notnull_1){$.each($1a,function($2){with(this){",
close: "}});}"
},
"if": {
open: "if(($notnull_1) && $1a){",
close: "}"
},
"else": {
_default: { $1: "true" },
open: "}else if(($notnull_1) && $1a){"
},
"html": {
// Unecoded expression evaluation.
open: "if($notnull_1){__.push($1a);}"
},
"=": {
// Encoded expression evaluation. Abbreviated form is ${}.
_default: { $1: "$data" },
open: "if($notnull_1){__.push($.encode($1a));}"
},
"!": {
// Comment tag. Skipped by parser
open: ""
}
},
// This stub can be overridden, e.g. in jquery.tmplPlus for providing rendered events
complete: function (items) {
newTmplItems = {};
},
// Call this from code which overrides domManip, or equivalent
// Manage cloning/storing template items etc.
afterManip: function afterManip(elem, fragClone, callback) {
// Provides cloned fragment ready for fixup prior to and after insertion into DOM
var content = fragClone.nodeType === 11 ?
jQuery.makeArray(fragClone.childNodes) :
fragClone.nodeType === 1 ? [fragClone] : [];
// Return fragment to original caller (e.g. append) for DOM insertion
callback.call(elem, fragClone);
// Fragment has been inserted:- Add inserted nodes to tmplItem data structure. Replace inserted element annotations by jQuery.data.
storeTmplItems(content);
cloneIndex++;
}
});
//========================== Private helper functions, used by code above ==========================
function build(tmplItem, nested, content) {
// Convert hierarchical content into flat string array
// and finally return array of fragments ready for DOM insertion
var frag, ret = content ? jQuery.map(content, function (item) {
return (typeof item === "string") ?
// Insert template item annotations, to be converted to jQuery.data( "tmplItem" ) when elems are inserted into DOM.
(tmplItem.key ? item.replace(/(<\w+)(?=[\s>])(?![^>]*_tmplitem)([^>]*)/g, "$1 " + tmplItmAtt + "=\"" + tmplItem.key + "\" $2") : item) :
// This is a child template item. Build nested template.
build(item, tmplItem, item._ctnt);
}) :
// If content is not defined, insert tmplItem directly. Not a template item. May be a string, or a string array, e.g. from {{html $item.html()}}.
tmplItem;
if (nested) {
return ret;
}
// top-level template
ret = ret.join("");
// Support templates which have initial or final text nodes, or consist only of text
// Also support HTML entities within the HTML markup.
ret.replace(/^\s*([^<\s][^<]*)?(<[\w\W]+>)([^>]*[^>\s])?\s*$/, function (all, before, middle, after) {
frag = jQuery(middle).get();
storeTmplItems(frag);
if (before) {
frag = unencode(before).concat(frag);
}
if (after) {
frag = frag.concat(unencode(after));
}
});
return frag ? frag : unencode(ret);
}
function unencode(text) {
// Use createElement, since createTextNode will not render HTML entities correctly
var el = document.createElement("div");
el.innerHTML = text;
return jQuery.makeArray(el.childNodes);
}
// Generate a reusable function that will serve to render a template against data
function buildTmplFn(markup) {
return new Function("jQuery", "$item",
// Use the variable __ to hold a string array while building the compiled template. (See https://github.com/jquery/jquery-tmpl/issues#issue/10).
"var $=jQuery,call,__=[],$data=$item.data;" +
// Introduce the data as local variables using with(){}
"with($data){__.push('" +
// Convert the template into pure JavaScript
jQuery.trim(markup)
.replace(/([\\'])/g, "\\$1")
.replace(/[\r\t\n]/g, " ")
.replace(/\$\{([^\}]*)\}/g, "{{= $1}}")
.replace(/\{\{(\/?)(\w+|.)(?:\(((?:[^\}]|\}(?!\}))*?)?\))?(?:\s+(.*?)?)?(\(((?:[^\}]|\}(?!\}))*?)\))?\s*\}\}/g,
function (all, slash, type, fnargs, target, parens, args) {
var tag = jQuery.tmpl.tag[ type ], def, expr, exprAutoFnDetect;
if (!tag) {
throw "Unknown template tag: " + type;
}
def = tag._default || [];
if (parens && !/\w$/.test(target)) {
target += parens;
parens = "";
}
if (target) {
target = unescape(target);
args = args ? ("," + unescape(args) + ")") : (parens ? ")" : "");
// Support for target being things like a.toLowerCase();
// In that case don't call with template item as 'this' pointer. Just evaluate...
expr = parens ? (target.indexOf(".") > -1 ? target + unescape(parens) : ("(" + target + ").call($item" + args)) : target;
exprAutoFnDetect = parens ? expr : "(typeof(" + target + ")==='function'?(" + target + ").call($item):(" + target + "))";
} else {
exprAutoFnDetect = expr = def.$1 || "null";
}
fnargs = unescape(fnargs);
return "');" +
tag[ slash ? "close" : "open" ]
.split("$notnull_1").join(target ? "typeof(" + target + ")!=='undefined' && (" + target + ")!=null" : "true")
.split("$1a").join(exprAutoFnDetect)
.split("$1").join(expr)
.split("$2").join(fnargs || def.$2 || "") +
"__.push('";
}) +
"');}return __;"
);
}
function updateWrapped(options, wrapped) {
// Build the wrapped content.
options._wrap = build(options, true,
// Suport imperative scenario in which options.wrapped can be set to a selector or an HTML string.
jQuery.isArray(wrapped) ? wrapped : [htmlExpr.test(wrapped) ? wrapped : jQuery(wrapped).html()]
).join("");
}
function unescape(args) {
return args ? args.replace(/\\'/g, "'").replace(/\\\\/g, "\\") : null;
}
function outerHtml(elem) {
var div = document.createElement("div");
div.appendChild(elem.cloneNode(true));
return div.innerHTML;
}
// Store template items in jQuery.data(), ensuring a unique tmplItem data data structure for each rendered template instance.
function storeTmplItems(content) {
var keySuffix = "_" + cloneIndex, elem, elems, newClonedItems = {}, i, l, m;
for (i = 0, l = content.length; i < l; i++) {
if ((elem = content[i]).nodeType !== 1) {
continue;
}
elems = elem.getElementsByTagName("*");
for (m = elems.length - 1; m >= 0; m--) {
processItemKey(elems[m]);
}
processItemKey(elem);
}
function processItemKey(el) {
var pntKey, pntNode = el, pntItem, tmplItem, key;
// Ensure that each rendered template inserted into the DOM has its own template item,
if ((key = el.getAttribute(tmplItmAtt))) {
while (pntNode.parentNode && (pntNode = pntNode.parentNode).nodeType === 1 && !(pntKey = pntNode.getAttribute(tmplItmAtt))) {
}
if (pntKey !== key) {
// The next ancestor with a _tmplitem expando is on a different key than this one.
// So this is a top-level element within this template item
// Set pntNode to the key of the parentNode, or to 0 if pntNode.parentNode is null, or pntNode is a fragment.
pntNode = pntNode.parentNode ? (pntNode.nodeType === 11 ? 0 : (pntNode.getAttribute(tmplItmAtt) || 0)) : 0;
if (!(tmplItem = newTmplItems[key])) {
// The item is for wrapped content, and was copied from the temporary parent wrappedItem.
tmplItem = wrappedItems[key];
tmplItem = newTmplItem(tmplItem, newTmplItems[pntNode] || wrappedItems[pntNode]);
tmplItem.key = ++itemKey;
newTmplItems[itemKey] = tmplItem;
}
if (cloneIndex) {
cloneTmplItem(key);
}
}
el.removeAttribute(tmplItmAtt);
} else if (cloneIndex && (tmplItem = jQuery.data(el, "tmplItem"))) {
// This was a rendered element, cloned during append or appendTo etc.
// TmplItem stored in jQuery data has already been cloned in cloneCopyEvent. We must replace it with a fresh cloned tmplItem.
cloneTmplItem(tmplItem.key);
newTmplItems[tmplItem.key] = tmplItem;
pntNode = jQuery.data(el.parentNode, "tmplItem");
pntNode = pntNode ? pntNode.key : 0;
}
if (tmplItem) {
pntItem = tmplItem;
// Find the template item of the parent element.
// (Using !=, not !==, since pntItem.key is number, and pntNode may be a string)
while (pntItem && pntItem.key != pntNode) {
// Add this element as a top-level node for this rendered template item, as well as for any
// ancestor items between this item and the item of its parent element
pntItem.nodes.push(el);
pntItem = pntItem.parent;
}
// Delete content built during rendering - reduce API surface area and memory use, and avoid exposing of stale data after rendering...
delete tmplItem._ctnt;
delete tmplItem._wrap;
// Store template item as jQuery data on the element
jQuery.data(el, "tmplItem", tmplItem);
}
function cloneTmplItem(key) {
key = key + keySuffix;
tmplItem = newClonedItems[key] =
(newClonedItems[key] || newTmplItem(tmplItem, newTmplItems[tmplItem.parent.key + keySuffix] || tmplItem.parent));
}
}
}
//---- Helper functions for template item ----
function tiCalls(content, tmpl, data, options) {
if (!content) {
return stack.pop();
}
stack.push({ _: content, tmpl: tmpl, item: this, data: data, options: options });
}
function tiNest(tmpl, data, options) {
// nested template, using {{tmpl}} tag
return jQuery.tmpl(jQuery.template(tmpl), data, options, this);
}
function tiWrap(call, wrapped) {
// nested template, using {{wrap}} tag
var options = call.options || {};
options.wrapped = wrapped;
// Apply the template, which may incorporate wrapped content,
return jQuery.tmpl(jQuery.template(call.tmpl), call.data, options, call.item);
}
function tiHtml(filter, textOnly) {
var wrapped = this._wrap;
return jQuery.map(
jQuery(jQuery.isArray(wrapped) ? wrapped.join("") : wrapped).filter(filter || "*"),
function (e) {
return textOnly ?
e.innerText || e.textContent :
e.outerHTML || outerHtml(e);
});
}
function tiUpdate() {
var coll = this.nodes;
jQuery.tmpl(null, null, null, this).insertBefore(coll[0]);
jQuery(coll).remove();
}
})(jQuery);

530
static/js/libs/jquery.ui.widget.js vendored Normal file
View File

@@ -0,0 +1,530 @@
/*
* jQuery UI Widget 1.10.3+amd
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/jQuery.widget/
*/
(function (factory) {
if (typeof define === "function" && define.amd) {
// Register as an anonymous AMD module:
define(["jquery"], factory);
} else {
// Browser globals:
factory(jQuery);
}
}(function( $, undefined ) {
var uuid = 0,
slice = Array.prototype.slice,
_cleanData = $.cleanData;
$.cleanData = function( elems ) {
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
try {
$( elem ).triggerHandler( "remove" );
// http://bugs.jquery.com/ticket/8235
} catch( e ) {}
}
_cleanData( elems );
};
$.widget = function( name, base, prototype ) {
var fullName, existingConstructor, constructor, basePrototype,
// proxiedPrototype allows the provided prototype to remain unmodified
// so that it can be used as a mixin for multiple widgets (#8876)
proxiedPrototype = {},
namespace = name.split( "." )[ 0 ];
name = name.split( "." )[ 1 ];
fullName = namespace + "-" + name;
if ( !prototype ) {
prototype = base;
base = $.Widget;
}
// create selector for plugin
$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
return !!$.data( elem, fullName );
};
$[ namespace ] = $[ namespace ] || {};
existingConstructor = $[ namespace ][ name ];
constructor = $[ namespace ][ name ] = function( options, element ) {
// allow instantiation without "new" keyword
if ( !this._createWidget ) {
return new constructor( options, element );
}
// allow instantiation without initializing for simple inheritance
// must use "new" keyword (the code above always passes args)
if ( arguments.length ) {
this._createWidget( options, element );
}
};
// extend with the existing constructor to carry over any static properties
$.extend( constructor, existingConstructor, {
version: prototype.version,
// copy the object used to create the prototype in case we need to
// redefine the widget later
_proto: $.extend( {}, prototype ),
// track widgets that inherit from this widget in case this widget is
// redefined after a widget inherits from it
_childConstructors: []
});
basePrototype = new base();
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
basePrototype.options = $.widget.extend( {}, basePrototype.options );
$.each( prototype, function( prop, value ) {
if ( !$.isFunction( value ) ) {
proxiedPrototype[ prop ] = value;
return;
}
proxiedPrototype[ prop ] = (function() {
var _super = function() {
return base.prototype[ prop ].apply( this, arguments );
},
_superApply = function( args ) {
return base.prototype[ prop ].apply( this, args );
};
return function() {
var __super = this._super,
__superApply = this._superApply,
returnValue;
this._super = _super;
this._superApply = _superApply;
returnValue = value.apply( this, arguments );
this._super = __super;
this._superApply = __superApply;
return returnValue;
};
})();
});
constructor.prototype = $.widget.extend( basePrototype, {
// TODO: remove support for widgetEventPrefix
// always use the name + a colon as the prefix, e.g., draggable:start
// don't prefix for widgets that aren't DOM-based
widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
}, proxiedPrototype, {
constructor: constructor,
namespace: namespace,
widgetName: name,
widgetFullName: fullName
});
// If this widget is being redefined then we need to find all widgets that
// are inheriting from it and redefine all of them so that they inherit from
// the new version of this widget. We're essentially trying to replace one
// level in the prototype chain.
if ( existingConstructor ) {
$.each( existingConstructor._childConstructors, function( i, child ) {
var childPrototype = child.prototype;
// redefine the child widget using the same prototype that was
// originally used, but inherit from the new version of the base
$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
});
// remove the list of existing child constructors from the old constructor
// so the old child constructors can be garbage collected
delete existingConstructor._childConstructors;
} else {
base._childConstructors.push( constructor );
}
$.widget.bridge( name, constructor );
};
$.widget.extend = function( target ) {
var input = slice.call( arguments, 1 ),
inputIndex = 0,
inputLength = input.length,
key,
value;
for ( ; inputIndex < inputLength; inputIndex++ ) {
for ( key in input[ inputIndex ] ) {
value = input[ inputIndex ][ key ];
if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
// Clone objects
if ( $.isPlainObject( value ) ) {
target[ key ] = $.isPlainObject( target[ key ] ) ?
$.widget.extend( {}, target[ key ], value ) :
// Don't extend strings, arrays, etc. with objects
$.widget.extend( {}, value );
// Copy everything else by reference
} else {
target[ key ] = value;
}
}
}
}
return target;
};
$.widget.bridge = function( name, object ) {
var fullName = object.prototype.widgetFullName || name;
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
args = slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.widget.extend.apply( null, [ options ].concat(args) ) :
options;
if ( isMethodCall ) {
this.each(function() {
var methodValue,
instance = $.data( this, fullName );
if ( !instance ) {
return $.error( "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'" );
}
if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
return $.error( "no such method '" + options + "' for " + name + " widget instance" );
}
methodValue = instance[ options ].apply( instance, args );
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue && methodValue.jquery ?
returnValue.pushStack( methodValue.get() ) :
methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data( this, fullName );
if ( instance ) {
instance.option( options || {} )._init();
} else {
$.data( this, fullName, new object( options, this ) );
}
});
}
return returnValue;
};
};
$.Widget = function( /* options, element */ ) {};
$.Widget._childConstructors = [];
$.Widget.prototype = {
widgetName: "widget",
widgetEventPrefix: "",
defaultElement: "<div>",
options: {
disabled: false,
// callbacks
create: null
},
_createWidget: function( options, element ) {
element = $( element || this.defaultElement || this )[ 0 ];
this.element = $( element );
this.uuid = uuid++;
this.eventNamespace = "." + this.widgetName + this.uuid;
this.options = $.widget.extend( {},
this.options,
this._getCreateOptions(),
options );
this.bindings = $();
this.hoverable = $();
this.focusable = $();
if ( element !== this ) {
$.data( element, this.widgetFullName, this );
this._on( true, this.element, {
remove: function( event ) {
if ( event.target === element ) {
this.destroy();
}
}
});
this.document = $( element.style ?
// element within the document
element.ownerDocument :
// element is window or document
element.document || element );
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
}
this._create();
this._trigger( "create", null, this._getCreateEventData() );
this._init();
},
_getCreateOptions: $.noop,
_getCreateEventData: $.noop,
_create: $.noop,
_init: $.noop,
destroy: function() {
this._destroy();
// we can probably remove the unbind calls in 2.0
// all event bindings should go through this._on()
this.element
.unbind( this.eventNamespace )
// 1.9 BC for #7810
// TODO remove dual storage
.removeData( this.widgetName )
.removeData( this.widgetFullName )
// support: jquery <1.6.3
// http://bugs.jquery.com/ticket/9413
.removeData( $.camelCase( this.widgetFullName ) );
this.widget()
.unbind( this.eventNamespace )
.removeAttr( "aria-disabled" )
.removeClass(
this.widgetFullName + "-disabled " +
"ui-state-disabled" );
// clean up events and states
this.bindings.unbind( this.eventNamespace );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
},
_destroy: $.noop,
widget: function() {
return this.element;
},
option: function( key, value ) {
var options = key,
parts,
curOption,
i;
if ( arguments.length === 0 ) {
// don't return a reference to the internal hash
return $.widget.extend( {}, this.options );
}
if ( typeof key === "string" ) {
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = {};
parts = key.split( "." );
key = parts.shift();
if ( parts.length ) {
curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
for ( i = 0; i < parts.length - 1; i++ ) {
curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
curOption = curOption[ parts[ i ] ];
}
key = parts.pop();
if ( value === undefined ) {
return curOption[ key ] === undefined ? null : curOption[ key ];
}
curOption[ key ] = value;
} else {
if ( value === undefined ) {
return this.options[ key ] === undefined ? null : this.options[ key ];
}
options[ key ] = value;
}
}
this._setOptions( options );
return this;
},
_setOptions: function( options ) {
var key;
for ( key in options ) {
this._setOption( key, options[ key ] );
}
return this;
},
_setOption: function( key, value ) {
this.options[ key ] = value;
if ( key === "disabled" ) {
this.widget()
.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
.attr( "aria-disabled", value );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
}
return this;
},
enable: function() {
return this._setOption( "disabled", false );
},
disable: function() {
return this._setOption( "disabled", true );
},
_on: function( suppressDisabledCheck, element, handlers ) {
var delegateElement,
instance = this;
// no suppressDisabledCheck flag, shuffle arguments
if ( typeof suppressDisabledCheck !== "boolean" ) {
handlers = element;
element = suppressDisabledCheck;
suppressDisabledCheck = false;
}
// no element argument, shuffle and use this.element
if ( !handlers ) {
handlers = element;
element = this.element;
delegateElement = this.widget();
} else {
// accept selectors, DOM elements
element = delegateElement = $( element );
this.bindings = this.bindings.add( element );
}
$.each( handlers, function( event, handler ) {
function handlerProxy() {
// allow widgets to customize the disabled handling
// - disabled as an array instead of boolean
// - disabled class as method for disabling individual parts
if ( !suppressDisabledCheck &&
( instance.options.disabled === true ||
$( this ).hasClass( "ui-state-disabled" ) ) ) {
return;
}
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
// copy the guid so direct unbinding works
if ( typeof handler !== "string" ) {
handlerProxy.guid = handler.guid =
handler.guid || handlerProxy.guid || $.guid++;
}
var match = event.match( /^(\w+)\s*(.*)$/ ),
eventName = match[1] + instance.eventNamespace,
selector = match[2];
if ( selector ) {
delegateElement.delegate( selector, eventName, handlerProxy );
} else {
element.bind( eventName, handlerProxy );
}
});
},
_off: function( element, eventName ) {
eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
element.unbind( eventName ).undelegate( eventName );
},
_delay: function( handler, delay ) {
function handlerProxy() {
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
var instance = this;
return setTimeout( handlerProxy, delay || 0 );
},
_hoverable: function( element ) {
this.hoverable = this.hoverable.add( element );
this._on( element, {
mouseenter: function( event ) {
$( event.currentTarget ).addClass( "ui-state-hover" );
},
mouseleave: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-hover" );
}
});
},
_focusable: function( element ) {
this.focusable = this.focusable.add( element );
this._on( element, {
focusin: function( event ) {
$( event.currentTarget ).addClass( "ui-state-focus" );
},
focusout: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-focus" );
}
});
},
_trigger: function( type, event, data ) {
var prop, orig,
callback = this.options[ type ];
data = data || {};
event = $.Event( event );
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
// the original event may come from any element
// so we need to reset the target on the new event
event.target = this.element[ 0 ];
// copy original event properties over to the new event
orig = event.originalEvent;
if ( orig ) {
for ( prop in orig ) {
if ( !( prop in event ) ) {
event[ prop ] = orig[ prop ];
}
}
}
this.element.trigger( event, data );
return !( $.isFunction( callback ) &&
callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
event.isDefaultPrevented() );
}
};
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
if ( typeof options === "string" ) {
options = { effect: options };
}
var hasOptions,
effectName = !options ?
method :
options === true || typeof options === "number" ?
defaultEffect :
options.effect || defaultEffect;
options = options || {};
if ( typeof options === "number" ) {
options = { duration: options };
}
hasOptions = !$.isEmptyObject( options );
options.complete = callback;
if ( options.delay ) {
element.delay( options.delay );
}
if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
element[ method ]( options );
} else if ( effectName !== method && element[ effectName ] ) {
element[ effectName ]( options.duration, options.easing, callback );
} else {
element.queue(function( next ) {
$( this )[ method ]();
if ( callback ) {
callback.call( element[ 0 ] );
}
next();
});
}
};
});
}));

View File

@@ -0,0 +1,118 @@
/*
* jQuery postMessage Transport Plugin 1.1.1
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint unparam: true, nomen: true */
/*global define, window, document */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['jquery'], factory);
} else {
// Browser globals:
factory(window.jQuery);
}
}(function ($) {
'use strict';
var counter = 0,
names = [
'accepts',
'cache',
'contents',
'contentType',
'crossDomain',
'data',
'dataType',
'headers',
'ifModified',
'mimeType',
'password',
'processData',
'timeout',
'traditional',
'type',
'url',
'username'
],
convert = function (p) {
return p;
};
$.ajaxSetup({
converters: {
'postmessage text': convert,
'postmessage json': convert,
'postmessage html': convert
}
});
$.ajaxTransport('postmessage', function (options) {
if (options.postMessage && window.postMessage) {
var iframe,
loc = $('<a>').prop('href', options.postMessage)[0],
target = loc.protocol + '//' + loc.host,
xhrUpload = options.xhr().upload;
return {
send: function (_, completeCallback) {
counter += 1;
var message = {
id: 'postmessage-transport-' + counter
},
eventName = 'message.' + message.id;
iframe = $(
'<iframe style="display:none;" src="' +
options.postMessage + '" name="' +
message.id + '"></iframe>'
).bind('load', function () {
$.each(names, function (i, name) {
message[name] = options[name];
});
message.dataType = message.dataType.replace('postmessage ', '');
$(window).bind(eventName, function (e) {
e = e.originalEvent;
var data = e.data,
ev;
if (e.origin === target && data.id === message.id) {
if (data.type === 'progress') {
ev = document.createEvent('Event');
ev.initEvent(data.type, false, true);
$.extend(ev, data);
xhrUpload.dispatchEvent(ev);
} else {
completeCallback(
data.status,
data.statusText,
{postmessage: data.result},
data.headers
);
iframe.remove();
$(window).unbind(eventName);
}
}
});
iframe[0].contentWindow.postMessage(
message,
target
);
}).appendTo(document.body);
},
abort: function () {
if (iframe) {
iframe.remove();
}
}
};
}
});
}));

View File

@@ -0,0 +1,87 @@
/*
* jQuery XDomainRequest Transport Plugin 1.1.3
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*
* Based on Julian Aubourg's ajaxHooks xdr.js:
* https://github.com/jaubourg/ajaxHooks/
*/
/*jslint unparam: true */
/*global define, window, XDomainRequest */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['jquery'], factory);
} else {
// Browser globals:
factory(window.jQuery);
}
}(function ($) {
'use strict';
if (window.XDomainRequest && !$.support.cors) {
$.ajaxTransport(function (s) {
if (s.crossDomain && s.async) {
if (s.timeout) {
s.xdrTimeout = s.timeout;
delete s.timeout;
}
var xdr;
return {
send: function (headers, completeCallback) {
var addParamChar = /\?/.test(s.url) ? '&' : '?';
function callback(status, statusText, responses, responseHeaders) {
xdr.onload = xdr.onerror = xdr.ontimeout = $.noop;
xdr = null;
completeCallback(status, statusText, responses, responseHeaders);
}
xdr = new XDomainRequest();
// XDomainRequest only supports GET and POST:
if (s.type === 'DELETE') {
s.url = s.url + addParamChar + '_method=DELETE';
s.type = 'POST';
} else if (s.type === 'PUT') {
s.url = s.url + addParamChar + '_method=PUT';
s.type = 'POST';
} else if (s.type === 'PATCH') {
s.url = s.url + addParamChar + '_method=PATCH';
s.type = 'POST';
}
xdr.open(s.type, s.url);
xdr.onload = function () {
callback(
200,
'OK',
{text: xdr.responseText},
'Content-Type: ' + xdr.contentType
);
};
xdr.onerror = function () {
callback(404, 'Not Found');
};
if (s.xdrTimeout) {
xdr.ontimeout = function () {
callback(0, 'timeout');
};
xdr.timeout = s.xdrTimeout;
}
xdr.send((s.hasContent && s.data) || null);
},
abort: function () {
if (xdr) {
xdr.onerror = $.noop();
xdr.abort();
}
}
};
}
});
}
}));

View File

@@ -0,0 +1,403 @@
/*
* jQuery File Upload AngularJS Plugin 1.4.2
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true */
/*global define, angular */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'angular',
'./jquery.fileupload-image.js',
'./jquery.fileupload-audio.js',
'./jquery.fileupload-video.js',
'./jquery.fileupload-validate.js'
], factory);
} else {
factory();
}
}(function () {
'use strict';
angular.module('blueimp.fileupload', [])
// The fileUpload service provides configuration options
// for the fileUpload directive and default handlers for
// File Upload events:
.provider('fileUpload', function () {
var scopeApply = function () {
var scope = angular.element(this)
.fileupload('option', 'scope')(),
$timeout = angular.injector(['ng'])
.get('$timeout');
// Safe apply, makes sure $apply is called
// asynchronously outside of the $digest cycle:
$timeout(function () {
scope.$apply();
});
},
$config;
$config = this.defaults = {
handleResponse: function (e, data) {
var files = data.result && data.result.files;
if (files) {
data.scope().replace(data.files, files);
} else if (data.errorThrown ||
data.textStatus === 'error') {
data.files[0].error = data.errorThrown ||
data.textStatus;
}
},
add: function (e, data) {
var scope = data.scope();
data.process(function () {
return scope.process(data);
}).always(
function () {
var file = data.files[0],
submit = function () {
return data.submit();
},
i;
for (i = 0; i < data.files.length; i += 1) {
data.files[i]._index = i;
}
file.$cancel = function () {
scope.clear(data.files);
return data.abort();
};
file.$state = function () {
return data.state();
};
file.$progress = function () {
return data.progress();
};
file.$response = function () {
return data.response();
};
if (file.$state() === 'rejected') {
file._$submit = submit;
} else {
file.$submit = submit;
}
scope.$apply(function () {
var method = scope.option('prependFiles') ?
'unshift' : 'push';
Array.prototype[method].apply(
scope.queue,
data.files
);
if (file.$submit &&
(scope.option('autoUpload') ||
data.autoUpload) &&
data.autoUpload !== false) {
file.$submit();
}
});
}
);
},
progress: function (e, data) {
data.scope().$apply();
},
done: function (e, data) {
var that = this;
data.scope().$apply(function () {
data.handleResponse.call(that, e, data);
});
},
fail: function (e, data) {
var that = this;
if (data.errorThrown === 'abort') {
return;
}
if (data.dataType &&
data.dataType.indexOf('json') === data.dataType.length - 4) {
try {
data.result = angular.fromJson(data.jqXHR.responseText);
} catch (ignore) {}
}
data.scope().$apply(function () {
data.handleResponse.call(that, e, data);
});
},
stop: scopeApply,
processstart: scopeApply,
processstop: scopeApply,
getNumberOfFiles: function () {
return this.scope().queue.length;
},
dataType: 'json',
prependFiles: true,
autoUpload: false
};
this.$get = [
function () {
return {
defaults: $config
};
}
];
})
// Format byte numbers to readable presentations:
.provider('formatFileSizeFilter', function () {
var $config = {
// Byte units following the IEC format
// http://en.wikipedia.org/wiki/Kilobyte
units: [
{size: 1000000000, suffix: ' GB'},
{size: 1000000, suffix: ' MB'},
{size: 1000, suffix: ' KB'}
]
};
this.defaults = $config;
this.$get = function () {
return function (bytes) {
if (!angular.isNumber(bytes)) {
return '';
}
var unit = true,
i = 0,
prefix,
suffix;
while (unit) {
unit = $config.units[i];
prefix = unit.prefix || '';
suffix = unit.suffix || '';
if (i === $config.units.length - 1 || bytes >= unit.size) {
return prefix + (bytes / unit.size).toFixed(2) + suffix;
}
i += 1;
}
};
};
})
// The FileUploadController initializes the fileupload widget and
// provides scope methods to control the File Upload functionality:
.controller('FileUploadController', [
'$scope', '$element', '$attrs', '$window', 'fileUpload',
function ($scope, $element, $attrs, $window, fileUpload) {
var uploadMethods = {
progress: function () {
return $element.fileupload('progress');
},
active: function () {
return $element.fileupload('active');
},
option: function (option, data) {
return $element.fileupload('option', option, data);
},
add: function (data) {
return $element.fileupload('add', data);
},
send: function (data) {
return $element.fileupload('send', data);
},
process: function (data) {
return $element.fileupload('process', data);
},
processing: function (data) {
return $element.fileupload('processing', data);
}
};
$scope.disabled = !$window.jQuery.support.fileInput;
$scope.queue = $scope.queue || [];
$scope.clear = function (files) {
var queue = this.queue,
i = queue.length,
file = files,
length = 1;
if (angular.isArray(files)) {
file = files[0];
length = files.length;
}
while (i) {
i -= 1;
if (queue[i] === file) {
return queue.splice(i, length);
}
}
};
$scope.replace = function (oldFiles, newFiles) {
var queue = this.queue,
file = oldFiles[0],
i,
j;
for (i = 0; i < queue.length; i += 1) {
if (queue[i] === file) {
for (j = 0; j < newFiles.length; j += 1) {
queue[i + j] = newFiles[j];
}
return;
}
}
};
$scope.applyOnQueue = function (method) {
var list = this.queue.slice(0),
i,
file;
for (i = 0; i < list.length; i += 1) {
file = list[i];
if (file[method]) {
file[method]();
}
}
};
$scope.submit = function () {
this.applyOnQueue('$submit');
};
$scope.cancel = function () {
this.applyOnQueue('$cancel');
};
// Add upload methods to the scope:
angular.extend($scope, uploadMethods);
// The fileupload widget will initialize with
// the options provided via "data-"-parameters,
// as well as those given via options object:
$element.fileupload(angular.extend(
{scope: function () {
return $scope;
}},
fileUpload.defaults
)).on('fileuploadadd', function (e, data) {
data.scope = $scope.option('scope');
}).on([
'fileuploadadd',
'fileuploadsubmit',
'fileuploadsend',
'fileuploaddone',
'fileuploadfail',
'fileuploadalways',
'fileuploadprogress',
'fileuploadprogressall',
'fileuploadstart',
'fileuploadstop',
'fileuploadchange',
'fileuploadpaste',
'fileuploaddrop',
'fileuploaddragover',
'fileuploadchunksend',
'fileuploadchunkdone',
'fileuploadchunkfail',
'fileuploadchunkalways',
'fileuploadprocessstart',
'fileuploadprocess',
'fileuploadprocessdone',
'fileuploadprocessfail',
'fileuploadprocessalways',
'fileuploadprocessstop'
].join(' '), function (e, data) {
if ($scope.$emit(e.type, data).defaultPrevented) {
e.preventDefault();
}
}).on('remove', function () {
// Remove upload methods from the scope,
// when the widget is removed:
var method;
for (method in uploadMethods) {
if (uploadMethods.hasOwnProperty(method)) {
delete $scope[method];
}
}
});
// Observe option changes:
$scope.$watch(
$attrs.fileUpload,
function (newOptions) {
if (newOptions) {
$element.fileupload('option', newOptions);
}
}
);
}
])
// Provide File Upload progress feedback:
.controller('FileUploadProgressController', [
'$scope', '$attrs', '$parse',
function ($scope, $attrs, $parse) {
var fn = $parse($attrs.fileUploadProgress),
update = function () {
var progress = fn($scope);
if (!progress || !progress.total) {
return;
}
$scope.num = Math.floor(
progress.loaded / progress.total * 100
);
};
update();
$scope.$watch(
$attrs.fileUploadProgress + '.loaded',
function (newValue, oldValue) {
if (newValue !== oldValue) {
update();
}
}
);
}
])
// Display File Upload previews:
.controller('FileUploadPreviewController', [
'$scope', '$element', '$attrs', '$parse',
function ($scope, $element, $attrs, $parse) {
var fn = $parse($attrs.fileUploadPreview),
file = fn($scope);
if (file.preview) {
$element.append(file.preview);
}
}
])
.directive('fileUpload', function () {
return {
controller: 'FileUploadController'
};
})
.directive('fileUploadProgress', function () {
return {
controller: 'FileUploadProgressController'
};
})
.directive('fileUploadPreview', function () {
return {
controller: 'FileUploadPreviewController'
};
})
// Enhance the HTML5 download attribute to
// allow drag&drop of files to the desktop:
.directive('download', function () {
return function (scope, elm) {
elm.on('dragstart', function (e) {
try {
e.originalEvent.dataTransfer.setData(
'DownloadURL',
[
'application/octet-stream',
elm.prop('download'),
elm.prop('href')
].join(':')
);
} catch (ignore) {}
});
};
});
}));

View File

@@ -0,0 +1,106 @@
/*
* jQuery File Upload Audio Preview Plugin 1.0.3
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true, regexp: true */
/*global define, window, document */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'load-image',
'jquery.fileupload-process'
], factory);
} else {
// Browser globals:
factory(
window.jQuery,
window.loadImage
);
}
}(function ($, loadImage) {
'use strict';
// Prepend to the default processQueue:
$.blueimp.fileupload.prototype.options.processQueue.unshift(
{
action: 'loadAudio',
// Use the action as prefix for the "@" options:
prefix: true,
fileTypes: '@',
maxFileSize: '@',
disabled: '@disableAudioPreview'
},
{
action: 'setAudio',
name: '@audioPreviewName',
disabled: '@disableAudioPreview'
}
);
// The File Upload Audio Preview plugin extends the fileupload widget
// with audio preview functionality:
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
// The regular expression for the types of audio files to load,
// matched against the file type:
loadAudioFileTypes: /^audio\/.*$/
},
_audioElement: document.createElement('audio'),
processActions: {
// Loads the audio file given via data.files and data.index
// as audio element if the browser supports playing it.
// Accepts the options fileTypes (regular expression)
// and maxFileSize (integer) to limit the files to load:
loadAudio: function (data, options) {
if (options.disabled) {
return data;
}
var file = data.files[data.index],
url,
audio;
if (this._audioElement.canPlayType &&
this._audioElement.canPlayType(file.type) &&
($.type(options.maxFileSize) !== 'number' ||
file.size <= options.maxFileSize) &&
(!options.fileTypes ||
options.fileTypes.test(file.type))) {
url = loadImage.createObjectURL(file);
if (url) {
audio = this._audioElement.cloneNode(false);
audio.src = url;
audio.controls = true;
data.audio = audio;
return data;
}
}
return data;
},
// Sets the audio element as a property of the file object:
setAudio: function (data, options) {
if (data.audio && !options.disabled) {
data.files[data.index][options.name || 'preview'] = data.audio;
}
return data;
}
}
});
}));

View File

@@ -0,0 +1,292 @@
/*
* jQuery File Upload Image Preview & Resize Plugin 1.2.3
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true, regexp: true */
/*global define, window, document, DataView, Blob, Uint8Array */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'load-image',
'load-image-meta',
'load-image-exif',
'load-image-ios',
'canvas-to-blob',
'jquery.fileupload-process'
], factory);
} else {
// Browser globals:
factory(
window.jQuery,
window.loadImage
);
}
}(function ($, loadImage) {
'use strict';
// Prepend to the default processQueue:
$.blueimp.fileupload.prototype.options.processQueue.unshift(
{
action: 'loadImageMetaData',
disableImageHead: '@',
disableExif: '@',
disableExifThumbnail: '@',
disableExifSub: '@',
disableExifGps: '@',
disabled: '@disableImageMetaDataLoad'
},
{
action: 'loadImage',
// Use the action as prefix for the "@" options:
prefix: true,
fileTypes: '@',
maxFileSize: '@',
noRevoke: '@',
disabled: '@disableImageLoad'
},
{
action: 'resizeImage',
// Use "image" as prefix for the "@" options:
prefix: 'image',
maxWidth: '@',
maxHeight: '@',
minWidth: '@',
minHeight: '@',
crop: '@',
disabled: '@disableImageResize'
},
{
action: 'saveImage',
disabled: '@disableImageResize'
},
{
action: 'saveImageMetaData',
disabled: '@disableImageMetaDataSave'
},
{
action: 'resizeImage',
// Use "preview" as prefix for the "@" options:
prefix: 'preview',
maxWidth: '@',
maxHeight: '@',
minWidth: '@',
minHeight: '@',
crop: '@',
orientation: '@',
thumbnail: '@',
canvas: '@',
disabled: '@disableImagePreview'
},
{
action: 'setImage',
name: '@imagePreviewName',
disabled: '@disableImagePreview'
}
);
// The File Upload Resize plugin extends the fileupload widget
// with image resize functionality:
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
// The regular expression for the types of images to load:
// matched against the file type:
loadImageFileTypes: /^image\/(gif|jpeg|png)$/,
// The maximum file size of images to load:
loadImageMaxFileSize: 10000000, // 10MB
// The maximum width of resized images:
imageMaxWidth: 1920,
// The maximum height of resized images:
imageMaxHeight: 1080,
// Define if resized images should be cropped or only scaled:
imageCrop: false,
// Disable the resize image functionality by default:
disableImageResize: true,
// The maximum width of the preview images:
previewMaxWidth: 80,
// The maximum height of the preview images:
previewMaxHeight: 80,
// Defines the preview orientation (1-8) or takes the orientation
// value from Exif data if set to true:
previewOrientation: true,
// Create the preview using the Exif data thumbnail:
previewThumbnail: true,
// Define if preview images should be cropped or only scaled:
previewCrop: false,
// Define if preview images should be resized as canvas elements:
previewCanvas: true
},
processActions: {
// Loads the image given via data.files and data.index
// as img element if the browser supports canvas.
// Accepts the options fileTypes (regular expression)
// and maxFileSize (integer) to limit the files to load:
loadImage: function (data, options) {
if (options.disabled) {
return data;
}
var that = this,
file = data.files[data.index],
dfd = $.Deferred();
if (($.type(options.maxFileSize) === 'number' &&
file.size > options.maxFileSize) ||
(options.fileTypes &&
!options.fileTypes.test(file.type)) ||
!loadImage(
file,
function (img) {
if (img.src) {
data.img = img;
}
dfd.resolveWith(that, [data]);
},
options
)) {
return data;
}
return dfd.promise();
},
// Resizes the image given as data.canvas or data.img
// and updates data.canvas or data.img with the resized image.
// Accepts the options maxWidth, maxHeight, minWidth,
// minHeight, canvas and crop:
resizeImage: function (data, options) {
if (options.disabled) {
return data;
}
var that = this,
dfd = $.Deferred(),
resolve = function (newImg) {
data[newImg.getContext ? 'canvas' : 'img'] = newImg;
dfd.resolveWith(that, [data]);
},
thumbnail,
img,
newImg;
options = $.extend({canvas: true}, options);
if (data.exif) {
if (options.orientation === true) {
options.orientation = data.exif.get('Orientation');
}
if (options.thumbnail) {
thumbnail = data.exif.get('Thumbnail');
if (thumbnail) {
loadImage(thumbnail, resolve, options);
return dfd.promise();
}
}
}
img = (options.canvas && data.canvas) || data.img;
if (img) {
newImg = loadImage.scale(img, options);
if (newImg.width !== img.width ||
newImg.height !== img.height) {
resolve(newImg);
return dfd.promise();
}
}
return data;
},
// Saves the processed image given as data.canvas
// inplace at data.index of data.files:
saveImage: function (data, options) {
if (!data.canvas || options.disabled) {
return data;
}
var that = this,
file = data.files[data.index],
name = file.name,
dfd = $.Deferred(),
callback = function (blob) {
if (!blob.name) {
if (file.type === blob.type) {
blob.name = file.name;
} else if (file.name) {
blob.name = file.name.replace(
/\..+$/,
'.' + blob.type.substr(6)
);
}
}
// Store the created blob at the position
// of the original file in the files list:
data.files[data.index] = blob;
dfd.resolveWith(that, [data]);
};
// Use canvas.mozGetAsFile directly, to retain the filename, as
// Gecko doesn't support the filename option for FormData.append:
if (data.canvas.mozGetAsFile) {
callback(data.canvas.mozGetAsFile(
(/^image\/(jpeg|png)$/.test(file.type) && name) ||
((name && name.replace(/\..+$/, '')) ||
'blob') + '.png',
file.type
));
} else if (data.canvas.toBlob) {
data.canvas.toBlob(callback, file.type);
} else {
return data;
}
return dfd.promise();
},
loadImageMetaData: function (data, options) {
if (options.disabled) {
return data;
}
var that = this,
dfd = $.Deferred();
loadImage.parseMetaData(data.files[data.index], function (result) {
$.extend(data, result);
dfd.resolveWith(that, [data]);
}, options);
return dfd.promise();
},
saveImageMetaData: function (data, options) {
if (!(data.imageHead && data.canvas &&
data.canvas.toBlob && !options.disabled)) {
return data;
}
var file = data.files[data.index],
blob = new Blob([
data.imageHead,
// Resized images always have a head size of 20 bytes,
// including the JPEG marker and a minimal JFIF header:
this._blobSlice.call(file, 20)
], {type: file.type});
blob.name = file.name;
data.files[data.index] = blob;
return data;
},
// Sets the resized version of the image as a property of the
// file object, must be called after "saveImage":
setImage: function (data, options) {
var img = data.canvas || data.img;
if (img && !options.disabled) {
data.files[data.index][options.name || 'preview'] = img;
}
return data;
}
}
});
}));

View File

@@ -0,0 +1,138 @@
/*
* jQuery File Upload jQuery UI Plugin 8.7.0
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true */
/*global define, window */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['jquery', './jquery.fileupload-ui.js'], factory);
} else {
// Browser globals:
factory(window.jQuery);
}
}(function ($) {
'use strict';
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
progress: function (e, data) {
if (data.context) {
data.context.find('.progress').progressbar(
'option',
'value',
parseInt(data.loaded / data.total * 100, 10)
);
}
},
progressall: function (e, data) {
var $this = $(this);
$this.find('.fileupload-progress')
.find('.progress').progressbar(
'option',
'value',
parseInt(data.loaded / data.total * 100, 10)
).end()
.find('.progress-extended').each(function () {
$(this).html(
($this.data('blueimp-fileupload') ||
$this.data('fileupload'))
._renderExtendedProgress(data)
);
});
}
},
_renderUpload: function (func, files) {
var node = this._super(func, files),
showIconText = $(window).width() > 480;
node.find('.progress').empty().progressbar();
node.find('.start').button({
icons: {primary: 'ui-icon-circle-arrow-e'},
text: showIconText
});
node.find('.cancel').button({
icons: {primary: 'ui-icon-cancel'},
text: showIconText
});
return node;
},
_renderDownload: function (func, files) {
var node = this._super(func, files),
showIconText = $(window).width() > 480;
node.find('.delete').button({
icons: {primary: 'ui-icon-trash'},
text: showIconText
});
return node;
},
_transition: function (node) {
var deferred = $.Deferred();
if (node.hasClass('fade')) {
node.fadeToggle(
this.options.transitionDuration,
this.options.transitionEasing,
function () {
deferred.resolveWith(node);
}
);
} else {
deferred.resolveWith(node);
}
return deferred;
},
_create: function () {
this._super();
this.element
.find('.fileupload-buttonbar')
.find('.fileinput-button').each(function () {
var input = $(this).find('input:file').detach();
$(this)
.button({icons: {primary: 'ui-icon-plusthick'}})
.append(input);
})
.end().find('.start')
.button({icons: {primary: 'ui-icon-circle-arrow-e'}})
.end().find('.cancel')
.button({icons: {primary: 'ui-icon-cancel'}})
.end().find('.delete')
.button({icons: {primary: 'ui-icon-trash'}})
.end().find('.progress').progressbar();
},
_destroy: function () {
this.element
.find('.fileupload-buttonbar')
.find('.fileinput-button').each(function () {
var input = $(this).find('input:file').detach();
$(this)
.button('destroy')
.append(input);
})
.end().find('.start')
.button('destroy')
.end().find('.cancel')
.button('destroy')
.end().find('.delete')
.button('destroy')
.end().find('.progress').progressbar('destroy');
this._super();
}
});
}));

View File

@@ -0,0 +1,164 @@
/*
* jQuery File Upload Processing Plugin 1.2.2
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2012, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true */
/*global define, window */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'jquery.fileupload'
], factory);
} else {
// Browser globals:
factory(
window.jQuery
);
}
}(function ($) {
'use strict';
var originalAdd = $.blueimp.fileupload.prototype.options.add;
// The File Upload Processing plugin extends the fileupload widget
// with file processing functionality:
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
// The list of processing actions:
processQueue: [
/*
{
action: 'log',
type: 'debug'
}
*/
],
add: function (e, data) {
var $this = $(this);
data.process(function () {
return $this.fileupload('process', data);
});
originalAdd.call(this, e, data);
}
},
processActions: {
/*
log: function (data, options) {
console[options.type](
'Processing "' + data.files[data.index].name + '"'
);
}
*/
},
_processFile: function (data) {
var that = this,
dfd = $.Deferred().resolveWith(that, [data]),
chain = dfd.promise();
this._trigger('process', null, data);
$.each(data.processQueue, function (i, settings) {
var func = function (data) {
return that.processActions[settings.action].call(
that,
data,
settings
);
};
chain = chain.pipe(func, settings.always && func);
});
chain
.done(function () {
that._trigger('processdone', null, data);
that._trigger('processalways', null, data);
})
.fail(function () {
that._trigger('processfail', null, data);
that._trigger('processalways', null, data);
});
return chain;
},
// Replaces the settings of each processQueue item that
// are strings starting with an "@", using the remaining
// substring as key for the option map,
// e.g. "@autoUpload" is replaced with options.autoUpload:
_transformProcessQueue: function (options) {
var processQueue = [];
$.each(options.processQueue, function () {
var settings = {},
action = this.action,
prefix = this.prefix === true ? action : this.prefix;
$.each(this, function (key, value) {
if ($.type(value) === 'string' &&
value.charAt(0) === '@') {
settings[key] = options[
value.slice(1) || (prefix ? prefix +
key.charAt(0).toUpperCase() + key.slice(1) : key)
];
} else {
settings[key] = value;
}
});
processQueue.push(settings);
});
options.processQueue = processQueue;
},
// Returns the number of files currently in the processsing queue:
processing: function () {
return this._processing;
},
// Processes the files given as files property of the data parameter,
// returns a Promise object that allows to bind callbacks:
process: function (data) {
var that = this,
options = $.extend({}, this.options, data);
if (options.processQueue && options.processQueue.length) {
this._transformProcessQueue(options);
if (this._processing === 0) {
this._trigger('processstart');
}
$.each(data.files, function (index) {
var opts = index ? $.extend({}, options) : options,
func = function () {
return that._processFile(opts);
};
opts.index = index;
that._processing += 1;
that._processingQueue = that._processingQueue.pipe(func, func)
.always(function () {
that._processing -= 1;
if (that._processing === 0) {
that._trigger('processstop');
}
});
});
}
return this._processingQueue;
},
_create: function () {
this._super();
this._processing = 0;
this._processingQueue = $.Deferred().resolveWith(this)
.promise();
}
});
}));

View File

@@ -0,0 +1,643 @@
/*
* jQuery File Upload User Interface Plugin 8.7.1
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true, regexp: true */
/*global define, window, URL, webkitURL, FileReader */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'tmpl',
'jquery.fileupload-image',
'jquery.fileupload-audio',
'jquery.fileupload-video',
'jquery.fileupload-validate'
], factory);
} else {
// Browser globals:
factory(
window.jQuery,
window.tmpl
);
}
}(function ($, tmpl, loadImage) {
'use strict';
$.blueimp.fileupload.prototype._specialOptions.push(
'filesContainer',
'uploadTemplateId',
'downloadTemplateId'
);
// The UI version extends the file upload widget
// and adds complete user interface interaction:
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
// By default, files added to the widget are uploaded as soon
// as the user clicks on the start buttons. To enable automatic
// uploads, set the following option to true:
autoUpload: false,
// The ID of the upload template:
uploadTemplateId: 'template-upload',
// The ID of the download template:
downloadTemplateId: 'template-download',
// The container for the list of files. If undefined, it is set to
// an element with class "files" inside of the widget element:
filesContainer: undefined,
// By default, files are appended to the files container.
// Set the following option to true, to prepend files instead:
prependFiles: false,
// The expected data type of the upload response, sets the dataType
// option of the $.ajax upload requests:
dataType: 'json',
// Function returning the current number of files,
// used by the maxNumberOfFiles validation:
getNumberOfFiles: function () {
return this.filesContainer.children().length;
},
// Callback to retrieve the list of files from the server response:
getFilesFromResponse: function (data) {
if (data.result && $.isArray(data.result.files)) {
return data.result.files;
}
return [];
},
// The add callback is invoked as soon as files are added to the fileupload
// widget (via file input selection, drag & drop or add API call).
// See the basic file upload widget for more information:
add: function (e, data) {
var $this = $(this),
that = $this.data('blueimp-fileupload') ||
$this.data('fileupload'),
options = that.options,
files = data.files;
data.process(function () {
return $this.fileupload('process', data);
}).always(function () {
data.context = that._renderUpload(files).data('data', data);
that._renderPreviews(data);
options.filesContainer[
options.prependFiles ? 'prepend' : 'append'
](data.context);
that._forceReflow(data.context);
that._transition(data.context).done(
function () {
if ((that._trigger('added', e, data) !== false) &&
(options.autoUpload || data.autoUpload) &&
data.autoUpload !== false && !data.files.error) {
data.submit();
}
}
);
});
},
// Callback for the start of each file upload request:
send: function (e, data) {
var that = $(this).data('blueimp-fileupload') ||
$(this).data('fileupload');
if (data.context && data.dataType &&
data.dataType.substr(0, 6) === 'iframe') {
// Iframe Transport does not support progress events.
// In lack of an indeterminate progress bar, we set
// the progress to 100%, showing the full animated bar:
data.context
.find('.progress').addClass(
!$.support.transition && 'progress-animated'
)
.attr('aria-valuenow', 100)
.find('.bar').css(
'width',
'100%'
);
}
return that._trigger('sent', e, data);
},
// Callback for successful uploads:
done: function (e, data) {
var that = $(this).data('blueimp-fileupload') ||
$(this).data('fileupload'),
getFilesFromResponse = data.getFilesFromResponse ||
that.options.getFilesFromResponse,
files = getFilesFromResponse(data),
template,
deferred;
if (data.context) {
data.context.each(function (index) {
var file = files[index] ||
{error: 'Empty file upload result'};
deferred = that._addFinishedDeferreds();
that._transition($(this)).done(
function () {
var node = $(this);
template = that._renderDownload([file])
.replaceAll(node);
that._forceReflow(template);
that._transition(template).done(
function () {
data.context = $(this);
that._trigger('completed', e, data);
that._trigger('finished', e, data);
deferred.resolve();
}
);
}
);
});
} else {
template = that._renderDownload(files)[
that.options.prependFiles ? 'prependTo' : 'appendTo'
](that.options.filesContainer);
that._forceReflow(template);
deferred = that._addFinishedDeferreds();
that._transition(template).done(
function () {
data.context = $(this);
that._trigger('completed', e, data);
that._trigger('finished', e, data);
deferred.resolve();
}
);
}
},
// Callback for failed (abort or error) uploads:
fail: function (e, data) {
var that = $(this).data('blueimp-fileupload') ||
$(this).data('fileupload'),
template,
deferred;
if (data.context) {
data.context.each(function (index) {
if (data.errorThrown !== 'abort') {
var file = data.files[index];
file.error = file.error || data.errorThrown ||
true;
deferred = that._addFinishedDeferreds();
that._transition($(this)).done(
function () {
var node = $(this);
template = that._renderDownload([file])
.replaceAll(node);
that._forceReflow(template);
that._transition(template).done(
function () {
data.context = $(this);
that._trigger('failed', e, data);
that._trigger('finished', e, data);
deferred.resolve();
}
);
}
);
} else {
deferred = that._addFinishedDeferreds();
that._transition($(this)).done(
function () {
$(this).remove();
that._trigger('failed', e, data);
that._trigger('finished', e, data);
deferred.resolve();
}
);
}
});
} else if (data.errorThrown !== 'abort') {
data.context = that._renderUpload(data.files)[
that.options.prependFiles ? 'prependTo' : 'appendTo'
](that.options.filesContainer)
.data('data', data);
that._forceReflow(data.context);
deferred = that._addFinishedDeferreds();
that._transition(data.context).done(
function () {
data.context = $(this);
that._trigger('failed', e, data);
that._trigger('finished', e, data);
deferred.resolve();
}
);
} else {
that._trigger('failed', e, data);
that._trigger('finished', e, data);
that._addFinishedDeferreds().resolve();
}
},
// Callback for upload progress events:
progress: function (e, data) {
if (data.context) {
var progress = Math.floor(data.loaded / data.total * 100);
data.context.find('.progress')
.attr('aria-valuenow', progress)
.find('.bar').css(
'width',
progress + '%'
);
}
},
// Callback for global upload progress events:
progressall: function (e, data) {
var $this = $(this),
progress = Math.floor(data.loaded / data.total * 100),
globalProgressNode = $this.find('.fileupload-progress'),
extendedProgressNode = globalProgressNode
.find('.progress-extended');
if (extendedProgressNode.length) {
extendedProgressNode.html(
($this.data('blueimp-fileupload') || $this.data('fileupload'))
._renderExtendedProgress(data)
);
}
globalProgressNode
.find('.progress')
.attr('aria-valuenow', progress)
.find('.bar').css(
'width',
progress + '%'
);
},
// Callback for uploads start, equivalent to the global ajaxStart event:
start: function (e) {
var that = $(this).data('blueimp-fileupload') ||
$(this).data('fileupload');
that._resetFinishedDeferreds();
that._transition($(this).find('.fileupload-progress')).done(
function () {
that._trigger('started', e);
}
);
},
// Callback for uploads stop, equivalent to the global ajaxStop event:
stop: function (e) {
var that = $(this).data('blueimp-fileupload') ||
$(this).data('fileupload'),
deferred = that._addFinishedDeferreds();
$.when.apply($, that._getFinishedDeferreds())
.done(function () {
that._trigger('stopped', e);
});
that._transition($(this).find('.fileupload-progress')).done(
function () {
$(this).find('.progress')
.attr('aria-valuenow', '0')
.find('.bar').css('width', '0%');
$(this).find('.progress-extended').html('&nbsp;');
deferred.resolve();
}
);
},
processstart: function () {
$(this).addClass('fileupload-processing');
},
processstop: function () {
$(this).removeClass('fileupload-processing');
},
// Callback for file deletion:
destroy: function (e, data) {
var that = $(this).data('blueimp-fileupload') ||
$(this).data('fileupload'),
removeNode = function () {
that._transition(data.context).done(
function () {
$(this).remove();
that._trigger('destroyed', e, data);
}
);
};
if (data.url) {
$.ajax(data).done(removeNode);
} else {
removeNode();
}
}
},
_resetFinishedDeferreds: function () {
this._finishedUploads = [];
},
_addFinishedDeferreds: function (deferred) {
if (!deferred) {
deferred = $.Deferred();
}
this._finishedUploads.push(deferred);
return deferred;
},
_getFinishedDeferreds: function () {
return this._finishedUploads;
},
// Link handler, that allows to download files
// by drag & drop of the links to the desktop:
_enableDragToDesktop: function () {
var link = $(this),
url = link.prop('href'),
name = link.prop('download'),
type = 'application/octet-stream';
link.bind('dragstart', function (e) {
try {
e.originalEvent.dataTransfer.setData(
'DownloadURL',
[type, name, url].join(':')
);
} catch (ignore) {}
});
},
_formatFileSize: function (bytes) {
if (typeof bytes !== 'number') {
return '';
}
if (bytes >= 1000000000) {
return (bytes / 1000000000).toFixed(2) + ' GB';
}
if (bytes >= 1000000) {
return (bytes / 1000000).toFixed(2) + ' MB';
}
return (bytes / 1000).toFixed(2) + ' KB';
},
_formatBitrate: function (bits) {
if (typeof bits !== 'number') {
return '';
}
if (bits >= 1000000000) {
return (bits / 1000000000).toFixed(2) + ' Gbit/s';
}
if (bits >= 1000000) {
return (bits / 1000000).toFixed(2) + ' Mbit/s';
}
if (bits >= 1000) {
return (bits / 1000).toFixed(2) + ' kbit/s';
}
return bits.toFixed(2) + ' bit/s';
},
_formatTime: function (seconds) {
var date = new Date(seconds * 1000),
days = Math.floor(seconds / 86400);
days = days ? days + 'd ' : '';
return days +
('0' + date.getUTCHours()).slice(-2) + ':' +
('0' + date.getUTCMinutes()).slice(-2) + ':' +
('0' + date.getUTCSeconds()).slice(-2);
},
_formatPercentage: function (floatValue) {
return (floatValue * 100).toFixed(2) + ' %';
},
_renderExtendedProgress: function (data) {
return this._formatBitrate(data.bitrate) + ' | ' +
this._formatTime(
(data.total - data.loaded) * 8 / data.bitrate
) + ' | ' +
this._formatPercentage(
data.loaded / data.total
) + ' | ' +
this._formatFileSize(data.loaded) + ' / ' +
this._formatFileSize(data.total);
},
_renderTemplate: function (func, files) {
if (!func) {
return $();
}
var result = func({
files: files,
formatFileSize: this._formatFileSize,
options: this.options
});
if (result instanceof $) {
return result;
}
return $(this.options.templatesContainer).html(result).children();
},
_renderPreviews: function (data) {
data.context.find('.preview').each(function (index, elm) {
$(elm).append(data.files[index].preview);
});
},
_renderUpload: function (files) {
return this._renderTemplate(
this.options.uploadTemplate,
files
);
},
_renderDownload: function (files) {
return this._renderTemplate(
this.options.downloadTemplate,
files
).find('a[download]').each(this._enableDragToDesktop).end();
},
_startHandler: function (e) {
e.preventDefault();
var button = $(e.currentTarget),
template = button.closest('.template-upload'),
data = template.data('data');
if (data && data.submit && !data.jqXHR && data.submit()) {
button.prop('disabled', true);
}
},
_cancelHandler: function (e) {
e.preventDefault();
var template = $(e.currentTarget).closest('.template-upload'),
data = template.data('data') || {};
if (!data.jqXHR) {
data.errorThrown = 'abort';
this._trigger('fail', e, data);
} else {
data.jqXHR.abort();
}
},
_deleteHandler: function (e) {
e.preventDefault();
var button = $(e.currentTarget);
this._trigger('destroy', e, $.extend({
context: button.closest('.template-download'),
type: 'DELETE'
}, button.data()));
},
_forceReflow: function (node) {
return $.support.transition && node.length &&
node[0].offsetWidth;
},
_transition: function (node) {
var dfd = $.Deferred();
if ($.support.transition && node.hasClass('fade') && node.is(':visible')) {
node.bind(
$.support.transition.end,
function (e) {
// Make sure we don't respond to other transitions events
// in the container element, e.g. from button elements:
if (e.target === node[0]) {
node.unbind($.support.transition.end);
dfd.resolveWith(node);
}
}
).toggleClass('in');
} else {
node.toggleClass('in');
dfd.resolveWith(node);
}
return dfd;
},
_initButtonBarEventHandlers: function () {
var fileUploadButtonBar = this.element.find('.fileupload-buttonbar'),
filesList = this.options.filesContainer;
this._on(fileUploadButtonBar.find('.start'), {
click: function (e) {
e.preventDefault();
filesList.find('.start').click();
}
});
this._on(fileUploadButtonBar.find('.cancel'), {
click: function (e) {
e.preventDefault();
filesList.find('.cancel').click();
}
});
this._on(fileUploadButtonBar.find('.delete'), {
click: function (e) {
e.preventDefault();
filesList.find('.toggle:checked')
.closest('.template-download')
.find('.delete').click();
fileUploadButtonBar.find('.toggle')
.prop('checked', false);
}
});
this._on(fileUploadButtonBar.find('.toggle'), {
change: function (e) {
filesList.find('.toggle').prop(
'checked',
$(e.currentTarget).is(':checked')
);
}
});
},
_destroyButtonBarEventHandlers: function () {
this._off(
this.element.find('.fileupload-buttonbar')
.find('.start, .cancel, .delete'),
'click'
);
this._off(
this.element.find('.fileupload-buttonbar .toggle'),
'change.'
);
},
_initEventHandlers: function () {
this._super();
this._on(this.options.filesContainer, {
'click .start': this._startHandler,
'click .cancel': this._cancelHandler,
'click .delete': this._deleteHandler
});
this._initButtonBarEventHandlers();
},
_destroyEventHandlers: function () {
this._destroyButtonBarEventHandlers();
this._off(this.options.filesContainer, 'click');
this._super();
},
_enableFileInputButton: function () {
this.element.find('.fileinput-button input')
.prop('disabled', false)
.parent().removeClass('disabled');
},
_disableFileInputButton: function () {
this.element.find('.fileinput-button input')
.prop('disabled', true)
.parent().addClass('disabled');
},
_initTemplates: function () {
var options = this.options;
options.templatesContainer = this.document[0].createElement(
options.filesContainer.prop('nodeName')
);
if (tmpl) {
if (options.uploadTemplateId) {
options.uploadTemplate = tmpl(options.uploadTemplateId);
}
if (options.downloadTemplateId) {
options.downloadTemplate = tmpl(options.downloadTemplateId);
}
}
},
_initFilesContainer: function () {
var options = this.options;
if (options.filesContainer === undefined) {
options.filesContainer = this.element.find('.files');
} else if (!(options.filesContainer instanceof $)) {
options.filesContainer = $(options.filesContainer);
}
},
_initSpecialOptions: function () {
this._super();
this._initFilesContainer();
this._initTemplates();
},
_create: function () {
this._super();
this._resetFinishedDeferreds();
if (!$.support.fileInput) {
this._disableFileInputButton();
}
},
enable: function () {
var wasDisabled = false;
if (this.options.disabled) {
wasDisabled = true;
}
this._super();
if (wasDisabled) {
this.element.find('input, button').prop('disabled', false);
this._enableFileInputButton();
}
},
disable: function () {
if (!this.options.disabled) {
this.element.find('input, button').prop('disabled', true);
this._disableFileInputButton();
}
this._super();
}
});
}));

View File

@@ -0,0 +1,116 @@
/*
* jQuery File Upload Validation Plugin 1.1
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true, regexp: true */
/*global define, window */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'jquery.fileupload-process'
], factory);
} else {
// Browser globals:
factory(
window.jQuery
);
}
}(function ($) {
'use strict';
// Append to the default processQueue:
$.blueimp.fileupload.prototype.options.processQueue.push(
{
action: 'validate',
// Always trigger this action,
// even if the previous action was rejected:
always: true,
// Options taken from the global options map:
acceptFileTypes: '@',
maxFileSize: '@',
minFileSize: '@',
maxNumberOfFiles: '@',
disabled: '@disableValidation'
}
);
// The File Upload Validation plugin extends the fileupload widget
// with file validation functionality:
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
/*
// The regular expression for allowed file types, matches
// against either file type or file name:
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
// The maximum allowed file size in bytes:
maxFileSize: 10000000, // 10 MB
// The minimum allowed file size in bytes:
minFileSize: undefined, // No minimal file size
// The limit of files to be uploaded:
maxNumberOfFiles: 10,
*/
// Function returning the current number of files,
// has to be overriden for maxNumberOfFiles validation:
getNumberOfFiles: $.noop,
// Error and info messages:
messages: {
maxNumberOfFiles: 'Maximum number of files exceeded',
acceptFileTypes: 'File type not allowed',
maxFileSize: 'File is too large',
minFileSize: 'File is too small'
}
},
processActions: {
validate: function (data, options) {
if (options.disabled) {
return data;
}
var dfd = $.Deferred(),
settings = this.options,
file = data.files[data.index],
numberOfFiles = settings.getNumberOfFiles();
if (numberOfFiles && $.type(options.maxNumberOfFiles) === 'number' &&
numberOfFiles + data.files.length > options.maxNumberOfFiles) {
file.error = settings.i18n('maxNumberOfFiles');
} else if (options.acceptFileTypes &&
!(options.acceptFileTypes.test(file.type) ||
options.acceptFileTypes.test(file.name))) {
file.error = settings.i18n('acceptFileTypes');
} else if (options.maxFileSize && file.size > options.maxFileSize) {
file.error = settings.i18n('maxFileSize');
} else if ($.type(file.size) === 'number' &&
file.size < options.minFileSize) {
file.error = settings.i18n('minFileSize');
} else {
delete file.error;
}
if (file.error || data.files.error) {
data.files.error = true;
dfd.rejectWith(this, [data]);
} else {
dfd.resolveWith(this, [data]);
}
return dfd.promise();
}
}
});
}));

View File

@@ -0,0 +1,106 @@
/*
* jQuery File Upload Video Preview Plugin 1.0.3
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, unparam: true, regexp: true */
/*global define, window, document */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define([
'jquery',
'load-image',
'jquery.fileupload-process'
], factory);
} else {
// Browser globals:
factory(
window.jQuery,
window.loadImage
);
}
}(function ($, loadImage) {
'use strict';
// Prepend to the default processQueue:
$.blueimp.fileupload.prototype.options.processQueue.unshift(
{
action: 'loadVideo',
// Use the action as prefix for the "@" options:
prefix: true,
fileTypes: '@',
maxFileSize: '@',
disabled: '@disableVideoPreview'
},
{
action: 'setVideo',
name: '@videoPreviewName',
disabled: '@disableVideoPreview'
}
);
// The File Upload Video Preview plugin extends the fileupload widget
// with video preview functionality:
$.widget('blueimp.fileupload', $.blueimp.fileupload, {
options: {
// The regular expression for the types of video files to load,
// matched against the file type:
loadVideoFileTypes: /^video\/.*$/
},
_videoElement: document.createElement('video'),
processActions: {
// Loads the video file given via data.files and data.index
// as video element if the browser supports playing it.
// Accepts the options fileTypes (regular expression)
// and maxFileSize (integer) to limit the files to load:
loadVideo: function (data, options) {
if (options.disabled) {
return data;
}
var file = data.files[data.index],
url,
video;
if (this._videoElement.canPlayType &&
this._videoElement.canPlayType(file.type) &&
($.type(options.maxFileSize) !== 'number' ||
file.size <= options.maxFileSize) &&
(!options.fileTypes ||
options.fileTypes.test(file.type))) {
url = loadImage.createObjectURL(file);
if (url) {
video = this._videoElement.cloneNode(false);
video.src = url;
video.controls = true;
data.video = video;
return data;
}
}
return data;
},
// Sets the video element as a property of the file object:
setVideo: function (data, options) {
if (data.video && !options.disabled) {
data.files[data.index][options.name || 'preview'] = data.video;
}
return data;
}
}
});
}));

1333
static/js/libs/upload/jquery.fileupload.js vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,385 @@
/*
* JavaScript Load Image Exif Map 1.0.1
* https://github.com/blueimp/JavaScript-Load-Image
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Exif tags mapping based on
* https://github.com/jseidelin/exif-js
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*global define, window */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['load-image', 'load-image-exif'], factory);
} else {
// Browser globals:
factory(window.loadImage);
}
}(function (loadImage) {
'use strict';
var tags,
map,
prop;
loadImage.ExifMap.prototype.tags = {
// =================
// TIFF tags (IFD0):
// =================
0x0100: 'ImageWidth',
0x0101: 'ImageHeight',
0x8769: 'ExifIFDPointer',
0x8825: 'GPSInfoIFDPointer',
0xA005: 'InteroperabilityIFDPointer',
0x0102: 'BitsPerSample',
0x0103: 'Compression',
0x0106: 'PhotometricInterpretation',
0x0112: 'Orientation',
0x0115: 'SamplesPerPixel',
0x011C: 'PlanarConfiguration',
0x0212: 'YCbCrSubSampling',
0x0213: 'YCbCrPositioning',
0x011A: 'XResolution',
0x011B: 'YResolution',
0x0128: 'ResolutionUnit',
0x0111: 'StripOffsets',
0x0116: 'RowsPerStrip',
0x0117: 'StripByteCounts',
0x0201: 'JPEGInterchangeFormat',
0x0202: 'JPEGInterchangeFormatLength',
0x012D: 'TransferFunction',
0x013E: 'WhitePoint',
0x013F: 'PrimaryChromaticities',
0x0211: 'YCbCrCoefficients',
0x0214: 'ReferenceBlackWhite',
0x0132: 'DateTime',
0x010E: 'ImageDescription',
0x010F: 'Make',
0x0110: 'Model',
0x0131: 'Software',
0x013B: 'Artist',
0x8298: 'Copyright',
// ==================
// Exif Sub IFD tags:
// ==================
0x9000: 'ExifVersion', // EXIF version
0xA000: 'FlashpixVersion', // Flashpix format version
0xA001: 'ColorSpace', // Color space information tag
0xA002: 'PixelXDimension', // Valid width of meaningful image
0xA003: 'PixelYDimension', // Valid height of meaningful image
0xA500: 'Gamma',
0x9101: 'ComponentsConfiguration', // Information about channels
0x9102: 'CompressedBitsPerPixel', // Compressed bits per pixel
0x927C: 'MakerNote', // Any desired information written by the manufacturer
0x9286: 'UserComment', // Comments by user
0xA004: 'RelatedSoundFile', // Name of related sound file
0x9003: 'DateTimeOriginal', // Date and time when the original image was generated
0x9004: 'DateTimeDigitized', // Date and time when the image was stored digitally
0x9290: 'SubSecTime', // Fractions of seconds for DateTime
0x9291: 'SubSecTimeOriginal', // Fractions of seconds for DateTimeOriginal
0x9292: 'SubSecTimeDigitized', // Fractions of seconds for DateTimeDigitized
0x829A: 'ExposureTime', // Exposure time (in seconds)
0x829D: 'FNumber',
0x8822: 'ExposureProgram', // Exposure program
0x8824: 'SpectralSensitivity', // Spectral sensitivity
0x8827: 'PhotographicSensitivity', // EXIF 2.3, ISOSpeedRatings in EXIF 2.2
0x8828: 'OECF', // Optoelectric conversion factor
0x8830: 'SensitivityType',
0x8831: 'StandardOutputSensitivity',
0x8832: 'RecommendedExposureIndex',
0x8833: 'ISOSpeed',
0x8834: 'ISOSpeedLatitudeyyy',
0x8835: 'ISOSpeedLatitudezzz',
0x9201: 'ShutterSpeedValue', // Shutter speed
0x9202: 'ApertureValue', // Lens aperture
0x9203: 'BrightnessValue', // Value of brightness
0x9204: 'ExposureBias', // Exposure bias
0x9205: 'MaxApertureValue', // Smallest F number of lens
0x9206: 'SubjectDistance', // Distance to subject in meters
0x9207: 'MeteringMode', // Metering mode
0x9208: 'LightSource', // Kind of light source
0x9209: 'Flash', // Flash status
0x9214: 'SubjectArea', // Location and area of main subject
0x920A: 'FocalLength', // Focal length of the lens in mm
0xA20B: 'FlashEnergy', // Strobe energy in BCPS
0xA20C: 'SpatialFrequencyResponse',
0xA20E: 'FocalPlaneXResolution', // Number of pixels in width direction per FPRUnit
0xA20F: 'FocalPlaneYResolution', // Number of pixels in height direction per FPRUnit
0xA210: 'FocalPlaneResolutionUnit', // Unit for measuring the focal plane resolution
0xA214: 'SubjectLocation', // Location of subject in image
0xA215: 'ExposureIndex', // Exposure index selected on camera
0xA217: 'SensingMethod', // Image sensor type
0xA300: 'FileSource', // Image source (3 == DSC)
0xA301: 'SceneType', // Scene type (1 == directly photographed)
0xA302: 'CFAPattern', // Color filter array geometric pattern
0xA401: 'CustomRendered', // Special processing
0xA402: 'ExposureMode', // Exposure mode
0xA403: 'WhiteBalance', // 1 = auto white balance, 2 = manual
0xA404: 'DigitalZoomRatio', // Digital zoom ratio
0xA405: 'FocalLengthIn35mmFilm',
0xA406: 'SceneCaptureType', // Type of scene
0xA407: 'GainControl', // Degree of overall image gain adjustment
0xA408: 'Contrast', // Direction of contrast processing applied by camera
0xA409: 'Saturation', // Direction of saturation processing applied by camera
0xA40A: 'Sharpness', // Direction of sharpness processing applied by camera
0xA40B: 'DeviceSettingDescription',
0xA40C: 'SubjectDistanceRange', // Distance to subject
0xA420: 'ImageUniqueID', // Identifier assigned uniquely to each image
0xA430: 'CameraOwnerName',
0xA431: 'BodySerialNumber',
0xA432: 'LensSpecification',
0xA433: 'LensMake',
0xA434: 'LensModel',
0xA435: 'LensSerialNumber',
// ==============
// GPS Info tags:
// ==============
0x0000: 'GPSVersionID',
0x0001: 'GPSLatitudeRef',
0x0002: 'GPSLatitude',
0x0003: 'GPSLongitudeRef',
0x0004: 'GPSLongitude',
0x0005: 'GPSAltitudeRef',
0x0006: 'GPSAltitude',
0x0007: 'GPSTimeStamp',
0x0008: 'GPSSatellites',
0x0009: 'GPSStatus',
0x000A: 'GPSMeasureMode',
0x000B: 'GPSDOP',
0x000C: 'GPSSpeedRef',
0x000D: 'GPSSpeed',
0x000E: 'GPSTrackRef',
0x000F: 'GPSTrack',
0x0010: 'GPSImgDirectionRef',
0x0011: 'GPSImgDirection',
0x0012: 'GPSMapDatum',
0x0013: 'GPSDestLatitudeRef',
0x0014: 'GPSDestLatitude',
0x0015: 'GPSDestLongitudeRef',
0x0016: 'GPSDestLongitude',
0x0017: 'GPSDestBearingRef',
0x0018: 'GPSDestBearing',
0x0019: 'GPSDestDistanceRef',
0x001A: 'GPSDestDistance',
0x001B: 'GPSProcessingMethod',
0x001C: 'GPSAreaInformation',
0x001D: 'GPSDateStamp',
0x001E: 'GPSDifferential',
0x001F: 'GPSHPositioningError'
};
loadImage.ExifMap.prototype.stringValues = {
ExposureProgram: {
0: 'Undefined',
1: 'Manual',
2: 'Normal program',
3: 'Aperture priority',
4: 'Shutter priority',
5: 'Creative program',
6: 'Action program',
7: 'Portrait mode',
8: 'Landscape mode'
},
MeteringMode: {
0: 'Unknown',
1: 'Average',
2: 'CenterWeightedAverage',
3: 'Spot',
4: 'MultiSpot',
5: 'Pattern',
6: 'Partial',
255: 'Other'
},
LightSource: {
0: 'Unknown',
1: 'Daylight',
2: 'Fluorescent',
3: 'Tungsten (incandescent light)',
4: 'Flash',
9: 'Fine weather',
10: 'Cloudy weather',
11: 'Shade',
12: 'Daylight fluorescent (D 5700 - 7100K)',
13: 'Day white fluorescent (N 4600 - 5400K)',
14: 'Cool white fluorescent (W 3900 - 4500K)',
15: 'White fluorescent (WW 3200 - 3700K)',
17: 'Standard light A',
18: 'Standard light B',
19: 'Standard light C',
20: 'D55',
21: 'D65',
22: 'D75',
23: 'D50',
24: 'ISO studio tungsten',
255: 'Other'
},
Flash: {
0x0000: 'Flash did not fire',
0x0001: 'Flash fired',
0x0005: 'Strobe return light not detected',
0x0007: 'Strobe return light detected',
0x0009: 'Flash fired, compulsory flash mode',
0x000D: 'Flash fired, compulsory flash mode, return light not detected',
0x000F: 'Flash fired, compulsory flash mode, return light detected',
0x0010: 'Flash did not fire, compulsory flash mode',
0x0018: 'Flash did not fire, auto mode',
0x0019: 'Flash fired, auto mode',
0x001D: 'Flash fired, auto mode, return light not detected',
0x001F: 'Flash fired, auto mode, return light detected',
0x0020: 'No flash function',
0x0041: 'Flash fired, red-eye reduction mode',
0x0045: 'Flash fired, red-eye reduction mode, return light not detected',
0x0047: 'Flash fired, red-eye reduction mode, return light detected',
0x0049: 'Flash fired, compulsory flash mode, red-eye reduction mode',
0x004D: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected',
0x004F: 'Flash fired, compulsory flash mode, red-eye reduction mode, return light detected',
0x0059: 'Flash fired, auto mode, red-eye reduction mode',
0x005D: 'Flash fired, auto mode, return light not detected, red-eye reduction mode',
0x005F: 'Flash fired, auto mode, return light detected, red-eye reduction mode'
},
SensingMethod: {
1: 'Undefined',
2: 'One-chip color area sensor',
3: 'Two-chip color area sensor',
4: 'Three-chip color area sensor',
5: 'Color sequential area sensor',
7: 'Trilinear sensor',
8: 'Color sequential linear sensor'
},
SceneCaptureType: {
0: 'Standard',
1: 'Landscape',
2: 'Portrait',
3: 'Night scene'
},
SceneType: {
1: 'Directly photographed'
},
CustomRendered: {
0: 'Normal process',
1: 'Custom process'
},
WhiteBalance: {
0: 'Auto white balance',
1: 'Manual white balance'
},
GainControl: {
0: 'None',
1: 'Low gain up',
2: 'High gain up',
3: 'Low gain down',
4: 'High gain down'
},
Contrast: {
0: 'Normal',
1: 'Soft',
2: 'Hard'
},
Saturation: {
0: 'Normal',
1: 'Low saturation',
2: 'High saturation'
},
Sharpness: {
0: 'Normal',
1: 'Soft',
2: 'Hard'
},
SubjectDistanceRange: {
0: 'Unknown',
1: 'Macro',
2: 'Close view',
3: 'Distant view'
},
FileSource: {
3: 'DSC'
},
ComponentsConfiguration: {
0: '',
1: 'Y',
2: 'Cb',
3: 'Cr',
4: 'R',
5: 'G',
6: 'B'
},
Orientation: {
1: 'top-left',
2: 'top-right',
3: 'bottom-right',
4: 'bottom-left',
5: 'left-top',
6: 'right-top',
7: 'right-bottom',
8: 'left-bottom'
}
};
loadImage.ExifMap.prototype.getText = function (id) {
var value = this.get(id);
switch (id) {
case 'LightSource':
case 'Flash':
case 'MeteringMode':
case 'ExposureProgram':
case 'SensingMethod':
case 'SceneCaptureType':
case 'SceneType':
case 'CustomRendered':
case 'WhiteBalance':
case 'GainControl':
case 'Contrast':
case 'Saturation':
case 'Sharpness':
case 'SubjectDistanceRange':
case 'FileSource':
case 'Orientation':
return this.stringValues[id][value];
case 'ExifVersion':
case 'FlashpixVersion':
return String.fromCharCode(value[0], value[1], value[2], value[3]);
case 'ComponentsConfiguration':
return this.stringValues[id][value[0]]
+ this.stringValues[id][value[1]]
+ this.stringValues[id][value[2]]
+ this.stringValues[id][value[3]];
case 'GPSVersionID':
return value[0] + '.' + value[1] + '.' + value[2] + '.' + value[3];
}
return String(value);
};
tags = loadImage.ExifMap.prototype.tags;
map = loadImage.ExifMap.prototype.map;
// Map the tag names to tags:
for (prop in tags) {
if (tags.hasOwnProperty(prop)) {
map[tags[prop]] = prop;
}
}
loadImage.ExifMap.prototype.getAll = function () {
var map = {},
prop,
id;
for (prop in this) {
if (this.hasOwnProperty(prop)) {
id = tags[prop];
if (id) {
map[id] = this.getText(id);
}
}
}
return map;
};
}));

View File

@@ -0,0 +1,299 @@
/*
* JavaScript Load Image Exif Parser 1.0.0
* https://github.com/blueimp/JavaScript-Load-Image
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint unparam: true */
/*global define, window, console */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['load-image', 'load-image-meta'], factory);
} else {
// Browser globals:
factory(window.loadImage);
}
}(function (loadImage) {
'use strict';
loadImage.ExifMap = function () {
return this;
};
loadImage.ExifMap.prototype.map = {
'Orientation': 0x0112
};
loadImage.ExifMap.prototype.get = function (id) {
return this[id] || this[this.map[id]];
};
loadImage.getExifThumbnail = function (dataView, offset, length) {
var hexData,
i,
b;
if (!length || offset + length > dataView.byteLength) {
console.log('Invalid Exif data: Invalid thumbnail data.');
return;
}
hexData = [];
for (i = 0; i < length; i += 1) {
b = dataView.getUint8(offset + i);
hexData.push((b < 16 ? '0' : '') + b.toString(16));
}
return 'data:image/jpeg,%' + hexData.join('%');
};
loadImage.exifTagTypes = {
// byte, 8-bit unsigned int:
1: {
getValue: function (dataView, dataOffset) {
return dataView.getUint8(dataOffset);
},
size: 1
},
// ascii, 8-bit byte:
2: {
getValue: function (dataView, dataOffset) {
return String.fromCharCode(dataView.getUint8(dataOffset));
},
size: 1,
ascii: true
},
// short, 16 bit int:
3: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getUint16(dataOffset, littleEndian);
},
size: 2
},
// long, 32 bit int:
4: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getUint32(dataOffset, littleEndian);
},
size: 4
},
// rational = two long values, first is numerator, second is denominator:
5: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getUint32(dataOffset, littleEndian) /
dataView.getUint32(dataOffset + 4, littleEndian);
},
size: 8
},
// slong, 32 bit signed int:
9: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getInt32(dataOffset, littleEndian);
},
size: 4
},
// srational, two slongs, first is numerator, second is denominator:
10: {
getValue: function (dataView, dataOffset, littleEndian) {
return dataView.getInt32(dataOffset, littleEndian) /
dataView.getInt32(dataOffset + 4, littleEndian);
},
size: 8
}
};
// undefined, 8-bit byte, value depending on field:
loadImage.exifTagTypes[7] = loadImage.exifTagTypes[1];
loadImage.getExifValue = function (dataView, tiffOffset, offset, type, length, littleEndian) {
var tagType = loadImage.exifTagTypes[type],
tagSize,
dataOffset,
values,
i,
str,
c;
if (!tagType) {
console.log('Invalid Exif data: Invalid tag type.');
return;
}
tagSize = tagType.size * length;
// Determine if the value is contained in the dataOffset bytes,
// or if the value at the dataOffset is a pointer to the actual data:
dataOffset = tagSize > 4 ?
tiffOffset + dataView.getUint32(offset + 8, littleEndian) : (offset + 8);
if (dataOffset + tagSize > dataView.byteLength) {
console.log('Invalid Exif data: Invalid data offset.');
return;
}
if (length === 1) {
return tagType.getValue(dataView, dataOffset, littleEndian);
}
values = [];
for (i = 0; i < length; i += 1) {
values[i] = tagType.getValue(dataView, dataOffset + i * tagType.size, littleEndian);
}
if (tagType.ascii) {
str = '';
// Concatenate the chars:
for (i = 0; i < values.length; i += 1) {
c = values[i];
// Ignore the terminating NULL byte(s):
if (c === '\u0000') {
break;
}
str += c;
}
return str;
}
return values;
};
loadImage.parseExifTag = function (dataView, tiffOffset, offset, littleEndian, data) {
var tag = dataView.getUint16(offset, littleEndian);
data.exif[tag] = loadImage.getExifValue(
dataView,
tiffOffset,
offset,
dataView.getUint16(offset + 2, littleEndian), // tag type
dataView.getUint32(offset + 4, littleEndian), // tag length
littleEndian
);
};
loadImage.parseExifTags = function (dataView, tiffOffset, dirOffset, littleEndian, data) {
var tagsNumber,
dirEndOffset,
i;
if (dirOffset + 6 > dataView.byteLength) {
console.log('Invalid Exif data: Invalid directory offset.');
return;
}
tagsNumber = dataView.getUint16(dirOffset, littleEndian);
dirEndOffset = dirOffset + 2 + 12 * tagsNumber;
if (dirEndOffset + 4 > dataView.byteLength) {
console.log('Invalid Exif data: Invalid directory size.');
return;
}
for (i = 0; i < tagsNumber; i += 1) {
this.parseExifTag(
dataView,
tiffOffset,
dirOffset + 2 + 12 * i, // tag offset
littleEndian,
data
);
}
// Return the offset to the next directory:
return dataView.getUint32(dirEndOffset, littleEndian);
};
loadImage.parseExifData = function (dataView, offset, length, data, options) {
if (options.disableExif) {
return;
}
var tiffOffset = offset + 10,
littleEndian,
dirOffset,
thumbnailData;
// Check for the ASCII code for "Exif" (0x45786966):
if (dataView.getUint32(offset + 4) !== 0x45786966) {
// No Exif data, might be XMP data instead
return;
}
if (tiffOffset + 8 > dataView.byteLength) {
console.log('Invalid Exif data: Invalid segment size.');
return;
}
// Check for the two null bytes:
if (dataView.getUint16(offset + 8) !== 0x0000) {
console.log('Invalid Exif data: Missing byte alignment offset.');
return;
}
// Check the byte alignment:
switch (dataView.getUint16(tiffOffset)) {
case 0x4949:
littleEndian = true;
break;
case 0x4D4D:
littleEndian = false;
break;
default:
console.log('Invalid Exif data: Invalid byte alignment marker.');
return;
}
// Check for the TIFF tag marker (0x002A):
if (dataView.getUint16(tiffOffset + 2, littleEndian) !== 0x002A) {
console.log('Invalid Exif data: Missing TIFF marker.');
return;
}
// Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:
dirOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
// Create the exif object to store the tags:
data.exif = new loadImage.ExifMap();
// Parse the tags of the main image directory and retrieve the
// offset to the next directory, usually the thumbnail directory:
dirOffset = loadImage.parseExifTags(
dataView,
tiffOffset,
tiffOffset + dirOffset,
littleEndian,
data
);
if (dirOffset && !options.disableExifThumbnail) {
thumbnailData = {exif: {}};
dirOffset = loadImage.parseExifTags(
dataView,
tiffOffset,
tiffOffset + dirOffset,
littleEndian,
thumbnailData
);
// Check for JPEG Thumbnail offset:
if (thumbnailData.exif[0x0201]) {
data.exif.Thumbnail = loadImage.getExifThumbnail(
dataView,
tiffOffset + thumbnailData.exif[0x0201],
thumbnailData.exif[0x0202] // Thumbnail data length
);
}
}
// Check for Exif Sub IFD Pointer:
if (data.exif[0x8769] && !options.disableExifSub) {
loadImage.parseExifTags(
dataView,
tiffOffset,
tiffOffset + data.exif[0x8769], // directory offset
littleEndian,
data
);
}
// Check for GPS Info IFD Pointer:
if (data.exif[0x8825] && !options.disableExifGps) {
loadImage.parseExifTags(
dataView,
tiffOffset,
tiffOffset + data.exif[0x8825], // directory offset
littleEndian,
data
);
}
};
// Registers the Exif parser for the APP1 JPEG meta data segment:
loadImage.metaDataParsers.jpeg[0xffe1].push(loadImage.parseExifData);
// Adds the following properties to the parseMetaData callback data:
// * exif: The exif tags, parsed by the parseExifData method
// Adds the following options to the parseMetaData method:
// * disableExif: Disables Exif parsing.
// * disableExifThumbnail: Disables parsing of the Exif Thumbnail.
// * disableExifSub: Disables parsing of the Exif Sub IFD.
// * disableExifGps: Disables parsing of the Exif GPS Info IFD.
}));

View File

@@ -0,0 +1,181 @@
/*
* 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 define, window, document */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['load-image'], factory);
} else {
// Browser globals:
factory(window.loadImage);
}
}(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
);
};
}));

View File

@@ -0,0 +1,137 @@
/*
* JavaScript Load Image Meta 1.0.1
* https://github.com/blueimp/JavaScript-Load-Image
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Image meta data handling implementation
* based on the help and contribution of
* Achim Stöhr.
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint continue:true */
/*global define, window, DataView, Blob, Uint8Array, console */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['load-image'], factory);
} else {
// Browser globals:
factory(window.loadImage);
}
}(function (loadImage) {
'use strict';
var hasblobSlice = window.Blob && (Blob.prototype.slice ||
Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
loadImage.blobSlice = hasblobSlice && function () {
var slice = this.slice || this.webkitSlice || this.mozSlice;
return slice.apply(this, arguments);
};
loadImage.metaDataParsers = {
jpeg: {
0xffe1: [] // APP1 marker
}
};
// Parses image meta data and calls the callback with an object argument
// with the following properties:
// * imageHead: The complete image head as ArrayBuffer (Uint8Array for IE10)
// The options arguments accepts an object and supports the following properties:
// * maxMetaDataSize: Defines the maximum number of bytes to parse.
// * disableImageHead: Disables creating the imageHead property.
loadImage.parseMetaData = function (file, callback, options) {
options = options || {};
var that = this,
// 256 KiB should contain all EXIF/ICC/IPTC segments:
maxMetaDataSize = options.maxMetaDataSize || 262144,
data = {},
noMetaData = !(window.DataView && file && file.size >= 12 &&
file.type === 'image/jpeg' && loadImage.blobSlice);
if (noMetaData || !loadImage.readFile(
loadImage.blobSlice.call(file, 0, maxMetaDataSize),
function (e) {
// Note on endianness:
// Since the marker and length bytes in JPEG files are always
// stored in big endian order, we can leave the endian parameter
// of the DataView methods undefined, defaulting to big endian.
var buffer = e.target.result,
dataView = new DataView(buffer),
offset = 2,
maxOffset = dataView.byteLength - 4,
headLength = offset,
markerBytes,
markerLength,
parsers,
i;
// Check for the JPEG marker (0xffd8):
if (dataView.getUint16(0) === 0xffd8) {
while (offset < maxOffset) {
markerBytes = dataView.getUint16(offset);
// Search for APPn (0xffeN) and COM (0xfffe) markers,
// which contain application-specific meta-data like
// Exif, ICC and IPTC data and text comments:
if ((markerBytes >= 0xffe0 && markerBytes <= 0xffef) ||
markerBytes === 0xfffe) {
// The marker bytes (2) are always followed by
// the length bytes (2), indicating the length of the
// marker segment, which includes the length bytes,
// but not the marker bytes, so we add 2:
markerLength = dataView.getUint16(offset + 2) + 2;
if (offset + markerLength > dataView.byteLength) {
console.log('Invalid meta data: Invalid segment size.');
break;
}
parsers = loadImage.metaDataParsers.jpeg[markerBytes];
if (parsers) {
for (i = 0; i < parsers.length; i += 1) {
parsers[i].call(
that,
dataView,
offset,
markerLength,
data,
options
);
}
}
offset += markerLength;
headLength = offset;
} else {
// Not an APPn or COM marker, probably safe to
// assume that this is the end of the meta data
break;
}
}
// Meta length must be longer than JPEG marker (2)
// plus APPn marker (2), followed by length bytes (2):
if (!options.disableImageHead && headLength > 6) {
if (buffer.slice) {
data.imageHead = buffer.slice(0, headLength);
} else {
// Workaround for IE10, which does not yet
// support ArrayBuffer.slice:
data.imageHead = new Uint8Array(buffer)
.subarray(0, headLength);
}
}
} else {
console.log('Invalid JPEG file: Missing JPEG marker.');
}
callback(data);
},
'readAsArrayBuffer'
)) {
callback(data);
}
};
}));

View File

@@ -0,0 +1,159 @@
/*
* JavaScript Load Image Orientation 1.0.0
* https://github.com/blueimp/JavaScript-Load-Image
*
* Copyright 2013, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*global define, window */
(function (factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// Register as an anonymous AMD module:
define(['load-image'], factory);
} else {
// Browser globals:
factory(window.loadImage);
}
}(function (loadImage) {
'use strict';
var originalHasCanvasOptionMethod = loadImage.hasCanvasOption;
// This method is used to determine if the target image
// should be a canvas element:
loadImage.hasCanvasOption = function (options) {
return originalHasCanvasOptionMethod(options) || options.orientation;
};
// Transform image orientation based on
// the given EXIF orientation option:
loadImage.transformCoordinates = function (canvas, options) {
var ctx = canvas.getContext('2d'),
width = canvas.width,
height = canvas.height,
orientation = options.orientation;
if (!orientation) {
return;
}
if (orientation > 4) {
canvas.width = height;
canvas.height = width;
}
switch (orientation) {
case 2:
// horizontal flip
ctx.translate(width, 0);
ctx.scale(-1, 1);
break;
case 3:
// 180° rotate left
ctx.translate(width, height);
ctx.rotate(Math.PI);
break;
case 4:
// vertical flip
ctx.translate(0, height);
ctx.scale(1, -1);
break;
case 5:
// vertical flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.scale(1, -1);
break;
case 6:
// 90° rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(0, -height);
break;
case 7:
// horizontal flip + 90 rotate right
ctx.rotate(0.5 * Math.PI);
ctx.translate(width, -height);
ctx.scale(-1, 1);
break;
case 8:
// 90° rotate left
ctx.rotate(-0.5 * Math.PI);
ctx.translate(-width, 0);
break;
}
};
// Transforms coordinate and dimension options
// based on the given orientation option:
loadImage.getTransformedOptions = function (options) {
if (!options.orientation || options.orientation === 1) {
return options;
}
var newOptions = {},
i;
for (i in options) {
if (options.hasOwnProperty(i)) {
newOptions[i] = options[i];
}
}
switch (options.orientation) {
case 2:
// horizontal flip
newOptions.left = options.right;
newOptions.right = options.left;
break;
case 3:
// 180° rotate left
newOptions.left = options.right;
newOptions.top = options.bottom;
newOptions.right = options.left;
newOptions.bottom = options.top;
break;
case 4:
// vertical flip
newOptions.top = options.bottom;
newOptions.bottom = options.top;
break;
case 5:
// vertical flip + 90 rotate right
newOptions.left = options.top;
newOptions.top = options.left;
newOptions.right = options.bottom;
newOptions.bottom = options.right;
break;
case 6:
// 90° rotate right
newOptions.left = options.top;
newOptions.top = options.right;
newOptions.right = options.bottom;
newOptions.bottom = options.left;
break;
case 7:
// horizontal flip + 90 rotate right
newOptions.left = options.bottom;
newOptions.top = options.right;
newOptions.right = options.top;
newOptions.bottom = options.left;
break;
case 8:
// 90° rotate left
newOptions.left = options.bottom;
newOptions.top = options.left;
newOptions.right = options.top;
newOptions.bottom = options.right;
break;
}
if (options.orientation > 4) {
newOptions.maxWidth = options.maxHeight;
newOptions.maxHeight = options.maxWidth;
newOptions.minWidth = options.minHeight;
newOptions.minHeight = options.minWidth;
newOptions.sourceWidth = options.sourceHeight;
newOptions.sourceHeight = options.sourceWidth;
}
return newOptions;
};
}));

View File

@@ -0,0 +1,276 @@
/*
* JavaScript Load Image 1.9.0
* https://github.com/blueimp/JavaScript-Load-Image
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true */
/*global define, window, document, URL, webkitURL, Blob, File, FileReader */
(function ($) {
'use strict';
// Loads an image for a given File object.
// Invokes the callback with an img or optional canvas
// element (if supported by the browser) as parameter:
var loadImage = function (file, callback, options) {
var img = document.createElement('img'),
url,
oUrl;
img.onerror = callback;
img.onload = function () {
if (oUrl && !(options && options.noRevoke)) {
loadImage.revokeObjectURL(oUrl);
}
if (callback) {
callback(loadImage.scale(img, options));
}
};
if (loadImage.isInstanceOf('Blob', file) ||
// Files are also Blob instances, but some browsers
// (Firefox 3.6) support the File API but not Blobs:
loadImage.isInstanceOf('File', file)) {
url = oUrl = loadImage.createObjectURL(file);
// Store the file type for resize processing:
img._type = file.type;
} else if (typeof file === 'string') {
url = file;
if (options && options.crossOrigin) {
img.crossOrigin = options.crossOrigin;
}
} else {
return false;
}
if (url) {
img.src = url;
return img;
}
return loadImage.readFile(file, function (e) {
var target = e.target;
if (target && target.result) {
img.src = target.result;
} else {
if (callback) {
callback(e);
}
}
});
},
// The check for URL.revokeObjectURL fixes an issue with Opera 12,
// which provides URL.createObjectURL but doesn't properly implement it:
urlAPI = (window.createObjectURL && window) ||
(window.URL && URL.revokeObjectURL && URL) ||
(window.webkitURL && webkitURL);
loadImage.isInstanceOf = function (type, obj) {
// Cross-frame instanceof check
return Object.prototype.toString.call(obj) === '[object ' + type + ']';
};
// Transform image coordinates, allows to override e.g.
// the canvas orientation based on the orientation option,
// gets canvas, options passed as arguments:
loadImage.transformCoordinates = function () {
return;
};
// Returns transformed options, allows to override e.g.
// coordinate and dimension options based on the orientation:
loadImage.getTransformedOptions = function (options) {
return options;
};
// Canvas render method, allows to override the
// rendering e.g. to work around issues on iOS:
loadImage.renderImageToCanvas = function (
canvas,
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
destX,
destY,
destWidth,
destHeight
) {
canvas.getContext('2d').drawImage(
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
destX,
destY,
destWidth,
destHeight
);
return canvas;
};
// This method is used to determine if the target image
// should be a canvas element:
loadImage.hasCanvasOption = function (options) {
return options.canvas || options.crop;
};
// Scales and/or crops the given image (img or canvas HTML element)
// using the given options.
// Returns a canvas object if the browser supports canvas
// and the hasCanvasOption method returns true or a canvas
// object is passed as image, else the scaled image:
loadImage.scale = function (img, options) {
options = options || {};
var canvas = document.createElement('canvas'),
useCanvas = img.getContext ||
(loadImage.hasCanvasOption(options) && canvas.getContext),
width = img.naturalWidth || img.width,
height = img.naturalHeight || img.height,
destWidth = width,
destHeight = height,
maxWidth,
maxHeight,
minWidth,
minHeight,
sourceWidth,
sourceHeight,
sourceX,
sourceY,
tmp,
scaleUp = function () {
var scale = Math.max(
(minWidth || destWidth) / destWidth,
(minHeight || destHeight) / destHeight
);
if (scale > 1) {
destWidth = Math.ceil(destWidth * scale);
destHeight = Math.ceil(destHeight * scale);
}
},
scaleDown = function () {
var scale = Math.min(
(maxWidth || destWidth) / destWidth,
(maxHeight || destHeight) / destHeight
);
if (scale < 1) {
destWidth = Math.ceil(destWidth * scale);
destHeight = Math.ceil(destHeight * scale);
}
};
if (useCanvas) {
options = loadImage.getTransformedOptions(options);
sourceX = options.left || 0;
sourceY = options.top || 0;
if (options.sourceWidth) {
sourceWidth = options.sourceWidth;
if (options.right !== undefined && options.left === undefined) {
sourceX = width - sourceWidth - options.right;
}
} else {
sourceWidth = width - sourceX - (options.right || 0);
}
if (options.sourceHeight) {
sourceHeight = options.sourceHeight;
if (options.bottom !== undefined && options.top === undefined) {
sourceY = height - sourceHeight - options.bottom;
}
} else {
sourceHeight = height - sourceY - (options.bottom || 0);
}
destWidth = sourceWidth;
destHeight = sourceHeight;
}
maxWidth = options.maxWidth;
maxHeight = options.maxHeight;
minWidth = options.minWidth;
minHeight = options.minHeight;
if (useCanvas && maxWidth && maxHeight && options.crop) {
destWidth = maxWidth;
destHeight = maxHeight;
tmp = sourceWidth / sourceHeight - maxWidth / maxHeight;
if (tmp < 0) {
sourceHeight = maxHeight * sourceWidth / maxWidth;
if (options.top === undefined && options.bottom === undefined) {
sourceY = (height - sourceHeight) / 2;
}
} else if (tmp > 0) {
sourceWidth = maxWidth * sourceHeight / maxHeight;
if (options.left === undefined && options.right === undefined) {
sourceX = (width - sourceWidth) / 2;
}
}
} else {
if (options.contain || options.cover) {
minWidth = maxWidth = maxWidth || minWidth;
minHeight = maxHeight = maxHeight || minHeight;
}
if (options.cover) {
scaleDown();
scaleUp();
} else {
scaleUp();
scaleDown();
}
}
if (useCanvas) {
canvas.width = destWidth;
canvas.height = destHeight;
loadImage.transformCoordinates(
canvas,
options
);
return loadImage.renderImageToCanvas(
canvas,
img,
sourceX,
sourceY,
sourceWidth,
sourceHeight,
0,
0,
destWidth,
destHeight
);
}
img.width = destWidth;
img.height = destHeight;
return img;
};
loadImage.createObjectURL = function (file) {
return urlAPI ? urlAPI.createObjectURL(file) : false;
};
loadImage.revokeObjectURL = function (url) {
return urlAPI ? urlAPI.revokeObjectURL(url) : false;
};
// Loads a given File object via FileReader interface,
// invokes the callback with the event object (load or error).
// The result can be read via event.target.result:
loadImage.readFile = function (file, callback, method) {
if (window.FileReader) {
var fileReader = new FileReader();
fileReader.onload = fileReader.onerror = callback;
method = method || 'readAsDataURL';
if (fileReader[method]) {
fileReader[method](file);
return fileReader;
}
}
return false;
};
if (typeof define === 'function' && define.amd) {
define(function () {
return loadImage;
});
} else {
$.loadImage = loadImage;
}
}(this));

View File

@@ -0,0 +1,76 @@
/*
* jQuery File Upload Plugin JS Example 8.3.0
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2010, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*/
/*jslint nomen: true, regexp: true */
/*global $, window, blueimp */
$(function () {
'use strict';
// Initialize the jQuery File Upload widget:
$('#fileupload').fileupload({
// Uncomment the following to send cross-domain cookies:
//xhrFields: {withCredentials: true},
url: 'server/php/'
});
// Enable iframe cross-domain access via redirect option:
$('#fileupload').fileupload(
'option',
'redirect',
window.location.href.replace(
/\/[^\/]*$/,
'/cors/result.html?%s'
)
);
if (window.location.hostname === 'blueimp.github.io') {
// Demo settings:
$('#fileupload').fileupload('option', {
url: '//jquery-file-upload.appspot.com/',
// Enable image resizing, except for Android and Opera,
// which actually support image resizing, but fail to
// send Blob objects via XHR requests:
disableImageResize: /Android(?!.*Chrome)|Opera/
.test(window.navigator.userAgent),
maxFileSize: 5000000,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i
});
// Upload server status check for browsers with CORS support:
if ($.support.cors) {
$.ajax({
url: '//jquery-file-upload.appspot.com/',
type: 'HEAD'
}).fail(function () {
$('<span class="alert alert-error"/>')
.text('Upload server currently unavailable - ' +
new Date())
.appendTo('#fileupload');
});
}
} else {
// Load existing files:
$('#fileupload').addClass('fileupload-processing');
$.ajax({
// Uncomment the following to send cross-domain cookies:
//xhrFields: {withCredentials: true},
url: $('#fileupload').fileupload('option', 'url'),
dataType: 'json',
context: $('#fileupload')[0]
}).always(function () {
$(this).removeClass('fileupload-processing');
}).done(function (result) {
$(this).fileupload('option', 'done')
.call(this, null, {result: result});
});
}
});

View File

@@ -0,0 +1,86 @@
/*
* JavaScript Templates 2.3.0
* https://github.com/blueimp/JavaScript-Templates
*
* Copyright 2011, Sebastian Tschan
* https://blueimp.net
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/MIT
*
* Inspired by John Resig's JavaScript Micro-Templating:
* http://ejohn.org/blog/javascript-micro-templating/
*/
/*jslint evil: true, regexp: true, unparam: true */
/*global document, define */
(function ($) {
"use strict";
var tmpl = function (str, data) {
var f = !/[^\w\-\.:]/.test(str) ? tmpl.cache[str] = tmpl.cache[str] ||
tmpl(tmpl.load(str)) :
new Function(
tmpl.arg + ',tmpl',
"var _e=tmpl.encode" + tmpl.helper + ",_s='" +
str.replace(tmpl.regexp, tmpl.func) +
"';return _s;"
);
return data ? f(data, tmpl) : function (data) {
return f(data, tmpl);
};
};
tmpl.cache = {};
tmpl.load = function (id) {
return document.getElementById(id).innerHTML;
};
tmpl.regexp = /([\s'\\])(?!(?:[^{]|\{(?!%))*%\})|(?:\{%(=|#)([\s\S]+?)%\})|(\{%)|(%\})/g;
tmpl.func = function (s, p1, p2, p3, p4, p5) {
if (p1) { // whitespace, quote and backspace in HTML context
return {
"\n": "\\n",
"\r": "\\r",
"\t": "\\t",
" " : " "
}[p1] || "\\" + p1;
}
if (p2) { // interpolation: {%=prop%}, or unescaped: {%#prop%}
if (p2 === "=") {
return "'+_e(" + p3 + ")+'";
}
return "'+" + p3 + "+'";
}
if (p4) { // evaluation start tag: {%
return "';";
}
if (p5) { // evaluation end tag: %}
return "_s+='";
}
};
tmpl.encReg = /[<>&"'\x00]/g;
tmpl.encMap = {
"<" : "&lt;",
">" : "&gt;",
"&" : "&amp;",
"\"" : "&quot;",
"'" : "&#39;"
};
tmpl.encode = function (s) {
return String(s).replace(
tmpl.encReg,
function (c) {
return tmpl.encMap[c] || "";
}
);
};
tmpl.arg = "o";
tmpl.helper = ",print=function(s,e){_s+=e&&(s||'')||_e(s);}" +
",include=function(s,d){_s+=tmpl(s,d);}";
if (typeof define === "function" && define.amd) {
define(function () {
return tmpl;
});
} else {
$.tmpl = tmpl;
}
}(this));

View File

@@ -0,0 +1,530 @@
/*
* jQuery UI Widget 1.10.3+amd
* https://github.com/blueimp/jQuery-File-Upload
*
* Copyright 2013 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://api.jqueryui.com/jQuery.widget/
*/
(function (factory) {
if (typeof define === "function" && define.amd) {
// Register as an anonymous AMD module:
define(["jquery"], factory);
} else {
// Browser globals:
factory(jQuery);
}
}(function( $, undefined ) {
var uuid = 0,
slice = Array.prototype.slice,
_cleanData = $.cleanData;
$.cleanData = function( elems ) {
for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
try {
$( elem ).triggerHandler( "remove" );
// http://bugs.jquery.com/ticket/8235
} catch( e ) {}
}
_cleanData( elems );
};
$.widget = function( name, base, prototype ) {
var fullName, existingConstructor, constructor, basePrototype,
// proxiedPrototype allows the provided prototype to remain unmodified
// so that it can be used as a mixin for multiple widgets (#8876)
proxiedPrototype = {},
namespace = name.split( "." )[ 0 ];
name = name.split( "." )[ 1 ];
fullName = namespace + "-" + name;
if ( !prototype ) {
prototype = base;
base = $.Widget;
}
// create selector for plugin
$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
return !!$.data( elem, fullName );
};
$[ namespace ] = $[ namespace ] || {};
existingConstructor = $[ namespace ][ name ];
constructor = $[ namespace ][ name ] = function( options, element ) {
// allow instantiation without "new" keyword
if ( !this._createWidget ) {
return new constructor( options, element );
}
// allow instantiation without initializing for simple inheritance
// must use "new" keyword (the code above always passes args)
if ( arguments.length ) {
this._createWidget( options, element );
}
};
// extend with the existing constructor to carry over any static properties
$.extend( constructor, existingConstructor, {
version: prototype.version,
// copy the object used to create the prototype in case we need to
// redefine the widget later
_proto: $.extend( {}, prototype ),
// track widgets that inherit from this widget in case this widget is
// redefined after a widget inherits from it
_childConstructors: []
});
basePrototype = new base();
// we need to make the options hash a property directly on the new instance
// otherwise we'll modify the options hash on the prototype that we're
// inheriting from
basePrototype.options = $.widget.extend( {}, basePrototype.options );
$.each( prototype, function( prop, value ) {
if ( !$.isFunction( value ) ) {
proxiedPrototype[ prop ] = value;
return;
}
proxiedPrototype[ prop ] = (function() {
var _super = function() {
return base.prototype[ prop ].apply( this, arguments );
},
_superApply = function( args ) {
return base.prototype[ prop ].apply( this, args );
};
return function() {
var __super = this._super,
__superApply = this._superApply,
returnValue;
this._super = _super;
this._superApply = _superApply;
returnValue = value.apply( this, arguments );
this._super = __super;
this._superApply = __superApply;
return returnValue;
};
})();
});
constructor.prototype = $.widget.extend( basePrototype, {
// TODO: remove support for widgetEventPrefix
// always use the name + a colon as the prefix, e.g., draggable:start
// don't prefix for widgets that aren't DOM-based
widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
}, proxiedPrototype, {
constructor: constructor,
namespace: namespace,
widgetName: name,
widgetFullName: fullName
});
// If this widget is being redefined then we need to find all widgets that
// are inheriting from it and redefine all of them so that they inherit from
// the new version of this widget. We're essentially trying to replace one
// level in the prototype chain.
if ( existingConstructor ) {
$.each( existingConstructor._childConstructors, function( i, child ) {
var childPrototype = child.prototype;
// redefine the child widget using the same prototype that was
// originally used, but inherit from the new version of the base
$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
});
// remove the list of existing child constructors from the old constructor
// so the old child constructors can be garbage collected
delete existingConstructor._childConstructors;
} else {
base._childConstructors.push( constructor );
}
$.widget.bridge( name, constructor );
};
$.widget.extend = function( target ) {
var input = slice.call( arguments, 1 ),
inputIndex = 0,
inputLength = input.length,
key,
value;
for ( ; inputIndex < inputLength; inputIndex++ ) {
for ( key in input[ inputIndex ] ) {
value = input[ inputIndex ][ key ];
if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
// Clone objects
if ( $.isPlainObject( value ) ) {
target[ key ] = $.isPlainObject( target[ key ] ) ?
$.widget.extend( {}, target[ key ], value ) :
// Don't extend strings, arrays, etc. with objects
$.widget.extend( {}, value );
// Copy everything else by reference
} else {
target[ key ] = value;
}
}
}
}
return target;
};
$.widget.bridge = function( name, object ) {
var fullName = object.prototype.widgetFullName || name;
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
args = slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.widget.extend.apply( null, [ options ].concat(args) ) :
options;
if ( isMethodCall ) {
this.each(function() {
var methodValue,
instance = $.data( this, fullName );
if ( !instance ) {
return $.error( "cannot call methods on " + name + " prior to initialization; " +
"attempted to call method '" + options + "'" );
}
if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
return $.error( "no such method '" + options + "' for " + name + " widget instance" );
}
methodValue = instance[ options ].apply( instance, args );
if ( methodValue !== instance && methodValue !== undefined ) {
returnValue = methodValue && methodValue.jquery ?
returnValue.pushStack( methodValue.get() ) :
methodValue;
return false;
}
});
} else {
this.each(function() {
var instance = $.data( this, fullName );
if ( instance ) {
instance.option( options || {} )._init();
} else {
$.data( this, fullName, new object( options, this ) );
}
});
}
return returnValue;
};
};
$.Widget = function( /* options, element */ ) {};
$.Widget._childConstructors = [];
$.Widget.prototype = {
widgetName: "widget",
widgetEventPrefix: "",
defaultElement: "<div>",
options: {
disabled: false,
// callbacks
create: null
},
_createWidget: function( options, element ) {
element = $( element || this.defaultElement || this )[ 0 ];
this.element = $( element );
this.uuid = uuid++;
this.eventNamespace = "." + this.widgetName + this.uuid;
this.options = $.widget.extend( {},
this.options,
this._getCreateOptions(),
options );
this.bindings = $();
this.hoverable = $();
this.focusable = $();
if ( element !== this ) {
$.data( element, this.widgetFullName, this );
this._on( true, this.element, {
remove: function( event ) {
if ( event.target === element ) {
this.destroy();
}
}
});
this.document = $( element.style ?
// element within the document
element.ownerDocument :
// element is window or document
element.document || element );
this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
}
this._create();
this._trigger( "create", null, this._getCreateEventData() );
this._init();
},
_getCreateOptions: $.noop,
_getCreateEventData: $.noop,
_create: $.noop,
_init: $.noop,
destroy: function() {
this._destroy();
// we can probably remove the unbind calls in 2.0
// all event bindings should go through this._on()
this.element
.unbind( this.eventNamespace )
// 1.9 BC for #7810
// TODO remove dual storage
.removeData( this.widgetName )
.removeData( this.widgetFullName )
// support: jquery <1.6.3
// http://bugs.jquery.com/ticket/9413
.removeData( $.camelCase( this.widgetFullName ) );
this.widget()
.unbind( this.eventNamespace )
.removeAttr( "aria-disabled" )
.removeClass(
this.widgetFullName + "-disabled " +
"ui-state-disabled" );
// clean up events and states
this.bindings.unbind( this.eventNamespace );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
},
_destroy: $.noop,
widget: function() {
return this.element;
},
option: function( key, value ) {
var options = key,
parts,
curOption,
i;
if ( arguments.length === 0 ) {
// don't return a reference to the internal hash
return $.widget.extend( {}, this.options );
}
if ( typeof key === "string" ) {
// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
options = {};
parts = key.split( "." );
key = parts.shift();
if ( parts.length ) {
curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
for ( i = 0; i < parts.length - 1; i++ ) {
curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
curOption = curOption[ parts[ i ] ];
}
key = parts.pop();
if ( value === undefined ) {
return curOption[ key ] === undefined ? null : curOption[ key ];
}
curOption[ key ] = value;
} else {
if ( value === undefined ) {
return this.options[ key ] === undefined ? null : this.options[ key ];
}
options[ key ] = value;
}
}
this._setOptions( options );
return this;
},
_setOptions: function( options ) {
var key;
for ( key in options ) {
this._setOption( key, options[ key ] );
}
return this;
},
_setOption: function( key, value ) {
this.options[ key ] = value;
if ( key === "disabled" ) {
this.widget()
.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
.attr( "aria-disabled", value );
this.hoverable.removeClass( "ui-state-hover" );
this.focusable.removeClass( "ui-state-focus" );
}
return this;
},
enable: function() {
return this._setOption( "disabled", false );
},
disable: function() {
return this._setOption( "disabled", true );
},
_on: function( suppressDisabledCheck, element, handlers ) {
var delegateElement,
instance = this;
// no suppressDisabledCheck flag, shuffle arguments
if ( typeof suppressDisabledCheck !== "boolean" ) {
handlers = element;
element = suppressDisabledCheck;
suppressDisabledCheck = false;
}
// no element argument, shuffle and use this.element
if ( !handlers ) {
handlers = element;
element = this.element;
delegateElement = this.widget();
} else {
// accept selectors, DOM elements
element = delegateElement = $( element );
this.bindings = this.bindings.add( element );
}
$.each( handlers, function( event, handler ) {
function handlerProxy() {
// allow widgets to customize the disabled handling
// - disabled as an array instead of boolean
// - disabled class as method for disabling individual parts
if ( !suppressDisabledCheck &&
( instance.options.disabled === true ||
$( this ).hasClass( "ui-state-disabled" ) ) ) {
return;
}
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
// copy the guid so direct unbinding works
if ( typeof handler !== "string" ) {
handlerProxy.guid = handler.guid =
handler.guid || handlerProxy.guid || $.guid++;
}
var match = event.match( /^(\w+)\s*(.*)$/ ),
eventName = match[1] + instance.eventNamespace,
selector = match[2];
if ( selector ) {
delegateElement.delegate( selector, eventName, handlerProxy );
} else {
element.bind( eventName, handlerProxy );
}
});
},
_off: function( element, eventName ) {
eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
element.unbind( eventName ).undelegate( eventName );
},
_delay: function( handler, delay ) {
function handlerProxy() {
return ( typeof handler === "string" ? instance[ handler ] : handler )
.apply( instance, arguments );
}
var instance = this;
return setTimeout( handlerProxy, delay || 0 );
},
_hoverable: function( element ) {
this.hoverable = this.hoverable.add( element );
this._on( element, {
mouseenter: function( event ) {
$( event.currentTarget ).addClass( "ui-state-hover" );
},
mouseleave: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-hover" );
}
});
},
_focusable: function( element ) {
this.focusable = this.focusable.add( element );
this._on( element, {
focusin: function( event ) {
$( event.currentTarget ).addClass( "ui-state-focus" );
},
focusout: function( event ) {
$( event.currentTarget ).removeClass( "ui-state-focus" );
}
});
},
_trigger: function( type, event, data ) {
var prop, orig,
callback = this.options[ type ];
data = data || {};
event = $.Event( event );
event.type = ( type === this.widgetEventPrefix ?
type :
this.widgetEventPrefix + type ).toLowerCase();
// the original event may come from any element
// so we need to reset the target on the new event
event.target = this.element[ 0 ];
// copy original event properties over to the new event
orig = event.originalEvent;
if ( orig ) {
for ( prop in orig ) {
if ( !( prop in event ) ) {
event[ prop ] = orig[ prop ];
}
}
}
this.element.trigger( event, data );
return !( $.isFunction( callback ) &&
callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
event.isDefaultPrevented() );
}
};
$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
if ( typeof options === "string" ) {
options = { effect: options };
}
var hasOptions,
effectName = !options ?
method :
options === true || typeof options === "number" ?
defaultEffect :
options.effect || defaultEffect;
options = options || {};
if ( typeof options === "number" ) {
options = { duration: options };
}
hasOptions = !$.isEmptyObject( options );
options.complete = callback;
if ( options.delay ) {
element.delay( options.delay );
}
if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
element[ method ]( options );
} else if ( effectName !== method && element[ effectName ] ) {
element[ effectName ]( options.duration, options.easing, callback );
} else {
element.queue(function( next ) {
$( this )[ method ]();
if ( callback ) {
callback.call( element[ 0 ] );
}
next();
});
}
};
});
}));

View File

@@ -8,17 +8,15 @@
{% compress css %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap-responsive.css">
{% comment %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/fuelux.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/fuelux-responsive.css">
{% endcomment %}
<link rel="stylesheet" href="{{ STATIC_URL }}css/jasny/jasny-bootstrap.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/jasny/jasny-bootstrap-responsive.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap-datepicker.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/bootstrap-timepicker.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/colorbox.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/animate-custom.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/select2.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/deepsouthsounds.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/uploadifive.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/jquery.fileupload-ui.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/com.podnoms.player.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/emoticons.css">
<link rel="stylesheet" href="{{ STATIC_URL }}css/toastr.css">
@@ -59,13 +57,10 @@
<script src="{{ STATIC_URL }}js/libs/jquery.form.js"></script>
<script src="{{ STATIC_URL }}js/libs/jquery.cookie.js"></script>
<script src="{{ STATIC_URL }}js/libs/jquery.tablesorter.js"></script>
<script src="{{ STATIC_URL }}js/libs/uploadify/jquery.uploadifive.js"></script>
<script src="{{ STATIC_URL }}js/libs/jasny/bootstrap-fileupload.js"></script>
<script src="{{ STATIC_URL }}js/libs/modernizr.js"></script>
<script src="{{ STATIC_URL }}js/libs/point.js"></script>
<script src="{{ STATIC_URL }}js/libs/matrix.js"></script>
<script src="{{ STATIC_URL }}js/libs/select2.js"></script>
<script src="{{ STATIC_URL }}js/libs/ajaxfileupload.js"></script>
<script src="{{ STATIC_URL }}js/app/site.js"></script>
<script src="{{ STATIC_URL }}js/com.podnoms.storage.js"></script>
<script src="{{ STATIC_URL }}js/com.podnoms.player.js"></script>

View File

@@ -1,8 +1,101 @@
<div class="clearfix control-group" id="div-upload-mix">
<div class="controls">
<input type="hidden" class="upload-hash" id="upload-hash-mix">
<input id="mix-upload" type="file" multiple="false" name="mix-upload"/>
<h5>MP3 only for now please..</h5>
<form id="fileupload" action="//_upload/" method="POST" enctype="multipart/form-data">
<input type="hidden" id="upload-hash" name="upload-hash">
<div class="fileupload-buttonbar">
<div class="span7">
<span class="btn btn-success fileinput-button">
<i class="icon-plus icon-white"></i>
<span>Add files...</span>
<input type="file" name="files[]" accept="audio/mp3">
</span>
<span class="fileupload-loading"></span>
</div>
<div class="span5 fileupload-progress fade">
<div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0"
aria-valuemax="100">
<div class="bar" style="width:0%;"></div>
</div>
<div class="progress-extended">&nbsp;</div>
</div>
</div>
<table role="presentation" class="table table-striped">
<tbody class="files"></tbody>
</table>
</form>
{% verbatim %}
<script id="template-upload" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-upload fade">
<td>
<span class="preview"></span>
</td>
<td>
<p class="name">{%=file.name%}</p>
{% if (file.error) { %}
<div><span class="label label-important">Error</span> {%=file.error%}</div>
{% } %}
</td>
<td>
<p class="size">{%=o.formatFileSize(file.size)%}</p>
{% if (!o.files.error) { %}
<div class="progress progress-success progress-striped active" role="progressbar" aria-valuemin="0"
aria-valuemax="100" aria-valuenow="0">
<div class="bar" style="width:0%;"></div>
</div>
{% } %}
</td>
<td>
{% if (!o.files.error && !i && !o.options.autoUpload) { %}
<button class="btn btn-primary start">
<i class="icon-upload icon-white"></i>
<span>Start</span>
</button>
{% } %}
{% if (!i) { %}
<button class="btn btn-warning cancel">
<i class="icon-ban-circle icon-white"></i>
<span>Cancel</span>
</button>
{% } %}
</td>
</tr>
{% } %}
</script>
<script id="template-download" type="text/x-tmpl">
{% for (var i=0, file; file=o.files[i]; i++) { %}
<tr class="template-download fade">
<td>
<span class="preview">
{% if (file.thumbnailUrl) { %}
<a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" data-gallery><img
src="{%=file.thumbnailUrl%}"></a>
{% } %}
</span>
</td>
<td>
<p class="name">
<a href="{%=file.url%}" title="{%=file.name%}" download="{%=file.name%}" {%=file.thumbnailUrl?'data-gallery':''%}>{%=file.name%}</a>
</p>
{% if (file.error) { %}
<div><span class="label label-important">Error</span> {%=file.error%}</div>
{% } %}
</td>
<td>
<span class="size">{%=o.formatFileSize(file.size)%}</span>
</td>
<td>
<button class="btn btn-danger delete" data-type="{%=file.deleteType%}" data-url="{%=file.deleteUrl%}"
{% if (file.deleteWithCredentials) { %} data-xhr-fields='{"withCredentials":true}'{% } %}>
<i class="icon-trash icon-white"></i>
<span>Delete</span>
</button>
<input type="checkbox" name="delete" value="1" class="toggle">
</td>
</tr>
{% } %}
</script>
{% endverbatim %}
</div>
</div>
<div class="well" id="mix-details">
@@ -25,28 +118,22 @@
<div class="span9">
<div class="control-group">
<label class="control-label">Description</label>
<div class="controls">
<textarea style="width: 98%" id="description" name="description" rows="12" placeholder="Tracklist would be nice…"><%=description%></textarea>
<textarea style="width: 98%" id="description" name="description" rows="12"
placeholder="Tracklist would be nice…"><%=description%></textarea>
</div>
</div>
</div>
<div class="span3">
<div class="fileupload fileupload-new" data-provides="fileupload">
<div class="fileupload-new thumbnail" style="width: 200px; height: 150px;">
<img src="<%=mix_image%>"/>
</div>
<div class="fileupload-preview fileupload-exists thumbnail"
style="max-width: 200px; max-height: 150px; line-height: 20px;">
<div id="mix-imageupload" class="fileupload fileupload-new" data-provides="fileupload">
<div class="fileupload-preview thumbnail" style="width: 200px; height: 150px;">
</div>
<div>
<span class="btn btn-file">
<span class="fileupload-new">
Select image
</span>
<span class="fileupload-exists">
Change
</span>
<input id="mix_image" type="file" size="45" name="mix_image" class="input">
<span class="fileupload-new">Select image</span>
<span class="fileupload-exists">Change</span>
<input type="file" />
</span>
<a href="#" class="btn fileupload-exists" data-dismiss="fileupload">Remove</a>
</div>
@@ -62,6 +149,7 @@
<div class="row">
<div class="control-group">
<label class="control-label">Genres</label>
<div class="controls">
<input type="hidden" id="genres" style="width:98%"/>
</div>