Browse Source

Merge branch 'multiselect' into 2.1

Tobias Reich 11 years ago
parent
commit
d71be1d3ce

+ 1 - 0
assets/css/modules/content.css

@@ -26,6 +26,7 @@
 	position: absolute;
 	padding: 50px 0px 33px 0px;
 	width: 100%;
+	min-height: calc(100% - 90px);
 	-webkit-overflow-scrolling: touch;
 }
 

+ 13 - 0
assets/css/modules/multiselect.css

@@ -0,0 +1,13 @@
+/**
+ * @name        multiselect.css
+ * @author      Tobias Reich
+ * @copyright   2014 by Tobias Reich
+ */
+
+#multiselect {
+	position: absolute;
+	background-color: RGBA(0, 94, 204, .3);
+	border: 1px solid RGBA(0, 94, 204, 1);
+	border-radius: 3px;
+	z-index: 3;
+}

+ 53 - 25
assets/js/modules/album.js

@@ -121,21 +121,28 @@ album = {
 
 	},
 
-	delete: function(albumID) {
+	delete: function(albumIDs) {
 
 		var params,
 			buttons,
 			albumTitle;
+			
+		if (!albumIDs) return false;
+		if (albumIDs instanceof Array===false) albumIDs = [albumIDs];
 
 		buttons = [
-			["Delete Album and Photos", function() {
+			["", function() {
 
-				params = "deleteAlbum&albumID=" + albumID;
+				params = "deleteAlbum&albumIDs=" + albumIDs;
 				lychee.api(params, function(data) {
 
 					if (visible.albums()) {
-						albums.json.num--;
-						view.albums.content.delete(albumID);
+					
+						albumIDs.forEach(function(id, index, array) {
+							albums.json.num--;
+							view.albums.content.delete(id);
+						});
+						
 					} else lychee.goto("");
 
 					if (data!==true) lychee.error(null, params, data);
@@ -143,69 +150,90 @@ album = {
 				});
 
 			}],
-			["Keep Album", function() {}]
+			["", function() {}]
 		];
 
-		if (albumID==="0") {
+		if (albumIDs==="0") {
 
 			buttons[0][0] = "Clear Unsorted";
+			buttons[1][0] = "Keep Unsorted";
+			
 			modal.show("Clear Unsorted", "Are you sure you want to delete all photos from 'Unsorted'?<br>This action can't be undone!", buttons)
 
-		} else {
-
+		} else if (albumIDs.length===1) {
+		
+			buttons[0][0] = "Delete Album and Photos";
+			buttons[1][0] = "Keep Album";
+		
+			// Get title
 			if (album.json) albumTitle = album.json.title;
-			else if (albums.json) albumTitle = albums.json.content[albumID].title;
+			else if (albums.json) albumTitle = albums.json.content[albumIDs].title;
+			
 			modal.show("Delete Album", "Are you sure you want to delete the album '" + albumTitle + "' and all of the photos it contains? This action can't be undone!", buttons);
 
+		} else {
+		
+			buttons[0][0] = "Delete Albums and Photos";
+			buttons[1][0] = "Keep Albums";
+			
+			modal.show("Delete Albums", "Are you sure you want to delete all " + albumIDs.length + " selected albums and all of the photos they contain? This action can't be undone!", buttons);
+		
 		}
 
 	},
 
-	setTitle: function(albumID) {
+	setTitle: function(albumIDs) {
 
 		var oldTitle = "",
 			newTitle,
 			params,
 			buttons;
 
-		if (!albumID) return false;
-		if (album.json) oldTitle = album.json.title;
-		else if (albums.json) oldTitle = albums.json.content[albumID].title;
+		if (!albumIDs) return false;
+		if (albumIDs instanceof Array===false) albumIDs = [albumIDs];
+		
+		if (albumIDs.length===1) {
+			// Get old title if only one album is selected
+			if (album.json) oldTitle = album.json.title;
+			else if (albums.json) oldTitle = albums.json.content[albumIDs].title;
+		}
 
 		buttons = [
 			["Set Title", function() {
 
-				newTitle = $(".message input.text").val();
-
-				if (newTitle==="") newTitle = "Untitled";
+				newTitle = ($(".message input.text").val()==="") ? "Untitled" : $(".message input.text").val();
 
-				if (albumID!==""&&albumID!=null&&albumID&&newTitle.length<31) {
+				if (newTitle.length<31) {
 
 					if (visible.album()) {
 
 						album.json.title = newTitle;
-						view.album.title(oldTitle);
+						view.album.title();
 
 					} else if (visible.albums()) {
-
-						albums.json.content[albumID].title = newTitle;
-						view.albums.content.title(albumID);
+					
+						albumIDs.forEach(function(id, index, array) {
+							albums.json.content[id].title = newTitle;
+							view.albums.content.title(id);
+						});
 
 					}
 
-					params = "setAlbumTitle&albumID=" + albumID + "&title=" + escape(encodeURI(newTitle));
+					params = "setAlbumTitle&albumIDs=" + albumIDs + "&title=" + escape(encodeURI(newTitle));
 					lychee.api(params, function(data) {
 
 						if (data!==true) lychee.error(null, params, data);
 
 					});
 
-				} else if (newTitle.length>0) loadingBar.show("error", "New title too short or too long. Please try again!");
+				} else if (newTitle.length>30) loadingBar.show("error", "New title too long. Please try another one!");
 
 			}],
 			["Cancel", function() {}]
 		];
-		modal.show("Set Title", "Please enter a new title for this album: <input class='text' type='text' placeholder='Title' value='" + oldTitle + "'>", buttons);
+		
+		if (albumIDs.length===1) modal.show("Set Title", "Please enter a new title for this album: <input class='text' type='text' placeholder='Title' value='" + oldTitle + "'>", buttons);
+		else modal.show("Set Titles", "Please enter a title for all " + albumIDs.length + " selected album: <input class='text' type='text' placeholder='Title' value='" + oldTitle + "'>", buttons);
 
 	},
 

+ 6 - 0
assets/js/modules/build.js

@@ -18,6 +18,12 @@ build = {
 		return "<div id='" + id + "' class='edit'><a class='icon-pencil'></a></div>";
 
 	},
+		
+	multiselect: function(top, left) {
+	
+		return "<div id='multiselect' style='top: " + top + "px; left: " + left + "px;'></div>";
+	
+	},
 
 	album: function(albumJSON) {
 

+ 89 - 35
assets/js/modules/contextMenu.js

@@ -11,20 +11,31 @@ contextMenu = {
 
 	show: function(items, mouse_x, mouse_y, orientation) {
 
-		if (visible.contextMenu()) contextMenu.close();
+		contextMenu.close();
 
 		$("body")
 			.css("overflow", "hidden")
 			.append(build.contextMenu(items));
 
+		// Do not leave the screen
 		if ((mouse_x+$(".contextmenu").outerWidth(true))>$("html").width()) orientation = "left";
 		if ((mouse_y+$(".contextmenu").outerHeight(true))>$("html").height()) mouse_y -= (mouse_y+$(".contextmenu").outerHeight(true)-$("html").height())
 
+		if (mouse_x>$(document).width()) mouse_x = $(document).width();
+		if (mouse_x<0) mouse_x = 0;
+		if (mouse_y>$(document).height()) mouse_y = $(document).height();
+		if (mouse_y<0) mouse_y = 0;	
+		
 		if (orientation==="left") mouse_x -= $(".contextmenu").outerWidth(true);
 
-		if (!mouse_x||!mouse_y) {
-			mouse_x = "10px";
-			mouse_y = "10px";
+		if (mouse_x===null||
+			mouse_x===undefined||
+			mouse_x===NaN||
+			mouse_y===null||
+			mouse_y===undefined||
+			mouse_y===NaN) {
+				mouse_x = "10px";
+				mouse_y = "10px";
 		}
 
 		$(".contextmenu").css({
@@ -38,11 +49,9 @@ contextMenu = {
 	add: function(e) {
 
 		var mouse_x = e.pageX,
-			mouse_y = e.pageY,
+			mouse_y = e.pageY - $(document).scrollTop(),
 			items;
 
-		mouse_y -= $(document).scrollTop();
-
 		contextMenu.fns = [
 			function() { $("#upload_files").click() },
 			function() { upload.start.url() },
@@ -68,11 +77,9 @@ contextMenu = {
 	settings: function(e) {
 
 		var mouse_x = e.pageX,
-			mouse_y = e.pageY,
+			mouse_y = e.pageY - $(document).scrollTop(),
 			items;
 
-		mouse_y -= $(document).scrollTop();
-
 		contextMenu.fns = [
 			function() { settings.setLogin() },
 			function() { settings.setSorting() },
@@ -95,12 +102,10 @@ contextMenu = {
 	album: function(albumID, e) {
 
 		var mouse_x = e.pageX,
-			mouse_y = e.pageY,
+			mouse_y = e.pageY - $(document).scrollTop(),
 			items;
 
-		if (albumID==="0"||albumID==="f"||albumID==="s") return false;
-
-		mouse_y -= $(document).scrollTop();
+		if (albumID==="0"||albumID==="f"||albumID==="s") return false;	
 
 		contextMenu.fns = [
 			function() { album.setTitle(albumID) },
@@ -117,20 +122,40 @@ contextMenu = {
 		$(".album[data-id='" + albumID + "']").addClass("active");
 
 	},
+	
+	albumMulti: function(albumIDs, e) {
+		
+		var mouse_x = e.pageX,
+			mouse_y = e.pageY - $(document).scrollTop(),
+			items;
+
+		multiselect.stopResize();
+
+		contextMenu.fns = [
+			function() { album.setTitle(albumIDs) },
+			function() { album.delete(albumIDs) },
+		];
+
+		items = [
+			["<a class='icon-edit'></a> Rename All", 0],
+			["<a class='icon-trash'></a> Delete All", 1]
+		];
+
+		contextMenu.show(items, mouse_x, mouse_y, "right");
+
+	},
 
 	photo: function(photoID, e) {
 
 		var mouse_x = e.pageX,
-			mouse_y = e.pageY,
+			mouse_y = e.pageY - $(document).scrollTop(),
 			items;
 
-		mouse_y -= $(document).scrollTop();
-
 		contextMenu.fns = [
-			function() { photo.setStar(photoID) },
-			function() { photo.setTitle(photoID) },
-			function() { contextMenu.move(photoID, e, "right") },
-			function() { photo.delete(photoID) }
+			function() { photo.setStar([photoID]) },
+			function() { photo.setTitle([photoID]) },
+			function() { contextMenu.move([photoID], e, "right") },
+			function() { photo.delete([photoID]) }
 		];
 
 		items = [
@@ -146,18 +171,45 @@ contextMenu = {
 		$(".photo[data-id='" + photoID + "']").addClass("active");
 
 	},
+	
+	photoMulti: function(photoIDs, e) {
+	
+		var mouse_x = e.pageX,
+			mouse_y = e.pageY - $(document).scrollTop(),
+			items;
 
-	move: function(photoID, e, orientation) {
+		multiselect.stopResize();
+
+		contextMenu.fns = [
+			function() { photo.setStar(photoIDs) },
+			function() { photo.setTitle(photoIDs) },
+			function() { contextMenu.move(photoIDs, e, "right") },
+			function() { photo.delete(photoIDs) }
+		];
+
+		items = [
+			["<a class='icon-star'></a> Star All", 0],
+			["separator", -1],
+			["<a class='icon-edit'></a> Rename All", 1],
+			["<a class='icon-folder-open'></a> Move All", 2],
+			["<a class='icon-trash'></a> Delete All", 3]
+		];
+
+		contextMenu.show(items, mouse_x, mouse_y, "right");
+
+	},
+
+	move: function(photoIDs, e, orientation) {
 
 		var mouse_x = e.pageX,
-			mouse_y = e.pageY,
+			mouse_y = e.pageY - $(document).scrollTop(),
 			items = [];
 
-		contextMenu.fns = [];
+		contextMenu.close(true);
 
 		if (album.getID()!=="0") {
 			items = [
-				["Unsorted", 0, "photo.setAlbum(0, " + photoID + ")"],
+				["Unsorted", 0, "photo.setAlbum([" + photoIDs + "], 0)"],
 				["separator", -1]
 			];
 		}
@@ -168,14 +220,10 @@ contextMenu = {
 				items = [["New Album", 0, "album.add()"]];
 			} else {
 				$.each(data.content, function(index) {
-					if (this.id!=album.getID()) items.push([this.title, 0, "photo.setAlbum(" + this.id + ", " + photoID + ")"]);
+					if (this.id!=album.getID()) items.push([this.title, 0, "photo.setAlbum([" + photoIDs + "], " + this.id + ")"]);
 				});
 			}
 
-			contextMenu.close();
-
-			$(".photo[data-id='" + photoID + "']").addClass("active");
-
 			if (!visible.photo()) contextMenu.show(items, mouse_x, mouse_y, "right");
 			else contextMenu.show(items, mouse_x, mouse_y, "left");
 
@@ -255,13 +303,19 @@ contextMenu = {
 
 	},
 
-	close: function() {
-
-		contextMenu.js = null;
-
+	close: function(leaveSelection) {
+	
+		if (!visible.contextMenu()) return false;
+	
+		contextMenu.fns = [];
+		
 		$(".contextmenu_bg, .contextmenu").remove();
-		$(".photo.active, .album.active").removeClass("active");
 		$("body").css("overflow", "auto");
+		
+		if (leaveSelection!==true) {
+			$(".photo.active, .album.active").removeClass("active");
+			if (visible.multiselect()) multiselect.close();
+		}
 
 	}
 

+ 14 - 10
assets/js/modules/init.js

@@ -11,13 +11,17 @@ $(document).ready(function(){
 
 	/* Notifications */
 	if (window.webkitNotifications) window.webkitNotifications.requestPermission();
-	
+
 	/* Disable ContextMenu */
 	$(document).bind("contextmenu", function(e) { e.preventDefault() });
 
 	/* Tooltips */
 	if (!mobileBrowser()) $(".tools").tipsy({gravity: 'n', fade: false, delayIn: 0, opacity: 1});
 
+	/* Multiselect */
+	$("#content").on("mousedown", multiselect.show);
+	$(document).on("mouseup", multiselect.getSelection);
+
 	/* Header */
 	$("#hostedwith").on(event_name, function() { window.open(lychee.website,"_newtab") });
 	$("#button_signin").on(event_name, lychee.loginDialog);
@@ -32,18 +36,18 @@ $(document).ready(function(){
 	});
 	$("#button_download").on(event_name, function() { photo.getArchive(photo.getID()) });
 	$("#button_trash_album").on(event_name, function() { album.delete(album.getID()) });
-	$("#button_move").on(event_name, function(e) { contextMenu.move(photo.getID(), e) });
-	$("#button_trash").on(event_name, function() { photo.delete(photo.getID()) });
+	$("#button_move").on(event_name, function(e) { contextMenu.move([photo.getID()], e) });
+	$("#button_trash").on(event_name, function() { photo.delete([photo.getID()]) });
 	$("#button_info_album").on(event_name, function() { view.infobox.show() });
 	$("#button_info").on(event_name, function() { view.infobox.show() });
 	$("#button_archive").on(event_name, function() { album.getArchive(album.getID()) });
-	$("#button_star").on(event_name, function() { photo.setStar(photo.getID()) });
+	$("#button_star").on(event_name, function() { photo.setStar([photo.getID()]) });
 
 	/* Search */
 	$("#search").on("keyup click", function() { search.find($(this).val()) });
-	
+
 	/* Clear Search */
-	$("#clearSearch").on(event_name, function () { 
+	$("#clearSearch").on(event_name, function () {
 	    $("#search").focus();
 	    search.reset();
 	});
@@ -66,14 +70,14 @@ $(document).ready(function(){
 		.on(event_name, ".header a", function() { view.infobox.hide() })
 		.on(event_name, "#edit_title_album", function() { album.setTitle(album.getID()) })
 		.on(event_name, "#edit_description_album", function() { album.setDescription(album.getID()) })
-		.on(event_name, "#edit_title", function() { photo.setTitle(photo.getID()) })
+		.on(event_name, "#edit_title", function() { photo.setTitle([photo.getID()]) })
 		.on(event_name, "#edit_description", function() { photo.setDescription(photo.getID()) });
 
 	/* Keyboard */
 	Mousetrap
 		.bind('u', function() { $("#upload_files").click() })
 		.bind('s', function() { if (visible.photo()) $("#button_star").click() })
-		.bind('command+backspace', function() { if (visible.photo()&&!visible.message()) photo.delete(photo.getID()) })
+		.bind('command+backspace', function() { if (visible.photo()&&!visible.message()) photo.delete([photo.getID()]) })
 		.bind('left', function() { if (visible.photo()) $("#imageview a#previous").click() })
 		.bind('right', function() { if (visible.photo()) $("#imageview a#next").click() })
 		.bind('i', function() {
@@ -103,10 +107,10 @@ $(document).ready(function(){
 
 		/* Header */
 		.on(event_name, "#title.editable", function() {
-			if (visible.photo()) photo.setTitle(photo.getID());
+			if (visible.photo()) photo.setTitle([photo.getID()]);
 			else album.setTitle(album.getID());
 		})
-    
+
 		/* Navigation */
 		.on("click", ".album", function() { lychee.goto($(this).attr("data-id")) })
 		.on("click", ".photo", function() { lychee.goto(album.getID() + "/" + $(this).attr("data-id")) })

+ 1 - 0
assets/js/modules/lychee.js

@@ -161,6 +161,7 @@ var lychee = {
 			hash = document.location.hash.replace("#", "").split("/");
 
 		contextMenu.close();
+		multiselect.close();
 
 		if (hash[0]!==undefined) albumID = hash[0];
 		if (hash[1]!==undefined) photoID = hash[1];

+ 168 - 0
assets/js/modules/multiselect.js

@@ -0,0 +1,168 @@
+/**
+ * @name		Multiselect Module
+ * @description	Select multiple albums or photos.
+ * @author		Tobias Reich
+ * @copyright	2014 by Tobias Reich
+ */
+
+multiselect = {
+
+	position: {
+	
+		top: null,
+		right: null,
+		bottom: null,
+		left: null
+		
+	},
+
+	show: function(e) {
+	
+		if (mobileBrowser()) return false;
+		if ($('.album:hover, .photo:hover').length!=0) return false;
+		if (visible.multiselect()) $('#multiselect').remove();
+	
+		multiselect.position.top = e.pageY;
+		multiselect.position.right = -1 * (e.pageX - $(document).width());
+		multiselect.position.bottom = -1 * (multiselect.position.top - $(window).height());
+		multiselect.position.left = e.pageX;
+				
+		$('body').append(build.multiselect(multiselect.position.top, multiselect.position.left));
+		$(document).on('mousemove', multiselect.resize);
+	
+	},
+	
+	resize: function(e) {
+		
+		var mouse_x = e.pageX,
+			mouse_y = e.pageY,
+			newHeight,
+			newWidth;
+			
+		if (multiselect.position.top===null||
+			multiselect.position.right===null||
+			multiselect.position.bottom===null||
+			multiselect.position.left===null) return false;
+			
+		if (mouse_y>=multiselect.position.top) {
+		
+			// Do not leave the screen
+			newHeight = e.pageY - multiselect.position.top;
+			if ((multiselect.position.top+newHeight)>=$(document).height())
+				newHeight -= (multiselect.position.top + newHeight) - $(document).height() + 2;
+		
+			$('#multiselect').css({
+				top: multiselect.position.top,
+				bottom: 'inherit',
+				height: newHeight
+			});
+					
+		} else {
+		
+			$('#multiselect').css({
+				top: 'inherit',
+				bottom: multiselect.position.bottom,
+				height: multiselect.position.top - e.pageY
+			});
+		
+		}
+		
+		if (mouse_x>=multiselect.position.left) {
+		
+			// Do not leave the screen
+			newWidth = e.pageX - multiselect.position.left;
+			if ((multiselect.position.left+newWidth)>=$(document).width())
+				newWidth -= (multiselect.position.left + newWidth) - $(document).width() + 2;
+		
+			$('#multiselect').css({
+				right: 'inherit',
+				left: multiselect.position.left,
+				width: newWidth
+			});
+		
+		} else {
+		
+			$('#multiselect').css({
+				right: multiselect.position.right,
+				left: 'inherit',
+				width: multiselect.position.left - e.pageX
+			});
+		
+		}
+			
+	},
+	
+	stopResize: function() {
+	
+		$(document).off('mousemove');
+		
+	},
+	
+	getSize: function() {
+	
+		if (!visible.multiselect()) return false;
+	
+		return {
+			top: $('#multiselect').offset().top,
+			left: $('#multiselect').offset().left,
+			width: parseInt($('#multiselect').css('width').replace('px', '')),
+			height: parseInt($('#multiselect').css('height').replace('px', ''))
+		}
+	
+	},
+	
+	getSelection: function(e) {
+	
+		var id,
+			ids = [],
+			offset,
+			size = multiselect.getSize();
+			
+		if (visible.contextMenu()) return false;
+		if (!visible.multiselect()) return false;
+		
+		$('.photo, .album').each(function() {
+		
+			offset = $(this).offset();
+			
+			if (offset.top>=size.top&&
+				offset.left>=size.left&&
+				(offset.top+206)<=(size.top+size.height)&&
+				(offset.left+206)<=(size.left+size.width)) {
+				
+					id = $(this).data('id');
+					
+					if (id!=="0"&&id!==0&&id!=="f"&&id!=="s"&&id!==null&id!==undefined) {
+				
+						ids.push(id);
+						$(this).addClass('active');
+					
+					}
+					
+				}
+
+		});
+						
+		if (ids.length!=0&&visible.album()) contextMenu.photoMulti(ids, e);
+		else if (ids.length!=0&&visible.albums()) contextMenu.albumMulti(ids, e);
+		else multiselect.close();
+	
+	},
+		
+	close: function() {
+	
+		multiselect.stopResize();
+		
+		multiselect.position.top = null;
+		multiselect.position.right = null;
+		multiselect.position.bottom = null;
+		multiselect.position.left = null;
+		
+		lychee.animate('#multiselect', "fadeOut");
+		setTimeout(function() {
+			$('#multiselect').remove();
+		}, 300);
+	
+	}
+
+}

+ 86 - 47
assets/js/modules/photo.js

@@ -56,40 +56,47 @@ photo = {
 
 	},
 
-	delete: function(photoID) {
+	delete: function(photoIDs) {
 
 		var params,
 			buttons,
 			photoTitle;
 
-		if (!photoID) return false;
+		if (!photoIDs) return false;
+		if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
 
-		if (visible.photo()) photoTitle = photo.json.title;
-		else photoTitle = album.json.content[photoID].title;
-		if (photoTitle=="") photoTitle = "Untitled";
+		if (photoIDs.length===1) {
+			// Get title if only one photo is selected
+			if (visible.photo()) photoTitle = photo.json.title;
+			else photoTitle = album.json.content[photoIDs].title;
+			if (photoTitle=="") photoTitle = "Untitled";
+		}
 
 		buttons = [
-			["Delete Photo", function() {
+			["", function() {
 
-				// Change reference for the next and previous photo
-				if (album.json.content[photoID].nextPhoto!==""||album.json.content[photoID].previousPhoto!=="") {
+				photoIDs.forEach(function(id, index, array) {
 
-					nextPhoto = album.json.content[photoID].nextPhoto;
-					previousPhoto = album.json.content[photoID].previousPhoto;
+					// Change reference for the next and previous photo
+					if (album.json.content[id].nextPhoto!==""||album.json.content[id].previousPhoto!=="") {
 
-					album.json.content[previousPhoto].nextPhoto = nextPhoto;
-					album.json.content[nextPhoto].previousPhoto = previousPhoto;
+						nextPhoto = album.json.content[id].nextPhoto;
+						previousPhoto = album.json.content[id].previousPhoto;
 
-				}
+						album.json.content[previousPhoto].nextPhoto = nextPhoto;
+						album.json.content[nextPhoto].previousPhoto = previousPhoto;
 
-				album.json.content[photoID] = null;
+					}
 
-				view.album.content.delete(photoID);
+					album.json.content[id] = null;
+					view.album.content.delete(id);
+
+				});
 
 				// Only when search is not active
 				if (!visible.albums()) lychee.goto(album.getID());
 
-				params = "deletePhoto&photoID=" + photoID;
+				params = "deletePhoto&photoIDs=" + photoIDs;
 				lychee.api(params, function(data) {
 
 					if (data!==true) lychee.error(null, params, data);
@@ -97,99 +104,131 @@ photo = {
 				});
 
 			}],
-			["Keep Photo", function() {}]
+			["", function() {}]
 		];
-		modal.show("Delete Photo", "Are you sure you want to delete the photo '" + photoTitle + "'?<br>This action can't be undone!", buttons);
+
+		if (photoIDs.length===1) {
+
+			buttons[0][0] = "Delete Photo";
+			buttons[1][0] = "Keep Photo";
+
+			modal.show("Delete Photo", "Are you sure you want to delete the photo '" + photoTitle + "'?<br>This action can't be undone!", buttons);
+
+		} else {
+
+			buttons[0][0] = "Delete Photos";
+			buttons[1][0] = "Keep Photos";
+
+			modal.show("Delete Photos", "Are you sure you want to delete all " + photoIDs.length + " selected photo?<br>This action can't be undone!", buttons);
+
+		}
 
 	},
 
-	setTitle: function(photoID) {
+	setTitle: function(photoIDs) {
 
 		var oldTitle = "",
 			newTitle,
 			params,
 			buttons;
 
-		if (!photoID) return false;
-		if (photo.json) oldTitle = photo.json.title;
-		else if (album.json) oldTitle = album.json.content[photoID].title;
+		if (!photoIDs) return false;
+		if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
+
+		if (photoIDs.length===1) {
+			// Get old title if only one photo is selected
+			if (photo.json) oldTitle = photo.json.title;
+			else if (album.json) oldTitle = album.json.content[photoIDs].title;
+		}
 
 		buttons = [
 			["Set Title", function() {
 
 				newTitle = $(".message input.text").val();
 
-				if (photoID!=null&&photoID&&newTitle.length<31) {
+				if (newTitle.length<31) {
 
 					if (visible.photo()) {
 						photo.json.title = (newTitle==="") ? "Untitled" : newTitle;
-						view.photo.title(oldTitle);
+						view.photo.title();
 					}
 
-					album.json.content[photoID].title = newTitle;
-					view.album.content.title(photoID);
+					photoIDs.forEach(function(id, index, array) {
+						album.json.content[id].title = newTitle;
+						view.album.content.title(id);
+					});
 
-					params = "setPhotoTitle&photoID=" + photoID + "&title=" + escape(encodeURI(newTitle));
+					params = "setPhotoTitle&photoIDs=" + photoIDs + "&title=" + escape(encodeURI(newTitle));
 					lychee.api(params, function(data) {
 
 						if (data!==true) lychee.error(null, params, data);
 
 					});
 
-				} else if (newTitle.length>0) loadingBar.show("error", "New title to short or too long. Please try another one!");
+				} else if (newTitle.length>30) loadingBar.show("error", "New title too long. Please try another one!");
 
 			}],
 			["Cancel", function() {}]
 		];
-		modal.show("Set Title", "Please enter a new title for this photo: <input class='text' type='text' placeholder='Title' value='" + oldTitle + "'>", buttons);
+
+		if (photoIDs.length===1) modal.show("Set Title", "Please enter a new title for this photo: <input class='text' type='text' placeholder='Title' value='" + oldTitle + "'>", buttons);
+		else modal.show("Set Titles", "Please enter a title for all " + photoIDs.length + " selected photos: <input class='text' type='text' placeholder='Title' value=''>", buttons);
 
 	},
 
-	setAlbum: function(albumID, photoID) {
+	setAlbum: function(photoIDs, albumID) {
 
-		var params;
+		var params,
+			nextPhoto,
+			previousPhoto;
+
+		if (!photoIDs) return false;
+		if (visible.photo) lychee.goto(album.getID());
+		if (photoIDs instanceof Array===false) photoIDs = [photoIDs];
 
-		if (albumID>=0) {
+		photoIDs.forEach(function(id, index, array) {
 
 			// Change reference for the next and previous photo
-			if (album.json.content[photoID].nextPhoto!==""||album.json.content[photoID].previousPhoto!=="") {
+			if (album.json.content[id].nextPhoto!==""||album.json.content[id].previousPhoto!=="") {
 
-				nextPhoto = album.json.content[photoID].nextPhoto;
-				previousPhoto = album.json.content[photoID].previousPhoto;
+				nextPhoto = album.json.content[id].nextPhoto;
+				previousPhoto = album.json.content[id].previousPhoto;
 
 				album.json.content[previousPhoto].nextPhoto = nextPhoto;
 				album.json.content[nextPhoto].previousPhoto = previousPhoto;
 
 			}
 
-			if (visible.photo) lychee.goto(album.getID());
-			album.json.content[photoID] = null;
-			view.album.content.delete(photoID);
+			album.json.content[id] = null;
+			view.album.content.delete(id);
 
-			params = "setAlbum&photoID=" + photoID + "&albumID=" + albumID;
-			lychee.api(params, function(data) {
+		});
 
-				if (data!==true) lychee.error(null, params, data);
+		params = "setAlbum&photoIDs=" + photoIDs + "&albumID=" + albumID;
+		lychee.api(params, function(data) {
 
-			});
+			if (data!==true) lychee.error(null, params, data);
 
-		}
+		});
 
 	},
 
-	setStar: function(photoID) {
+	setStar: function(photoIDs) {
 
 		var params;
 
+		if (!photoIDs) return false;
 		if (visible.photo()) {
 			photo.json.star = (photo.json.star==0) ? 1 : 0;
 			view.photo.star();
 		}
 
-		album.json.content[photoID].star = (album.json.content[photoID].star==0) ? 1 : 0;
-		view.album.content.star(photoID);
+		photoIDs.forEach(function(id, index, array) {
+			album.json.content[id].star = (album.json.content[id].star==0) ? 1 : 0;
+			view.album.content.star(id);
+		});
 
-		params = "setPhotoStar&photoID=" + photoID;
+		params = "setPhotoStar&photoIDs=" + photoIDs;
 		lychee.api(params, function(data) {
 
 			if (data!==true) lychee.error(null, params, data);

+ 2 - 2
assets/js/modules/view.js

@@ -214,7 +214,7 @@ view = {
 
 		},
 
-		title: function(oldTitle) {
+		title: function() {
 
 			if ((visible.album()||!album.json.init)&&!visible.photo()) {
 
@@ -404,7 +404,7 @@ view = {
 
 		},
 
-		title: function(oldTitle) {
+		title: function() {
 
 			if (photo.json.init) $("#infobox .attr_name").html(photo.json.title + " " + build.editIcon("edit_title"));
 			lychee.setTitle(photo.json.title, true);

+ 5 - 0
assets/js/modules/visible.js

@@ -45,6 +45,11 @@ visible = {
 	contextMenu: function() {
 		if ($(".contextmenu").length>0) return true;
 		else return false;
+	},
+	
+	multiselect: function() {
+		if ($("#multiselect").length>0) return true;
+		else return false;
 	}
 
 }

+ 10 - 8
index.html

@@ -12,7 +12,7 @@
 		<!-- CSS -->
 		<link type="text/css" rel="stylesheet" href="assets/css/min/reset.css">
 
-		<!-- Development
+		<!-- Development -->
 		<link type="text/css" rel="stylesheet" href="assets/css/modules/upload.css">
 		<link type="text/css" rel="stylesheet" href="assets/css/modules/tooltip.css">
 		<link type="text/css" rel="stylesheet" href="assets/css/modules/misc.css">
@@ -25,10 +25,11 @@
 		<link type="text/css" rel="stylesheet" href="assets/css/modules/font.css">
 		<link type="text/css" rel="stylesheet" href="assets/css/modules/contextmenu.css">
 		<link type="text/css" rel="stylesheet" href="assets/css/modules/content.css">
-		<link type="text/css" rel="stylesheet" href="assets/css/modules/animations.css"> -->
+		<link type="text/css" rel="stylesheet" href="assets/css/modules/animations.css">
+		<link type="text/css" rel="stylesheet" href="assets/css/modules/multiselect.css">
 
-		<!-- Production -->
-		<link type="text/css" rel="stylesheet" href="assets/css/min/main.css">
+		<!-- Production
+		<link type="text/css" rel="stylesheet" href="assets/css/min/main.css"> -->
 
 		<link rel="shortcut icon" href="assets/img/favicon.ico">
 		<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-iphone.png" sizes="120x120">
@@ -99,7 +100,7 @@
 	<!-- JS -->
 	<script defer type="text/javascript" src="assets/js/min/frameworks.js"></script>
 
-	<!-- Development
+	<!-- Development -->
 	<script defer type="text/javascript" src="assets/js/modules/init.js"></script>
 	<script defer type="text/javascript" src="assets/js/modules/lychee.js"></script>
 	<script defer type="text/javascript" src="assets/js/modules/build.js"></script>
@@ -114,10 +115,11 @@
 	<script defer type="text/javascript" src="assets/js/modules/visible.js"></script>
 	<script defer type="text/javascript" src="assets/js/modules/loadingBar.js"></script>
 	<script defer type="text/javascript" src="assets/js/modules/contextMenu.js"></script>
-	<script defer type="text/javascript" src="assets/js/modules/search.js"></script> -->
+	<script defer type="text/javascript" src="assets/js/modules/search.js"></script>
+	<script defer type="text/javascript" src="assets/js/modules/multiselect.js"></script>
 
-	<!-- Production -->
-	<script defer type="text/javascript" src="assets/js/min/main.js"></script>
+	<!-- Production
+	<script defer type="text/javascript" src="assets/js/min/main.js"></script> -->
 
 	</body>
 </html>

+ 20 - 16
php/api.php

@@ -54,13 +54,17 @@ if (!empty($_POST['function'])||!empty($_GET['function'])) {
 
 	// Get Settings
 	$settings = getSettings();
-
-	// Security
-	if (isset($_POST['albumID'])&&($_POST['albumID']==''||$_POST['albumID']<0||$_POST['albumID']>10000)) exit('Error: Wrong parameter type for albumID!');
-	if (isset($_POST['photoID'])&&$_POST['photoID']=='') exit('Error: Wrong parameter type for photoID!');
+	
+	// Escape
 	foreach(array_keys($_POST) as $key) $_POST[$key] = mysqli_real_escape_string($database, urldecode($_POST[$key]));
 	foreach(array_keys($_GET) as $key) $_GET[$key] = mysqli_real_escape_string($database, urldecode($_GET[$key]));
 
+	// Validate parameters
+	if (isset($_POST['albumIDs'])&&preg_match('/^[0-9\,]{1,}$/', $_POST['albumIDs'])!==1) exit('Error: Wrong parameter type for albumIDs!');
+	if (isset($_POST['photoIDs'])&&preg_match('/^[0-9\,]{1,}$/', $_POST['photoIDs'])!==1) exit('Error: Wrong parameter type for photoIDs!');
+	if (isset($_POST['albumID'])&&preg_match('/^[0-9sf]{1,}$/', $_POST['albumID'])!==1) exit('Error: Wrong parameter type for albumID!');
+	if (isset($_POST['photoID'])&&preg_match('/^[0-9]{14}$/', $_POST['photoID'])!==1) exit('Error: Wrong parameter type for photoID!');	
+
 	if (isset($_SESSION['login'])&&$_SESSION['login']==true) {
 
 		/**
@@ -83,8 +87,8 @@ if (!empty($_POST['function'])||!empty($_GET['function'])) {
 										echo addAlbum($_POST['title']);
 									break;
 
-			case 'setAlbumTitle':	if (isset($_POST['albumID'])&&isset($_POST['title']))
-										echo setAlbumTitle($_POST['albumID'], $_POST['title']);
+			case 'setAlbumTitle':	if (isset($_POST['albumIDs'])&&isset($_POST['title']))
+										echo setAlbumTitle($_POST['albumIDs'], $_POST['title']);
 									break;
 
 			case 'setAlbumDescription':	if (isset($_POST['albumID'])&&isset($_POST['description']))
@@ -100,8 +104,8 @@ if (!empty($_POST['function'])||!empty($_GET['function'])) {
 										echo setAlbumPassword($_POST['albumID'], $_POST['password']);
 									break;
 
-			case 'deleteAlbum':		if (isset($_POST['albumID']))
-										echo deleteAlbum($_POST['albumID']);
+			case 'deleteAlbum':		if (isset($_POST['albumIDs']))
+										echo deleteAlbum($_POST['albumIDs']);
 									break;
 
 			// Photo Functions
@@ -110,20 +114,20 @@ if (!empty($_POST['function'])||!empty($_GET['function'])) {
 										echo json_encode(getPhoto($_POST['photoID'], $_POST['albumID']));
 									break;
 
-			case 'deletePhoto':		if (isset($_POST['photoID']))
-										echo deletePhoto($_POST['photoID']);
+			case 'deletePhoto':		if (isset($_POST['photoIDs']))
+										echo deletePhoto($_POST['photoIDs']);
 									break;
 
-			case 'setAlbum':		if (isset($_POST['photoID'])&&isset($_POST['albumID']))
-										echo setAlbum($_POST['photoID'], $_POST['albumID']);
+			case 'setAlbum':		if (isset($_POST['photoIDs'])&&isset($_POST['albumID']))
+										echo setAlbum($_POST['photoIDs'], $_POST['albumID']);
 									break;
 
-			case 'setPhotoTitle':	if (isset($_POST['photoID'])&&isset($_POST['title']))
-										echo setPhotoTitle($_POST['photoID'], $_POST['title']);
+			case 'setPhotoTitle':	if (isset($_POST['photoIDs'])&&isset($_POST['title']))
+										echo setPhotoTitle($_POST['photoIDs'], $_POST['title']);
 									break;
 
-			case 'setPhotoStar':	if (isset($_POST['photoID']))
-										echo setPhotoStar($_POST['photoID']);
+			case 'setPhotoStar':	if (isset($_POST['photoIDs']))
+										echo setPhotoStar($_POST['photoIDs']);
 									break;
 
 			case 'setPhotoPublic':	if (isset($_POST['photoID'])&&isset($_POST['url']))

+ 26 - 28
php/modules/album.php

@@ -189,14 +189,14 @@ function getAlbum($albumID) {
 
 }
 
-function setAlbumTitle($albumID, $title) {
+function setAlbumTitle($albumIDs, $title) {
 
 	global $database;
 
     if (strlen($title)<1||strlen($title)>30) return false;
-    $result = $database->query("UPDATE lychee_albums SET title = '$title' WHERE id = '$albumID';");
+    $result = $database->query("UPDATE lychee_albums SET title = '$title' WHERE id IN ($albumIDs);");
+    
     if (!$result) return false;
-
     return true;
 
 }
@@ -204,34 +204,32 @@ function setAlbumTitle($albumID, $title) {
 function setAlbumDescription($albumID, $description) {
 
 	global $database;
-
+	
 	$description = htmlentities($description);
-    if (strlen($description)>800) return false;
-    $result = $database->query("UPDATE lychee_albums SET description = '$description' WHERE id = '$albumID';");
-
-    if (!$result) return false;
-    return true;
+	if (strlen($description)>800) return false;
+	$result = $database->query("UPDATE lychee_albums SET description = '$description' WHERE id = '$albumID';");
+	
+	if (!$result) return false;
+	return true;
 
 }
 
-function deleteAlbum($albumID) {
+function deleteAlbum($albumIDs) {
 
 	global $database;
-
+	
 	$error = false;
-
-    $result = $database->query("SELECT id FROM lychee_photos WHERE album = '$albumID';");
-    while($row =  $result->fetch_object()) {
-        if (!deletePhoto($row->id)) $error = true;
-    }
-
-    if ($albumID!=0) {
-        $result = $database->query("DELETE FROM lychee_albums WHERE id = '$albumID';");
-        if (!$result) return false;
-    }
-
-    if ($error) return false;
-    return true;
+	$result = $database->query("SELECT id FROM lychee_photos WHERE album IN ($albumIDs);");
+	
+	// Delete photos
+	while ($row = $result->fetch_object())
+		if (!deletePhoto($row->id)) $error = true;
+	
+	// Delete album
+	$result = $database->query("DELETE FROM lychee_albums WHERE id IN ($albumIDs);");
+	
+	if ($error||!$result) return false;
+	return true;
 
 }
 
@@ -308,7 +306,7 @@ function setAlbumPublic($albumID, $password) {
 	}
 
 	if (strlen($password)>0) return setAlbumPassword($albumID, $password);
-	else return true;
+	return true;
 
 }
 
@@ -329,10 +327,10 @@ function checkAlbumPassword($albumID, $password) {
 
 	$result = $database->query("SELECT password FROM lychee_albums WHERE id = '$albumID';");
 	$row = $result->fetch_object();
+	
 	if ($row->password=="") return true;
-
 	else if ($row->password==$password) return true;
-	else return false;
+	return false;
 
 }
 
@@ -344,7 +342,7 @@ function isAlbumPublic($albumID) {
 	$row = $result->fetch_object();
 
 	if ($row->public==1) return true;
-	else return false;
+	return false;
 
 }
 

+ 44 - 29
php/modules/photo.php

@@ -71,42 +71,48 @@ function setPhotoPublic($photoID, $url) {
 
 }
 
-function setPhotoStar($photoID) {
+function setPhotoStar($photoIDs) {
 
 	global $database;
-
-    $result = $database->query("SELECT star FROM lychee_photos WHERE id = '$photoID';");
-    $row = $result->fetch_object();
-    if ($row->star == 0) {
-        $star = 1;
-    } else {
-        $star = 0;
+	
+	$error = false;
+    $result = $database->query("SELECT id, star FROM lychee_photos WHERE id IN ($photoIDs);");
+    
+    while ($row = $result->fetch_object()) {
+        
+    	if ($row->star==0) $star = 1;
+    	else $star = 0;
+    	
+    	$star = $database->query("UPDATE lychee_photos SET star = '$star' WHERE id = '$row->id';");
+    	if (!$star) $error = true;
+    	
     }
-    $result = $database->query("UPDATE lychee_photos SET star = '$star' WHERE id = '$photoID';");
+    
+    if ($error) return false;
     return true;
 
 }
 
-function setAlbum($photoID, $newAlbum) {
+function setAlbum($photoIDs, $albumID) {
 
 	global $database;
 
-    $result = $database->query("UPDATE lychee_photos SET album = '$newAlbum' WHERE id = '$photoID';");
+    $result = $database->query("UPDATE lychee_photos SET album = '$albumID' WHERE id IN ($photoIDs);");
 
     if (!$result) return false;
-    else return true;
+    return true;
 
 }
 
-function setPhotoTitle($photoID, $title) {
+function setPhotoTitle($photoIDs, $title) {
 
 	global $database;
 
     if (strlen($title)>30) return false;
-    $result = $database->query("UPDATE lychee_photos SET title = '$title' WHERE id = '$photoID';");
+    $result = $database->query("UPDATE lychee_photos SET title = '$title' WHERE id IN ($photoIDs);");
 
     if (!$result) return false;
-    else return true;
+    return true;
 
 }
 
@@ -123,22 +129,31 @@ function setPhotoDescription($photoID, $description) {
 
 }
 
-function deletePhoto($photoID) {
+function deletePhoto($photoIDs) {
 
 	global $database;
-
-    $result = $database->query("SELECT * FROM lychee_photos WHERE id = '$photoID';");
-    if (!$result) return false;
-    $row = $result->fetch_object();
-    $retinaUrl = explode(".", $row->thumbUrl);
-    $unlink1 = unlink("../uploads/big/".$row->url);
-    $unlink2 = unlink("../uploads/thumb/".$row->thumbUrl);
-    $unlink3 = unlink("../uploads/thumb/".$retinaUrl[0].'@2x.'.$retinaUrl[1]);
-    $result = $database->query("DELETE FROM lychee_photos WHERE id = '$photoID';");
-    if (!$unlink1 || !$unlink2 || !$unlink3) return false;
-    if (!$result) return false;
-
-    return true;
+	
+	$result = $database->query("SELECT * FROM lychee_photos WHERE id IN ($photoIDs);");
+	
+	while ($row = $result->fetch_object()) {
+	
+		// Get retina thumb url
+		$thumbUrl2x = explode(".", $row->thumbUrl);
+		$thumbUrl2x = $thumbUrl2x[0] . '@2x.' . $thumbUrl2x[1];
+		
+		// Delete files
+		if (!unlink('../uploads/big/' . $row->url)) return false;
+		if (!unlink('../uploads/thumb/' . $row->thumbUrl)) return false;
+		if (!unlink('../uploads/thumb/' . $thumbUrl2x)) return false;
+		
+		// Delete db entry
+		$delete = $database->query("DELETE FROM lychee_photos WHERE id = $row->id;");
+		if (!$delete) return false;
+		
+	}
+		
+	if (!$result) return false;
+	return true;
 
 }