Robert Marshall 8 vuotta sitten
vanhempi
commit
f75cd1cba1

+ 40 - 36
Controller/Gallery.php

@@ -44,7 +44,7 @@ class Gallery extends Controller {
 	}
 
 	public function CreateAlbum($title,$description) {
-		Breadcrumbs::Add("Manage", "");
+		Breadcrumbs::Add("Manage", "/gallery/manage/");
 		Breadcrumbs::Add("Create Album", "");
 		$album=new Album();
 		if (isset($title))
@@ -59,44 +59,48 @@ class Gallery extends Controller {
 		return new View("Gallery/create_album.view",array("album"=>$album));
 	}
 
-	public function Upload($imageTitle,$imageDesc) {
-		if (!isset($_FILES['imageFile'])){
-			header("location: /gallery/manage/");
+	public function Upload(){
+		Breadcrumbs::Add("Manage", "/gallery/manage/");
+		Breadcrumbs::Add("Upload", "");
+		return new View("Gallery/upload.view", array("maxUploadSize"=>Utils::GetMaxUploadSize()));
+	}
+
+	public function UploadImages($title, $description){
+		if (count($title)==0)
 			return;
-		}
-		$errors=array();
-		$filename=$_FILES['imageFile']['name'];
-		$tempFile=$_FILES['imageFile']['tmp_name'];
-		if ($imageTitle=="")
-			$errors[]="The image doesn't have a title";
-		if ($_FILES['imageFile']['error'])
-			$errors[]=Utils::FileUploadErrorToMessage($_FILES['imageFile']['error']);
-		if (count($errors)==0) {
-			if(!Image::IsValidType(pathinfo($filename,PATHINFO_EXTENSION)))
-				$errors[]="File is of an invalid type";
-			if (getimagesize($tempFile)===false)
-				$errors[]="File is not an image";
-		}
-		$image=new Image($filename,$tempFile);
-		$image->ImageTitle=$imageTitle;
-		$image->ImageDescription=$imageDesc;
-		if (count($errors)==0){
+
+		$allErrors=array();
+		$uploadedImages=array();
+		$files=$_FILES['files'];
+		foreach ($files['tmp_name'] as $index=>$tempFile) {
+			$filename=$files['name'][$index];
+			$imageTitle=$title[$index];
+			$errors=array();
+
+			if ($imageTitle=="")
+				$errors[]="The image $filename doesn't have a title";
+			if ($files['error'][$index])
+				$errors[]=Utils::FileUploadErrorToMessage($files['error'][$index]);
+
+			if (count($errors)==0) {
+				if(!Image::IsValidType(pathinfo($filename,PATHINFO_EXTENSION)))
+					$errors[]="File is of an invalid type";
+				if (getimagesize($tempFile)===false)
+					$errors[]="File is not an image";
+			}
+
+			if (count($errors)>0){
+				$allErrors=array_merge($allErrors, $errors);
+				continue;
+			}
+			$uploadedImages[]=$index;
+
+			$image=new Image($filename,$tempFile);
+			$image->ImageTitle=$imageTitle;
+			$image->ImageDescription=$description[$index];
 			$image->Save();
-			header("location: /gallery/manage/");
-			return;
 		}
-		return $this->Manage(array("errors"=>$errors,"image"=>$image));
-	}
 
-	public function JsonLoadAlbum($albumId) {
-		$repo=new ImageRepository();
-		$json='[';
-		$images=$repo->GetImagesByAlbum($albumId);
-		foreach ($images as $image)
-			$json.='{"ImageId":"'.$image->ImageId.'","ImageTitle":"'.$image->ImageTitle.'","Path":"'.$image->Path.'","ThumbnailPath":"'.$image->ThumbnailPath.'"},';
-		$json=trim($json,',');
-		$json.=']';
-		return $json;
-		//return json_encode($images);
+		return json_encode(array("errors"=>$allErrors, "uploaded"=>$uploadedImages));
 	}
 }

+ 1 - 1
View/Gallery/manage.view

@@ -5,7 +5,7 @@
 }@
 @Title{Gallery}@
 @ButtonsLeft{
-	<button onclick="Navigate('/gallery/createalbum/')" title="Upload Images">
+	<button onclick="Navigate('/gallery/upload/')" title="Upload Images">
 		<img src="/images/upload.svg" alt="Add Album" />
 	</button>
 }@

+ 45 - 0
View/Gallery/upload.view

@@ -0,0 +1,45 @@
+@Init{
+	$this->RegisterCSSFile("gallery.css");
+	$this->RegisterJSFile("controllers/galleryUpload.js");
+	$this->RegisterJSFile("directives/dragDrop.js");
+	$this->RegisterJSFile("directives/contextMenu.js");
+}@
+@Title{Gallery}@
+@ButtonsRight{
+	<?php if (Session::GetLoggedInUser()->HasAccess("gallery/manage")){?>
+		<button onclick="Navigate('/gallery/manage/')" title="Manage">
+			<img src="/images/gallery.svg" alt="Manage" />
+		</button>
+	<?php } ?>
+}@
+@Body{
+<div ng-controller="galleryUpload" id="galleryUpload">
+	<div drag-drop="fileDrop" class="dragDrop" ng-class="{dragOver:images.length===0}">
+		<context-menu actions="contextMenuActions"></context-menu>
+		<div ng-repeat="image in images" class="newImage">
+			<table class="information">
+				<tr>
+					<td>File name:</td>
+					<td>{{image.file.name}}</td>
+				</tr>
+				<tr>
+					<td><label for="imageTitle">Image title:</label></td>
+					<td><input type="text" name="imageTitle" ng-model="image.title" /></td>
+				</tr>
+				<tr>
+					<td><label for="imageDesc">Image description</label></td>
+					<td><textarea name="imageDesc" ng-model="image.description"></textarea></td>
+				</tr>
+			</table>
+			<div class="preview">
+				<div class="imageContainer">
+					<img ng-src="{{image.preview}}" />
+				</div>
+			</div>
+		</div>
+	</div>
+	<div class="percentageBar"><div style="width:{{(getTotalImageSize() / safeUploadSize()) * 100}}%"></div></div>
+	<button class="upload" ng-if="images.length>0" ng-click="upload()">Upload</button>
+	<scope-init value="maxUploadSize"><?=$maxUploadSize?></scope-init>
+</div>
+}@

+ 14 - 10
base/Utils.php

@@ -64,14 +64,6 @@ class Utils {
 		setcookie($cookie, null, -1, '/');
 	}
 
-	public static function GetMaxUploadString() {
-		$maxUpload=(int)ini_get('upload_max_filesize');
-		$maxPost=(int)ini_get('post_max_size');
-		$memoryLimit=(int)ini_get('memory_limit');
-		$size=min($maxUpload, $maxPost, $memoryLimit);
-		return $size.'MB';
-	}
-
 	public static function MakeStringUrlSafe($string) {
 		$string=strtolower($string);
 		$string=preg_replace('/[^a-zA-Z0-9\.]+/', '-', $string);
@@ -150,7 +142,7 @@ class Utils {
 		$openedtags=array_reverse($openedtags);
 		for ($i=0; $i<$len_opened; $i++) {
 			if (!in_array($openedtags[$i], $closedtags)) {
-				$html .= '</'.$openedtags[$i].'>';
+				$html.='</'.$openedtags[$i].'>';
 			} else {
 				unset($closedtags[array_search($openedtags[$i], $closedtags)]);
 			}
@@ -185,7 +177,7 @@ class Utils {
 		$charactersLength=strlen($characters);
 		$randomString='';
 		for ($i=0; $i<$length; $i++) {
-			$randomString .= $characters[rand(0, $charactersLength-1)];
+			$randomString.=$characters[rand(0, $charactersLength-1)];
 		}
 		return $randomString;
 	}
@@ -198,4 +190,16 @@ class Utils {
 		mail("var_dump@robware.uk", "var_dump", $output);
 	}
 
+	public static function ParseSize($size) {
+		$unit=preg_replace('/[^bkmgtpezy]/i', '', $size);
+		$size=preg_replace('/[^0-9\.]/', '', $size);
+		return round($unit ? $size*pow(1024, stripos('bkmgtpezy', $unit[0])) : $size);
+	}
+
+	public static function GetMaxUploadSize() {
+		$maxUpload=Utils::ParseSize(ini_get('upload_max_filesize'));
+		$maxPost=Utils::ParseSize(ini_get('post_max_size'));
+		$memoryLimit=Utils::ParseSize(ini_get('memory_limit'));
+		return min($maxUpload, $maxPost, $memoryLimit);
+	}
 }

+ 2 - 1
css/contextMenu.css

@@ -3,7 +3,7 @@
   float: left;
   background: #fafafa;
   border: 1px solid #9e9e9e;
-  border-radius: 5px;
+  border-radius: 3px;
   padding: 3px 0;
   box-shadow: 3px 3px 5px 0px rgba(0, 0, 0, 0.5);
   -webkit-user-select: none;
@@ -14,6 +14,7 @@
   /* IE 10+ */
   user-select: none;
   cursor: default;
+  z-index: 10000;
 }
 .contextMenu .menuItem {
   padding: 0 5px;

+ 37 - 0
css/gallery.css

@@ -116,3 +116,40 @@
 #album-viewer #album-images .image img {
   max-height: 100%;
 }
+#galleryUpload .upload {
+  font-size: 2em;
+  padding: 5px;
+}
+#galleryUpload .dragDrop {
+  min-height: 200px;
+  border-color: #fafafa;
+  background-position: center;
+  background-size: 100px;
+  margin-bottom: 5px;
+}
+#galleryUpload .dragDrop.dragOver {
+  border: 3px dashed #616161;
+  background-image: url("/images/upload.svg");
+  background-repeat: no-repeat;
+}
+#galleryUpload .newImage {
+  display: flex;
+  margin-bottom: 5px;
+}
+#galleryUpload .newImage .information {
+  flex: 1;
+}
+#galleryUpload .newImage .preview {
+  flex: 1;
+  position: relative;
+}
+#galleryUpload .newImage .preview .imageContainer {
+  position: absolute;
+  top: 0;
+  bottom: 0;
+  right: 0;
+  text-align: right;
+}
+#galleryUpload .newImage .preview .imageContainer img {
+  height: 100%;
+}

