diff --git a/.gitignore b/.gitignore
index 59e8f45..4519587 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,13 @@
/node_modules
/public/hot
/public/storage
+
+public/uploads/*
+!public/uploads/.gitkeep
+
+/public/medias/*
+!public/medias/.gitkeep
+
/storage/*.key
/vendor
.env
diff --git a/app/Http/Controllers/MediaController.php b/app/Http/Controllers/MediaController.php
index b3f3691..842b930 100644
--- a/app/Http/Controllers/MediaController.php
+++ b/app/Http/Controllers/MediaController.php
@@ -35,9 +35,9 @@ class MediaController extends Controller
$limit = $request->input('limit');
if($dossier_id == "0"){
- $response['data'] = Media::where('sujet_id', $sujet_id)->whereNull('dossier_id')->offset($offset)->limit($limit)->get();
+ $response['data'] = Media::where('sujet_id', $sujet_id)->whereNull('dossier_id')->offset($offset)->limit($limit)->orderBy('created_at', 'desc')->get();
}else{
- $response['data'] = Media::where('sujet_id', $sujet_id)->where('dossier_id', $dossier_id)->offset($offset)->limit($limit)->get();
+ $response['data'] = Media::where('sujet_id', $sujet_id)->where('dossier_id', $dossier_id)->offset($offset)->limit($limit)->orderBy('created_at', 'desc')->get();
}
return json_encode($response);
diff --git a/app/Http/Controllers/UploadController.php b/app/Http/Controllers/UploadController.php
new file mode 100644
index 0000000..fe3170d
--- /dev/null
+++ b/app/Http/Controllers/UploadController.php
@@ -0,0 +1,164 @@
+input('uid');
+ $sujet_id = $request->input('sujet_id');
+ $dossier_id = $request->input('dossier_id');
+ if($dossier_id == 0) $dossier_id = null;
+
+ $headers = $request->headers->all();
+ $acceptFileTypes = array('gif','jpeg', 'jpg','png','mp4','mp3','psd','ai','eps','svg','indd','pdf');
+
+ if ($request->hasFile('files')) {
+ $file = $request->file('files');
+
+ foreach($file as $files){
+ $is_last_chunk = false;
+ $is_chunked_upload = !empty($headers['content-range']);
+ if ($is_chunked_upload) {
+ if (preg_match('|(\d+)/(\d+)|', $headers['content-range'][0], $range)) {
+ if ($range[1] == $range[2] - 1) {
+ $is_last_chunk = true;
+ }
+ }
+ }
+
+ $extension = strtolower($files->getClientOriginalExtension());
+ if (!in_array($extension, $acceptFileTypes)) return array("false");
+
+ if($is_chunked_upload){
+ $filename = $uid."_".$range[1].".".$extension;
+ }else{
+ $filename = $uid.'.'.$extension;
+ }
+
+ $uploadPath = public_path() . '/uploads/';
+ $files->move($uploadPath, $filename);
+
+ // si c'est le dernier morceux d'un fichier
+ if($is_last_chunk){
+ if($dossier = opendir($uploadPath)){
+ $dirFiles = array();
+ // on compile tous les chunk du fichier
+ while(false !== ($fichier = readdir($dossier))){
+ if($fichier != '.' && $fichier != '..' ){
+ if(strpos($fichier,$uid) !== false){
+ $dirFiles[$fichier] = str_replace(".".$extension, "", str_replace($uid."_", "", $fichier));
+ }
+ }
+ }
+ closedir($dossier);
+ // on les trie pour les assembler dans le bon ordre
+ asort ($dirFiles, SORT_NUMERIC );
+ // on les assemble dans un fichier final
+ $filename = $uid . '.' . $extension;
+ foreach($dirFiles as $key => $fichier) {
+ $file = fopen($uploadPath.$key, 'rb');
+ $buff = fread($file, filesize($uploadPath.$key));
+ fclose($file);
+
+ $final = fopen($uploadPath.$filename, 'a');
+ $write = fwrite($final, $buff);
+ fclose($final);
+
+ unlink($uploadPath.$key);
+ }
+
+
+ }
+ }
+
+ // si c'est le dernier morceau d'un fichier ou que c'est un envoi simple
+ if($is_last_chunk || !$is_chunked_upload ){
+
+ // On déplace le fichier dans le répertoire des médias
+ $destinationPath = $this->get_mediaFolder()."/";
+ Storage::move("uploads/".$filename, $destinationPath.$filename);
+
+ // on créé un média en bdd
+ $media = new Media();
+ $media->libelle = $filename;
+ $media->extension = $extension;
+ $media->source = $destinationPath.$filename;
+ $media->taille = $this->formatBytes($this->get_file_size($destinationPath.$filename));
+
+ switch ($extension) {
+ // Photo (1)
+ case 'gif':
+ case 'jpeg':
+ case 'jpg':
+ case 'png':
+ $media->typemedia_id = 1;
+ list($width, $height) = getimagesize($destinationPath.$filename);
+ $media->largeur = $width;
+ $media->hauteur = $height;
+ $media->resolution = $this->get_dpi($destinationPath.$filename);
+ $this->createThumbnail($destinationPath.$filename, $destinationPath.$uid.'_thumb.'.$extension, 200);
+ break;
+ // Illustration/Vectorielle (2)
+ case 'ai':
+ case 'svg':
+ $media->typemedia_id = 2;
+ break;
+ // Vidéo (3)
+ case 'mp4':
+ $media->typemedia_id =3 ;
+ $getID3 = new \getID3;
+ if($fileInfo = $getID3->analyze($destinationPath.$filename)){
+ $media->largeur = $fileInfo['video']['resolution_x'];
+ $media->hauteur = $fileInfo['video']['resolution_y'];
+ $media->duree = $fileInfo['playtime_string'];
+ }
+ break;
+ // Audio (4)
+ case 'mp3':
+ $media->typemedia_id = 4;
+ $getID3 = new \getID3;
+ if($fileInfo = $getID3->analyze($destinationPath.$filename)){
+ $media->duree= $fileInfo['playtime_string'];
+ }
+ break;
+ // Document (5)
+ case 'eps':
+ case 'indd':
+ case 'pdf':
+ case 'psd':
+ default:
+ $media->typemedia_id = 5;
+ break;
+ }
+
+
+ $media->dossier_id = $dossier_id;
+ $media->sujet_id = $sujet_id;
+ $media->save();
+ }
+
+ $filest = array();
+ $filest['name'] = $filename;
+ $filest['uid'] = $uid;
+ $filesa['files'][]=$filest;
+ }
+ return $filesa;
+ }
+ }
+
+}
diff --git a/app/Http/Traits/MediaTools.php b/app/Http/Traits/MediaTools.php
new file mode 100644
index 0000000..c18ab81
--- /dev/null
+++ b/app/Http/Traits/MediaTools.php
@@ -0,0 +1,221 @@
+fix_integer_overflow(filesize($file_path));
+ }
+
+ public function get_mediaFolder() {
+ $mediaPath = "medias/";
+ $directories = Storage::allDirectories($mediaPath );
+ $selectedFolder = "";
+
+ // on obtient le dernier dossier créé
+ foreach ($directories as $key => $foldername) {
+ if($selectedFolder < $foldername){
+ $selectedFolder = $foldername;
+ }
+ }
+ // si on n'a pas de dossier, on le créé
+ if($selectedFolder == ""){
+ $selectedFolder = $mediaPath.uniqid();
+ Storage::makeDirectory($selectedFolder);
+ }else{
+ // on compte le nombre de fichier
+ $files = Storage::allFiles($selectedFolder);
+ // si on en a plus de 1000, on créé un nouveau dossier
+ if(count($files)> 1000){
+ $selectedFolder = $mediaPath.uniqid();
+ Storage::makeDirectory($selectedFolder);
+ }
+ }
+ return $selectedFolder;
+ }
+
+ public function fix_integer_overflow($size) {
+ if ($size < 0) {
+ $size += 2.0 * (PHP_INT_MAX + 1);
+ }
+ return $size;
+ }
+
+ public function formatBytes($octet, $precision = 2) {
+ // Array contenant les differents unités
+ $unite = array('octet','Ko','Mo','Go');
+
+ if ($octet < 1000) // octet
+ {
+ return $octet.$unite[0];
+ }
+ else
+ {
+ if ($octet < 1000000) // ko
+ {
+ $ko = round($octet/1024,2);
+ return $ko.$unite[1];
+ }
+ else // Mo ou Go
+ {
+ if ($octet < 1000000000) // Mo
+ {
+ $mo = round($octet/(1024*1024),2);
+ return $mo.$unite[2];
+ }
+ else // Go
+ {
+ $go = round($octet/(1024*1024*1024),2);
+ return $go.$unite[3];
+ }
+ }
+ }
+ }
+
+ public function get_dpi($filename){
+
+ // open the file and read first 20 bytes.
+ $a = fopen($filename,'r');
+ $string = fread($a,20);
+ fclose($a);
+
+ // get the value of byte 14th up to 18th
+ $data = bin2hex(substr($string,14,4));
+ $x = substr($data,0,4);
+ $y = substr($data,4,4);
+ //return array(hexdec($x),hexdec($y));
+ return hexdec($x);
+ }
+
+
+ /**
+ * @param $src - a valid file location
+ * @param $dest - a valid file target
+ * @param $targetWidth - desired output width
+ * @param $targetHeight - desired output height or null
+ */
+ function createThumbnail($src, $dest, $targetWidth, $targetHeight = null) {
+
+
+
+ // Link image type to correct image loader and saver
+ // - makes it easier to add additional types later on
+ // - makes the function easier to read
+ $IMAGETYPE_JPEG = 2;
+ $IMAGETYPE_PNG = 3;
+ $IMAGETYPE_GIF = 1;
+ $IMAGE_HANDLERS = [
+ 2 => [
+ 'load' => 'imagecreatefromjpeg',
+ 'save' => 'imagejpeg',
+ 'quality' => 100
+ ],
+ 3 => [
+ 'load' => 'imagecreatefrompng',
+ 'save' => 'imagepng',
+ 'quality' => 0
+ ],
+ 1 => [
+ 'load' => 'imagecreatefromgif',
+ 'save' => 'imagegif'
+ ]
+ ];
+
+ // 1. Load the image from the given $src
+ // - see if the file actually exists
+ // - check if it's of a valid image type
+ // - load the image resource
+
+ // get the type of the image
+ // we need the type to determine the correct loader
+ $type = exif_imagetype($src);
+
+ // if no valid type or no handler found -> exit
+ if (!$type || !$IMAGE_HANDLERS[$type]) {
+ return null;
+ }
+
+ // load the image with the correct loader
+ $image = call_user_func($IMAGE_HANDLERS[$type]['load'], $src);
+
+ // no image found at supplied location -> exit
+ if (!$image) {
+ return null;
+ }
+
+
+ // 2. Create a thumbnail and resize the loaded $image
+ // - get the image dimensions
+ // - define the output size appropriately
+ // - create a thumbnail based on that size
+ // - set alpha transparency for GIFs and PNGs
+ // - draw the final thumbnail
+
+ // get original image width and height
+ $width = imagesx($image);
+ $height = imagesy($image);
+
+ // maintain aspect ratio when no height set
+ if ($targetHeight == null) {
+
+ // get width to height ratio
+ $ratio = $width / $height;
+
+ // if is portrait
+ // use ratio to scale height to fit in square
+ if ($width > $height) {
+ $targetHeight = floor($targetWidth / $ratio);
+ }
+ // if is landscape
+ // use ratio to scale width to fit in square
+ else {
+ $targetHeight = $targetWidth;
+ $targetWidth = floor($targetWidth * $ratio);
+ }
+ }
+
+ // create duplicate image based on calculated target size
+ $thumbnail = imagecreatetruecolor($targetWidth, $targetHeight);
+
+ // set transparency options for GIFs and PNGs
+ if ($type == $IMAGETYPE_GIF || $type == $IMAGETYPE_PNG) {
+
+ // make image transparent
+ imagecolortransparent(
+ $thumbnail,
+ imagecolorallocate($thumbnail, 0, 0, 0)
+ );
+
+ // additional settings for PNGs
+ if ($type == $IMAGETYPE_PNG) {
+ imagealphablending($thumbnail, false);
+ imagesavealpha($thumbnail, true);
+ }
+ }
+
+ // copy entire source image to duplicate image and resize
+ imagecopyresampled(
+ $thumbnail,
+ $image,
+ 0, 0, 0, 0,
+ $targetWidth, $targetHeight,
+ $width, $height
+ );
+
+
+ // 3. Save the $thumbnail to disk
+ // - call the correct save method
+ // - set the correct quality level
+
+ // save the duplicate version of the image to disk
+ return call_user_func(
+ $IMAGE_HANDLERS[$type]['save'],
+ $thumbnail,
+ $dest,
+ $IMAGE_HANDLERS[$type]['quality']
+ );
+ }
+
+}
diff --git a/app/Media.php b/app/Media.php
index 7cdc749..d992fd8 100644
--- a/app/Media.php
+++ b/app/Media.php
@@ -23,4 +23,12 @@ class Media extends Model
{
return $this->belongsTo('App\Sujet', 'sujet_id');
}
+
+ /**
+ * Get the typemedia that owns the media.
+ */
+ public function typemedia()
+ {
+ return $this->belongsTo('App\Typemedia', 'typemedia_id');
+ }
}
diff --git a/app/Typemedia.php b/app/Typemedia.php
new file mode 100644
index 0000000..2a23f2e
--- /dev/null
+++ b/app/Typemedia.php
@@ -0,0 +1,15 @@
+=5.3.0"
+ },
+ "require-dev": {
+ "jakub-onderka/php-parallel-lint": "^0.9 || ^1.0"
+ },
+ "suggest": {
+ "ext-SimpleXML": "SimpleXML extension is required to analyze RIFF/WAV/BWF audio files (also requires `ext-libxml`).",
+ "ext-com_dotnet": "COM extension is required when loading files larger than 2GB on Windows.",
+ "ext-ctype": "ctype extension is required when loading files larger than 2GB on 32-bit PHP (also on 64-bit PHP on Windows) or executing `getid3_lib::CopyTagsToComments`.",
+ "ext-dba": "DBA extension is required to use the DBA database as a cache storage.",
+ "ext-exif": "EXIF extension is required for graphic modules.",
+ "ext-iconv": "iconv extension is required to work with different character sets (when `ext-mbstring` is not available).",
+ "ext-json": "JSON extension is required to analyze Apple Quicktime videos.",
+ "ext-libxml": "libxml extension is required to analyze RIFF/WAV/BWF audio files.",
+ "ext-mbstring": "mbstring extension is required to work with different character sets.",
+ "ext-mysql": "MySQL extension is required to use the MySQL database as a cache storage (deprecated in PHP 5.5, removed in PHP >= 7.0, use `ext-mysqli` instead).",
+ "ext-mysqli": "MySQLi extension is required to use the MySQL database as a cache storage.",
+ "ext-rar": "RAR extension is required for RAR archive module.",
+ "ext-sqlite3": "SQLite3 extension is required to use the SQLite3 database as a cache storage.",
+ "ext-xml": "XML extension is required for graphic modules to analyze the XML metadata.",
+ "ext-zlib": "Zlib extension is required for archive modules and compressed metadata."
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "1.9.x-dev"
+ }
+ },
+ "autoload": {
+ "classmap": [
+ "getid3/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "GPL-1.0-or-later",
+ "LGPL-3.0-only",
+ "MPL-2.0"
+ ],
+ "description": "PHP script that extracts useful information from popular multimedia file formats",
+ "homepage": "https://www.getid3.org/",
+ "keywords": [
+ "codecs",
+ "php",
+ "tags"
+ ],
+ "time": "2019-02-07T17:30:03+00:00"
+ },
{
"name": "laravel/framework",
"version": "v5.8.2",
diff --git a/config/filesystems.php b/config/filesystems.php
index 77fa5de..910571a 100644
--- a/config/filesystems.php
+++ b/config/filesystems.php
@@ -45,7 +45,7 @@ return [
'local' => [
'driver' => 'local',
- 'root' => storage_path('app'),
+ 'root' => public_path(),
],
'public' => [
diff --git a/public/css/app.css b/public/css/app.css
index 0ea5a63..fc98168 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -154,6 +154,21 @@ table {
box-sizing: inherit;
}
+.group:before,
+.group:after {
+ content: "";
+ display: table;
+}
+
+.group:after {
+ clear: both;
+}
+
+.group {
+ zoom: 1;
+ /* For IE 6/7 (trigger hasLayout) */
+}
+
/*!
* animate.css -http://daneden.me/animate
* Version - 3.7.0
@@ -12191,6 +12206,183 @@ readers do not read off random characters that represent icons */
font-family: "Font Awesome 5 Brands";
}
+/*
+ * jQuery File Upload Plugin CSS
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2013, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+.upload_container {
+ display: none;
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ width: 300px;
+ height: 400px;
+ background-color: #ffffff;
+ z-index: 100;
+ border-radius: 10px;
+ /* Fixes for IE < 8 */
+}
+
+.upload_container .fileinput-button {
+ position: relative;
+ overflow: hidden;
+ display: inline-block;
+ margin-bottom: 0;
+ font-size: 14px;
+ font-weight: 400;
+ line-height: 1.42857143;
+ text-align: left;
+ white-space: nowrap;
+ vertical-align: middle;
+ touch-action: manipulation;
+ cursor: pointer;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 4px;
+ width: 100%;
+ font-size: 15px;
+ color: #3b3b3b;
+ padding: 16px;
+ cursor: pointer;
+ border-bottom: 1px solid #aaaaaa;
+}
+
+.upload_container .fileinput-button i {
+ color: #2489D1;
+ font-size: 18px;
+ margin-right: 10px;
+}
+
+.upload_container .fileinput-button input {
+ position: absolute;
+ top: 0;
+ right: 0;
+ margin: 0;
+ opacity: 0;
+ font-size: 200px !important;
+ direction: ltr;
+ cursor: pointer;
+}
+
+@media screen\9 {
+ .upload_container .fileinput-button input {
+ filter: alpha(opacity=0);
+ font-size: 100%;
+ height: 100%;
+ }
+}
+
+.upload_container .progress {
+ display: none;
+ height: 6px;
+ overflow: hidden;
+ background-color: #f5f5f5;
+ position: absolute;
+ top: 0px;
+ left: 0;
+ z-index: 999999999999;
+ width: 100%;
+}
+
+.upload_container .progress .progress-bar {
+ float: left;
+ width: 0;
+ height: 100%;
+ font-size: 12px;
+ line-height: 20px;
+ color: #fff;
+ text-align: center;
+ background-color: #337ab7;
+ box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
+ transition: width 0.6s ease;
+}
+
+.upload_container .progress .progress-bar.progress-bar-success {
+ background-color: #5cb85c;
+}
+
+.upload_container .content_wrapper {
+ height: 286px;
+ overflow-y: auto;
+}
+
+.upload_container #files > div {
+ width: 100%;
+ padding: 10px;
+ border-bottom: 1px solid #e8ebed;
+}
+
+.upload_container #files > div div:first-child {
+ color: #000000;
+ margin-bottom: 5px;
+ font-size: 14px;
+ position: relative;
+}
+
+.upload_container #files > div div:first-child span {
+ white-space: nowrap;
+ display: block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ max-width: 91%;
+}
+
+.upload_container #files > div div:last-child {
+ color: #3b3b3b;
+}
+
+.upload_container #files > div button {
+ display: none;
+ border: none;
+ background-color: transparent;
+ position: absolute;
+ right: 0;
+ top: -3px;
+ cursor: pointer;
+}
+
+.upload_container #files > div button i {
+ color: #337ab7;
+ font-size: 19px;
+}
+
+.upload_container #files > div:hover button {
+ display: block;
+}
+
+.upload_container .upload_bar {
+ border-top: 1px solid #aaaaaa;
+ width: 100%;
+ height: 60px;
+ position: absolute;
+ bottom: 0;
+ padding: 13px 10px;
+}
+
+.upload_container .upload_bar #submit {
+ display: inline-block;
+}
+
+.upload_container .upload_bar #cancel,
+.upload_container .upload_bar #close_upload_form {
+ color: #3b3b3b;
+ cursor: pointer;
+ margin-left: 15px;
+}
+
/******************************************
HEADER
******************************************/
@@ -12502,6 +12694,47 @@ readers do not read off random characters that represent icons */
width: 100%;
}
+/******************************************
+ UPLOADER
+******************************************/
+
+.uploader-window {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background: rgba(0, 86, 132, 0.9);
+ z-index: 250000;
+ display: none;
+ text-align: center;
+}
+
+.uploader-window .uploader-window-content {
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ right: 10px;
+ bottom: 10px;
+ border: 1px dashed #fff;
+ pointer-events: none;
+}
+
+.uploader-window .uploader-window-content h1 {
+ margin: -0.5em 0 0;
+ position: absolute;
+ font-weight: bold;
+ top: 50%;
+ left: 0;
+ right: 0;
+ -webkit-transform: translateY(-50%);
+ transform: translateY(-50%);
+ font-size: 37px;
+ color: #fff;
+ padding: 0;
+ pointer-events: none;
+}
+
/******************************************
SIDE MENU
******************************************/
@@ -12850,12 +13083,6 @@ h1 {
display: inline-block;
}
-.group:after {
- content: "";
- display: table;
- clear: both;
-}
-
.filter_row {
margin-bottom: 10px;
}
@@ -13030,12 +13257,66 @@ table tfoot tr {
min-height: 129px;
}
+.media_manager .main_content header .search_wrapper .search_filters {
+ display: inline-flex;
+ align-items: center;
+ padding: 0px 10px;
+}
+
+.media_manager .main_content header .search_wrapper .search_filters > div {
+ display: flex;
+ align-items: center;
+ margin-right: 10px;
+}
+
+.media_manager .main_content header .search_wrapper .search_filters > div i {
+ margin-left: 10px;
+}
+
.media_manager .main_content header .action_wrapper {
background-color: #3b3b3b;
width: 100%;
min-height: 45px;
}
+.media_manager .main_content header .action_wrapper a {
+ font-size: 14px;
+ padding: 15px 20px;
+ display: inline-block;
+ border-right: 1px solid #505050;
+ float: left;
+ transition: 0.3s ease-in-out;
+}
+
+.media_manager .main_content header .action_wrapper a:hover {
+ background-color: #333333;
+}
+
+.media_manager .main_content header .action_wrapper a i {
+ background-color: #000000;
+ border-radius: 20px;
+ margin-right: 5px;
+ padding: 5px 6px;
+ font-size: 11px;
+ color: #ffffff;
+}
+
+.media_manager .main_content header .action_wrapper a.action_download_media i {
+ background-color: #f6993f;
+}
+
+.media_manager .main_content header .action_wrapper a.action_add_basket i {
+ background-color: #4dc0b5;
+}
+
+.media_manager .main_content header .action_wrapper a.action_delete_media i {
+ background-color: #e3342f;
+}
+
+.media_manager .main_content header .action_wrapper a.action_tag_media i {
+ background-color: #6cb2eb;
+}
+
.media_manager .main_content .scrollLoader {
position: absolute;
border-bottom: 0;
@@ -13082,21 +13363,147 @@ table tfoot tr {
top: 0;
}
-.media_manager .main_content .media_container .imageItem {
- padding: 51px 20px;
- margin: 30px 10px;
+.media_manager .main_content .media_container .wrapper {
+ display: flex;
+ flex-wrap: wrap;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem {
display: inline-block;
border: 1px solid grey;
+ width: 202px;
+ margin: 8px 10px;
+ background-color: #333333;
+ transition: 0.3s ease-in-out;
+ position: relative;
+ padding-bottom: 25px;
+ cursor: pointer;
}
-.media_manager .main_content .media_container .imageItem[aria-grabbed=true] {
- background: #8ad;
- color: #fff;
+.media_manager .main_content .media_container .wrapper .imageItem .contextMenu {
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.7);
+ left: 0px;
+ top: 0px;
+ z-index: 2;
+ display: none;
+ -webkit-transform: rotateY(180deg);
+ transform: rotateY(180deg);
+ color: #ffffff;
+ justify-content: center;
+ align-items: center;
}
-.media_manager .main_content .media_container .imageItem:focus {
- outline: none;
- box-shadow: 0 0 0 2px #68b, inset 0 0 0 1px #ddd;
+.media_manager .main_content .media_container .wrapper .imageItem .contextMenu .menu-option {
+ padding: 10px;
+ transition: 0.3s ease-in-out;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .contextMenu .menu-option i {
+ margin-right: 5px;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .contextMenu .menu-option:hover {
+ background-color: #2ea8e5;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem.flip {
+ -webkit-transform: rotateY(180deg);
+ transform: rotateY(180deg);
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem.flip .contextMenu {
+ display: flex;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .media {
+ height: 200px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ position: relative;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .media::before {
+ -webkit-transform: translate(-50%, -50%);
+ transform: translate(-50%, -50%);
+ display: block;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ text-align: center;
+ text-indent: 0;
+ color: #ffffff;
+ font-size: 59px;
+ z-index: 1;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .media.video::before {
+ content: "\F167";
+ font-family: "Font Awesome 5 Brands";
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .media.son::before {
+ content: "\F58F";
+ font-family: "Font Awesome 5 Pro";
+ font-weight: 900;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .media.doc::before {
+ content: "\F15C";
+ font-family: "Font Awesome 5 Pro";
+ font-weight: 900;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .libelle {
+ padding: 4px;
+ text-align: center;
+ color: #ffffff;
+ margin: 0;
+ word-wrap: break-word;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .info {
+ position: absolute;
+ bottom: 0;
+ padding: 4px;
+ font-size: 11px;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem .info i {
+ margin-right: 7px;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem[aria-grabbed=true]::before {
+ content: "\F058";
+ font-family: "Font Awesome 5 Pro";
+ font-weight: 900;
+ display: block;
+ position: absolute;
+ left: 5px;
+ top: 5px;
+ text-align: center;
+ text-indent: 0;
+ color: #2489D1;
+ font-size: 25px;
+ z-index: 2;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem[aria-grabbed=true]::after {
+ content: "";
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0, 0, 0, 0.5);
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ z-index: 1;
+}
+
+.media_manager .main_content .media_container .wrapper .imageItem:hover {
+ background-color: #3b3b3b;
}
.media_manager [draggable] {
@@ -13112,6 +13519,7 @@ table tfoot tr {
width: 340px;
height: 100%;
transition: 0.3s ease-in-out;
+ z-index: 1;
}
.media_manager .side_menu.collapse {
diff --git a/public/js/app.js b/public/js/app.js
index c99d66f..33599dd 100644
--- a/public/js/app.js
+++ b/public/js/app.js
@@ -11090,6 +11090,7 @@ $(document).ready(function () {
$("#sujet_selector").on('change', function (event) {
var sujetId = $("#sujet_selector").val();
if (sujetId != "") $('.dd').nestable('destroy');
+ $(".currentFolder").val(0);
initFolderManager(sujetId);
}); // ---- Gestion des média : sélectionner un dossier et favoris
@@ -11349,6 +11350,18 @@ $(document).ready(function () {
dossier_id: dossier_id,
libelle: libelle,
description: description
+ },
+ success: function success(result) {
+ $(".folder_container .dd3-item[data-id='" + dossier_id + "'] > .dd3-content span").html(libelle);
+ $.fancybox.getInstance().close();
+ },
+ error: function error(jqXHR, textStatus, errorThrown) {
+ $.fancybox.getInstance().close();
+ $.fancyMessage({
+ title: "Attention",
+ message: "Erreur lors de l'édition du dossier, merci d'actualiser la page.",
+ type: "error"
+ });
}
});
}); // ---- Gestion des média : au début du drag, set les valeurs des éléments draggés
@@ -11370,14 +11383,22 @@ $(document).ready(function () {
}
}); // ---- Gestion des média : déselectionne les éléments si on clique en dehors
- $(".main_content").on("click", function (event) {
+ $(".main_content .wrapper").on("click", function (event) {
if (event.target !== this) return;
$(".imageItem").attr("aria-grabbed", "false");
+ $(".imageItem").removeClass('flip');
+ }); // ---- Gestion des média : déselectionne les éléments si on clique en dehors
+
+ $(".main_content .wrapper").on("contextmenu", '.imageItem', function (event) {
+ event.preventDefault();
+ $(".imageItem").removeClass('flip');
+ $(this).addClass('flip');
}); // ---- Gestion des média : gère la sélection des éléments
var lastSelected;
$("html").on("click", ".imageItem", function (event) {
event.preventDefault();
+ $(".imageItem").removeClass('flip');
if (event.ctrlKey) {
if ($(this).attr("aria-grabbed") == "true") {
@@ -11408,11 +11429,10 @@ $(document).ready(function () {
imageItem.eq(i).attr("aria-grabbed", select);
}
} else {
- $(".imageItem").attr("aria-grabbed", "false");
-
if ($(this).attr("aria-grabbed") == "true") {
- $(this).attr("aria-grabbed", "false");
+ $(".imageItem").attr("aria-grabbed", "false");
} else {
+ $(".imageItem").attr("aria-grabbed", "false");
$(this).attr("aria-grabbed", "true");
}
@@ -11492,7 +11512,8 @@ $(document).ready(function () {
}
});
}
- });
+ }); // ---- Gestion de l'infinite scroll
+
$(".main_content .media_container").on("scroll resize", function () {
if ($(window).data('ajaxready') == false) return;
if ($(window).data('noMedia') == true) return;
@@ -11504,6 +11525,146 @@ $(document).ready(function () {
loadMedia(dossier_id, sujet_id);
}
});
+ $('#close_upload_form').on('click', function (event) {
+ event.preventDefault();
+ $('.upload_container').hide("fast");
+ });
+ $('.action_add_media').on('click', function (event) {
+ event.preventDefault();
+ $('.upload_container').show("fast");
+ });
+
+ if ($('#fileupload').length) {
+ $.ajaxSetup({
+ headers: {
+ 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
+ }
+ });
+ var cancel_btn = $('').addClass('cancel').html('');
+ var upload_btn = $('').addClass('upload').css('display', 'none');
+ $('#submit').on('click', function (event) {
+ event.preventDefault();
+ $("#upload_errors").html("");
+ $('.upload').click(); //click upload buttons and upload all files in the queue
+ });
+ $('#cancel').on('click', function (event) {
+ event.preventDefault();
+ $('.cancel').click(); //click cancel buttons and remove all files in the queue
+
+ $("#upload_errors").html("");
+ });
+ $(".uploader-window").on("dragleave", function () {
+ $(this).stop().fadeOut("fast");
+ });
+ var currentfiles = [];
+ $('#fileupload').fileupload({
+ url: "/ajax/upload-media",
+ dataType: 'json',
+ limitMultiFileUploads: 10,
+ maxChunkSize: 10000000,
+ dragover: function dragover(e, data) {
+ $(".uploader-window").fadeIn("fast");
+ },
+ add: function add(e, data) {
+ data.files = $.map(data.files, function (file, i) {
+ if ($.inArray(file.name, currentfiles) >= 0) {
+ $.fancyMessage({
+ title: "Attention",
+ message: "Nous ne pouvons pas envoyer plusieurs fichiers du même nom. Veuillez renommer vos fichiers puis essayez à nouveau.",
+ type: "error"
+ });
+ return null;
+ }
+
+ return file;
+ });
+ $.each(data.files, function (index, file) {
+ var uploadErrors = [];
+ var acceptFileTypes = /^(gif|jpe?g|png|mp4|mp3|psd|ai|eps|svg|indd|pdf)$/i;
+
+ if (file.name.split('.').pop().length && !acceptFileTypes.test(file.name.split('.').pop())) {
+ uploadErrors.push('Ce type de fichier n\'est pas accepté');
+ }
+
+ if (data.originalFiles[0]['size'].length && data.originalFiles[0]['size'] > 5000000) {
+ uploadErrors.push('La taille du fichier est trop grande.');
+ }
+
+ if (uploadErrors.length > 0) {
+ alert(uploadErrors.join("\n"));
+ return false;
+ }
+
+ currentfiles.push($.trim(file.name));
+ var row = document.createElement('div');
+ var col1 = document.createElement('div');
+ var col2 = document.createElement('div');
+ $(col1).append("" + file.name + "").append(cancel_btn.clone(true).data(data));
+ $(col2).append("" + file.type + " - " + FileConvertSize(file.size) + "");
+ $(col2).append(upload_btn.clone(true).data(data));
+ $(row).append(col1, col2);
+ $(".files").append(row);
+ $('.upload').eq(-1).on('click', function () {
+ data.formData = {
+ uid: create_UUID(),
+ sujet_id: $("#sujet_selector").val(),
+ dossier_id: $(".currentFolder").val()
+ };
+ data.submit();
+ });
+ $('.cancel').eq(-1).on('click', function () {
+ $(this).parent().parent().remove();
+ currentfiles = arrayRemove(currentfiles, $.trim(file.name));
+ });
+ });
+ },
+ start: function start(e, data) {
+ $('#progress').show();
+ },
+ drop: function drop(e, data) {
+ $(".uploader-window").stop().fadeOut("fast");
+ },
+ done: function done(e, data) {
+ $.each(data.files, function (index, file) {
+ currentfiles = arrayRemove(currentfiles, $.trim(file.name));
+ }); //---- Chargement des médias
+
+ var currentFolder = $(".currentFolder").val();
+
+ if (currentFolder == 0 || currentFolder == "") {
+ var count = $(".folder_container .root_folder .count");
+ } else {
+ var count = $(".folder_container .dd3-item[data-id='" + currentFolder + "'] > .dd3-content .count");
+ }
+
+ count.html(parseInt(count.html()) + data.files.length);
+ },
+ stop: function stop(e) {
+ $(".files").html("");
+ currentfiles = [];
+ $('#progress .progress-bar').css('width', '0%');
+ $('#progress').hide();
+ $('.upload_container').hide("fast");
+ var currentFolder = $(".currentFolder").val();
+
+ if (currentFolder == 0 || currentFolder == "") {
+ $(".root_folder").trigger("click");
+ } else {
+ $('.dd .dd-item[data-id="' + currentFolder + '"] > .dd3-content').trigger("click");
+ }
+ },
+ fail: function fail(e, data) {
+ $.each(data.files, function (index, file) {
+ $('
Erreur durant le transfert du fichier ' + file.name + '.
').appendTo('#upload_errors');
+ currentfiles = arrayRemove(currentfiles, $.trim(file.name));
+ });
+ },
+ progressall: function progressall(e, data) {
+ var progress = parseInt(data.loaded / data.total * 100, 10);
+ $('#progress .progress-bar').css('width', progress + '%');
+ }
+ }).prop('disabled', !$.support.fileInput).parent().addClass($.support.fileInput ? undefined : 'disabled');
+ }
}); // ---- Gestion des média : init des dossiers
function initFolderManager(sujetId) {
@@ -11637,9 +11798,51 @@ function loadMedia(dossier_id, sujet_id) {
}
$.each(data, function (index, item) {
- output += 'Mon image';
- output += item.libelle;
- output += '';
+ var ico = "";
+ var detail = "";
+ var itemClass = "";
+ var thumb = "";
+
+ switch (item.typemedia_id) {
+ case 1:
+ case 2:
+ ico = '';
+ detail = " - " + item.largeur + "x" + item.hauteur + "px";
+ thumb = item.source.split('.')[0] + "_thumb." + item.extension;
+ break;
+
+ case 3:
+ ico = '';
+ detail = " - durée " + item.duree;
+ itemClass = "video";
+ break;
+
+ case 4:
+ ico = '';
+ detail = " - durée " + item.duree;
+ itemClass = "son";
+ break;
+
+ case 5:
+ ico = '';
+ itemClass = "doc";
+ break;
+
+ default:
+ }
+
+ output += '';
+ output += '';
+ output += '
';
+ output += '
' + item.libelle + '
';
+ output += '
' + ico + item.extension + ' - ' + item.taille + detail + '
';
+ output += '
';
});
$('.media_container .wrapper').append(output);
@@ -11699,6 +11902,31 @@ function addItem(parent_id, item_id, content) {
$('.dd .dd-item[data-id="' + parent_id + '"] > ol').append(html);
}
+function arrayRemove(arr, value) {
+ return arr.filter(function (ele) {
+ return ele != value;
+ });
+}
+
+function FileConvertSize(aSize) {
+ aSize = Math.abs(parseInt(aSize, 10));
+ var def = [[1, 'octets'], [1024, 'ko'], [1024 * 1024, 'Mo'], [1024 * 1024 * 1024, 'Go'], [1024 * 1024 * 1024 * 1024, 'To']];
+
+ for (var i = 0; i < def.length; i++) {
+ if (aSize < def[i][0]) return (aSize / def[i - 1][0]).toFixed(2) + ' ' + def[i - 1][1];
+ }
+}
+
+function create_UUID() {
+ var dt = new Date().getTime();
+ var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
+ var r = (dt + Math.random() * 16) % 16 | 0;
+ dt = Math.floor(dt / 16);
+ return (c == 'x' ? r : r & 0x3 | 0x8).toString(16);
+ });
+ return uuid;
+}
+
/***/ }),
/***/ "./resources/sass/app.scss":
diff --git a/public/js/uploadfile/jquery.fileupload.js b/public/js/uploadfile/jquery.fileupload.js
new file mode 100644
index 0000000..700f901
--- /dev/null
+++ b/public/js/uploadfile/jquery.fileupload.js
@@ -0,0 +1,1502 @@
+/*
+ * jQuery File Upload Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, document, location, Blob, FormData */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'jquery-ui/ui/widget'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('./vendor/jquery.ui.widget')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ // Detect file input support, based on
+ // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
+ $.support.fileInput = !(new RegExp(
+ // Handle devices which give false positives for the feature detection:
+ '(Android (1\\.[0156]|2\\.[01]))' +
+ '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
+ '|(w(eb)?OSBrowser)|(webOS)' +
+ '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
+ ).test(window.navigator.userAgent) ||
+ // Feature detection for all other devices:
+ $('').prop('disabled'));
+
+ // The FileReader API is not actually used, but works as feature detection,
+ // as some Safari versions (5?) support XHR file uploads via the FormData API,
+ // but not non-multipart XHR file uploads.
+ // window.XMLHttpRequestUpload is not available on IE10, so we check for
+ // window.ProgressEvent instead to detect XHR2 file upload capability:
+ $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
+ $.support.xhrFormDataFileUpload = !!window.FormData;
+
+ // Detect support for Blob slicing (required for chunked uploads):
+ $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
+ Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
+
+ // Helper function to create drag handlers for dragover/dragenter/dragleave:
+ function getDragHandler(type) {
+ var isDragOver = type === 'dragover';
+ return function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var dataTransfer = e.dataTransfer;
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
+ this._trigger(
+ type,
+ $.Event(type, {delegatedEvent: e})
+ ) !== false) {
+ e.preventDefault();
+ if (isDragOver) {
+ dataTransfer.dropEffect = 'copy';
+ }
+ }
+ };
+ }
+
+ // The fileupload widget listens for change events on file input fields defined
+ // via fileInput setting and paste or drop events of the given dropZone.
+ // In addition to the default jQuery Widget methods, the fileupload widget
+ // exposes the "add" and "send" methods, to add or directly send files using
+ // the fileupload API.
+ // By default, files added via file input selection, paste, drag & drop or
+ // "add" method are uploaded immediately, but it is possible to override
+ // the "add" callback option to queue file uploads.
+ $.widget('blueimp.fileupload', {
+
+ options: {
+ // The drop target element(s), by the default the complete document.
+ // Set to null to disable drag & drop support:
+ dropZone: $(document),
+ // The paste target element(s), by the default undefined.
+ // Set to a DOM node or jQuery object to enable file pasting:
+ pasteZone: undefined,
+ // The file input field(s), that are listened to for change events.
+ // If undefined, it is set to the file input fields inside
+ // of the widget element on plugin initialization.
+ // Set to null to disable the change listener.
+ fileInput: undefined,
+ // By default, the file input field is replaced with a clone after
+ // each input field change event. This is required for iframe transport
+ // queues and allows change events to be fired for the same file
+ // selection, but can be disabled by setting the following option to false:
+ replaceFileInput: true,
+ // The parameter name for the file form data (the request argument name).
+ // If undefined or empty, the name property of the file input field is
+ // used, or "files[]" if the file input name property is also empty,
+ // can be a string or an array of strings:
+ paramName: undefined,
+ // By default, each file of a selection is uploaded using an individual
+ // request for XHR type uploads. Set to false to upload file
+ // selections in one request each:
+ singleFileUploads: true,
+ // To limit the number of files uploaded with one XHR request,
+ // set the following option to an integer greater than 0:
+ limitMultiFileUploads: undefined,
+ // The following option limits the number of files uploaded with one
+ // XHR request to keep the request size under or equal to the defined
+ // limit in bytes:
+ limitMultiFileUploadSize: undefined,
+ // Multipart file uploads add a number of bytes to each uploaded file,
+ // therefore the following option adds an overhead for each file used
+ // in the limitMultiFileUploadSize configuration:
+ limitMultiFileUploadSizeOverhead: 512,
+ // Set the following option to true to issue all file upload requests
+ // in a sequential order:
+ sequentialUploads: false,
+ // To limit the number of concurrent uploads,
+ // set the following option to an integer greater than 0:
+ limitConcurrentUploads: undefined,
+ // Set the following option to true to force iframe transport uploads:
+ forceIframeTransport: false,
+ // Set the following option to the location of a redirect url on the
+ // origin server, for cross-domain iframe transport uploads:
+ redirect: undefined,
+ // The parameter name for the redirect url, sent as part of the form
+ // data and set to 'redirect' if this option is empty:
+ redirectParamName: undefined,
+ // Set the following option to the location of a postMessage window,
+ // to enable postMessage transport uploads:
+ postMessage: undefined,
+ // By default, XHR file uploads are sent as multipart/form-data.
+ // The iframe transport is always using multipart/form-data.
+ // Set to false to enable non-multipart XHR uploads:
+ multipart: true,
+ // To upload large files in smaller chunks, set the following option
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
+ // or the browser does not support the required Blob API, files will
+ // be uploaded as a whole.
+ maxChunkSize: undefined,
+ // When a non-multipart upload or a chunked multipart upload has been
+ // aborted, this option can be used to resume the upload by setting
+ // it to the size of the already uploaded bytes. This option is most
+ // useful when modifying the options object inside of the "add" or
+ // "send" callbacks, as the options are cloned for each file upload.
+ uploadedBytes: undefined,
+ // By default, failed (abort or error) file uploads are removed from the
+ // global progress calculation. Set the following option to false to
+ // prevent recalculating the global progress data:
+ recalculateProgress: true,
+ // Interval in milliseconds to calculate and trigger progress events:
+ progressInterval: 100,
+ // Interval in milliseconds to calculate progress bitrate:
+ bitrateInterval: 500,
+ // By default, uploads are started automatically when adding files:
+ autoUpload: true,
+
+ // Error and info messages:
+ messages: {
+ uploadedBytes: 'Uploaded bytes exceed file size'
+ },
+
+ // Translation function, gets the message key to be translated
+ // and an object with context specific data as arguments:
+ i18n: function (message, context) {
+ message = this.messages[message] || message.toString();
+ if (context) {
+ $.each(context, function (key, value) {
+ message = message.replace('{' + key + '}', value);
+ });
+ }
+ return message;
+ },
+
+ // Additional form data to be sent along with the file uploads can be set
+ // using this option, which accepts an array of objects with name and
+ // value properties, a function returning such an array, a FormData
+ // object (for XHR file uploads), or a simple object.
+ // The form of the first fileInput is given as parameter to the function:
+ formData: function (form) {
+ return form.serializeArray();
+ },
+
+ // The add callback is invoked as soon as files are added to the fileupload
+ // widget (via file input selection, drag & drop, paste or add API call).
+ // If the singleFileUploads option is enabled, this callback will be
+ // called once for each file in the selection for XHR file uploads, else
+ // once for each file selection.
+ //
+ // The upload starts when the submit method is invoked on the data parameter.
+ // The data object contains a files property holding the added files
+ // and allows you to override plugin options as well as define ajax settings.
+ //
+ // Listeners for this callback can also be bound the following way:
+ // .bind('fileuploadadd', func);
+ //
+ // data.submit() returns a Promise object and allows to attach additional
+ // handlers using jQuery's Deferred callbacks:
+ // data.submit().done(func).fail(func).always(func);
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ if (data.autoUpload || (data.autoUpload !== false &&
+ $(this).fileupload('option', 'autoUpload'))) {
+ data.process().done(function () {
+ data.submit();
+ });
+ }
+ },
+
+ // Other callbacks:
+
+ // Callback for the submit event of each file upload:
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
+
+ // Callback for the start of each file upload request:
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
+
+ // Callback for successful uploads:
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
+
+ // Callback for failed (abort or error) uploads:
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
+
+ // Callback for completed (success, abort or error) requests:
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
+
+ // Callback for upload progress events:
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
+
+ // Callback for global upload progress events:
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
+
+ // Callback for uploads start, equivalent to the global ajaxStart event:
+ // start: function (e) {}, // .bind('fileuploadstart', func);
+
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
+
+ // Callback for change events of the fileInput(s):
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
+
+ // Callback for paste events to the pasteZone(s):
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
+
+ // Callback for drop events of the dropZone(s):
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
+
+ // Callback for dragover events of the dropZone(s):
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
+
+ // Callback before the start of each chunk upload request (before form data initialization):
+ // chunkbeforesend: function (e, data) {}, // .bind('fileuploadchunkbeforesend', func);
+
+ // Callback for the start of each chunk upload request:
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
+
+ // Callback for successful chunk uploads:
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
+
+ // Callback for failed (abort or error) chunk uploads:
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
+
+ // Callback for completed (success, abort or error) chunk upload requests:
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
+
+ // The plugin options are used as settings object for the ajax calls.
+ // The following are jQuery ajax settings required for the file uploads:
+ processData: false,
+ contentType: false,
+ cache: false,
+ timeout: 0
+ },
+
+ // A list of options that require reinitializing event listeners and/or
+ // special initialization code:
+ _specialOptions: [
+ 'fileInput',
+ 'dropZone',
+ 'pasteZone',
+ 'multipart',
+ 'forceIframeTransport'
+ ],
+
+ _blobSlice: $.support.blobSlice && function () {
+ var slice = this.slice || this.webkitSlice || this.mozSlice;
+ return slice.apply(this, arguments);
+ },
+
+ _BitrateTimer: function () {
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
+ this.loaded = 0;
+ this.bitrate = 0;
+ this.getBitrate = function (now, loaded, interval) {
+ var timeDiff = now - this.timestamp;
+ if (!this.bitrate || !interval || timeDiff > interval) {
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
+ this.loaded = loaded;
+ this.timestamp = now;
+ }
+ return this.bitrate;
+ };
+ },
+
+ _isXHRUpload: function (options) {
+ return !options.forceIframeTransport &&
+ ((!options.multipart && $.support.xhrFileUpload) ||
+ $.support.xhrFormDataFileUpload);
+ },
+
+ _getFormData: function (options) {
+ var formData;
+ if ($.type(options.formData) === 'function') {
+ return options.formData(options.form);
+ }
+ if ($.isArray(options.formData)) {
+ return options.formData;
+ }
+ if ($.type(options.formData) === 'object') {
+ formData = [];
+ $.each(options.formData, function (name, value) {
+ formData.push({name: name, value: value});
+ });
+ return formData;
+ }
+ return [];
+ },
+
+ _getTotal: function (files) {
+ var total = 0;
+ $.each(files, function (index, file) {
+ total += file.size || 1;
+ });
+ return total;
+ },
+
+ _initProgressObject: function (obj) {
+ var progress = {
+ loaded: 0,
+ total: 0,
+ bitrate: 0
+ };
+ if (obj._progress) {
+ $.extend(obj._progress, progress);
+ } else {
+ obj._progress = progress;
+ }
+ },
+
+ _initResponseObject: function (obj) {
+ var prop;
+ if (obj._response) {
+ for (prop in obj._response) {
+ if (obj._response.hasOwnProperty(prop)) {
+ delete obj._response[prop];
+ }
+ }
+ } else {
+ obj._response = {};
+ }
+ },
+
+ _onProgress: function (e, data) {
+ if (e.lengthComputable) {
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
+ loaded;
+ if (data._time && data.progressInterval &&
+ (now - data._time < data.progressInterval) &&
+ e.loaded !== e.total) {
+ return;
+ }
+ data._time = now;
+ loaded = Math.floor(
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
+ ) + (data.uploadedBytes || 0);
+ // Add the difference from the previously loaded state
+ // to the global loaded counter:
+ this._progress.loaded += (loaded - data._progress.loaded);
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
+ now,
+ this._progress.loaded,
+ data.bitrateInterval
+ );
+ data._progress.loaded = data.loaded = loaded;
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
+ now,
+ loaded,
+ data.bitrateInterval
+ );
+ // Trigger a custom progress event with a total data property set
+ // to the file size(s) of the current upload and a loaded data
+ // property calculated accordingly:
+ this._trigger(
+ 'progress',
+ $.Event('progress', {delegatedEvent: e}),
+ data
+ );
+ // Trigger a global progress event for all current file uploads,
+ // including ajax calls queued for sequential file uploads:
+ this._trigger(
+ 'progressall',
+ $.Event('progressall', {delegatedEvent: e}),
+ this._progress
+ );
+ }
+ },
+
+ _initProgressListener: function (options) {
+ var that = this,
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+ // Accesss to the native XHR object is required to add event listeners
+ // for the upload progress event:
+ if (xhr.upload) {
+ $(xhr.upload).bind('progress', function (e) {
+ var oe = e.originalEvent;
+ // Make sure the progress event properties get copied over:
+ e.lengthComputable = oe.lengthComputable;
+ e.loaded = oe.loaded;
+ e.total = oe.total;
+ that._onProgress(e, options);
+ });
+ options.xhr = function () {
+ return xhr;
+ };
+ }
+ },
+
+ _deinitProgressListener: function (options) {
+ var xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+ if (xhr.upload) {
+ $(xhr.upload).unbind('progress');
+ }
+ },
+
+ _isInstanceOf: function (type, obj) {
+ // Cross-frame instanceof check
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+ },
+
+ _initXHRData: function (options) {
+ var that = this,
+ formData,
+ file = options.files[0],
+ // Ignore non-multipart setting if not supported:
+ multipart = options.multipart || !$.support.xhrFileUpload,
+ paramName = $.type(options.paramName) === 'array' ?
+ options.paramName[0] : options.paramName;
+ options.headers = $.extend({}, options.headers);
+ if (options.contentRange) {
+ options.headers['Content-Range'] = options.contentRange;
+ }
+ if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
+ encodeURI(file.uploadName || file.name) + '"';
+ }
+ if (!multipart) {
+ options.contentType = file.type || 'application/octet-stream';
+ options.data = options.blob || file;
+ } else if ($.support.xhrFormDataFileUpload) {
+ if (options.postMessage) {
+ // window.postMessage does not allow sending FormData
+ // objects, so we just add the File/Blob objects to
+ // the formData array and let the postMessage window
+ // create the FormData object out of this array:
+ formData = this._getFormData(options);
+ if (options.blob) {
+ formData.push({
+ name: paramName,
+ value: options.blob
+ });
+ } else {
+ $.each(options.files, function (index, file) {
+ formData.push({
+ name: ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) || paramName,
+ value: file
+ });
+ });
+ }
+ } else {
+ if (that._isInstanceOf('FormData', options.formData)) {
+ formData = options.formData;
+ } else {
+ formData = new FormData();
+ $.each(this._getFormData(options), function (index, field) {
+ formData.append(field.name, field.value);
+ });
+ }
+ if (options.blob) {
+ formData.append(
+ paramName,
+ options.blob,
+ file.uploadName || file.name
+ );
+ } else {
+ $.each(options.files, function (index, file) {
+ // This check allows the tests to run with
+ // dummy objects:
+ if (that._isInstanceOf('File', file) ||
+ that._isInstanceOf('Blob', file)) {
+ formData.append(
+ ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) || paramName,
+ file,
+ file.uploadName || file.name
+ );
+ }
+ });
+ }
+ }
+ options.data = formData;
+ }
+ // Blob reference is not needed anymore, free memory:
+ options.blob = null;
+ },
+
+ _initIframeSettings: function (options) {
+ var targetHost = $('').prop('href', options.url).prop('host');
+ // Setting the dataType to iframe enables the iframe transport:
+ options.dataType = 'iframe ' + (options.dataType || '');
+ // The iframe transport accepts a serialized array as form data:
+ options.formData = this._getFormData(options);
+ // Add redirect url to form data on cross-domain uploads:
+ if (options.redirect && targetHost && targetHost !== location.host) {
+ options.formData.push({
+ name: options.redirectParamName || 'redirect',
+ value: options.redirect
+ });
+ }
+ },
+
+ _initDataSettings: function (options) {
+ if (this._isXHRUpload(options)) {
+ if (!this._chunkedUpload(options, true)) {
+ if (!options.data) {
+ this._initXHRData(options);
+ }
+ this._initProgressListener(options);
+ }
+ if (options.postMessage) {
+ // Setting the dataType to postmessage enables the
+ // postMessage transport:
+ options.dataType = 'postmessage ' + (options.dataType || '');
+ }
+ } else {
+ this._initIframeSettings(options);
+ }
+ },
+
+ _getParamName: function (options) {
+ var fileInput = $(options.fileInput),
+ paramName = options.paramName;
+ if (!paramName) {
+ paramName = [];
+ fileInput.each(function () {
+ var input = $(this),
+ name = input.prop('name') || 'files[]',
+ i = (input.prop('files') || [1]).length;
+ while (i) {
+ paramName.push(name);
+ i -= 1;
+ }
+ });
+ if (!paramName.length) {
+ paramName = [fileInput.prop('name') || 'files[]'];
+ }
+ } else if (!$.isArray(paramName)) {
+ paramName = [paramName];
+ }
+ return paramName;
+ },
+
+ _initFormSettings: function (options) {
+ // Retrieve missing options from the input field and the
+ // associated form, if available:
+ if (!options.form || !options.form.length) {
+ options.form = $(options.fileInput.prop('form'));
+ // If the given file input doesn't have an associated form,
+ // use the default widget file input's form:
+ if (!options.form.length) {
+ options.form = $(this.options.fileInput.prop('form'));
+ }
+ }
+ options.paramName = this._getParamName(options);
+ if (!options.url) {
+ options.url = options.form.prop('action') || location.href;
+ }
+ // The HTTP request method must be "POST" or "PUT":
+ options.type = (options.type ||
+ ($.type(options.form.prop('method')) === 'string' &&
+ options.form.prop('method')) || ''
+ ).toUpperCase();
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
+ options.type !== 'PATCH') {
+ options.type = 'POST';
+ }
+ if (!options.formAcceptCharset) {
+ options.formAcceptCharset = options.form.attr('accept-charset');
+ }
+ },
+
+ _getAJAXSettings: function (data) {
+ var options = $.extend({}, this.options, data);
+ this._initFormSettings(options);
+ this._initDataSettings(options);
+ return options;
+ },
+
+ // jQuery 1.6 doesn't provide .state(),
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
+ _getDeferredState: function (deferred) {
+ if (deferred.state) {
+ return deferred.state();
+ }
+ if (deferred.isResolved()) {
+ return 'resolved';
+ }
+ if (deferred.isRejected()) {
+ return 'rejected';
+ }
+ return 'pending';
+ },
+
+ // Maps jqXHR callbacks to the equivalent
+ // methods of the given Promise object:
+ _enhancePromise: function (promise) {
+ promise.success = promise.done;
+ promise.error = promise.fail;
+ promise.complete = promise.always;
+ return promise;
+ },
+
+ // Creates and returns a Promise object enhanced with
+ // the jqXHR methods abort, success, error and complete:
+ _getXHRPromise: function (resolveOrReject, context, args) {
+ var dfd = $.Deferred(),
+ promise = dfd.promise();
+ context = context || this.options.context || promise;
+ if (resolveOrReject === true) {
+ dfd.resolveWith(context, args);
+ } else if (resolveOrReject === false) {
+ dfd.rejectWith(context, args);
+ }
+ promise.abort = dfd.promise;
+ return this._enhancePromise(promise);
+ },
+
+ // Adds convenience methods to the data callback argument:
+ _addConvenienceMethods: function (e, data) {
+ var that = this,
+ getPromise = function (args) {
+ return $.Deferred().resolveWith(that, args).promise();
+ };
+ data.process = function (resolveFunc, rejectFunc) {
+ if (resolveFunc || rejectFunc) {
+ data._processQueue = this._processQueue =
+ (this._processQueue || getPromise([this])).then(
+ function () {
+ if (data.errorThrown) {
+ return $.Deferred()
+ .rejectWith(that, [data]).promise();
+ }
+ return getPromise(arguments);
+ }
+ ).then(resolveFunc, rejectFunc);
+ }
+ return this._processQueue || getPromise([this]);
+ };
+ data.submit = function () {
+ if (this.state() !== 'pending') {
+ data.jqXHR = this.jqXHR =
+ (that._trigger(
+ 'submit',
+ $.Event('submit', {delegatedEvent: e}),
+ this
+ ) !== false) && that._onSend(e, this);
+ }
+ return this.jqXHR || that._getXHRPromise();
+ };
+ data.abort = function () {
+ if (this.jqXHR) {
+ return this.jqXHR.abort();
+ }
+ this.errorThrown = 'abort';
+ that._trigger('fail', null, this);
+ return that._getXHRPromise(false);
+ };
+ data.state = function () {
+ if (this.jqXHR) {
+ return that._getDeferredState(this.jqXHR);
+ }
+ if (this._processQueue) {
+ return that._getDeferredState(this._processQueue);
+ }
+ };
+ data.processing = function () {
+ return !this.jqXHR && this._processQueue && that
+ ._getDeferredState(this._processQueue) === 'pending';
+ };
+ data.progress = function () {
+ return this._progress;
+ };
+ data.response = function () {
+ return this._response;
+ };
+ },
+
+ // Parses the Range header from the server response
+ // and returns the uploaded bytes:
+ _getUploadedBytes: function (jqXHR) {
+ var range = jqXHR.getResponseHeader('Range'),
+ parts = range && range.split('-'),
+ upperBytesPos = parts && parts.length > 1 &&
+ parseInt(parts[1], 10);
+ return upperBytesPos && upperBytesPos + 1;
+ },
+
+ // Uploads a file in multiple, sequential requests
+ // by splitting the file up in multiple blob chunks.
+ // If the second parameter is true, only tests if the file
+ // should be uploaded in chunks, but does not invoke any
+ // upload requests:
+ _chunkedUpload: function (options, testOnly) {
+ options.uploadedBytes = options.uploadedBytes || 0;
+ var that = this,
+ file = options.files[0],
+ fs = file.size,
+ ub = options.uploadedBytes,
+ mcs = options.maxChunkSize || fs,
+ slice = this._blobSlice,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ upload;
+ if (!(this._isXHRUpload(options) && slice && (ub || ($.type(mcs) === 'function' ? mcs(options) : mcs) < fs)) ||
+ options.data) {
+ return false;
+ }
+ if (testOnly) {
+ return true;
+ }
+ if (ub >= fs) {
+ file.error = options.i18n('uploadedBytes');
+ return this._getXHRPromise(
+ false,
+ options.context,
+ [null, 'error', file.error]
+ );
+ }
+ // The chunk upload method:
+ upload = function () {
+ // Clone the options object for each chunk upload:
+ var o = $.extend({}, options),
+ currentLoaded = o._progress.loaded;
+ o.blob = slice.call(
+ file,
+ ub,
+ ub + ($.type(mcs) === 'function' ? mcs(o) : mcs),
+ file.type
+ );
+ // Store the current chunk size, as the blob itself
+ // will be dereferenced after data processing:
+ o.chunkSize = o.blob.size;
+ // Expose the chunk bytes position range:
+ o.contentRange = 'bytes ' + ub + '-' +
+ (ub + o.chunkSize - 1) + '/' + fs;
+ // Trigger chunkbeforesend to allow form data to be updated for this chunk
+ that._trigger('chunkbeforesend', null, o);
+ // Process the upload data (the blob and potential form data):
+ that._initXHRData(o);
+ // Add progress listeners for this chunk upload:
+ that._initProgressListener(o);
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
+ that._getXHRPromise(false, o.context))
+ .done(function (result, textStatus, jqXHR) {
+ ub = that._getUploadedBytes(jqXHR) ||
+ (ub + o.chunkSize);
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered
+ // for this chunk:
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
+ that._onProgress($.Event('progress', {
+ lengthComputable: true,
+ loaded: ub - o.uploadedBytes,
+ total: ub - o.uploadedBytes
+ }), o);
+ }
+ options.uploadedBytes = o.uploadedBytes = ub;
+ o.result = result;
+ o.textStatus = textStatus;
+ o.jqXHR = jqXHR;
+ that._trigger('chunkdone', null, o);
+ that._trigger('chunkalways', null, o);
+ if (ub < fs) {
+ // File upload not yet complete,
+ // continue with the next chunk:
+ upload();
+ } else {
+ dfd.resolveWith(
+ o.context,
+ [result, textStatus, jqXHR]
+ );
+ }
+ })
+ .fail(function (jqXHR, textStatus, errorThrown) {
+ o.jqXHR = jqXHR;
+ o.textStatus = textStatus;
+ o.errorThrown = errorThrown;
+ that._trigger('chunkfail', null, o);
+ that._trigger('chunkalways', null, o);
+ dfd.rejectWith(
+ o.context,
+ [jqXHR, textStatus, errorThrown]
+ );
+ })
+ .always(function () {
+ that._deinitProgressListener(o);
+ });
+ };
+ this._enhancePromise(promise);
+ promise.abort = function () {
+ return jqXHR.abort();
+ };
+ upload();
+ return promise;
+ },
+
+ _beforeSend: function (e, data) {
+ if (this._active === 0) {
+ // the start callback is triggered when an upload starts
+ // and no other uploads are currently running,
+ // equivalent to the global ajaxStart event:
+ this._trigger('start');
+ // Set timer for global bitrate progress calculation:
+ this._bitrateTimer = new this._BitrateTimer();
+ // Reset the global progress values:
+ this._progress.loaded = this._progress.total = 0;
+ this._progress.bitrate = 0;
+ }
+ // Make sure the container objects for the .response() and
+ // .progress() methods on the data object are available
+ // and reset to their initial state:
+ this._initResponseObject(data);
+ this._initProgressObject(data);
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
+ data._progress.bitrate = data.bitrate = 0;
+ this._active += 1;
+ // Initialize the global progress values:
+ this._progress.loaded += data.loaded;
+ this._progress.total += data.total;
+ },
+
+ _onDone: function (result, textStatus, jqXHR, options) {
+ var total = options._progress.total,
+ response = options._response;
+ if (options._progress.loaded < total) {
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered:
+ this._onProgress($.Event('progress', {
+ lengthComputable: true,
+ loaded: total,
+ total: total
+ }), options);
+ }
+ response.result = options.result = result;
+ response.textStatus = options.textStatus = textStatus;
+ response.jqXHR = options.jqXHR = jqXHR;
+ this._trigger('done', null, options);
+ },
+
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
+ var response = options._response;
+ if (options.recalculateProgress) {
+ // Remove the failed (error or abort) file upload from
+ // the global progress calculation:
+ this._progress.loaded -= options._progress.loaded;
+ this._progress.total -= options._progress.total;
+ }
+ response.jqXHR = options.jqXHR = jqXHR;
+ response.textStatus = options.textStatus = textStatus;
+ response.errorThrown = options.errorThrown = errorThrown;
+ this._trigger('fail', null, options);
+ },
+
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
+ // options object via done and fail callbacks
+ this._trigger('always', null, options);
+ },
+
+ _onSend: function (e, data) {
+ if (!data.submit) {
+ this._addConvenienceMethods(e, data);
+ }
+ var that = this,
+ jqXHR,
+ aborted,
+ slot,
+ pipe,
+ options = that._getAJAXSettings(data),
+ send = function () {
+ that._sending += 1;
+ // Set timer for bitrate progress calculation:
+ options._bitrateTimer = new that._BitrateTimer();
+ jqXHR = jqXHR || (
+ ((aborted || that._trigger(
+ 'send',
+ $.Event('send', {delegatedEvent: e}),
+ options
+ ) === false) &&
+ that._getXHRPromise(false, options.context, aborted)) ||
+ that._chunkedUpload(options) || $.ajax(options)
+ ).done(function (result, textStatus, jqXHR) {
+ that._onDone(result, textStatus, jqXHR, options);
+ }).fail(function (jqXHR, textStatus, errorThrown) {
+ that._onFail(jqXHR, textStatus, errorThrown, options);
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
+ that._deinitProgressListener(options);
+ that._onAlways(
+ jqXHRorResult,
+ textStatus,
+ jqXHRorError,
+ options
+ );
+ that._sending -= 1;
+ that._active -= 1;
+ if (options.limitConcurrentUploads &&
+ options.limitConcurrentUploads > that._sending) {
+ // Start the next queued upload,
+ // that has not been aborted:
+ var nextSlot = that._slots.shift();
+ while (nextSlot) {
+ if (that._getDeferredState(nextSlot) === 'pending') {
+ nextSlot.resolve();
+ break;
+ }
+ nextSlot = that._slots.shift();
+ }
+ }
+ if (that._active === 0) {
+ // The stop callback is triggered when all uploads have
+ // been completed, equivalent to the global ajaxStop event:
+ that._trigger('stop');
+ }
+ });
+ return jqXHR;
+ };
+ this._beforeSend(e, options);
+ if (this.options.sequentialUploads ||
+ (this.options.limitConcurrentUploads &&
+ this.options.limitConcurrentUploads <= this._sending)) {
+ if (this.options.limitConcurrentUploads > 1) {
+ slot = $.Deferred();
+ this._slots.push(slot);
+ pipe = slot.then(send);
+ } else {
+ this._sequence = this._sequence.then(send, send);
+ pipe = this._sequence;
+ }
+ // Return the piped Promise object, enhanced with an abort method,
+ // which is delegated to the jqXHR object of the current upload,
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
+ pipe.abort = function () {
+ aborted = [undefined, 'abort', 'abort'];
+ if (!jqXHR) {
+ if (slot) {
+ slot.rejectWith(options.context, aborted);
+ }
+ return send();
+ }
+ return jqXHR.abort();
+ };
+ return this._enhancePromise(pipe);
+ }
+ return send();
+ },
+
+ _onAdd: function (e, data) {
+ var that = this,
+ result = true,
+ options = $.extend({}, this.options, data),
+ files = data.files,
+ filesLength = files.length,
+ limit = options.limitMultiFileUploads,
+ limitSize = options.limitMultiFileUploadSize,
+ overhead = options.limitMultiFileUploadSizeOverhead,
+ batchSize = 0,
+ paramName = this._getParamName(options),
+ paramNameSet,
+ paramNameSlice,
+ fileSet,
+ i,
+ j = 0;
+ if (!filesLength) {
+ return false;
+ }
+ if (limitSize && files[0].size === undefined) {
+ limitSize = undefined;
+ }
+ if (!(options.singleFileUploads || limit || limitSize) ||
+ !this._isXHRUpload(options)) {
+ fileSet = [files];
+ paramNameSet = [paramName];
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i += limit) {
+ fileSet.push(files.slice(i, i + limit));
+ paramNameSlice = paramName.slice(i, i + limit);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ }
+ } else if (!options.singleFileUploads && limitSize) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i = i + 1) {
+ batchSize += files[i].size + overhead;
+ if (i + 1 === filesLength ||
+ ((batchSize + files[i + 1].size + overhead) > limitSize) ||
+ (limit && i + 1 - j >= limit)) {
+ fileSet.push(files.slice(j, i + 1));
+ paramNameSlice = paramName.slice(j, i + 1);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ j = i + 1;
+ batchSize = 0;
+ }
+ }
+ } else {
+ paramNameSet = paramName;
+ }
+ data.originalFiles = files;
+ $.each(fileSet || files, function (index, element) {
+ var newData = $.extend({}, data);
+ newData.files = fileSet ? element : [element];
+ newData.paramName = paramNameSet[index];
+ that._initResponseObject(newData);
+ that._initProgressObject(newData);
+ that._addConvenienceMethods(e, newData);
+ result = that._trigger(
+ 'add',
+ $.Event('add', {delegatedEvent: e}),
+ newData
+ );
+ return result;
+ });
+ return result;
+ },
+
+ _replaceFileInput: function (data) {
+ var input = data.fileInput,
+ inputClone = input.clone(true),
+ restoreFocus = input.is(document.activeElement);
+ // Add a reference for the new cloned file input to the data argument:
+ data.fileInputClone = inputClone;
+ $('').append(inputClone)[0].reset();
+ // Detaching allows to insert the fileInput on another form
+ // without loosing the file input value:
+ input.after(inputClone).detach();
+ // If the fileInput had focus before it was detached,
+ // restore focus to the inputClone.
+ if (restoreFocus) {
+ inputClone.focus();
+ }
+ // Avoid memory leaks with the detached file input:
+ $.cleanData(input.unbind('remove'));
+ // Replace the original file input element in the fileInput
+ // elements set with the clone, which has been copied including
+ // event handlers:
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
+ if (el === input[0]) {
+ return inputClone[0];
+ }
+ return el;
+ });
+ // If the widget has been initialized on the file input itself,
+ // override this.element with the file input clone:
+ if (input[0] === this.element[0]) {
+ this.element = inputClone;
+ }
+ },
+
+ _handleFileTreeEntry: function (entry, path) {
+ var that = this,
+ dfd = $.Deferred(),
+ entries = [],
+ dirReader,
+ errorHandler = function (e) {
+ if (e && !e.entry) {
+ e.entry = entry;
+ }
+ // Since $.when returns immediately if one
+ // Deferred is rejected, we use resolve instead.
+ // This allows valid files and invalid items
+ // to be returned together in one set:
+ dfd.resolve([e]);
+ },
+ successHandler = function (entries) {
+ that._handleFileTreeEntries(
+ entries,
+ path + entry.name + '/'
+ ).done(function (files) {
+ dfd.resolve(files);
+ }).fail(errorHandler);
+ },
+ readEntries = function () {
+ dirReader.readEntries(function (results) {
+ if (!results.length) {
+ successHandler(entries);
+ } else {
+ entries = entries.concat(results);
+ readEntries();
+ }
+ }, errorHandler);
+ };
+ path = path || '';
+ if (entry.isFile) {
+ if (entry._file) {
+ // Workaround for Chrome bug #149735
+ entry._file.relativePath = path;
+ dfd.resolve(entry._file);
+ } else {
+ entry.file(function (file) {
+ file.relativePath = path;
+ dfd.resolve(file);
+ }, errorHandler);
+ }
+ } else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ readEntries();
+ } else {
+ // Return an empty list for file system items
+ // other than files or directories:
+ dfd.resolve([]);
+ }
+ return dfd.promise();
+ },
+
+ _handleFileTreeEntries: function (entries, path) {
+ var that = this;
+ return $.when.apply(
+ $,
+ $.map(entries, function (entry) {
+ return that._handleFileTreeEntry(entry, path);
+ })
+ ).then(function () {
+ return Array.prototype.concat.apply(
+ [],
+ arguments
+ );
+ });
+ },
+
+ _getDroppedFiles: function (dataTransfer) {
+ dataTransfer = dataTransfer || {};
+ var items = dataTransfer.items;
+ if (items && items.length && (items[0].webkitGetAsEntry ||
+ items[0].getAsEntry)) {
+ return this._handleFileTreeEntries(
+ $.map(items, function (item) {
+ var entry;
+ if (item.webkitGetAsEntry) {
+ entry = item.webkitGetAsEntry();
+ if (entry) {
+ // Workaround for Chrome bug #149735:
+ entry._file = item.getAsFile();
+ }
+ return entry;
+ }
+ return item.getAsEntry();
+ })
+ );
+ }
+ return $.Deferred().resolve(
+ $.makeArray(dataTransfer.files)
+ ).promise();
+ },
+
+ _getSingleFileInputFiles: function (fileInput) {
+ fileInput = $(fileInput);
+ var entries = fileInput.prop('webkitEntries') ||
+ fileInput.prop('entries'),
+ files,
+ value;
+ if (entries && entries.length) {
+ return this._handleFileTreeEntries(entries);
+ }
+ files = $.makeArray(fileInput.prop('files'));
+ if (!files.length) {
+ value = fileInput.prop('value');
+ if (!value) {
+ return $.Deferred().resolve([]).promise();
+ }
+ // If the files property is not available, the browser does not
+ // support the File API and we add a pseudo File object with
+ // the input value as name with path information removed:
+ files = [{name: value.replace(/^.*\\/, '')}];
+ } else if (files[0].name === undefined && files[0].fileName) {
+ // File normalization for Safari 4 and Firefox 3:
+ $.each(files, function (index, file) {
+ file.name = file.fileName;
+ file.size = file.fileSize;
+ });
+ }
+ return $.Deferred().resolve(files).promise();
+ },
+
+ _getFileInputFiles: function (fileInput) {
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
+ return this._getSingleFileInputFiles(fileInput);
+ }
+ return $.when.apply(
+ $,
+ $.map(fileInput, this._getSingleFileInputFiles)
+ ).then(function () {
+ return Array.prototype.concat.apply(
+ [],
+ arguments
+ );
+ });
+ },
+
+ _onChange: function (e) {
+ var that = this,
+ data = {
+ fileInput: $(e.target),
+ form: $(e.target.form)
+ };
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ if (that.options.replaceFileInput) {
+ that._replaceFileInput(data);
+ }
+ if (that._trigger(
+ 'change',
+ $.Event('change', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ that._onAdd(e, data);
+ }
+ });
+ },
+
+ _onPaste: function (e) {
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
+ e.originalEvent.clipboardData.items,
+ data = {files: []};
+ if (items && items.length) {
+ $.each(items, function (index, item) {
+ var file = item.getAsFile && item.getAsFile();
+ if (file) {
+ data.files.push(file);
+ }
+ });
+ if (this._trigger(
+ 'paste',
+ $.Event('paste', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ this._onAdd(e, data);
+ }
+ }
+ },
+
+ _onDrop: function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var that = this,
+ dataTransfer = e.dataTransfer,
+ data = {};
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
+ e.preventDefault();
+ this._getDroppedFiles(dataTransfer).always(function (files) {
+ data.files = files;
+ if (that._trigger(
+ 'drop',
+ $.Event('drop', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ that._onAdd(e, data);
+ }
+ });
+ }
+ },
+
+ _onDragOver: getDragHandler('dragover'),
+
+ _onDragEnter: getDragHandler('dragenter'),
+
+ _onDragLeave: getDragHandler('dragleave'),
+
+ _initEventHandlers: function () {
+ if (this._isXHRUpload(this.options)) {
+ this._on(this.options.dropZone, {
+ dragover: this._onDragOver,
+ drop: this._onDrop,
+ // event.preventDefault() on dragenter is required for IE10+:
+ dragenter: this._onDragEnter,
+ // dragleave is not required, but added for completeness:
+ dragleave: this._onDragLeave
+ });
+ this._on(this.options.pasteZone, {
+ paste: this._onPaste
+ });
+ }
+ if ($.support.fileInput) {
+ this._on(this.options.fileInput, {
+ change: this._onChange
+ });
+ }
+ },
+
+ _destroyEventHandlers: function () {
+ this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
+ this._off(this.options.pasteZone, 'paste');
+ this._off(this.options.fileInput, 'change');
+ },
+
+ _destroy: function () {
+ this._destroyEventHandlers();
+ },
+
+ _setOption: function (key, value) {
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
+ if (reinit) {
+ this._destroyEventHandlers();
+ }
+ this._super(key, value);
+ if (reinit) {
+ this._initSpecialOptions();
+ this._initEventHandlers();
+ }
+ },
+
+ _initSpecialOptions: function () {
+ var options = this.options;
+ if (options.fileInput === undefined) {
+ options.fileInput = this.element.is('input[type="file"]') ?
+ this.element : this.element.find('input[type="file"]');
+ } else if (!(options.fileInput instanceof $)) {
+ options.fileInput = $(options.fileInput);
+ }
+ if (!(options.dropZone instanceof $)) {
+ options.dropZone = $(options.dropZone);
+ }
+ if (!(options.pasteZone instanceof $)) {
+ options.pasteZone = $(options.pasteZone);
+ }
+ },
+
+ _getRegExp: function (str) {
+ var parts = str.split('/'),
+ modifiers = parts.pop();
+ parts.shift();
+ return new RegExp(parts.join('/'), modifiers);
+ },
+
+ _isRegExpOption: function (key, value) {
+ return key !== 'url' && $.type(value) === 'string' &&
+ /^\/.*\/[igm]{0,3}$/.test(value);
+ },
+
+ _initDataAttributes: function () {
+ var that = this,
+ options = this.options,
+ data = this.element.data();
+ // Initialize options set via HTML5 data-attributes:
+ $.each(
+ this.element[0].attributes,
+ function (index, attr) {
+ var key = attr.name.toLowerCase(),
+ value;
+ if (/^data-/.test(key)) {
+ // Convert hyphen-ated key to camelCase:
+ key = key.slice(5).replace(/-[a-z]/g, function (str) {
+ return str.charAt(1).toUpperCase();
+ });
+ value = data[key];
+ if (that._isRegExpOption(key, value)) {
+ value = that._getRegExp(value);
+ }
+ options[key] = value;
+ }
+ }
+ );
+ },
+
+ _create: function () {
+ this._initDataAttributes();
+ this._initSpecialOptions();
+ this._slots = [];
+ this._sequence = this._getXHRPromise(true);
+ this._sending = this._active = 0;
+ this._initProgressObject(this);
+ this._initEventHandlers();
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the number of active uploads:
+ active: function () {
+ return this._active;
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the widget upload progress.
+ // It returns an object with loaded, total and bitrate properties
+ // for the running uploads:
+ progress: function () {
+ return this._progress;
+ },
+
+ // This method is exposed to the widget API and allows adding files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files property and can contain additional options:
+ // .fileupload('add', {files: filesList});
+ add: function (data) {
+ var that = this;
+ if (!data || this.options.disabled) {
+ return;
+ }
+ if (data.fileInput && !data.files) {
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ that._onAdd(null, data);
+ });
+ } else {
+ data.files = $.makeArray(data.files);
+ this._onAdd(null, data);
+ }
+ },
+
+ // This method is exposed to the widget API and allows sending files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files or fileInput property and can contain additional options:
+ // .fileupload('send', {files: filesList});
+ // The method returns a Promise object for the file upload call.
+ send: function (data) {
+ if (data && !this.options.disabled) {
+ if (data.fileInput && !data.files) {
+ var that = this,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ aborted;
+ promise.abort = function () {
+ aborted = true;
+ if (jqXHR) {
+ return jqXHR.abort();
+ }
+ dfd.reject(null, 'abort', 'abort');
+ return promise;
+ };
+ this._getFileInputFiles(data.fileInput).always(
+ function (files) {
+ if (aborted) {
+ return;
+ }
+ if (!files.length) {
+ dfd.reject();
+ return;
+ }
+ data.files = files;
+ jqXHR = that._onSend(null, data);
+ jqXHR.then(
+ function (result, textStatus, jqXHR) {
+ dfd.resolve(result, textStatus, jqXHR);
+ },
+ function (jqXHR, textStatus, errorThrown) {
+ dfd.reject(jqXHR, textStatus, errorThrown);
+ }
+ );
+ }
+ );
+ return this._enhancePromise(promise);
+ }
+ data.files = $.makeArray(data.files);
+ if (data.files.length) {
+ return this._onSend(null, data);
+ }
+ }
+ return this._getXHRPromise(false, data && data.context);
+ }
+
+ });
+
+}));
diff --git a/public/js/uploadfile/jquery.iframe-transport.js b/public/js/uploadfile/jquery.iframe-transport.js
new file mode 100644
index 0000000..8d25c46
--- /dev/null
+++ b/public/js/uploadfile/jquery.iframe-transport.js
@@ -0,0 +1,224 @@
+/*
+ * jQuery Iframe Transport Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ */
+
+/* global define, require, window, document, JSON */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define(['jquery'], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(require('jquery'));
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ // Helper variable to create unique names for the transport iframes:
+ var counter = 0,
+ jsonAPI = $,
+ jsonParse = 'parseJSON';
+
+ if ('JSON' in window && 'parse' in JSON) {
+ jsonAPI = JSON;
+ jsonParse = 'parse';
+ }
+
+ // The iframe transport accepts four 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}]
+ // options.initialIframeSrc: the URL of the initial iframe src,
+ // by default set to "javascript:false;"
+ $.ajaxTransport('iframe', function (options) {
+ if (options.async) {
+ // javascript:false as initial iframe src
+ // prevents warning popups on HTTPS in IE6:
+ /*jshint scripturl: true */
+ var initialIframeSrc = options.initialIframeSrc || 'javascript:false;',
+ /*jshint scripturl: false */
+ form,
+ iframe,
+ addParamChar;
+ return {
+ send: function (_, completeCallback) {
+ 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';
+ }
+ // 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 = $(
+ ''
+ ).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):
+ $('')
+ .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) {
+ $('')
+ .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');
+ // Remove the HTML5 form attribute from the input(s):
+ options.fileInput.removeAttr('form');
+ }
+ 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]);
+ // Restore the original name and form properties:
+ $(input)
+ .prop('name', clone.prop('name'))
+ .attr('form', clone.attr('form'));
+ 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', initialIframeSrc);
+ }
+ 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 && jsonAPI[jsonParse]($(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());
+ }
+ }
+ });
+
+}));
diff --git a/public/js/uploadfile/vendor/jquery.ui.widget.js b/public/js/uploadfile/vendor/jquery.ui.widget.js
new file mode 100644
index 0000000..914b8ff
--- /dev/null
+++ b/public/js/uploadfile/vendor/jquery.ui.widget.js
@@ -0,0 +1,752 @@
+/*! jQuery UI - v1.12.1+CommonJS - 2018-02-10
+ * http://jqueryui.com
+ * Includes: widget.js
+ * Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+ if ( typeof define === "function" && define.amd ) {
+
+ // AMD. Register as an anonymous module.
+ define([ "jquery" ], factory );
+ } else if ( typeof exports === "object" ) {
+
+ // Node/CommonJS
+ factory( require( "jquery" ) );
+ } else {
+
+ // Browser globals
+ factory( jQuery );
+ }
+}(function( $ ) {
+
+ $.ui = $.ui || {};
+
+ var version = $.ui.version = "1.12.1";
+
+
+ /*!
+ * jQuery UI Widget 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+ //>>label: Widget
+ //>>group: Core
+ //>>description: Provides a factory for creating stateful widgets with a common API.
+ //>>docs: http://api.jqueryui.com/jQuery.widget/
+ //>>demos: http://jqueryui.com/widget/
+
+
+
+ var widgetUuid = 0;
+ var widgetSlice = Array.prototype.slice;
+
+ $.cleanData = ( function( orig ) {
+ return function( elems ) {
+ var events, elem, i;
+ for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
+ try {
+
+ // Only trigger remove when necessary to save time
+ events = $._data( elem, "events" );
+ if ( events && events.remove ) {
+ $( elem ).triggerHandler( "remove" );
+ }
+
+ // Http://bugs.jquery.com/ticket/8235
+ } catch ( e ) {}
+ }
+ orig( elems );
+ };
+ } )( $.cleanData );
+
+ $.widget = function( name, base, prototype ) {
+ var existingConstructor, constructor, basePrototype;
+
+ // ProxiedPrototype allows the provided prototype to remain unmodified
+ // so that it can be used as a mixin for multiple widgets (#8876)
+ var proxiedPrototype = {};
+
+ var namespace = name.split( "." )[ 0 ];
+ name = name.split( "." )[ 1 ];
+ var fullName = namespace + "-" + name;
+
+ if ( !prototype ) {
+ prototype = base;
+ base = $.Widget;
+ }
+
+ if ( $.isArray( prototype ) ) {
+ prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
+ }
+
+ // 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() {
+ function _super() {
+ return base.prototype[ prop ].apply( this, arguments );
+ }
+
+ function _superApply( args ) {
+ return base.prototype[ prop ].apply( this, args );
+ }
+
+ return function() {
+ var __super = this._super;
+ var __superApply = this._superApply;
+ var 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 ) : 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 );
+
+ return constructor;
+ };
+
+ $.widget.extend = function( target ) {
+ var input = widgetSlice.call( arguments, 1 );
+ var inputIndex = 0;
+ var inputLength = input.length;
+ var key;
+ var 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";
+ var args = widgetSlice.call( arguments, 1 );
+ var returnValue = this;
+
+ if ( isMethodCall ) {
+
+ // If this is an empty collection, we need to have the instance method
+ // return undefined instead of the jQuery instance
+ if ( !this.length && options === "instance" ) {
+ returnValue = undefined;
+ } else {
+ this.each( function() {
+ var methodValue;
+ var instance = $.data( this, fullName );
+
+ if ( options === "instance" ) {
+ returnValue = instance;
+ return false;
+ }
+
+ 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 {
+
+ // Allow multiple hashes to be passed on init
+ if ( args.length ) {
+ options = $.widget.extend.apply( null, [ options ].concat( args ) );
+ }
+
+ this.each( function() {
+ var instance = $.data( this, fullName );
+ if ( instance ) {
+ instance.option( options || {} );
+ if ( instance._init ) {
+ instance._init();
+ }
+ } else {
+ $.data( this, fullName, new object( options, this ) );
+ }
+ } );
+ }
+
+ return returnValue;
+ };
+ };
+
+ $.Widget = function( /* options, element */ ) {};
+ $.Widget._childConstructors = [];
+
+ $.Widget.prototype = {
+ widgetName: "widget",
+ widgetEventPrefix: "",
+ defaultElement: "",
+
+ options: {
+ classes: {},
+ disabled: false,
+
+ // Callbacks
+ create: null
+ },
+
+ _createWidget: function( options, element ) {
+ element = $( element || this.defaultElement || this )[ 0 ];
+ this.element = $( element );
+ this.uuid = widgetUuid++;
+ this.eventNamespace = "." + this.widgetName + this.uuid;
+
+ this.bindings = $();
+ this.hoverable = $();
+ this.focusable = $();
+ this.classesElementLookup = {};
+
+ 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.options = $.widget.extend( {},
+ this.options,
+ this._getCreateOptions(),
+ options );
+
+ this._create();
+
+ if ( this.options.disabled ) {
+ this._setOptionDisabled( this.options.disabled );
+ }
+
+ this._trigger( "create", null, this._getCreateEventData() );
+ this._init();
+ },
+
+ _getCreateOptions: function() {
+ return {};
+ },
+
+ _getCreateEventData: $.noop,
+
+ _create: $.noop,
+
+ _init: $.noop,
+
+ destroy: function() {
+ var that = this;
+
+ this._destroy();
+ $.each( this.classesElementLookup, function( key, value ) {
+ that._removeClass( value, key );
+ } );
+
+ // We can probably remove the unbind calls in 2.0
+ // all event bindings should go through this._on()
+ this.element
+ .off( this.eventNamespace )
+ .removeData( this.widgetFullName );
+ this.widget()
+ .off( this.eventNamespace )
+ .removeAttr( "aria-disabled" );
+
+ // Clean up events and states
+ this.bindings.off( this.eventNamespace );
+ },
+
+ _destroy: $.noop,
+
+ widget: function() {
+ return this.element;
+ },
+
+ option: function( key, value ) {
+ var options = key;
+ var parts;
+ var curOption;
+ var 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 ( arguments.length === 1 ) {
+ return curOption[ key ] === undefined ? null : curOption[ key ];
+ }
+ curOption[ key ] = value;
+ } else {
+ if ( arguments.length === 1 ) {
+ 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 ) {
+ if ( key === "classes" ) {
+ this._setOptionClasses( value );
+ }
+
+ this.options[ key ] = value;
+
+ if ( key === "disabled" ) {
+ this._setOptionDisabled( value );
+ }
+
+ return this;
+ },
+
+ _setOptionClasses: function( value ) {
+ var classKey, elements, currentElements;
+
+ for ( classKey in value ) {
+ currentElements = this.classesElementLookup[ classKey ];
+ if ( value[ classKey ] === this.options.classes[ classKey ] ||
+ !currentElements ||
+ !currentElements.length ) {
+ continue;
+ }
+
+ // We are doing this to create a new jQuery object because the _removeClass() call
+ // on the next line is going to destroy the reference to the current elements being
+ // tracked. We need to save a copy of this collection so that we can add the new classes
+ // below.
+ elements = $( currentElements.get() );
+ this._removeClass( currentElements, classKey );
+
+ // We don't use _addClass() here, because that uses this.options.classes
+ // for generating the string of classes. We want to use the value passed in from
+ // _setOption(), this is the new value of the classes option which was passed to
+ // _setOption(). We pass this value directly to _classes().
+ elements.addClass( this._classes( {
+ element: elements,
+ keys: classKey,
+ classes: value,
+ add: true
+ } ) );
+ }
+ },
+
+ _setOptionDisabled: function( value ) {
+ this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );
+
+ // If the widget is becoming disabled, then nothing is interactive
+ if ( value ) {
+ this._removeClass( this.hoverable, null, "ui-state-hover" );
+ this._removeClass( this.focusable, null, "ui-state-focus" );
+ }
+ },
+
+ enable: function() {
+ return this._setOptions( { disabled: false } );
+ },
+
+ disable: function() {
+ return this._setOptions( { disabled: true } );
+ },
+
+ _classes: function( options ) {
+ var full = [];
+ var that = this;
+
+ options = $.extend( {
+ element: this.element,
+ classes: this.options.classes || {}
+ }, options );
+
+ function processClassString( classes, checkOption ) {
+ var current, i;
+ for ( i = 0; i < classes.length; i++ ) {
+ current = that.classesElementLookup[ classes[ i ] ] || $();
+ if ( options.add ) {
+ current = $( $.unique( current.get().concat( options.element.get() ) ) );
+ } else {
+ current = $( current.not( options.element ).get() );
+ }
+ that.classesElementLookup[ classes[ i ] ] = current;
+ full.push( classes[ i ] );
+ if ( checkOption && options.classes[ classes[ i ] ] ) {
+ full.push( options.classes[ classes[ i ] ] );
+ }
+ }
+ }
+
+ this._on( options.element, {
+ "remove": "_untrackClassesElement"
+ } );
+
+ if ( options.keys ) {
+ processClassString( options.keys.match( /\S+/g ) || [], true );
+ }
+ if ( options.extra ) {
+ processClassString( options.extra.match( /\S+/g ) || [] );
+ }
+
+ return full.join( " " );
+ },
+
+ _untrackClassesElement: function( event ) {
+ var that = this;
+ $.each( that.classesElementLookup, function( key, value ) {
+ if ( $.inArray( event.target, value ) !== -1 ) {
+ that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
+ }
+ } );
+ },
+
+ _removeClass: function( element, keys, extra ) {
+ return this._toggleClass( element, keys, extra, false );
+ },
+
+ _addClass: function( element, keys, extra ) {
+ return this._toggleClass( element, keys, extra, true );
+ },
+
+ _toggleClass: function( element, keys, extra, add ) {
+ add = ( typeof add === "boolean" ) ? add : extra;
+ var shift = ( typeof element === "string" || element === null ),
+ options = {
+ extra: shift ? keys : extra,
+ keys: shift ? element : keys,
+ element: shift ? this.element : element,
+ add: add
+ };
+ options.element.toggleClass( this._classes( options ), add );
+ return this;
+ },
+
+ _on: function( suppressDisabledCheck, element, handlers ) {
+ var delegateElement;
+ var 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 {
+ 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*(.*)$/ );
+ var eventName = match[ 1 ] + instance.eventNamespace;
+ var selector = match[ 2 ];
+
+ if ( selector ) {
+ delegateElement.on( eventName, selector, handlerProxy );
+ } else {
+ element.on( eventName, handlerProxy );
+ }
+ } );
+ },
+
+ _off: function( element, eventName ) {
+ eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
+ this.eventNamespace;
+ element.off( eventName ).off( eventName );
+
+ // Clear the stack to avoid memory leaks (#10056)
+ this.bindings = $( this.bindings.not( element ).get() );
+ this.focusable = $( this.focusable.not( element ).get() );
+ this.hoverable = $( this.hoverable.not( element ).get() );
+ },
+
+ _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 ) {
+ this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
+ },
+ mouseleave: function( event ) {
+ this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
+ }
+ } );
+ },
+
+ _focusable: function( element ) {
+ this.focusable = this.focusable.add( element );
+ this._on( element, {
+ focusin: function( event ) {
+ this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
+ },
+ focusout: function( event ) {
+ this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
+ }
+ } );
+ },
+
+ _trigger: function( type, event, data ) {
+ var prop, orig;
+ var 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;
+ var 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();
+ } );
+ }
+ };
+ } );
+
+ var widget = $.widget;
+
+
+
+
+}));
diff --git a/public/medias/.gitkeep b/public/medias/.gitkeep
new file mode 100644
index 0000000..d8a1a27
--- /dev/null
+++ b/public/medias/.gitkeep
@@ -0,0 +1 @@
+gitkeep
\ No newline at end of file
diff --git a/public/uploads/.gitkeep b/public/uploads/.gitkeep
new file mode 100644
index 0000000..d8a1a27
--- /dev/null
+++ b/public/uploads/.gitkeep
@@ -0,0 +1 @@
+gitkeep
\ No newline at end of file
diff --git a/resources/js/media.js b/resources/js/media.js
index 9c55551..aa87681 100644
--- a/resources/js/media.js
+++ b/resources/js/media.js
@@ -27,6 +27,7 @@ $(document).ready(function() {
var sujetId = $("#sujet_selector").val();
if(sujetId != "")
$('.dd').nestable('destroy');
+ $(".currentFolder").val(0);
initFolderManager(sujetId);
});
@@ -265,6 +266,14 @@ $(document).ready(function() {
dossier_id: dossier_id,
libelle: libelle,
description: description,
+ },
+ success: function(result){
+ $(".folder_container .dd3-item[data-id='"+dossier_id+"'] > .dd3-content span").html(libelle);
+ $.fancybox.getInstance().close();
+ },
+ error: function(jqXHR, textStatus, errorThrown) {
+ $.fancybox.getInstance().close();
+ $.fancyMessage({title : "Attention", message : "Erreur lors de l'édition du dossier, merci d'actualiser la page.", type : "error"});
}
});
});
@@ -288,15 +297,24 @@ $(document).ready(function() {
});
// ---- Gestion des média : déselectionne les éléments si on clique en dehors
- $(".main_content").on("click", function(event) {
+ $(".main_content .wrapper").on("click", function(event) {
if (event.target !== this) return;
$(".imageItem").attr("aria-grabbed", "false");
+ $(".imageItem").removeClass('flip');
+ });
+
+ // ---- Gestion des média : déselectionne les éléments si on clique en dehors
+ $(".main_content .wrapper").on("contextmenu", '.imageItem' , function(event) {
+ event.preventDefault();
+ $(".imageItem").removeClass('flip');
+ $(this).addClass('flip')
});
// ---- Gestion des média : gère la sélection des éléments
var lastSelected ;
$("html").on("click", ".imageItem", function(event) {
event.preventDefault();
+ $(".imageItem").removeClass('flip');
if(event.ctrlKey){
if($(this).attr("aria-grabbed") == "true"){
$(this).attr("aria-grabbed", "false");
@@ -323,16 +341,14 @@ $(document).ready(function() {
imageItem.eq(i).attr("aria-grabbed", select);
}
}else{
- $(".imageItem").attr("aria-grabbed", "false");
if($(this).attr("aria-grabbed") == "true"){
- $(this).attr("aria-grabbed", "false");
+ $(".imageItem").attr("aria-grabbed", "false");
}else{
+ $(".imageItem").attr("aria-grabbed", "false");
$(this).attr("aria-grabbed", "true");
}
lastSelected = $(this);
}
-
-
});
// ---- Gestion des média : affichage des dossiers on draghover
@@ -404,6 +420,7 @@ $(document).ready(function() {
}
});
+ // ---- Gestion de l'infinite scroll
$(".main_content .media_container").on("scroll resize", function() {
if ($(window).data('ajaxready') == false) return;
if ($(window).data('noMedia') == true) return;
@@ -415,6 +432,156 @@ $(document).ready(function() {
loadMedia(dossier_id, sujet_id);
}
});
+
+ $('#close_upload_form').on('click',function(event){
+ event.preventDefault();
+ $('.upload_container').hide("fast");
+ });
+
+ $('.action_add_media').on('click',function(event){
+ event.preventDefault();
+ $('.upload_container').show("fast");
+ });
+
+
+
+ if($('#fileupload').length){
+ $.ajaxSetup({
+ headers: {
+ 'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
+ }
+ });
+
+ var cancel_btn = $('
')
+ .addClass('cancel')
+ .html('
');
+ var upload_btn = $('
')
+ .addClass('upload')
+ .css('display', 'none');
+
+ $('#submit').on('click',function(event){
+ event.preventDefault();
+ $("#upload_errors").html("");
+ $('.upload').click() //click upload buttons and upload all files in the queue
+ });
+ $('#cancel').on('click',function(event){
+ event.preventDefault();
+ $('.cancel').click() //click cancel buttons and remove all files in the queue
+ $("#upload_errors").html("");
+ });
+
+ $(".uploader-window").on("dragleave", function(){
+ $(this).stop().fadeOut( "fast");
+ });
+
+ var currentfiles = [];
+ $('#fileupload').fileupload({
+ url: "/ajax/upload-media",
+ dataType: 'json',
+ limitMultiFileUploads: 10,
+ maxChunkSize: 10000000,
+ dragover: function (e, data) {
+ $(".uploader-window").fadeIn( "fast");
+ },
+ add: function (e, data) {
+
+ data.files = $.map(data.files, function(file,i){
+ if ($.inArray(file.name,currentfiles) >= 0) {
+ $.fancyMessage({title : "Attention", message : "Nous ne pouvons pas envoyer plusieurs fichiers du même nom. Veuillez renommer vos fichiers puis essayez à nouveau.", type : "error"});
+ return null;
+ }
+ return file;
+ });
+
+ $.each(data.files, function (index, file) {
+
+ var uploadErrors = [];
+ var acceptFileTypes = /^(gif|jpe?g|png|mp4|mp3|psd|ai|eps|svg|indd|pdf)$/i;
+ if(file.name.split('.').pop().length && !acceptFileTypes.test(file.name.split('.').pop())) {
+ uploadErrors.push('Ce type de fichier n\'est pas accepté');
+ }
+ if(data.originalFiles[0]['size'].length && data.originalFiles[0]['size'] > 5000000) {
+ uploadErrors.push('La taille du fichier est trop grande.');
+ }
+ if(uploadErrors.length > 0) {
+ alert(uploadErrors.join("\n"));
+ return false;
+ }
+
+ currentfiles.push($.trim(file.name));
+ var row = document.createElement('div');
+ var col1 = document.createElement('div');
+ var col2 = document.createElement('div');
+ $(col1).append("
"+file.name+"").append(cancel_btn.clone(true).data(data));
+ $(col2).append("
"+file.type+" - "+FileConvertSize(file.size)+"");
+ $(col2).append(upload_btn.clone(true).data(data));
+ $(row).append(col1, col2);
+ $(".files").append(row);
+
+ $('.upload').eq(-1).on('click',function(){
+ data.formData = {
+ uid: create_UUID(),
+ sujet_id: $("#sujet_selector").val(),
+ dossier_id: $(".currentFolder").val(),
+ };
+ data.submit();
+ })
+ $('.cancel').eq(-1).on('click',function(){
+ $(this).parent().parent().remove();
+ currentfiles = arrayRemove(currentfiles, $.trim(file.name));
+ })
+ });
+
+ },
+ start: function (e, data) {
+ $('#progress').show();
+ },
+ drop: function (e, data) {
+ $(".uploader-window").stop().fadeOut( "fast");
+ },
+ done: function (e, data) {
+ $.each(data.files, function (index, file) {
+ currentfiles = arrayRemove(currentfiles, $.trim(file.name));
+ });
+ //---- Chargement des médias
+ var currentFolder = $(".currentFolder").val();
+ if(currentFolder == 0 || currentFolder == ""){
+ var count = $(".folder_container .root_folder .count");
+ }else{
+ var count = $(".folder_container .dd3-item[data-id='"+currentFolder+"'] > .dd3-content .count");
+ }
+ count.html(parseInt(count.html())+data.files.length);
+ },
+ stop: function (e,) {
+ $(".files").html("");
+ currentfiles = [];
+ $('#progress .progress-bar').css('width', '0%');
+ $('#progress').hide();
+ $('.upload_container').hide("fast");
+ var currentFolder = $(".currentFolder").val();
+ if(currentFolder == 0 || currentFolder == ""){
+ $(".root_folder").trigger("click");
+ }else{
+ $('.dd .dd-item[data-id="'+currentFolder+'"] > .dd3-content').trigger("click");
+ }
+ },
+ fail: function (e, data) {
+ $.each(data.files, function (index, file) {
+ $('
Erreur durant le transfert du fichier ' + file.name + '.
')
+ .appendTo('#upload_errors');
+ currentfiles = arrayRemove(currentfiles, $.trim(file.name));
+ });
+ },
+ progressall: function (e, data) {
+ var progress = parseInt(data.loaded / data.total * 100, 10);
+ $('#progress .progress-bar').css(
+ 'width',
+ progress + '%'
+ );
+ }
+ }).prop('disabled', !$.support.fileInput)
+ .parent().addClass($.support.fileInput ? undefined : 'disabled');
+ }
});
// ---- Gestion des média : init des dossiers
@@ -528,12 +695,52 @@ function loadMedia(dossier_id, sujet_id){
if(data.length == 0){
$(window).data('noMedia', true);
}
+
$.each(data, function (index, item) {
- output += '
Mon image';
- output += item.libelle;
- output += '';
+ var ico = "";
+ var detail = "";
+ var itemClass = "";
+ var thumb = "";
+ switch (item.typemedia_id) {
+ case 1:
+ case 2:
+ ico = '
';
+ detail = " - "+item.largeur+"x"+item.hauteur+"px";
+ thumb = item.source.split('.')[0]+"_thumb."+item.extension;
+ break;
+ case 3:
+ ico = '
';
+ detail = " - durée "+item.duree;
+ itemClass = "video";
+ break;
+ case 4:
+ ico = '
';
+ detail = " - durée "+item.duree;
+ itemClass = "son";
+ break;
+ case 5:
+ ico = '
';
+ itemClass = "doc";
+ break;
+ default:
+
+ }
+
+ output += '
';
+ output += '';
+ output += '
';
+ output += '
'+item.libelle+'
';
+ output += '
'+ico+item.extension+' - '+item.taille+detail+'
';
+ output += '
';
});
$('.media_container .wrapper').append(output);
+
if($(window).data('offset') == 0){
$(".media_container .loader").hide();
}else{
@@ -586,3 +793,27 @@ function addItem(parent_id, item_id, content){
html += "
";
$('.dd .dd-item[data-id="'+parent_id+'"] > ol').append(html);
}
+
+function arrayRemove(arr, value) {
+ return arr.filter(function(ele){
+ return ele != value;
+ });
+}
+
+function FileConvertSize(aSize){
+ aSize = Math.abs(parseInt(aSize, 10));
+ var def = [[1, 'octets'], [1024, 'ko'], [1024*1024, 'Mo'], [1024*1024*1024, 'Go'], [1024*1024*1024*1024, 'To']];
+ for(var i=0; i
div{
+ width: 100%;
+ padding: 10px;
+ border-bottom: 1px solid #e8ebed;
+
+ div:first-child{
+ color: $black;
+ margin-bottom: 5px;
+ font-size: 14px;
+ position: relative;
+
+ span{
+ white-space: nowrap;
+ display: block;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ max-width: 91%;
+ }
+ }
+ div:last-child{
+ color: $grey;
+ }
+ button{
+ display: none;
+ border: none;
+ background-color: transparent;
+ position: absolute;
+ right: 0;
+ top: -3px;
+ cursor: pointer;
+
+ i{
+ color: #337ab7;
+ font-size: 19px;
+ }
+ }
+ &:hover{
+ button{
+ display: block;
+ }
+ }
+ }
+ }
+ .upload_bar{
+ border-top: 1px solid $grey2;
+ width: 100%;
+ height: 60px;
+ position: absolute;
+ bottom: 0;
+ padding: 13px 10px;
+
+ #submit{
+ display: inline-block;
+ }
+ #cancel , #close_upload_form{
+ color: $grey;
+ cursor: pointer;
+ margin-left: 15px;
+ }
+ }
+}
diff --git a/resources/sass/partials/form.scss b/resources/sass/partials/form.scss
index 2e6e122..66170f4 100644
--- a/resources/sass/partials/form.scss
+++ b/resources/sass/partials/form.scss
@@ -115,12 +115,6 @@ h1{
display: inline-block;
}
-.group:after {
- content: "";
- display: table;
- clear: both;
-}
-
.filter_row{
margin-bottom: 10px;
.right_col{
diff --git a/resources/sass/partials/media.scss b/resources/sass/partials/media.scss
index 341029e..14599a5 100644
--- a/resources/sass/partials/media.scss
+++ b/resources/sass/partials/media.scss
@@ -13,11 +13,62 @@
.search_wrapper{
padding: 30px 5px 10px 40px;
min-height: 129px;
+
+ .search_filters{
+ display: inline-flex;
+ align-items: center;
+ padding: 0px 10px;
+
+ >div{
+ display: flex;
+ align-items: center;
+ margin-right: 10px;
+
+ i{
+ margin-left: 10px;
+ }
+ }
+ }
}
.action_wrapper{
background-color: $grey;
width: 100%;
min-height: 45px;
+
+ a{
+ font-size: 14px;
+ padding: 15px 20px;
+ display: inline-block;
+ border-right: 1px solid #505050;
+ float: left;
+ transition: 0.3s ease-in-out;
+
+ &:hover{
+ background-color: $grey3;
+ }
+
+ i{
+ background-color: $black;
+ border-radius: 20px;
+ margin-right: 5px;
+ padding: 5px 6px;
+ font-size: 11px;
+ color: $white;
+ }
+
+ &.action_download_media i{
+ background-color: $orange;
+ }
+ &.action_add_basket i{
+ background-color: $teal;
+ }
+ &.action_delete_media i{
+ background-color: $red;
+ }
+ &.action_tag_media i{
+ background-color: $cyan;
+ }
+ }
}
}
@@ -61,20 +112,147 @@
justify-content: center;
top: 0;
}
+ .wrapper{
+ display: flex;
+ flex-wrap: wrap;
- .imageItem{
- padding: 51px 20px;
- margin: 30px 10px;
- display: inline-block;
- border: 1px solid grey;
+ .imageItem{
+ display: inline-block;
+ border: 1px solid grey;
+ width: 202px;
+ margin: 8px 10px;
+ background-color: $grey3;
+ transition: 0.3s ease-in-out;
+ position: relative;
+ padding-bottom: 25px;
+ cursor: pointer;
- &[aria-grabbed="true"] {
- background: #8ad;
- color: #fff;
- }
- &:focus {
- outline: none;
- box-shadow: 0 0 0 2px #68b, inset 0 0 0 1px #ddd;
+ .contextMenu{
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.7);
+ left: 0px;
+ top: 0px;
+ z-index: 2;
+ display: none;
+ transform: rotateY(180deg);
+ color: $white;
+ justify-content: center;
+ align-items: center;
+
+ .menu-option{
+ padding: 10px;
+ transition: 0.3s ease-in-out;
+
+ i{
+ margin-right: 5px;
+ }
+ &:hover{
+ background-color: #2ea8e5;
+ }
+ }
+ }
+
+ &.flip{
+ transform: rotateY(180deg);
+
+ .contextMenu{
+ display: flex;
+ }
+ }
+
+
+ .media{
+ height: 200px;
+ background-size: contain;
+ background-repeat: no-repeat;
+ background-position: center;
+ position: relative;
+
+ &::before {
+ transform: translate(-50%,-50%);
+ display: block;
+ position: absolute;
+ left: 50%;
+ top: 50%;
+ text-align: center;
+ text-indent: 0;
+ color: $white;
+ font-size: 59px;
+ z-index: 1;
+ }
+ &.video{
+ &::before {
+ content: "\f167";
+ font-family: "Font Awesome 5 Brands";
+
+ }
+ }
+ &.son{
+ &::before {
+ content: "\f58f";
+ font-family: "Font Awesome 5 Pro";
+ font-weight: 900;
+
+ }
+ }
+ &.doc{
+ &::before {
+ content: "\f15c";
+ font-family: "Font Awesome 5 Pro";
+ font-weight: 900;
+
+ }
+ }
+ }
+ .libelle{
+ padding: 4px;
+ text-align: center;
+ color: $white;
+ margin: 0;
+ word-wrap: break-word;
+ }
+ .info{
+ position: absolute;
+ bottom: 0;
+ padding: 4px;
+ font-size: 11px;
+ i{
+ margin-right: 7px;
+ }
+ }
+
+
+ &[aria-grabbed="true"] {
+ &::before {
+ content: "\f058";
+ font-family: "Font Awesome 5 Pro";
+ font-weight: 900;
+ display: block;
+ position: absolute;
+ left: 5px;
+ top: 5px;
+ text-align: center;
+ text-indent: 0;
+ color: $lightblue;
+ font-size: 25px;
+ z-index: 2;
+ }
+ &::after {
+ content: "";
+ width: 100%;
+ height: 100%;
+ background-color: rgba(0,0,0,0.5);
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ z-index: 1;
+ }
+ }
+ &:hover{
+ background-color: $grey;
+ }
}
}
}
@@ -90,6 +268,7 @@
width: 340px;
height: 100%;
transition: 0.3s ease-in-out;
+ z-index: 1;
&.collapse{
transform: translateX(-100%);
diff --git a/resources/sass/partials/reset.scss b/resources/sass/partials/reset.scss
index ab81661..ca8f321 100644
--- a/resources/sass/partials/reset.scss
+++ b/resources/sass/partials/reset.scss
@@ -61,3 +61,15 @@ table {
*, *:before, *:after {
box-sizing: inherit;
}
+
+.group:before,
+.group:after {
+ content: "";
+ display: table;
+}
+.group:after {
+ clear: both;
+}
+.group {
+ zoom: 1; /* For IE 6/7 (trigger hasLayout) */
+}
diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php
index 7668400..9cef587 100644
--- a/resources/views/layouts/app.blade.php
+++ b/resources/views/layouts/app.blade.php
@@ -38,6 +38,13 @@
+
+
+
+
Déposez vos fichiers pour les téléverser
+
+
+
diff --git a/resources/views/media_manager.blade.php b/resources/views/media_manager.blade.php
index 88399b3..5140ed2 100644
--- a/resources/views/media_manager.blade.php
+++ b/resources/views/media_manager.blade.php
@@ -39,9 +39,24 @@
Rechercher
+
+
Type de média
+
Type de vue
+
Etiquette
+
+
+
+
+
+
+
-
+
+
+
+ Ajouter vos médias
+
+
+
+
+
+
+
@endsection
@section('scripts')
+
+
+
@endsection
diff --git a/resources/views/upload/show.blade.php b/resources/views/upload/show.blade.php
new file mode 100644
index 0000000..f78cc65
--- /dev/null
+++ b/resources/views/upload/show.blade.php
@@ -0,0 +1,31 @@
+@extends('layouts.app')
+@include('layouts.header')
+@section('content')
+
+
+
+
+@endsection
+@section('scripts')
+
+
+
+@endsection
diff --git a/routes/web.php b/routes/web.php
index 12bb348..67118ec 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -27,6 +27,9 @@ Route::group(['middleware' => ['auth']], function(){
Route::post('/ajax/load-media', 'MediaController@loadMedia');
Route::post('/ajax/move-media', 'MediaController@moveMedia');
+ Route::get('/upload', 'UploadController@index');
+ Route::post('/ajax/upload-media', 'UploadController@storeMedia');
+
Route::resource('user', 'UserController');
Route::post('/check-matricule', 'UserController@checkMatricule');
Route::resource('dossier', 'DossierController');