Merge branch 'subalbums' of https://github.com/hrniels/Lychee into feature/subalbums

pull/584/head
Tobias Reich 8 years ago
commit 1020b0d1dc

@ -72,8 +72,10 @@ final class Admin extends Access {
private static function getAlbumsAction() { private static function getAlbumsAction() {
Validator::required(isset($_POST['parent']), __METHOD__);
$albums = new Albums(); $albums = new Albums();
Response::json($albums->get(false)); Response::json($albums->get(false, $_POST['parent']));
} }
@ -90,10 +92,10 @@ final class Admin extends Access {
private static function addAlbumAction() { private static function addAlbumAction() {
Validator::required(isset($_POST['title']), __METHOD__); Validator::required(isset($_POST['title'], $_POST['parent']), __METHOD__);
$album = new Album(null); $album = new Album(null);
Response::json($album->add($_POST['title']), JSON_NUMERIC_CHECK); Response::json($album->add($_POST['title'], $_POST['parent']), JSON_NUMERIC_CHECK);
} }

@ -44,8 +44,10 @@ final class Guest extends Access {
private static function getAlbumsAction() { private static function getAlbumsAction() {
Validator::required(isset($_POST['parent']), __METHOD__);
$albums = new Albums(); $albums = new Albums();
Response::json($albums->get(true)); Response::json($albums->get(true, $_POST['parent']));
} }

@ -23,7 +23,7 @@ final class Album {
/** /**
* @return string|false ID of the created album. * @return string|false ID of the created album.
*/ */
public function add($title = 'Untitled') { public function add($title = 'Untitled', $parent = 0) {
// Call plugins // Call plugins
Plugins::get()->activate(__METHOD__, 0, func_get_args()); Plugins::get()->activate(__METHOD__, 0, func_get_args());
@ -35,7 +35,7 @@ final class Album {
$visible = 1; $visible = 1;
// Database // Database
$query = Database::prepare(Database::get(), "INSERT INTO ? (id, title, sysstamp, public, visible) VALUES ('?', '?', '?', '?', '?')", array(LYCHEE_TABLE_ALBUMS, $id, $title, $sysstamp, $public, $visible)); $query = Database::prepare(Database::get(), "INSERT INTO ? (id, title, sysstamp, public, visible, parent) VALUES ('?', '?', '?', '?', '?', '?')", array(LYCHEE_TABLE_ALBUMS, $id, $title, $sysstamp, $public, $visible, $parent));
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
// Call plugins // Call plugins
@ -79,6 +79,8 @@ final class Album {
// Parse thumbs or set default value // Parse thumbs or set default value
$album['thumbs'] = (isset($data['thumbs']) ? explode(',', $data['thumbs']) : array()); $album['thumbs'] = (isset($data['thumbs']) ? explode(',', $data['thumbs']) : array());
$album['parent'] = $data['parent'];
return $album; return $album;
} }
@ -193,12 +195,6 @@ final class Album {
// Call plugins // Call plugins
Plugins::get()->activate(__METHOD__, 0, func_get_args()); Plugins::get()->activate(__METHOD__, 0, func_get_args());
// Illicit chars
$badChars = array_merge(
array_map('chr', range(0,31)),
array("<", ">", ":", '"', "/", "\\", "|", "?", "*")
);
// Photos query // Photos query
switch($this->albumIDs) { switch($this->albumIDs) {
case 's': case 's':
@ -214,34 +210,34 @@ final class Album {
$zipTitle = 'Recent'; $zipTitle = 'Recent';
break; break;
default: default:
$photos = Database::prepare(Database::get(), "SELECT title, url FROM ? WHERE album = '?'", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs));
$zipTitle = 'Unsorted'; $zipTitle = 'Unsorted';
}
// Get title from database when album is not a SmartAlbum // Get title from database when album is not a SmartAlbum
if ($this->albumIDs!=0&&is_numeric($this->albumIDs)) { if ($this->albumIDs!=0 && is_numeric($this->albumIDs)) {
$query = Database::prepare(Database::get(), "SELECT title FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); $query = Database::prepare(Database::get(), "SELECT title FROM ? WHERE id = '?' LIMIT 1", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs));
$album = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $album = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
if ($album===false) return false; if ($album===false) return false;
// Get album object // Get album object
$album = $album->fetch_object(); $album = $album->fetch_object();
// Album not found? // Album not found?
if ($album===null) { if ($album===null) {
Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album'); Log::error(Database::get(), __METHOD__, __LINE__, 'Could not find specified album');
return false; return false;
} }
// Set title // Set title
$zipTitle = $album->title; $zipTitle = $album->title;
}
break;
} }
// Escape title // Escape title
$zipTitle = str_replace($badChars, '', $zipTitle); $zipTitle = $this->cleanZipName($zipTitle);
$filename = LYCHEE_DATA . $zipTitle . '.zip'; $filename = LYCHEE_DATA . $zipTitle . '.zip';
@ -252,17 +248,72 @@ final class Album {
return false; return false;
} }
// Execute query // Add photos to zip
$photos = Database::execute(Database::get(), $photos, __METHOD__, __LINE__); switch($this->albumIDs) {
case 's':
case 'f':
case 'r':
$this->addPhotosToZip($zip, $zipTitle, $photos);
break;
default:
$this->addAlbumToZip($zip, $zipTitle, $this->albumIDs);
break;
}
if ($album===null) return false; // Finish zip
$zip->close();
// Check if album empty // Send zip
if ($photos->num_rows==0) { header("Content-Type: application/zip");
Log::error(Database::get(), __METHOD__, __LINE__, 'Could not create ZipArchive without images'); header("Content-Disposition: attachment; filename=\"$zipTitle.zip\"");
return false; header("Content-Length: " . filesize($filename));
readfile($filename);
// Delete zip
unlink($filename);
// Call plugins
Plugins::get()->activate(__METHOD__, 1, func_get_args());
return true;
}
private function cleanZipName($name) {
// Illicit chars
$badChars = array_merge(
array_map('chr', range(0,31)),
array("<", ">", ":", '"', "/", "\\", "|", "?", "*")
);
return str_replace($badChars, '', $name);
}
private function addAlbumToZip($zip, $path, $albumID) {
// Fetch album title
$photos = Database::prepare(Database::get(), "SELECT title, url FROM ? WHERE album = '?'", array(LYCHEE_TABLE_PHOTOS, $albumID));
$this->addPhotosToZip($zip, $path, $photos);
// Fetch subalbums
$query = Database::prepare(Database::get(), "SELECT id, title FROM ? WHERE parent = '?'", array(LYCHEE_TABLE_ALBUMS, $albumID));
$albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
// Add them recursively
while($album = $albums->fetch_assoc()) {
$this->addAlbumToZip($zip, $path . '/' . $this->cleanZipName($album['title']), $album['id']);
} }
}
private function addPhotosToZip($zip, $path, $query) {
// Execute query
$photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
// Parse each path // Parse each path
$files = array(); $files = array();
while ($photo = $photos->fetch_object()) { while ($photo = $photos->fetch_object()) {
@ -271,7 +322,7 @@ final class Album {
$photo->url = LYCHEE_UPLOADS_BIG . $photo->url; $photo->url = LYCHEE_UPLOADS_BIG . $photo->url;
// Parse title // Parse title
$photo->title = str_replace($badChars, '', $photo->title); $photo->title = $this->cleanZipName($photo->title);
if (!isset($photo->title)||$photo->title==='') $photo->title = 'Untitled'; if (!isset($photo->title)||$photo->title==='') $photo->title = 'Untitled';
// Check if readable // Check if readable
@ -281,7 +332,7 @@ final class Album {
$extension = getExtension($photo->url, false); $extension = getExtension($photo->url, false);
// Set title for photo // Set title for photo
$zipFileName = $zipTitle . '/' . $photo->title . $extension; $zipFileName = $path . '/' . $photo->title . $extension;
// Check for duplicates // Check for duplicates
if (!empty($files)) { if (!empty($files)) {
@ -289,7 +340,7 @@ final class Album {
while (in_array($zipFileName, $files)) { while (in_array($zipFileName, $files)) {
// Set new title for photo // Set new title for photo
$zipFileName = $zipTitle . '/' . $photo->title . '-' . $i . $extension; $zipFileName = $path . '/' . $photo->title . '-' . $i . $extension;
$i++; $i++;
@ -304,23 +355,6 @@ final class Album {
} }
// Finish zip
$zip->close();
// Send zip
header("Content-Type: application/zip");
header("Content-Disposition: attachment; filename=\"$zipTitle.zip\"");
header("Content-Length: " . filesize($filename));
readfile($filename);
// Delete zip
unlink($filename);
// Call plugins
Plugins::get()->activate(__METHOD__, 1, func_get_args());
return true;
} }
/** /**
@ -441,6 +475,31 @@ final class Album {
} }
private function getSubAlbums($albumID) {
$query = Database::prepare(Database::get(), "SELECT id FROM ? WHERE parent = '?'", array(LYCHEE_TABLE_ALBUMS, $albumID));
$albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
$ids = array();
while($album = $albums->fetch_assoc()) {
$ids = array_merge($ids, array($album['id']), $this->getSubAlbums($album['id']));
}
return $ids;
}
private function addSubAlbumIDs($ids) {
$res = array();
foreach(explode(',', $ids) as $id)
$res = array_merge($res, array($id), $this->getSubAlbums($id));
return implode(',', $res);
}
/** /**
* @return boolean Returns true when successful. * @return boolean Returns true when successful.
*/ */
@ -457,8 +516,11 @@ final class Album {
$visible = ($visible==='1' ? 1 : 0); $visible = ($visible==='1' ? 1 : 0);
$downloadable = ($downloadable==='1' ? 1 : 0); $downloadable = ($downloadable==='1' ? 1 : 0);
// Get all album ids, including subalbums
$ids = $this->addSubAlbumIDs($this->albumIDs);
// Set public // Set public
$query = Database::prepare(Database::get(), "UPDATE ? SET public = '?', visible = '?', downloadable = '?', password = NULL WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $public, $visible, $downloadable, $this->albumIDs)); $query = Database::prepare(Database::get(), "UPDATE ? SET public = '?', visible = '?', downloadable = '?', password = NULL WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $public, $visible, $downloadable, $ids));
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
if ($result===false) return false; if ($result===false) return false;
@ -466,7 +528,7 @@ final class Album {
// Reset permissions for photos // Reset permissions for photos
if ($public===1) { if ($public===1) {
$query = Database::prepare(Database::get(), "UPDATE ? SET public = 0 WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); $query = Database::prepare(Database::get(), "UPDATE ? SET public = 0 WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $ids));
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
if ($result===false) return false; if ($result===false) return false;
@ -575,6 +637,14 @@ final class Album {
$albumID = array_splice($albumIDs, 0, 1); $albumID = array_splice($albumIDs, 0, 1);
$albumID = $albumID[0]; $albumID = $albumID[0];
// Ensure that we don't merge an album into its own subalbum
foreach($albumIDs as $id) {
foreach($this->getSubAlbums($id) as $sid) {
if($sid == $albumID) return false;
}
}
// Move photos
$query = Database::prepare(Database::get(), "UPDATE ? SET album = ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $albumID, $this->albumIDs)); $query = Database::prepare(Database::get(), "UPDATE ? SET album = ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $albumID, $this->albumIDs));
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
@ -584,6 +654,13 @@ final class Album {
// Convert to string // Convert to string
$filteredIDs = implode(',', $albumIDs); $filteredIDs = implode(',', $albumIDs);
// Move subalbums
$query = Database::prepare(Database::get(), "UPDATE ? SET parent = ? WHERE parent IN (?)", array(LYCHEE_TABLE_ALBUMS, $albumID, $filteredIDs));
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
if ($result===false) return false;
// Delete other albums
$query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $filteredIDs)); $query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $filteredIDs));
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
@ -606,11 +683,14 @@ final class Album {
// Call plugins // Call plugins
Plugins::get()->activate(__METHOD__, 0, func_get_args()); Plugins::get()->activate(__METHOD__, 0, func_get_args());
// Get all album ids, including subalbums
$ids = $this->addSubAlbumIDs($this->albumIDs);
// Init vars // Init vars
$photoIDs = array(); $photoIDs = array();
// Execute query // Execute query
$query = Database::prepare(Database::get(), "SELECT id FROM ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $this->albumIDs)); $query = Database::prepare(Database::get(), "SELECT id FROM ? WHERE album IN (?)", array(LYCHEE_TABLE_PHOTOS, $ids));
$photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $photos = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
if ($photos===false) return false; if ($photos===false) return false;
@ -631,7 +711,7 @@ final class Album {
} }
// Delete albums // Delete albums
$query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $this->albumIDs)); $query = Database::prepare(Database::get(), "DELETE FROM ? WHERE id IN (?)", array(LYCHEE_TABLE_ALBUMS, $ids));
$result = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $result = Database::execute(Database::get(), $query, __METHOD__, __LINE__);
// Call plugins // Call plugins

@ -16,7 +16,7 @@ final class Albums {
/** /**
* @return array|false Returns an array of albums or false on failure. * @return array|false Returns an array of albums or false on failure.
*/ */
public function get($public = true) { public function get($public = true, $parent = 0) {
// Call plugins // Call plugins
Plugins::get()->activate(__METHOD__, 0, func_get_args()); Plugins::get()->activate(__METHOD__, 0, func_get_args());
@ -32,8 +32,8 @@ final class Albums {
if ($public===false) $return['smartalbums'] = $this->getSmartAlbums(); if ($public===false) $return['smartalbums'] = $this->getSmartAlbums();
// Albums query // Albums query
if ($public===false) $query = Database::prepare(Database::get(), 'SELECT id, title, public, sysstamp, password FROM ? ' . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS)); if ($public===false) $query = Database::prepare(Database::get(), "SELECT id, title, public, sysstamp, password, parent FROM ? " . ($parent != -1 ? "WHERE parent = '?' " : "") . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS, $parent));
else $query = Database::prepare(Database::get(), 'SELECT id, title, public, sysstamp, password FROM ? WHERE public = 1 AND visible <> 0 ' . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS)); else $query = Database::prepare(Database::get(), "SELECT id, title, public, sysstamp, password, parent FROM ? " . ($parent != -1 ? "WHERE parent = '?' " : "") . " AND public = 1 AND visible <> 0 " . Settings::get()['sortingAlbums'], array(LYCHEE_TABLE_ALBUMS, $parent));
// Execute query // Execute query
$albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__); $albums = Database::execute(Database::get(), $query, __METHOD__, __LINE__);

@ -15,7 +15,8 @@ final class Database {
'030001', // 3.0.1 '030001', // 3.0.1
'030003', // 3.0.3 '030003', // 3.0.3
'030100', // 3.1.0 '030100', // 3.1.0
'030102' // 3.1.2 '030102', // 3.1.2
'030103' // 3.1.3
); );
/** /**

@ -0,0 +1,24 @@
<?php
/**
* Update to version 3.1.3
*/
use Lychee\Modules\Database;
use Lychee\Modules\Response;
// Add parent field to albums
$query = Database::prepare($connection, "SELECT `parent` FROM `?` LIMIT 1", array(LYCHEE_TABLE_ALBUMS));
if (!Database::execute($connection, $query, "update_030103", __LINE__)) {
$query = Database::prepare($connection, "ALTER TABLE `?` ADD `parent` BIGINT(14) NOT NULL DEFAULT 0", array(LYCHEE_TABLE_ALBUMS));
$result = Database::execute($connection, $query, "update_030103", __LINE__);
if (!$result) {
Log::error($database, 'update_030103', __LINE__, 'Could not update database (' . $database->error . ')');
return false;
}
}
// Set version
if (Database::setVersion($connection, '030103')===false) Response::error('Could not update version of database!');
?>

@ -1,6 +1,6 @@
{ {
"name": "Lychee", "name": "Lychee",
"version": "3.1.2", "version": "3.1.3",
"description": "Self-hosted photo-management done right.", "description": "Self-hosted photo-management done right.",
"authors": "Tobias Reich <tobias@electerious.com>", "authors": "Tobias Reich <tobias@electerious.com>",
"license": "MIT", "license": "MIT",

@ -5,7 +5,14 @@
album = { album = {
json: null json: null,
subjson: null
}
album.isSmartID = function(id) {
return id==='0' || id==='f' || id==='s' || id==='r'
} }
@ -14,22 +21,36 @@ album.getID = function() {
let id = null let id = null
let isID = (id) => { let isID = (id) => {
if (id==='0' || id==='f' || id==='s' || id==='r') return true if (album.isSmartID(id)) return true
return $.isNumeric(id) return $.isNumeric(id)
} }
if (photo.json) id = photo.json.album
else if (album.json) id = album.json.id
// Search // Search
if (isID(id)===false) id = $('.album:hover, .album.active').attr('data-id') if (isID(id)===false) id = $('.album:hover, .album.active').attr('data-id')
if (isID(id)===false) id = $('.photo:hover, .photo.active').attr('data-album-id') if (isID(id)===false) id = $('.photo:hover, .photo.active').attr('data-album-id')
if (isID(id)===false) {
if (photo.json) id = photo.json.album
else if (album.json) id = album.json.id
}
if (isID(id)===true) return id if (isID(id)===true) return id
else return false else return false
} }
album.getParent = function () {
let id = album.json.id;
if (album.isSmartID(id) || album.json.parent==0) {
return ''
} else {
return album.json.parent
}
}
album.load = function(albumID, refresh = false) { album.load = function(albumID, refresh = false) {
password.get(albumID, function() { password.get(albumID, function() {
@ -79,17 +100,44 @@ album.load = function(albumID, refresh = false) {
setTimeout(() => { setTimeout(() => {
view.album.init() let finish = function() {
view.album.init()
if (refresh===false) { if (refresh===false) {
lychee.animate(lychee.content, 'contentZoomIn') lychee.animate(lychee.content, 'contentZoomIn')
header.setMode('album') header.setMode('album')
}
}
if (!album.isSmartID(albumID)) {
params = {
parent: albumID
}
api.post('Albums::get', params, function(data) {
let waitTime = 0
album.subjson = data
// Calculate delay
let durationTime = (new Date().getTime() - startTime)
if (durationTime>300) waitTime = 0
else waitTime = 300 - durationTime
setTimeout(() => {
finish()
}, waitTime)
})
}
else {
finish()
} }
}, waitTime) }, waitTime)
}) })
}) })
} }
@ -100,18 +148,35 @@ album.parse = function() {
} }
album.add = function() { function buildAlbumOptions(albums, select, parent = 0, layer = 0) {
var cmbxOptions = ''
for (i in albums) {
if (albums[i].parent == parent) {
let title = (layer > 0 ? "&nbsp;&nbsp;".repeat(layer - 1) + "└ " : "") + albums[i].title
cmbxOptions += `<option `;
if (select == albums[i].id)
cmbxOptions += `selected="selected" `
cmbxOptions += `value='` + albums[i].id + `'>` + title + `</option>`
cmbxOptions += buildAlbumOptions(albums, select, albums[i].id, layer + 1)
}
}
return cmbxOptions
}
album.add = function(albumID = 0) {
const action = function(data) { const action = function(data) {
let title = data.title let title = data.title
let parent = data.parent
const isNumber = (n) => (!isNaN(parseFloat(n)) && isFinite(n)) const isNumber = (n) => (!isNaN(parseFloat(n)) && isFinite(n))
basicModal.close() basicModal.close()
let params = { let params = {
title title,
parent
} }
api.post('Album::add', params, function(data) { api.post('Album::add', params, function(data) {
@ -127,18 +192,28 @@ album.add = function() {
} }
basicModal.show({ api.post('Albums::get', {
body: `<p>Enter a title for the new album: <input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='Untitled'></p>`, parent: -1
buttons: { }, function (data) {
action: { var cmbxOptions = `<select name='parent'>`
title: 'Create Album', cmbxOptions += `<option value='0'>- None -</option>`
fn: action cmbxOptions += buildAlbumOptions(data.albums, albumID)
}, cmbxOptions += '</select>'
cancel: {
title: 'Cancel', basicModal.show({
fn: basicModal.close body: `<p>Enter a title for the new album: <input class='text' name='title' type='text' maxlength='50' placeholder='Title' value='Untitled'></p>`
+ `<p>Select the parent album:<br/>` + cmbxOptions + `</p>`,
buttons: {
action: {
title: 'Create Album',
fn: action
},
cancel: {
title: 'Cancel',
fn: basicModal.close
}
} }
} })
}) })
} }
@ -198,20 +273,20 @@ album.delete = function(albumIDs) {
cancel.title = 'Keep Album' cancel.title = 'Keep Album'
// Get title // Get title
if (album.json) albumTitle = album.json.title if (album.json && album.json.id == albumIDs[0]) albumTitle = album.json.title
else if (albums.json) albumTitle = albums.getByID(albumIDs).title else if (albums.json || album.subjson) albumTitle = albums.getByID(albumIDs).title
// Fallback for album without a title // Fallback for album without a title
if (albumTitle==='') albumTitle = 'Untitled' if (albumTitle==='') albumTitle = 'Untitled'
msg = lychee.html`<p>Are you sure you want to delete the album '$${ albumTitle }' and all of the photos it contains? This action can't be undone!</p>` msg = lychee.html`<p>Are you sure you want to delete the album '$${ albumTitle }' and all of the photos and subalbums it contains? This action can't be undone!</p>`
} else { } else {
action.title = 'Delete Albums and Photos' action.title = 'Delete Albums and Photos'
cancel.title = 'Keep Albums' cancel.title = 'Keep Albums'
msg = lychee.html`<p>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!</p>` msg = lychee.html`<p>Are you sure you want to delete all $${ albumIDs.length } selected albums and all of the photos and subalbums they contain? This action can't be undone!</p>`
} }
@ -243,8 +318,8 @@ album.setTitle = function(albumIDs) {
if (albumIDs.length===1) { if (albumIDs.length===1) {
// Get old title if only one album is selected // Get old title if only one album is selected
if (album.json) oldTitle = album.json.title if (album.json && album.json.id == albumIDs[0]) oldTitle = album.json.title
else if (albums.json) oldTitle = albums.getByID(albumIDs).title else if (albums.json || album.subjson) oldTitle = albums.getByID(albumIDs).title
} }
@ -258,10 +333,20 @@ album.setTitle = function(albumIDs) {
// Rename only one album // Rename only one album
album.json.title = newTitle if (album.json.id == albumIDs[0]) {
view.album.title() album.json.title = newTitle
view.album.title()
}
if (albums.json) albums.getByID(albumIDs[0]).title = newTitle if (albums.json || album.subjson) {
albumIDs.forEach(function(id) {
let a = albums.getByID(id)
if (a) {
a.title = newTitle
view.album.content.title(id)
}
})
}
} else if (visible.albums()) { } else if (visible.albums()) {
@ -539,7 +624,7 @@ album.getArchive = function(albumID) {
} }
album.merge = function(albumIDs) { album.merge = function(albumIDs, titles = []) {
let title = '' let title = ''
let sTitle = '' let sTitle = ''
@ -549,7 +634,8 @@ album.merge = function(albumIDs) {
if (albumIDs instanceof Array===false) albumIDs = [ albumIDs ] if (albumIDs instanceof Array===false) albumIDs = [ albumIDs ]
// Get title of first album // Get title of first album
if (albums.json) title = albums.getByID(albumIDs[0]).title if (titles.length > 0) title = titles[0]
else if (albums.json || album.subjson) title = albums.getByID(albumIDs[0]).title
// Fallback for first album without a title // Fallback for first album without a title
if (title==='') title = 'Untitled' if (title==='') title = 'Untitled'
@ -557,7 +643,8 @@ album.merge = function(albumIDs) {
if (albumIDs.length===2) { if (albumIDs.length===2) {
// Get title of second album // Get title of second album
if (albums.json) sTitle = albums.getByID(albumIDs[1]).title if (titles.length > 1) sTitle = titles[1]
else if (albums.json || album.subjson) sTitle = albums.getByID(albumIDs[1]).title
// Fallback for second album without a title // Fallback for second album without a title
if (sTitle==='') sTitle = 'Untitled' if (sTitle==='') sTitle = 'Untitled'

@ -17,7 +17,11 @@ albums.load = function() {
if (albums.json===null) { if (albums.json===null) {
api.post('Albums::get', {}, function(data) { params = {
parent: 0
}
api.post('Albums::get', params, function(data) {
let waitTime = 0 let waitTime = 0
@ -109,18 +113,21 @@ albums.getByID = function(albumID) {
// Function returns the JSON of an album // Function returns the JSON of an album
if (albumID==null) return undefined if (albumID==null) return undefined
if (!albums.json) return undefined if (albumID instanceof Array)
if (!albums.json.albums) return undefined albumID = albumID[0]
let json = undefined let json = undefined
$.each(albums.json.albums, function(i) { let func = function() {
if (this.id==albumID) json = this
let elem = albums.json.albums[i] }
if (elem.id==albumID) json = elem
}) if (albums.json && albums.json.albums) {
$.each(albums.json.albums, func)
}
else if (album.subjson && album.subjson.albums) {
$.each(album.subjson.albums, func)
}
return json return json

@ -3,9 +3,47 @@
* @copyright 2015 by Tobias Reich * @copyright 2015 by Tobias Reich
*/ */
function buildAlbumList(albums, exclude, action, parent = 0, layer = 0) {
let items = []
for (i in albums) {
if ((layer == 0 && !albums[i].parent) || albums[i].parent == parent) {
let album = albums[i]
let thumb = 'src/images/no_cover.svg'
if (album.thumbs && album.thumbs[0])
thumb = album.thumbs[0]
else if(album.thumbUrl)
thumb = album.thumbUrl
if (album.title==='') album.title = 'Untitled'
let prefix = layer > 0 ? "&nbsp;&nbsp;".repeat(layer - 1) + "└ " : ""
let html = prefix + lychee.html`<img class='cover' width='16' height='16' src='$${ thumb }'><div class='title'>$${ album.title }</div>`
if (exclude.indexOf(album.id) == -1) {
items.push({
title: html,
fn: () => action(album)
})
}
else {
html = "<div class='disabled'>" + html + "</div>"
items.push({
title: html,
fn: () => {}
})
}
items = items.concat(buildAlbumList(albums, exclude, action, album.id, layer + 1))
}
}
return items
}
contextMenu = {} contextMenu = {}
contextMenu.add = function(e) { contextMenu.add = function(albumID, e) {
let items = [ let items = [
{ title: build.iconic('image') + 'Upload Photo', fn: () => $('#upload_files').click() }, { title: build.iconic('image') + 'Upload Photo', fn: () => $('#upload_files').click() },
@ -14,7 +52,7 @@ contextMenu.add = function(e) {
{ title: build.iconic('dropbox', 'ionicons') + 'Import from Dropbox', fn: upload.start.dropbox }, { title: build.iconic('dropbox', 'ionicons') + 'Import from Dropbox', fn: upload.start.dropbox },
{ title: build.iconic('terminal') + 'Import from Server', fn: upload.start.server }, { title: build.iconic('terminal') + 'Import from Server', fn: upload.start.server },
{ }, { },
{ title: build.iconic('folder') + 'New Album', fn: album.add } { title: build.iconic('folder') + 'New Album', fn: () => album.add(albumID) }
] ]
basicContext.show(items, e.originalEvent) basicContext.show(items, e.originalEvent)
@ -47,7 +85,7 @@ contextMenu.album = function(albumID, e) {
// fn must call basicContext.close() first, // fn must call basicContext.close() first,
// in order to keep the selection // in order to keep the selection
if (albumID==='0' || albumID==='f' || albumID==='s' || albumID==='r') return false if (album.isSmartID(albumID)) return false
// Show merge-item when there's more than one album // Show merge-item when there's more than one album
let showMerge = (albums.json && albums.json.albums && Object.keys(albums.json.albums).length>1) let showMerge = (albums.json && albums.json.albums && Object.keys(albums.json.albums).length>1)
@ -90,26 +128,13 @@ contextMenu.albumMulti = function(albumIDs, e) {
contextMenu.albumTitle = function(albumID, e) { contextMenu.albumTitle = function(albumID, e) {
api.post('Albums::get', {}, function(data) { api.post('Albums::get', { parent: -1 }, function(data) {
let items = [] let items = []
if (data.albums && data.num>1) { if (data.albums && data.num>1) {
// Generate list of albums items = buildAlbumList(data.albums, [albumID], (a) => lychee.goto(a.id))
$.each(data.albums, function() {
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
if (this.title==='') this.title = 'Untitled'
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
if (this.id!=albumID) items.push({
title: html,
fn: () => lychee.goto(this.id)
})
})
items.unshift({ }) items.unshift({ })
@ -123,27 +148,34 @@ contextMenu.albumTitle = function(albumID, e) {
} }
function getAlbumFrom(albums, id) {
for (a in albums) {
if (albums[a].id == id)
return albums[a]
}
return null
}
contextMenu.mergeAlbum = function(albumID, e) { contextMenu.mergeAlbum = function(albumID, e) {
api.post('Albums::get', {}, function(data) { api.post('Albums::get', { parent: -1 }, function(data) {
let items = [] let items = []
if (data.albums && data.num>1) { if (data.albums && data.num>1) {
$.each(data.albums, function() { let selalbum = albums.getByID(albumID)
let title = selalbum.title
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
if (this.title==='') this.title = 'Untitled'
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>` // disable all parents; we cannot move them into us
let exclude = [albumID]
if (this.id!=albumID) items.push({ let a = getAlbumFrom(data.albums, selalbum.parent)
title: html, while (a != null) {
fn: () => album.merge([ albumID, this.id ]) exclude.push(a.id)
}) a = getAlbumFrom(data.albums, a.parent)
}
}) items = buildAlbumList(data.albums, exclude, (a) => album.merge([ albumID, a.id ], [title, a.title]))
} }
@ -177,14 +209,43 @@ contextMenu.photo = function(photoID, e) {
} }
function countSubAlbums(photoIDs) {
let count = 0
if (album.subjson) {
for (i in photoIDs) {
for (j in album.subjson.albums) {
if (album.subjson.albums[j].id == photoIDs[i]) {
count++
break
}
}
}
}
return count
}
contextMenu.photoMulti = function(photoIDs, e) { contextMenu.photoMulti = function(photoIDs, e) {
let subcount = countSubAlbums(photoIDs)
let photocount = photoIDs.length - subcount
if (subcount && photocount) {
$('.photo.active, .album.active').removeClass('active')
multiselect.close()
lychee.error("Please select either albums or photos!")
return
}
if (subcount) {
contextMenu.albumMulti(photoIDs, e)
return
}
multiselect.stopResize()
// Notice for 'Move All': // Notice for 'Move All':
// fn must call basicContext.close() first, // fn must call basicContext.close() first,
// in order to keep the selection and multiselect // in order to keep the selection and multiselect
multiselect.stopResize()
let items = [ let items = [
{ title: build.iconic('star') + 'Star All', fn: () => photo.setStar(photoIDs) }, { title: build.iconic('star') + 'Star All', fn: () => photo.setStar(photoIDs) },
{ title: build.iconic('tag') + 'Tag All', fn: () => photo.editTags(photoIDs) }, { title: build.iconic('tag') + 'Tag All', fn: () => photo.editTags(photoIDs) },
@ -211,19 +272,7 @@ contextMenu.photoTitle = function(albumID, photoID, e) {
items.push({ }) items.push({ })
// Generate list of albums items = items.concat(buildAlbumList(data.content, [photoID], (a) => lychee.goto(albumID + '/' + a.id)))
$.each(data.content, function(index) {
if (this.title==='') this.title = 'Untitled'
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbUrl }'><div class='title'>$${ this.title }</div>`
if (this.id!=photoID) items.push({
title: html,
fn: () => lychee.goto(albumID + '/' + this.id)
})
})
} }
@ -251,7 +300,7 @@ contextMenu.move = function(photoIDs, e) {
let items = [] let items = []
api.post('Albums::get', {}, function(data) { api.post('Albums::get', { parent: -1 }, function(data) {
if (data.num===0) { if (data.num===0) {
@ -262,20 +311,7 @@ contextMenu.move = function(photoIDs, e) {
} else { } else {
// Generate list of albums items = buildAlbumList(data.albums, [album.getID()], (a) => photo.setAlbum(photoIDs, a.id))
$.each(data.albums, function() {
if (!this.thumbs[0]) this.thumbs[0] = 'src/images/no_cover.svg'
if (this.title==='') this.title = 'Untitled'
let html = lychee.html`<img class='cover' width='16' height='16' src='$${ this.thumbs[0] }'><div class='title'>$${ this.title }</div>`
if (this.id!=album.getID()) items.push({
title: html,
fn: () => photo.setAlbum(photoIDs, this.id)
})
})
// Show Unsorted when unsorted is not the current album // Show Unsorted when unsorted is not the current album
if (album.getID()!=='0') { if (album.getID()!=='0') {

@ -44,7 +44,7 @@ header.bind = function() {
header.dom('#button_settings') .on(eventName, contextMenu.settings) header.dom('#button_settings') .on(eventName, contextMenu.settings)
header.dom('#button_info_album') .on(eventName, sidebar.toggle) header.dom('#button_info_album') .on(eventName, sidebar.toggle)
header.dom('#button_info') .on(eventName, sidebar.toggle) header.dom('#button_info') .on(eventName, sidebar.toggle)
header.dom('.button_add') .on(eventName, contextMenu.add) header.dom('.button_add') .on(eventName, function(e) { contextMenu.add(album.getID(), e) })
header.dom('#button_more') .on(eventName, function(e) { contextMenu.photoMore(photo.getID(), e) }) header.dom('#button_more') .on(eventName, function(e) { contextMenu.photoMore(photo.getID(), e) })
header.dom('#button_move') .on(eventName, function(e) { contextMenu.move([ photo.getID() ], e) }) header.dom('#button_move') .on(eventName, function(e) { contextMenu.move([ photo.getID() ], e) })
header.dom('.header__hostedwith') .on(eventName, function() { window.open(lychee.website) }) header.dom('.header__hostedwith') .on(eventName, function() { window.open(lychee.website) })
@ -52,7 +52,7 @@ header.bind = function() {
header.dom('#button_trash') .on(eventName, function() { photo.delete([ photo.getID() ]) }) header.dom('#button_trash') .on(eventName, function() { photo.delete([ photo.getID() ]) })
header.dom('#button_archive') .on(eventName, function() { album.getArchive(album.getID()) }) header.dom('#button_archive') .on(eventName, function() { album.getArchive(album.getID()) })
header.dom('#button_star') .on(eventName, function() { photo.setStar([ photo.getID() ]) }) header.dom('#button_star') .on(eventName, function() { photo.setStar([ photo.getID() ]) })
header.dom('#button_back_home') .on(eventName, function() { lychee.goto() }) header.dom('#button_back_home') .on(eventName, function() { lychee.goto(album.getParent()) })
header.dom('#button_back') .on(eventName, function() { lychee.goto(album.getID()) }) header.dom('#button_back') .on(eventName, function() { lychee.goto(album.getID()) })
header.dom('.header__search').on('keyup click', function() { search.find($(this).val()) }) header.dom('.header__search').on('keyup click', function() { search.find($(this).val()) })

@ -68,7 +68,7 @@ $(document).ready(function() {
if (basicModal.visible()===true) basicModal.cancel() if (basicModal.visible()===true) basicModal.cancel()
else if (visible.contextMenu()) contextMenu.close() else if (visible.contextMenu()) contextMenu.close()
else if (visible.photo()) lychee.goto(album.getID()) else if (visible.photo()) lychee.goto(album.getID())
else if (visible.album()) lychee.goto() else if (visible.album()) lychee.goto(album.getParent())
else if (visible.albums() && header.dom('.header__search').val().length!==0) search.reset() else if (visible.albums() && header.dom('.header__search').val().length!==0) search.reset()
return false return false
}) })

@ -6,8 +6,8 @@
lychee = { lychee = {
title : document.title, title : document.title,
version : '3.1.2', version : '3.1.3',
versionCode : '030102', versionCode : '030103',
updatePath : '//update.electerious.com/index.json', updatePath : '//update.electerious.com/index.json',
updateURL : 'https://github.com/electerious/Lychee', updateURL : 'https://github.com/electerious/Lychee',
@ -170,6 +170,7 @@ lychee.load = function() {
// Trash data // Trash data
photo.json = null photo.json = null
albums.json = null
// Show Photo // Show Photo
if (lychee.content.html()==='' || (header.dom('.header__search').length && header.dom('.header__search').val().length!==0)) { if (lychee.content.html()==='' || (header.dom('.header__search').length && header.dom('.header__search').val().length!==0)) {
@ -182,6 +183,7 @@ lychee.load = function() {
// Trash data // Trash data
photo.json = null photo.json = null
albums.json = null
// Show Album // Show Album
if (visible.photo()) view.photo.hide() if (visible.photo()) view.photo.hide()
@ -197,6 +199,7 @@ lychee.load = function() {
} }
// Trash data // Trash data
album.subjson = null
album.json = null album.json = null
photo.json = null photo.json = null

@ -199,7 +199,7 @@ multiselect.getSelection = function(e) {
let id = $(this).data('id') let id = $(this).data('id')
if (id!=='0' && id!==0 && id!=='f' && id!=='s' && id!=='r' && id!=null) { if (id!=null && id!==0 && !album.isSmartID(id)) {
ids.push(id) ids.push(id)
$(this).addClass('active') $(this).addClass('active')

@ -144,8 +144,22 @@ view.album = {
let photosData = '' let photosData = ''
// Sub albums
if (album.subjson && album.subjson.albums && album.subjson.num!==0) {
photosData = build.divider('Albums')
$.each(album.subjson.albums, function() {
albums.parse(this)
photosData += build.album(this)
})
}
if (album.json.content && album.json.content!==false) { if (album.json.content && album.json.content!==false) {
photosData += build.divider('Photos')
// Build photos // Build photos
$.each(album.json.content, function() { $.each(album.json.content, function() {
photosData += build.photo(this) photosData += build.photo(this)
@ -164,13 +178,25 @@ view.album = {
title: function(photoID) { title: function(photoID) {
let title = album.json.content[photoID].title if (album.json.content[photoID]) {
ntitle = album.json.content[photoID].title
prefix = '.photo'
}
else {
for (i in album.subjson.albums) {
if (album.subjson.albums[i].id == photoID) {
ntitle = album.subjson.albums[i].title
break
}
}
prefix = '.album'
}
title = lychee.escapeHTML(title) ntitle = lychee.escapeHTML(ntitle)
$('.photo[data-id="' + photoID + '"] .overlay h1') $(prefix + '[data-id="' + photoID + '"] .overlay h1')
.html(title) .html(ntitle)
.attr('title', title) .attr('title', ntitle)
}, },

@ -8,6 +8,10 @@
box-shadow: 0 0 0 1px black(.5); box-shadow: 0 0 0 1px black(.5);
} }
&__data .disabled {
opacity: 0.5;
}
&__data .title { &__data .title {
display: inline-block; display: inline-block;
margin: 0 0 3px 26px; margin: 0 0 3px 26px;

Loading…
Cancel
Save