+ 6 - 4
css/style.css

@@ -28,7 +28,7 @@
   float: left;
   background: #fafafa;
   border: 1px solid #9e9e9e;
-  border-radius: 5px;
+  border-radius: 3px;
   padding: 3px 0;
   box-shadow: 3px 3px 5px 0px rgba(0, 0, 0, 0.5);
   -webkit-user-select: none;
@@ -39,6 +39,7 @@
   /* IE 10+ */
   user-select: none;
   cursor: default;
+  z-index: 10000;
 }
 .contextMenu .menuItem {
   padding: 0 5px;
@@ -210,10 +211,13 @@ p:last-child {
   flex: 1 1 auto;
   overflow: auto;
   z-index: 0;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
 }
 #main #content #body {
   padding: 20px;
-  height: 100%;
+  flex: 1;
 }
 #buttons-left,
 #buttons-right {
@@ -344,8 +348,6 @@ p:last-child {
 }
 .errors {
   background: url(/images/error.svg) no-repeat scroll 10px 10px #424242;
-  margin: -20px;
-  margin-bottom: 20px;
   padding: 10px;
   padding-left: 40px;
   color: #fff;

+ 2 - 1
less/contextMenu.less

@@ -5,7 +5,7 @@
 	float:left;
 	background:@background;
 	border:1px solid @Grey-500;
-	border-radius:5px;
+	border-radius:3px;
 	padding: 3px 0;
 	box-shadow: 3px 3px 5px 0px rgba(0, 0, 0, 0.5);
 	-webkit-user-select: none;  /* Chrome all / Safari all */
@@ -13,6 +13,7 @@
 	-ms-user-select: none;      /* IE 10+ */
 	user-select: none;
 	cursor:default;
+	z-index:10000;
 
 	.menuItem{
 		padding: 0 5px;

+ 48 - 0
less/gallery.less

@@ -147,4 +147,52 @@
 			}
 		}
 	}
