فهرست منبع

updated processEditor

Piotr Labudda 8 سال پیش
والد
کامیت
ed79d74d21
4فایلهای تغییر یافته به همراه2926 افزوده شده و 161 حذف شده
  1. 526 7
      SE/se-lib/Route/UrlAction/ProcesEditor.php
  2. 1477 0
      SE/static/jquery.fileupload.js
  3. 738 154
      SE/static/procesEditor.js
  4. 185 0
      SE/static/videoPlayer.js

+ 526 - 7
SE/se-lib/Route/UrlAction/ProcesEditor.php

@@ -1,5 +1,26 @@
 <?php
 
+/*
+CREATE TABLE IF NOT EXISTS `CRM_VIDEO` (
+  `VIDEO_ID` int(11) NOT NULL,
+  `REMOTE_TABLE` int(11) NOT NULL,
+  `REMOTE_ID` int(11) NOT NULL,
+  `PARENT_ID` int(11) NOT NULL,
+  `THUMBNAIL_ID` int(11) NOT NULL,
+  `NAME` varchar(255) NOT NULL,
+  `VIDEO_START` int(11) NOT NULL,
+  `VIDEO_END` int(11) NOT NULL,
+  `PATH` varchar(255) NOT NULL
+) ENGINE=InnoDB DEFAULT CHARSET=latin2;
+
+
+ALTER TABLE `CRM_VIDEO`
+  ADD PRIMARY KEY (`VIDEO_ID`);
+
+
+*/
+
+
 Lib::loadClass('RouteBase');
 Lib::loadClass('ProcesHelper');
 Lib::loadClass('Config');