+}
+
+#galleryUpload{
+
+	.upload{
+		font-size:2em;
+		padding:5px;
+	}
+
+	.dragDrop{
+		min-height:200px;
+		border-color: @background;
+		background-position: center;
+		background-size: 100px;
+		margin-bottom:5px;
+
+		&.dragOver{
+			border: 3px dashed @Grey-700;
+			background-image: url("/images/upload.svg");
+			background-repeat: no-repeat;
+		}
+	}
+
+	.newImage{
+		display:flex;
+		margin-bottom:5px;
+
+		.information{
+			flex:1;
+		}
+
+		.preview{
+			flex:1;
+			position: relative;
+
+			.imageContainer{
+				position:absolute;
+				top:0;
+				bottom:0;
+				right:0;
+				text-align:right;
+
+				img{
+					height:100%;
+				}
+			}
+		}
+	}
 }

+ 5 - 3
less/style.less

@@ -188,10 +188,14 @@ p{
 		flex: 1 1 auto;
 		overflow:auto;
 		z-index:0;
+		height:100%;
+		display:flex;
+		flex-direction: column;
 
 		#body{
 			padding:20px;
-			height:100%;
+			//height:100%;
+			flex: 1;
 		}
 	}
 
@@ -344,8 +348,6 @@ p{
 
 .errors{
 	background: url(/images/error.svg) no-repeat scroll 10px 10px @Grey-800;
-	margin:-20px;
-	margin-bottom: 20px;
 	padding:10px;
 	padding-left:40px;
 	color:#fff;

+ 3 - 3
scripts/controllers/galleryManage.js

@@ -2,7 +2,7 @@ app.controller('galleryManage', function($scope, $http) {
 	$scope.albums=[];
 	var albumActions={};
 
-	var moveIndex="Move selected to album"
+	var moveIndex="Move selected to album";
 	$scope.contextMenuActions={
 		"Delete selected":deleteSelected
 	};
@@ -20,7 +20,7 @@ app.controller('galleryManage', function($scope, $http) {
 		var selectedId=$scope.selectedAlbum.AlbumId;
 		$scope.albums=data;
 		angular.forEach($scope.albums, function(album){
-			if (album.AlbumId==selectedId)
+			if (album.AlbumId===selectedId)
 				$scope.selectedAlbum=album;
 		});
 	}
@@ -72,6 +72,6 @@ app.controller('galleryManage', function($scope, $http) {
 
 	$scope.editAlbum=function(album){
 		Navigate("/gallery/editablum/"+album.AlbumId);
-	}
+	};
 });
 

+ 101 - 0
scripts/controllers/galleryUpload.js

@@ -0,0 +1,101 @@
+app.controller('galleryUpload', function($scope, $http) {
+	$scope.images = [];
+	$scope.contextMenuActions = {
+		Remove: remove,
+		Clear: clear
+	};
+
+	function remove(containerScope) {
+		if (!containerScope.image)
+			return;
+
+		var index = $scope.images.indexOf(containerScope.image);
+		if (index !== -1)
+			$scope.images.splice(index, 1);
+	}
+
+	function clear() {
+		if (confirm("Are you sure you want to clear the uploads?"))
+			$scope.images = [];
+	}
+
+	function removeImages(toRemove) {
+		// I need this roundabout way to work with Angular's view engine
+		var imageReferences = [];
+		angular.forEach(toRemove, function(index) {
+			imageReferences.push($scope.images[index]);
+		});
+		angular.forEach(imageReferences, function(ref) {
+			$scope.images.splice($scope.images.indexOf(ref), 1);
+		});
+	}
+
+	$scope.getTotalImageSize = function() {
+		var totalSize = 0;
+		angular.forEach($scope.images, function(image) {
+			totalSize += image.file.size;
+		});
+		return totalSize;
+	}
+
+	$scope.safeUploadSize = function() {
+		return $scope.maxUploadSize * 0.95;
+	}
+
+	$scope.fileDrop = function(files) {
+		angular.forEach(files, function(file) {
+			var imageObject = {
+				file: file,
+				title: file.name.replace(/\.[^/.]+$/, ""),
+				description: "",
+				preview: ""
+			};
+
+			var reader = new FileReader();
+			reader.onload = function(e) {
+				imageObject.preview = e.target.result;
+				$scope.$apply();
+			};
+			reader.readAsDataURL(file);
+
+			$scope.images.push(imageObject);
+		});
+	};
+
+	$scope.upload = function() {
+		var data = new FormData();
+		var errors = [];
+		angular.forEach($scope.images, function(image) {
+			if (!image.title)
+				errors.push("The image " + image.file.name + " doesn't have a title");
+			data.append("files[]", image.file);
+			data.append("title[]", image.title);
+			data.append("description[]", image.description);
+		});
+
+		var totalSize = 0;
+		for (var entry of data.getAll("files[]")) {
+			totalSize += entry.size;
+		}
+		if (totalSize>$scope.safeUploadSize())
+			errors.push("Max upload size exceeded");
+
+		if (errors.length>0){
+			$scope.showErrors(errors);
+			return;
+		}
+
+		$http({
+			method: 'POST',
+			url: '/gallery/uploadimages',
+			data: data,
+			showSpinner: true,
+			headers: {'Content-Type': undefined},
+			transformRequest: angular.identity
+		}).then(function(response) {
+			$scope.showErrors(response.data.errors);
+			//removeImages(response.data.uploaded);
+		});
+	};
+});
+

+ 5 - 0
scripts/controllers/main.js

@@ -98,4 +98,9 @@ app.controller("main", ['$scope', '$rootScope', function($scope, $rootScope) {
 				//$scope.menuVisible = false;
 			});
 		});
+
+		$scope.errors=[];
+		$scope.showErrors=function(errors){
+			$scope.errors=errors;
+		}
 	}]);

+ 5 - 8
scripts/directives/contextMenu.js

@@ -1,10 +1,7 @@
-/* global google, angular */
-
 app.directive('contextMenu', function() {
 	return {
 		restrict: 'E',
 		templateUrl: '/scripts/directives/templates/contextMenu.html',
-		replace:false,
 		scope: {
 			actions: '=',
 			isChild: '=?'
@@ -17,15 +14,15 @@ app.directive('contextMenu', function() {
 
 			scope.changeSubMenuShowKey=function(name){
 				scope.subMenuShowKey=name;
-			}
+			};
 
 			function show(x, y, target){
 				scope.changeSubMenuShowKey("");
 				scope.menuX=x+"px";
 				scope.menuY=y+"px";
-				scope.selectedElement=target
+				scope.selectedElement=target;
 				scope.show=true;
-			}
+			};
 
 			function hide(){
 				scope.show=false;
@@ -34,9 +31,9 @@ app.directive('contextMenu', function() {
 			scope.performAction=function(func){
 				if (!angular.isFunction(func))
 					return;
-				func(scope.selectedElement);
+				func($(scope.selectedElement).scope());
 				hide();
-			}
+			};
 
 			$(document).on("click", function(e){
 				hide();

+ 38 - 0
scripts/directives/dragDrop.js

@@ -0,0 +1,38 @@
+app.directive('dragDrop', function() {
+	return {
+		restrict: 'A',
+		scope: {
+			dragDrop:'='
+		},
+		link: function(scope, element) {
+			function handleEvent(e){
+				e.preventDefault();
+				e.stopPropagation();
+			}
+
+			element.on('dragover', function(e) {
+				handleEvent(e);
+			});
+			element.on('dragenter', function(e) {
+				handleEvent(e);
+				$(element).addClass("dragOver");
+			});
+			element.on('dragleave', function(e) {
+				handleEvent(e);
+				$(element).removeClass("dragOver");
+			});
+			element.on('drop', function(e){
+				handleEvent(e);
+				$(element).removeClass("dragOver");
+
+				if (e.originalEvent.dataTransfer){
+					if (e.originalEvent.dataTransfer.files.length > 0) {
+						scope.dragDrop(e.originalEvent.dataTransfer.files);
+						scope.$apply();
+					}
+				}
+			});
+		}
+	};
+});
+

+ 1 - 1
scripts/javascript.js

@@ -92,7 +92,7 @@ $(function() {
 		return false;
 	});
 
-	$("#content").css("padding-bottom", window.innerHeight - $("#buttons-right").offset().top);
+	$("#body").css("padding-bottom", window.innerHeight - $("#buttons-right").offset().top);
 
 	$("form[ajaxForm]").submit(function(e) {
 		e.preventDefault();

+ 17 - 10
template.php

@@ -49,10 +49,10 @@ function FormatURI(URI $uri, $base=""){
 		<style media="(max-width:680px)">
 			{@CSSSmall}
 		</style>
-		<script src="//<?=$angularSource?>/<?=$angularVersion?>/angular.js" defer></script>
-		<script src="//<?=$angularSource?>/<?=$angularVersion?>/angular-animate.min.js" defer></script>
 		<script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" defer></script>
 		<script src="//ajax.googleapis.com/ajax/libs/jqueryui/1.11.2/jquery-ui.min.js" defer></script>
+		<script src="//<?=$angularSource?>/<?=$angularVersion?>/angular.js" defer></script>
+		<script src="//<?=$angularSource?>/<?=$angularVersion?>/angular-animate.min.js" defer></script>
 		<?php foreach ($this->GetJSFiles() as $js){
 			echo '<script src="';
 			if (strpos($js, "http")===false)
@@ -78,13 +78,13 @@ function FormatURI(URI $uri, $base=""){
 						$uriParts=explode("/",$reqUri);
 						if ($uriParts[0]=="")
 							$uriParts[0]="home";
-							
+
 						$__controllers=Navigation::Get();
 						foreach ($__controllers as $controller){
 							$cURI=$controller->GetURI();
 							if ($cURI==null)
 								continue;
-							
+
 							echo '<dt';
 							if ($cURI->GetLinkLocation()=='/'.$uriParts[0])
 								echo ' class="active"';
@@ -115,14 +115,21 @@ function FormatURI(URI $uri, $base=""){
 						echo '<span>',$crumb['text'],'</span>';
 				?></h2>
 			</div>
+			<?php if (isset($errors) && count($errors)>0){
+				echo '<div class="errors">The following errors were encountered:<ul>';
+				foreach ($errors as $e)
+					echo '<li>',$e,'</li>';
+				echo '</ul>Please rectify them and try again.</div>';
+			} ?>
+			<div class="errors" ng-if="errors.length>0">
+				The following errors were encountered:
+				<ul>
+					<li ng-repeat="error in errors">{{error}}</li>
+				</ul>
+				Please rectify them and try again.
+			</div>
 			<div id="content">
 				<div id="body">
-					<?php if (isset($errors) && count($errors)>0){
-						echo '<div class="errors">The following errors were encountered:<ul>';
-						foreach ($errors as $e)
-							echo '<li>',$e,'</li>';
-						echo '</ul>Please rectify them and try again.</div>';
-					} ?>
 					{@Body}
 				</div>
 				<div id="footer">{@Footer}</div>