@@ -11,6 +32,257 @@ Lib::loadClass('Api_WfsNs');
 
 class Route_UrlAction_ProcesEditor extends RouteBase {// TODO: UrlActionBase @see Route_UrlAction
 
+	public function getProcesListAjaxAction(){
+		try {
+			$rows = DB::getPDO()->fetchAll("
+				SELECT * FROM `CRM_PROCES` WHERE FIND_IN_SET(`ID`, ( SELECT GROUP_CONCAT(Level SEPARATOR ',')
+				FROM ( SELECT @Ids := ( SELECT GROUP_CONCAT(`ID` SEPARATOR ',') FROM `CRM_PROCES` WHERE FIND_IN_SET(`PARENT_ID`, @Ids)
+				AND `TYPE` <> 'PROCES_INIT' ) Level FROM `CRM_PROCES` JOIN
+				(SELECT @Ids := 0) temp1
+				WHERE FIND_IN_SET(`PARENT_ID`, @Ids) ) temp2 ))
+				AND `PARENT_ID` >= 0 AND `A_STATUS` <> 'DELETED'
+			");
+			$state = array();
+			foreach ($rows as $key => $value) {
+				$id = intval($value["ID"]);
+				$parent_id = intval($value["PARENT_ID"]);
+				$state[$id]["ID"] = $value["ID"];
+				$state[$id]["DESC"] = $value["DESC"];
+				$state[$id]["OPIS"] = $value["OPIS"];
+				$state[$id]["TYPE"] = $value["TYPE"];
+				$state[$id]["IF_TRUE_GOTO"] = intval($value["IF_TRUE_GOTO"]);
+				$state[$id]["IF_TRUE_GOTO_FLAG"] = $value["IF_TRUE_GOTO_FLAG"];
+				$state[$id]["PARENT_ID"] = (intval($value["PARENT_ID"]) != 0) ? intval($value["PARENT_ID"]) : "#";
+				$state[$id]["SORT_PRIO"] = $value["SORT_PRIO"];
+				if (!isset($state[$id]["childs"])) $state[$id]["childs"] = array();
+				if (!isset($state[$parent_id]["childs"])) $state[$parent_id]["childs"] = array();
+				$temp = array();
+				$temp["ID"] = $id;
+				array_push($state[$parent_id]["childs"], $temp);
+			}
+			Response::sendJsonExit($state);
+		} catch (Exception $e) {
+			UI::alert('danger', "Error: " . $e->getMessage());
+		}
+	}
+
+	public function getPathAjaxAction(){
+		try {
+			$video_id = V::get('id', 0, $_GET, 'int');
+			$rows = DB::getPDO()->fetchAll("
+			SELECT f.ID, f.DESC
+			FROM (
+				SELECT @id AS _id, (SELECT @id := PARENT_ID FROM CRM_PROCES WHERE ID = _id)
+				FROM (SELECT @id := {$video_id}) tmp1
+				JOIN CRM_PROCES ON @id <> 0
+			) tmp2
+			JOIN CRM_PROCES f ON tmp2._id = f.ID
+			");
+			$rows = array_reverse($rows);
+			Response::sendJsonExit($rows);
+
+		} catch (Exception $e) {
+			UI::alert('danger', "Error: " . $e->getMessage());
+		}
+
+
+	}
+
+	private function addWatermark($url){
+
+		$watermark_file = "/Library/Server/Web/Data/Sites/Default/SE/stuff/images/play.png";
+
+		$image = new Imagick();
+		$image->readImage($url);
+
+		$watermark = new Imagick();
+		$watermark->readImage($watermark_file);
+
+		$image->resizeImage(500,500,Imagick::FILTER_LANCZOS,1);
+		$image->compositeImage($watermark, imagick::COMPOSITE_OVER, 0, 0);
+
+
+		$image->writeImage($url);
+		$image->clear();
+		$image->destroy();
+		$watermark->clear();
+		$watermark->destroy();
+
+	}
+
+	public function uploadVideoAction() {
+
+
+		if (!User::logged()) return;
+
+		$CRM_VIDEO = "/Library/Server/Web/Data/Sites/Default/PLIKI/bartosz_procesy5_pl/CRM_VIDEO/";
+		$url = $_FILES["files"]["tmp_name"][0];
+		$size = $_FILES["files"]["size"][0];
+		$name = $_FILES["files"]["name"][0];
+		$finfo = new finfo(FILEINFO_MIME_TYPE);
+		$type = $finfo->file($url);
+
+		$ext = pathinfo($name, PATHINFO_EXTENSION);
+		$imgExtList = array("image/png", "image/jpeg");
+		$vidExtList = array("video/mp4");
+
+
+		$info;
+
+		$statement = DB::getPDO()->prepare("INSERT INTO CRM_IMAGE(NAME, TYPE, REMOTE_TABLE, REMOTE_ID, IMAGE, SIZE)
+				VALUES(:NAME, :TYPE, :REMOTE_TABLE, :REMOTE_ID, :IMAGE, :SIZE)");
+
+		if(in_array($type, $imgExtList)){
+			$content = 'data:'.$type.';base64,'.base64_encode(file_get_contents($url));
+
+
+			$statement->execute(array(
+				":NAME" => $name,
+				":TYPE"  => $type,
+				":REMOTE_TABLE"  => "ADMIN_USERS",
+				":REMOTE_ID"  => User::getID(),
+				":IMAGE"  => $content,
+				":SIZE"  => $size
+			));
+
+			$info->statusCode = 0;
+			$info->statusText = "Plik załadowany pomyślnie" ;
+		}elseif(in_array($type, $vidExtList)){
+
+
+			$statement_video = DB::getPDO()->prepare("INSERT INTO CRM_VIDEO(NAME, TYPE, REMOTE_TABLE, REMOTE_ID, PARENT_ID, HAS_FILE, EXT)
+				VALUES(:NAME, :TYPE, :REMOTE_TABLE, :REMOTE_ID, :PARENT_ID, :HAS_FILE, :EXT)");
+
+			$statement_video->execute(array(
+				":NAME" => $name,
+				":TYPE"  => $type,
+				":REMOTE_TABLE"  => "ADMIN_USERS",
+				":PARENT_ID" => 0,
+				":REMOTE_ID"  => User::getID(),
+				":EXT" => $ext,
+				":HAS_FILE"  => 1,
+			));
+			$id = DB::getPDO()->lastInsertId();
+			echo $url."\n";
+			echo $CRM_VIDEO.$id.$ext;
+
+			$temp_file = tempnam(sys_get_temp_dir(), 'thumbnail_');
+			rename($temp_file, $temp_file .= '.png');
+			$cmd = '/usr/local/bin/ffmpeg -y -i '.$url.' -ss 00:00:1 -vframes 1 '.$temp_file.' 2>&1';
+			shell_exec($cmd);
+
+
+			$this->addWatermark($temp_file);
+			$img_content = 'data:'."image/png".';base64,'.base64_encode(file_get_contents($temp_file));
+
+			$img_size = filesize($temp_file);
+			$statement->execute(array(
+				":NAME" => "Miniaturka dla ".$name,
+				":TYPE"  => "image/png",
+				":REMOTE_TABLE"  => "CRM_VIDEO",
+				":REMOTE_ID"  => $id,
+				":IMAGE"  => $img_content,
+				":SIZE"  => $img_size
+			));
+			move_uploaded_file ( $url, $CRM_VIDEO.$id.".".$ext );
+		}else{
+			$info->statusCode = 1;
+			$info->statusText = "Nieobsługiwany format pliku ".$type ;
+		}
+
+
+		echo json_encode($info);
+
+
+	}
+
+
+	public function getVideoFileAjaxAction(){
+				try {
+			$video_id = V::get('id', 0, $_GET, 'int');
+			if ($video_id <= 0) throw new Exception("ID is not set.");
+			$sqlVideoId = DB::getPDO()->quote($video_id, PDO::PARAM_INT);
+			$rows = DB::getPDO()->fetchAll("
+				SELECT f.VIDEO_ID
+				FROM (
+					SELECT @id AS _id, (SELECT @id := PARENT_ID FROM CRM_VIDEO WHERE VIDEO_ID = _id)
+					FROM (SELECT @id := {$sqlVideoId}) tmp1
+					JOIN CRM_VIDEO ON @id <> 0
+				) tmp2
+				JOIN CRM_VIDEO f ON tmp2._id = f.VIDEO_ID
+				WHERE f.HAS_FILE = 1 and f.TYPE = 'video/mp4'
+			");
+			$id = $rows[0]["VIDEO_ID"];
+			$CRM_VIDEO = "/PLIKI/bartosz_procesy5_pl/CRM_VIDEO/";
+			$result["src"] = $CRM_VIDEO.$id.".mp4";
+			Response::sendJsonExit($result);
+
+		} catch (Exception $e) {
+			UI::alert('danger', "Error: " . $e->getMessage());
+		}
+
+	}
+
+	public function getThumbnailAjaxAction($video_id = -1){
+				try {
+			$doReturn = false;
+			if($video_id <= 0)
+				$video_id = V::get('id', 0, $_GET, 'int');
+			else
+				$doReturn = true;
+			if ($video_id <= 0) throw new Exception("ID is not set.");
+			$sqlVideoId = DB::getPDO()->quote($video_id, PDO::PARAM_INT);
+			$rows = DB::getPDO()->fetchAll("
+				SELECT f.VIDEO_ID
+				FROM (
+					SELECT @id AS _id, (SELECT @id := PARENT_ID FROM CRM_VIDEO WHERE VIDEO_ID = _id)
+					FROM (SELECT @id := {$sqlVideoId}) tmp1
+					JOIN CRM_VIDEO ON @id <> 0
+				) tmp2
+				JOIN CRM_VIDEO f ON tmp2._id = f.VIDEO_ID
+				WHERE f.PARENT_ID = 0
+			");
+			if(isset($rows[0]["VIDEO_ID"])){
+				$video_parent_id = $rows[0]["VIDEO_ID"];
+				$sqlVideoParentId = DB::getPDO()->quote($video_parent_id , PDO::PARAM_INT);
+				$rows = DB::getPDO()->fetchAll("
+					SELECT * FROM CRM_IMAGE WHERE REMOTE_TABLE = 'CRM_VIDEO' AND REMOTE_ID = {$sqlVideoParentId}
+				");
+				if($doReturn)
+					return $rows;
+				else
+					Response::sendJsonExit($rows);
+
+
+			}
+
+		} catch (Exception $e) {
+			UI::alert('danger', "Error: " . $e->getMessage());
+		}
+
+	}
+
+		public function getVideosClipboardAjaxAction(){
+				try {
+			$id = User::getID();
+			if ($id <= 0) throw new Exception("ID is not set.");
+			$sqlId = DB::getPDO()->quote($id, PDO::PARAM_INT);
+			$rows = DB::getPDO()->fetchAll("
+				SELECT *
+				FROM CRM_VIDEO
+				WHERE REMOTE_TABLE = 'ADMIN_USERS' and REMOTE_ID = {$sqlId}
+			");
+			foreach($rows as $key=>$value){
+				$rows[$key]["THUMBNAIL"] = $this->getThumbnailAjaxAction($value["VIDEO_ID"])[0];
+			}
+			Response::sendJsonExit($rows);
+
+		} catch (Exception $e) {
+			UI::alert('danger', "Error: " . $e->getMessage());
+		}
+
+	}
+
 	public function getResByParentAjaxAction() {
 		try {
 			$idParent = V::get('parent_id', 0, $_GET, 'int');
@@ -106,8 +378,11 @@ class Route_UrlAction_ProcesEditor extends RouteBase {// TODO: UrlActionBase @se
 		UI::menu();
 		try {
 			$id = V::get('id', 0, $_REQUEST, 'int');
-			if ($id <= 0) throw new Exception("Wrong ID");
-			UI::setTitleJsTag("Edytor Procesu {{$id}}");
+			if ($id <= 0) $id = -1;
+			if($id >= 0)
+				UI::setTitleJsTag("Edytor Procesu {{$id}}");
+			else
+				UI::setTitleJsTag("Tworzenie nowego procesu}");
 			$this->showEditor($id);
 		} catch (Exception $e) {
 			UI::alert('danger', "Error: " . $e->getMessage());
@@ -116,9 +391,13 @@ class Route_UrlAction_ProcesEditor extends RouteBase {// TODO: UrlActionBase @se
 	}
 
 	public function showEditor($id) {
+		echo '<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.8.1/css/bootstrap-slider.min.css" crossorigin="anonymous">';
 		echo '<link rel="stylesheet" type="text/css" href="static/sweetalert2.min.css">';
+		echo '<link rel="stylesheet" href="static/jstree-theme/style.min.css" />';
+		echo '<script src="static/jstree.min.js"></script>';
 		$this->showEditorCss();
 ?>
+
 		<div id="wrapper" class="toggled" style="overflow:hidden">
 			<div id='sidebar-wrapper'><div id="side"></div></div>
 			<div id='page-content-wrapper'>
@@ -126,9 +405,15 @@ class Route_UrlAction_ProcesEditor extends RouteBase {// TODO: UrlActionBase @se
 
 					<button class='lButton btn btn-default' id="btnZasoby" style="top:150px"><p>Zasoby</p></button>
 					<button class='lButton btn btn-default' style="top:315px" id="btnProcesy"><p>Procesy</p></button>
-					<button class='lButton btn btn-default' id="btnPhotos" style="top:480px"><p>Zdjęcia</p></button>
+					<!--<button class='lButton btn btn-default' id="btnPhotos" style="top:480px"><p>Media</p></button>-->
+				</div>
+				<div class=col-md-12 >
+				<div class=col-md-12 ><a id=toggleMenu style=cursor:pointer;>Przełącz menu</a>
+				</div>
+				<hr>
+				<div class="col-md-12 path" style=border-width:1px;text-align:center;>
+				</div>
 				</div>
-<div class=col-md-12 ><a id=toggleMenu style=cursor:pointer;>Przełącz menu</a></div>
 				<div id="main" style="margin-left:40px">
 					<center>Uruchamianie aplikacji.</center>
 				</div>
@@ -136,13 +421,119 @@ class Route_UrlAction_ProcesEditor extends RouteBase {// TODO: UrlActionBase @se
 		</div>
 		<script>var USER_ID = '<?= User::getID(); ?>';var BASE_URL = '<?= Request::getPathUri(); ?>';var BASE_WFS_URL = '<?= Api_WfsNs::getBaseWfsUri(); ?>';var mainProces_id = <?= $id; ?>;</script>
 		<script src="static/sweetalert2.min.js"></script>
-		<script src="static/procesEditor.js?v=21<?php echo "&_time=" . time(); ?><?php if (V::get('DBG', '', $_GET)) echo "&_time=" . time(); ?>"></script>
+		<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-slider/9.8.1/bootstrap-slider.min.js" crossorigin="anonymous"></script>
+
+		<script src="static/videoPlayer.js?v=19<?php echo "&_time=" . time(); ?>"></script>
+		<script src="static/jquery.fileupload.js"></script>
+		<script src="static/procesEditor.js?v=19<?php echo "&_time=" . time(); ?><?php if (V::get('DBG', '', $_GET)) echo "&_time=" . time(); ?>"></script>
 <?php
 	}
 
 	public function showEditorCss() {
 ?>
 <style type="text/css">
+#p5BModalBack{
+	width:100%;
+	position:fixed;
+	height:100%;
+	background-color:rgba(0,0,0,0.9);
+	z-index:2000;
+	text-align:center;
+}
+
+.p5BModalModal{
+	display: inline-block;
+	width:800px;
+	opacity:1;
+	box-shadow:0 5px 15px rgba(0,0,0,.5);
+	background-color:white;
+-webkit-background-clip: padding-box;
+background-clip: padding-box;
+border: 1px solid #999;
+border: 1px solid rgba(0,0,0,.2);
+border-radius: 6px;
+outline: 0;
+
+}
+
+.p5BModalContent{
+	text-align:left;
+	padding:5px;
+}
+
+#pathList{
+	min-height:400px;
+	max-height:650px;
+	overflow-y:scroll;
+
+	overflow-x:hidden;
+}
+
+.p5BModalHeader{
+	text-align:left;
+	font-size: 18px;
+    margin-bottom: 10px;
+    font-family: inherit;
+    font-weight: 500;
+	cursor:move;
+	height:30px;
+	border-bottom:1px solid silver;
+	font-size:1.5em;
+	padding-left:10px;
+
+}
+
+.p5BModalHeader span{
+	display:inline-block;
+	padding-top:5px;
+
+}
+
+.p5BModalHeader div{
+	font-family:verdana;
+	float:right;
+	padding-top:5px;
+	cursor:pointer;
+	border-color:black;
+	font-weight:bold;
+	padding-right:10px;
+	padding-top:6px;
+	color:gray;
+	height:100%;
+	padding-left:7px;
+	border-top-left-radius:3px;
+	border-bottom-left-radius:3px;
+	font-size:0.9em;
+}
+
+.p5BModalHeader div:hover{
+color:#e74c3c;
+
+}
+
+.hide{
+	display:none;
+
+}
+
+.p5BModalButton{
+	float:right;
+	margin:3px;
+
+}
+
+
+
+.pathList{
+	max-height:700px;
+	min-height:300px;
+	overflow-y:scroll;
+
+
+}
+
+
+
 .activeDrop{
 	background-color:#f1f1f1;
 
@@ -175,6 +566,52 @@ class Route_UrlAction_ProcesEditor extends RouteBase {// TODO: UrlActionBase @se
 	color:black;
 }
 
+.attImg, .attVid{
+
+	border-style:solid;
+	display:inline-block;
+	margin-top:5px;
+	margin-left:10px;
+}
+
+.attImg .img, .attVid .img{
+	height:165px;
+	background-color:black;
+	display: table-cell;
+    vertical-align: middle;
+}
+
+.attImg img, .attVid img{
+  max-width:165px;
+  max-height:165px;
+  width: auto;
+  height: auto;
+}
+
+.attImg .imgTitle, .attVid .imgTitle{
+	background-color:white;
+	cursor: move;
+	text-align:left;
+}
+
+.remImg{
+
+	cursor:pointer;
+	color:red;
+	padding:2px;
+
+	background-color:white;
+	border-bottom-style:solid;
+	border-right-style:solid;
+	border-color:black;
+	border-width:1px;
+}
+
+.attImg div, .attVid div{
+	font-family:tahoma;
+	text-align:center;
+}
+
 .lButton{
 	-webkit-transform: translate3d(0,0,0);
     position:fixed;
@@ -198,8 +635,7 @@ class Route_UrlAction_ProcesEditor extends RouteBase {// TODO: UrlActionBase @se
 }
 
 .ico {
-    float: right;
-    margin-top: 10px;
+    margin-top: 3px;
     margin-left: 5px;
     cursor: pointer;
 }
@@ -419,6 +855,9 @@ width:290px;
 #side .grad {
     cursor: pointer;
 }
+.vakata-context {
+     z-index:3000 !important;
+}
 #side .click {
     float: left;
     color: grey;
@@ -589,6 +1028,86 @@ width:290px;
     .resSelected {
         background-color: #d3d3d3;
     }
+
+	.slider .slider-handle {
+    background-color: red;
+    width: 8px;
+    margin-left: -4px !important;
+}
+.slider .slider-handle {} .p5VideoPlayer_vid {
+    background-color: black;
+    height: calc(100% - 20px);
+    width: 100%;
+}
+
+
+.p5VideoPlayer {
+
+	width:165px;
+	height:165px;
+
+}
+
+.p5VideoPlayer .ico {
+    font-size: 12px;
+    margin-left: 4px;
+    margin-right: 4px;
+    cursor: pointer;
+    opacity: 0.8;
+}
+.ico:hover {
+    opacity: 1;
+}
+.p5VideoPlayer_video-controls {
+    background-color: powderblue;
+	margin-top:-5px;
+
+}
+
+.p5VideoPlayer_video-controls .only_fullscreen{
+	display:none;
+}
+
+.p5VideoPlayer_video-controls-fs .only_fullscreen{
+	display:table-cell;
+}
+.p5VideoPlayer_video-controls-fs {
+    opacity: 0;
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    color: white;
+    font-size: .8em;
+	color:black;
+    width: 100%;
+    height: 20px;
+    z-index: 2147483647;
+}
+.p5VideoPlayer:hover .p5VideoPlayer_video-controls-fs {
+    opacity: 1;
+}
+.p5VideoPlayer_video-controls td {
+    padding: 0px;
+	padding-left:5px;
+	padding-right:5px;
+}
+.p5VideoPlayer_video-controls .slider {
+    width: 100%;
+}
+.przycinanie .slider {
+    width: 100%;
+}
+.slider .slider-selection {
+    background: #01b7ce;
+}
+video::-webkit-media-controls {
+    display: none !important;
+}
+video::-webkit-media-controls-enclosure {
+    display: none !important;
+}
+
 </style>
 <?php
 	}

+ 1477 - 0
SE/static/jquery.fileupload.js

@@ -0,0 +1,1477 @@
+/*
+ * jQuery File Upload Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* 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.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:
+        $('<input type="file">').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 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;
+                };
+            }
+        },
+
+        _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.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.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 = $('<a></a>').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 || 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 + 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;
+                // 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]
+                        );
+                    });
+            };
+            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._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;
+            $('<form></form>').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(),
+                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);
+                },
+                dirReader, entries = [];
+            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 empy 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');
+        },
+
+        _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);
+        }
+
+    });
+
+}));

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 738 - 154
SE/static/procesEditor.js


+ 185 - 0
SE/static/videoPlayer.js

@@ -0,0 +1,185 @@
+var instances = [];
+
+function secondsToTime(secs) {
+    var minutes = Math.floor(secs / 60);
+    var seconds = Math.floor(secs % 60);
+    if (isNaN(minutes)) minutes = 0;
+    if (isNaN(seconds)) seconds = 0;
+    if (minutes < 0) minutes = 0;
+    if (seconds < 0) seconds = 0;
+    if (seconds < 10) seconds = "0" + seconds;
+    if (minutes < 10) minutes = "0" + minutes;
+    var obj = {
+        "m": minutes,
+        "s": seconds,
+    };
+    return obj;
+}
+
+$(document).on("change", ".p5VideoPlayer_zakres", function(slideEvt) {
+    console.log("test");
+    var player = $(this).closest(".p5VideoPlayer")
+    var start = $(player).data("start")
+    var end = $(player).find('.p5VideoPlayer_vid')[0].duration - $(player).data("end")
+    $(player).find('.p5VideoPlayer_vid')[0].currentTime = slideEvt.value.newValue * ($(player).find('.p5VideoPlayer_vid')[0].duration - start - end) * 0.01 + start
+});
+
+
+function videoPlayer_updateProgress(player) {
+    var start = $(player).data("start")
+    var end = $(player).data("end")
+    var current = $(player).find('.p5VideoPlayer_vid')[0].currentTime - start;
+    var video = $(player).find(".p5VideoPlayer_vid");
+    end = $(player).find('.p5VideoPlayer_vid')[0].duration - end
+    var duration = $(player).find('.p5VideoPlayer_vid')[0].duration - start - end;
+    var result = (current / duration)
+
+    $(player).find(".p5VideoPlayer_zakres").slider('setValue', result * 100)
+    var currentObj = secondsToTime(current);
+    var durationObj = secondsToTime(duration);
+    $(player).find(".p5VideoPlayer_current").text(currentObj.m + ":" + currentObj.s);
+    $(player).find(".p5VideoPlayer_duration").text(durationObj.m + ":" + durationObj.s);
+    if (current < 0) {
+        $(player).find('.p5VideoPlayer_vid')[0].currentTime = start;
+    }
+
+    if (current >= duration) {
+        $(player).find('.p5VideoPlayer_vid')[0].currentTime = duration + start;
+        var el = $(player).find(".p5VideoPlayer_play");
+        $(video).trigger('pause');
+        $(el).removeClass("glyphicon-pause")
+        $(el).addClass("glyphicon-play")
+    }
+}
+
+
+$(document).on("click", ".img", function() {
+    if (!$(this).parent().hasClass("p5VideoPlayer")) return false;
+    var player = $(this).parent();
+    var id = $(player).data("videoid");
+    var link = BASE_URL + "index.php?_route=UrlAction_ProcesEditor&_task=getVideoFileAjax&id=" + id;
+    $.get(link, function(data) {
+        $(player).html(`
+				<video class="p5VideoPlayer_vid">
+	 		   <source src="` + data["src"] + `" type="video/mp4">
+	 		 Your browser does not support the video tag.
+	 		 </video>
+	 		   <div class="p5VideoPlayer_video-controls">
+	 		 	<table>
+	 		 	<tr>
+	 		     <td><span class="glyphicon glyphicon-play p5VideoPlayer_play ico"></span></td>
+	 		     <td style=width:100%;><input class="p5VideoPlayer_zakres"  type="text" data-slider-handle="custom2"
+	 		 	data-slider-min="0" data-slider-max="100" data-slider-step="0.01" data-slider-value="0"/>
+	 		 	</td>
+				<td class=only_fullscreen><span style=margin-top:15px;><span class=p5VideoPlayer_current>00:00</span>&nbsp/&nbsp<span class=p5VideoPlayer_duration>00:00</span> </span></td>
+	 		 		<td class=only_fullscreen><input class="p5VideoPlayer_glosnosc" style=margin-top:10px;width:30px; type="text" data-slider-handle="custom2"
+	 		 	data-slider-min="0" data-slider-max="100" data-slider-value="0"/>
+	 		 	</td>
+	 		 	<td><span class="glyphicon glyphicon-fullscreen ico p5VideoPlayer_fullscreen"></span></td>
+
+	 		 	</tr>
+	 		 	</table>
+
+	 		   </div>
+				`)
+        $(player).find('.p5VideoPlayer_vid')[0].currentTime = $(player).data("start");
+        $('.p5VideoPlayer_zakres').slider({
+
+            formatter: function(value) {
+                return value + "%";
+            }
+        });
+
+        $('.p5VideoPlayer_glosnosc').slider({
+            formatter: function(value) {
+                return value + "%";
+            }
+        });
+        videoPlayer_updateProgress(player);
+		var temp = {}
+		temp["vid"] = $(player).find('.p5VideoPlayer_vid')[0];
+        temp["INTERVAL"] = setInterval(function() {
+            videoPlayer_updateProgress(player);
+        }, 500);
+		instances.push(temp);
+    });
+
+
+
+});
+
+function videoPlayer_play(el) {
+    var player = $(el).closest(".p5VideoPlayer")
+
+    var video = $(player).find(".p5VideoPlayer_vid");
+    el = $(player).find(".p5VideoPlayer_play");
+    if ($(video).prop("paused")) {
+        $(video).trigger('play');
+        $(el).removeClass("glyphicon-play")
+        $(el).addClass("glyphicon-pause")
+
+
+    } else {
+        $(video).trigger('pause');
+        $(el).removeClass("glyphicon-pause")
+        $(el).addClass("glyphicon-play")
+
+    }
+    videoPlayer_updateProgress(player);
+}
+
+$('.p5VideoPlayer_vid').on('canplay canplaythrough', function() {
+    alert('canplay event fired');
+});
+
+
+$(document).on("click", ".p5VideoPlayer_play", function() {
+    videoPlayer_play(this);
+})
+
+$(document).on("click", ".p5VideoPlayer_vid", function() {
+    videoPlayer_play(this);
+})
+
+
+
+$(document).on('webkitfullscreenchange mozfullscreenchange fullscreenchange', ".p5VideoPlayer_vid", function() {
+    var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;
+    var player = $(this).closest(".p5VideoPlayer")
+    var video = $(player).find(".p5VideoPlayer_vid");
+    if (state) {
+        $(player).find(".p5VideoPlayer_video-controls").addClass("p5VideoPlayer_video-controls-fs");
+    } else {
+        $(player).find(".p5VideoPlayer_video-controls").removeClass("p5VideoPlayer_video-controls-fs");
+    }
+});
+
+$(document).on("click", ".p5VideoPlayer_fullscreen", function() {
+    var player = $(this).closest(".p5VideoPlayer")
+    var video = $(player).find(".p5VideoPlayer_vid");
+    var elem = video[0];
+    $(player).find(".p5VideoPlayer_video-controls").addClass("p5VideoPlayer_video-controls-fs");
+    var state = document.fullScreen || document.mozFullScreen || document.webkitIsFullScreen;
+    if (!state) {
+        if (elem.requestFullscreen) {
+            elem.requestFullscreen();
+        } else if (elem.mozRequestFullScreen) {
+            elem.mozRequestFullScreen();
+        } else if (elem.webkitRequestFullscreen) {
+            elem.webkitRequestFullscreen();
+        }
+    } else {
+        if (document.exitFullscreen) {
+            document.exitFullscreen();
+        } else if (document.webkitExitFullscreen) {
+            document.webkitExitFullscreen();
+        } else if (document.mozCancelFullScreen) {
+            document.mozCancelFullScreen();
+        } else if (document.msExitFullscreen) {
+            document.msExitFullscreen();
+        }
+
+    }
+
+
+});

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است