Creating an Automatic Gallery

#The Goal

# One of the few things I still like about dropbox is that you can link people to a folder to display an image gallery, but dropbox always displays an annoying pop-up to try to get people to sign up. So I’m going to try making my own auto-upload gallery, where you can just drag a picture into a folder and it’ll instantly be online in a gallery. 

# dropbox_gallery dropbox_pop-up

#Attempt 1

# The HTML is very simple and self-contained. I can have a NodeJS script generate the thumbnails using ImageMagick and create a fileList.txt and then the HTML has JavaScript that can read it.

gallery_noFolders

# Download this code

<!doctype html>
<html lang="en"><head>	<meta charset="UTF-8">
<title>Image Gallery</title>
<style>
	
	
	
	body{
		margin: 0;
	}
	
	a{
		text-decoration: none;
		color: slateblue;
	}
	
	#gallery{
		background-color: #d1e9ff;
		display: flex;
		flex-wrap: wrap;
		justify-content: space-evenly;
/*		justify-content: space-between;*/
		align-content: flex-start;
/*		align-content: space-between;*/
/*		align-content: stretch;*/
/*		align-items: flex-start;*/
		min-height: 100vh;
		margin: 0;
		padding: 0;
	}
	
	.thumb{
		background-color: white;
		outline: solid lightgrey 1px;
		list-style-type: none;
		margin: 1pc;
		padding: 0.5pc;
		font-size: 14pt;
		/*	Segoe UI is a default Windows 7 font with a thin elegant look.  It's also the default UI font for windows itself.	*/
		font-family: Segoe UI, sans-serif;
		text-align: center;
		
		width: 27vw;
		min-width: 12pc;
		max-width: 13pc;
		
		box-shadow: 0 3pt 0.8pc -0.5pc rgba(0,0,160, 1);
	}
	
	.thumbImage{
		width: 100%;
		/*	Remove the mysterious gap under wide images	*/
		margin: -0.4pc;
		padding-top: 0.4pc;
	}
	
	.folderImage{
		content: url("data:image/svg+xml;utf8,<svg enable-background='new 0 0 58 58' version='1.1' viewBox='0 0 58 47' xmlns='http://www.w3.org/2000/svg'><path d='m46.324 47h-44.759c-1.03 0-1.779-0.978-1.51-1.973l10.166-27.871c0.184-0.682 0.803-1.156 1.51-1.156h44.759c1.03 0 1.51 0.984 1.51 1.973l-10.166 27.871c-0.184 0.682-0.803 1.156-1.51 1.156z' fill='%23efce4a'/><path d='M 50.268,7 H 25 L 20,0 H 1.732 C 0.776,0 0,0.775 0,1.732 V 44.46 c 0.069,0.002 0.138,0.006 0.205,0.01 L 10.22,17.156 C 10.404,16.473 11.023,16 11.73,16 H 52 V 8.732 C 52,7.775 51.224,7 50.268,7 Z' fill='%23ebba16'/></svg>");
		/*	reduce its size...	*/
		padding: 0 0.5pc;
		padding-top: 0.5pc;
		/*	... relative to the image area	*/
		width: 100%;
		/*	make the padding shrink the item's contents, instead of growing its size	*/
		box-sizing: border-box;
	}
	
	.thumbImageCrop{
		background-color: white;
		/*	checkerboard pattern	*/
		background-image: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%);
/*		background-position: 0 0, 10px 10px;*/
/*		background-size: 20px 20px;*/
		background-position: 0 0, 1pc 1pc;
		background-size: 2pc 2pc;
		max-height: 11pc;
		overflow: hidden;
		/*	makes the border appear inside the image and trace its cropped edges.  Also shrinks the image to fit inside of its border	*/
		box-sizing: border-box;
		border: solid lightgrey 1px;
	}
	
	.fileName{
		text-align: center;
		margin: 0;
		padding-top: 0.5pc;
/*		padding-bottom: 1pc;*/
		line-height: 1.3em;
		width: 100%;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}
	
	
	
</style>
</head><body>
	
	
	<ul id="gallery">
		
	</ul>
	
	
	
<script>
(function(){
	
	
	
	// loaded files are parsed, and then stored into these variables
	var fileList_ary;
	var folderList_ary;
	
	
	
	// call this after those variables are populated
	function allFilesLoaded(){
		// generate thumbs
		// find gallery
		var gallery_elm = document.getElementById("gallery");
		var innerHtml_str = '';
		
		
		// add folders
		for(var i=0; i<folderList_ary.length; i++){
			var folderName = folderList_ary[i];

			// create HTML
			var thumbHtml_str = '';
			thumbHtml_str += '<li class="thumb">';
			thumbHtml_str += '	<a href="' + folderName + '" target="_blank">';
			thumbHtml_str += '			<div class="folderImage"></div>';
			// thumbHtml_str += '		</div>';
			thumbHtml_str += '		<p class="folderName">' + folderName + '</p>';
			thumbHtml_str += '	</a>';
			thumbHtml_str += '</li>';

			// add this thumbnail to the new html
			innerHtml_str += thumbHtml_str;
		}// for each folder
		
		
		
		// add files
		for(var i=0; i<fileList_ary.length; i++){
			var fileName = fileList_ary[i];
			var thumbPath = 'thumbs/' + fileName;

			// create HTML
			var thumbHtml_str = '';
			thumbHtml_str += '<li class="thumb">';
			thumbHtml_str += '	<a href="' + fileName + '" target="_blank">';
			thumbHtml_str += '		<div class="thumbImageCrop">';
			thumbHtml_str += '			<img class="thumbImage" src="' + thumbPath + '">';
			thumbHtml_str += '		</div>';
			thumbHtml_str += '	</a>';
			thumbHtml_str += '	<a href="' + fileName + '" download>';
			thumbHtml_str += '		<p class="fileName">' + fileName + '</p>';
			thumbHtml_str += '	</a>';
			thumbHtml_str += '</li>';

			// add this thumbnail to the new html
			innerHtml_str += thumbHtml_str;
		}// for each file
	
		
		
		// populate the gallery
		gallery_elm.innerHTML = innerHtml_str;
	}// allFilesLoaded()
	
	
	
	// check whether the data variables are populated, and then call allFilesLoaded()
	function check_allFilesLoaded(){
		// if:  any data is missing, then abort
		if( !fileList_ary )			return;
		if( !folderList_ary )		return;
		
		// else:  all data exists
		allFilesLoaded();
	}// check_allFilesLoaded()
	
	
	
	// read fileList.txt
	var files_xhr = new XMLHttpRequest();
	files_xhr.open("GET", "fileList.txt");
	files_xhr.responseType = "text";
	files_xhr.send();
	files_xhr.onload = function(){
		if( files_xhr.status === 200 ){
			var contents_str = files_xhr.responseText;
			var contents_ary = contents_str.split(/[\r\n]+/);
			// remove empty lines
			for(var i=contents_ary.length-1; i>=0; i--){
				if( contents_ary[i].length === 0 )		contents_ary.splice( i, 1 );
			}// for each line in contents_ary
			fileList_ary = contents_ary;
		}// if: success
		else
		{//if: fail
			fileList_ary = [];
		}//if: fail

		check_allFilesLoaded();
	}// onLoad()
	
	
	
	// read folderList.txt
	var folders_xhr = new XMLHttpRequest();
	folders_xhr.open("GET", "folderList.txt");
	folders_xhr.responseType = "text";
	folders_xhr.send();
	folders_xhr.onload = function(){
		if( folders_xhr.status === 200 ){
			var contents_str = folders_xhr.responseText;
			var contents_ary = contents_str.split(/[\r\n]+/);
			// remove empty lines
			for(var i=contents_ary.length-1; i>=0; i--){
				if( contents_ary[i].length === 0 )		contents_ary.splice( i, 1 );
			}// for each line in contents_ary
			folderList_ary = contents_ary;
		}// if: success
		else
		{//if: fail
			folderList_ary = [];
		}//if: fail
			
		
		check_allFilesLoaded();
	}// onLoad()
	
	
	
	
	
	
}());
</script>
</body></html>

#Thumbnails

# I tried using IrfanView to generate thumbnails, but it removes alpha channels from PNG images.

irfanView_thumb_issue

# Download this code

rem	"drop folder = createThumbs.cmd"


echo off

rem This requires Irfanview


set size=300


	rem Where are we?
set iview="C:\Program Files\IrfanView\i_view64.exe"
set infolder=%~1
set thisfolder=%~dp0
set outfolder=%~dp0thumbs

	rem empty the thumbnail folder
if exist "%outfolder%" del /s /q "%outfolder%"
if not exist "%outfolder%\NUL" mkdir "%outfolder%"

%iview% "%infolder%\*.jpg" /resize=( %size%, %size%) /resample /aspectratio /convert="%outfolder%\$N.jpg"
%iview% "%infolder%\*.png" /resize=( %size%, %size%) /resample /aspectratio /convert="%outfolder%\$N.png"
%iview% "%infolder%\*.gif" /resize=( %size%, %size%) /resample /aspectratio /convert="%outfolder%\$N.gif"

rem pause

# But it turns out that ImageMagick will preserve alpha channels if you generate thumbnails using its “mogrify” command.

# Okay, I figured out how to recursively replace pics with smaller versions.

for /r "%thisfolder%" %%G in (*.jpg) do mogrify -resize "800x800>" "%%G"

# Requires ImageMagick to be installed. Always backup your stuff before trying anything recursive.

ImageMagick

# Download this code

rem	"drop folder = recursively replace pics with thumbs (don't enlarge small pics).cmd"



echo off
	rem  This requires ImageMagick



set size=300



	rem  Where are we?
set selffolder=%~dp0

	rem  Set "picfolder" to the 1st parameter's value.  If no param, then use "selffolder" instead
if %1.==. (goto defaultFolder) else set picfolder=%~1\
:defaultFolderReturn



rem if exist "%picfolder%*.jpg" mogrify -quiet -format jpg -path "%thumbfolder%" -thumbnail %size%x%size% "%picfolder%*.jpg"
rem if exist "%picfolder%*.png" mogrify -quiet -format png -path "%thumbfolder%" -thumbnail %size%x%size% "%picfolder%*.png"
rem if exist "%picfolder%*.gif" mogrify -quiet -format gif -path "%thumbfolder%" -thumbnail %size%x%size% "%picfolder%*.gif"
for /r "%picfolder%" %%G in (*.*) do mogrify -resize %size%x%size% "%%G"
rem for /r "%picfolder%" %%G in (*.*) do echo "%%G"



rem pause



goto end

	:defaultFolder
		echo No param, using default folder:  %selffolder%
		set picfolder=%selffolder%\
	goto :defaultFolderReturn

:end

#“Showing” Transparency

# The checkerboard pattern is a fancy CSS gradient with sudden changes in color.

css_checkerboard

#Icons

# The folders are SVG’s that are inlined into the CSS using:

url("data:image/svg+xml;utf8, ... ")

# Probably Don’t Base64 SVG

dont_base64_svg

#Eek! A bug!

# For some reason, images wrapped in link tags tend to have this annoying gap added underneath them. It’s not margin OR padding so I’m stumped about what’s causing it. Maybe something to do with how I’m scaling the thumbnails proportionally?

weird_lower_gap

# I can hide the gap using a negative margin and add padding to keep everything below it in the same place. Yeah 0.4 picas may be a magic number but it seems to work reliably at any view scale.

# pic_margin_css_fix gallery_noFolders

.thumbImage {
	width: 100%
	/* Remove the mysterious gap under wide images */
	margin: -0.4pc;
	padding-top: 0.4pc;
}

# Wait, there’s a better way! Just change the picture’s “vertical-align” to any other value besides “baseline”

image_gap_fix

# It turns out that weird gap under the pictures is caused by a font thing called “descenders”. Lowercase letters like “g” and “y” hang down a little under the line of text and the web browser automatically makes room for these when you wrap anything in an HTML link, because it assumes that links always have text. There are a bunch of ways to fix this in the CSS style sheet.

text_descenders

#Links

# Clicking the thumbnail images opens the full-size pic in a separate tab. Clicking on the file name immediately downloads it to your default download folder. You can make links do this by adding an attribute called “download” to the <a> tag.

download_link_attribute

# If I add a folderList.txt I can also display links to other folders, which can also contain an index.htm file. That could be another gallery or a normal web page.

gallery_noFolders

#Automatic Sync

# I think what I’ll do is use SecondCopy to automatically copy my gallery folder when files change to another spot on my HD, and then make ImageMagick replace the copied pics with smaller versions, then tell WinSCP to sync that folder to the server.

SecondCopy_autoCopy

# SecondCopy is a backup program that can run a script after copying files, but it can’t pass any parameters to the script. So I’ll just have my script recursively shrink larger images in the “upload” folder, hopefully without recompressing existing ones.

#Attempt 2 (The Right Way)

# Just realized this whole thing can be MUCH simpler. If I create an .htaccess file and tell it to automatically use a custom HTML file for pages lacking an index.htm. The Apache server embeds a file list into the HTML, so my gallery can just read THAT.

# html_fileList_customized html_fileList_default

# Download this code

#	.htaccess



# Display folder contents
Options +Indexes

# DIRECTORY CUSTOMIZATION
<IfModule mod_autoindex.c>
	IndexOptions FancyIndexing FoldersFirst NameWidth=* DescriptionWidth=* SuppressHTMLPreamble
	HeaderName /css/folderIndex_gallery/header.htm
	ReadmeName /css/folderIndex_gallery/footer.htm
	
	DefaultIcon /css/folderIndex/misc.png
</IfModule>

# No need to generate a fileList.txt or folderList.txt. And if I compress the images before uploading them, generating thumbnails won’t really be necessary. I kinda like how Twitter lets you drag out “thumbnail” images from the page to download the full-size version.

twitter_drag_thumb

# It didn’t take long to adapt the JavaScript to read the server’s automatic file-list. You need to turn ON FancyIndexing in the .htaccess file to have enough information on-hand to identify folders.

gallery_from_apacheList

# It might be fun to leave the original file listing visible at the bottom to show that this is an enhanced version of it. But that would be redundant. And the gallery is supposed to be dead-simple by design, so I’ll probably hide the listing.

# Download header.htm

<html><head>
	<title>Humbird0's Lab: Gallery</title>
	<style>
	
	
	
	body{
		margin: 0;
	}
	
	a{
		text-decoration: none;
		color: slateblue;
	}
	
	#gallery{
		background-color: #d1e9ff;
		display: flex;
		flex-wrap: wrap;
		justify-content: space-evenly;
/*		justify-content: space-between;*/
		align-content: flex-start;
/*		align-content: space-between;*/
/*		align-content: stretch;*/
/*		align-items: flex-start;*/
		min-height: 100vh;
		/*min-height: 10vh;*/
		margin: 0;
		padding: 0;
	}
	
	.thumb{
		background-color: white;
		outline: solid lightgrey 1px;
		list-style-type: none;
		margin: 1pc;
		padding: 0.5pc;
		font-size: 14pt;
		/*	Segoe UI is a default Windows 7 font with a thin elegant look.  It's also the default UI font for windows itself.	*/
		font-family: Segoe UI, sans-serif;
		text-align: center;
		
		width: 27vw;
		min-width: 12pc;
		max-width: 13pc;
		
		box-shadow: 0 3pt 0.8pc -0.5pc rgba(0,0,160, 1);
	}
	
	.thumbImage{
		width: 100%;
		/*	Remove the mysterious gap under wide images	*/
		margin: -0.4pc;
		padding-top: 0.4pc;
	}
	
	.folderImage{
		/*	reduce its size...	*/
		padding: 0 0.5pc;
		padding-top: 0.5pc;
		/*	... relative to the image area	*/
		width: 100%;
		/*	make the padding shrink the item's contents, instead of growing its size	*/
		box-sizing: border-box;
		
		background-image: url("/css/folderIndex_gallery/folder.svg");
		background-repeat: no-repeat;
		height: 11pc;
	}
	
	.thumbImageCrop{
		background-color: white;
		/*	checkerboard pattern	*/
		background-image: linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%), linear-gradient(45deg, lightgrey 25%, transparent 25%, transparent 75%, lightgrey 75%);
/*		background-position: 0 0, 10px 10px;*/
/*		background-size: 20px 20px;*/
		background-position: 0 0, 1pc 1pc;
		background-size: 2pc 2pc;
		max-height: 11pc;
		overflow: hidden;
		/*	makes the border appear inside the image and trace its cropped edges.  Also shrinks the image to fit inside of its border	*/
		box-sizing: border-box;
		border: solid lightgrey 1px;
	}
	
	.fileName{
		text-align: center;
		margin: 0;
		padding-top: 0.5pc;
/*		padding-bottom: 1pc;*/
		line-height: 1.3em;
		width: 100%;
		white-space: nowrap;
		overflow: hidden;
		text-overflow: ellipsis;
	}
	
	
	/*	Hide the server's file-listing	*/
	pre{
		display: none;
	}
	
	
	
</style>
</head><body>
<ul id="gallery"></ul>

# Download footer.htm

<script>
(function(){
	

	// For any of this to work, "FancyIndexing" has to be turned on in the htaccess file (by mentioning it in the IndexOptions), because if it's turned off there's no way to tell which items are folders and which ones are files.
	
	
	
	// hide default file-listing if this JavaScript is able to run
	// ... if JavaScript is disabled, display the default file-list like normal
	var listingTags_ary = document.getElementsByTagName("pre");
	var listingTag = listingTags_ary[ listingTags_ary.length-1 ];		// get the last <pre> tag
	
	var defaultList_ary = document.getElementsByTagName("pre");
	var defaultList_elm = defaultList_ary[ defaultList_ary.length-1 ];
	defaultList_elm.classList.add("hidden");
	
	// un-hide the gallery if this JavaScript is able to run
	// ... if JavaScript is disabled, the fancy-gallery tag remains hidden
	var gallery_elm = document.getElementById("gallery");
	gallery_elm.classList.remove("hidden");
	
	
	// read file listing
	var imgTag_ary = defaultList_elm.getElementsByTagName("img");
	var linkTag_ary = defaultList_elm.getElementsByTagName("a");
	// FancyIndexing can include 4 links for changing the file-list order. If these sorting links are present, this will skip past them.  The rest of the links will correspond to each file or folder.
	var firstLinkText = linkTag_ary[0].childNodes[0].textContent;
	if( firstLinkText === "Name" ){
		var linkTag_ary_tmp = [];
		for(var i=4; i<linkTag_ary.length; i++){
			linkTag_ary_tmp.push( linkTag_ary[i] );
		}
		linkTag_ary = linkTag_ary_tmp;
		delete linkTag_ary_tmp;
	}//if:  the first few links are for re-sorting the file-list, then omit them
	
	
	// sort items into folders and image files  (ignore other files)
	// this is accomplished by looking at the img tags because their "alt" tag will differ depending on whether that line in the list refers to a folder or a file.
	var fileList_ary = [];
	var folderList_ary = [];
	for(var f=0; f<imgTag_ary.length; f++){
		var link_url = linkTag_ary[f].attributes.href.value;
		var linkText = linkTag_ary[f].childNodes[0].textContent;
		if( linkText === "Parent Directory" )		continue;		// don't show links to the parent folder  (disable this line of code to display them)
		var isFolder = (imgTag_ary[f].attributes.alt.value === "[DIR]");
		if( isFolder ){
			// folder
			folderList_ary.push( link_url );
		}else{
			// file
			var allowFile = false;
			if( link_url.indexOf(".jpg") > -1 ) 		allowFile = true;
			if( link_url.indexOf(".jpeg") > -1 )		allowFile = true;
			if( link_url.indexOf(".png") > -1 ) 		allowFile = true;
			if( link_url.indexOf(".gif") > -1 ) 		allowFile = true;
			if( allowFile )													fileList_ary.push( link_url );
		}
	}// for each item
	
	
	generateHtml();
	
	
	
	
	// call this after "fileList_ary" and "folderList_ary" are populated
	function generateHtml(){
		// generate thumbs
		var innerHtml_str = '';
		
		
		// add folders
		for(var i=0; i<folderList_ary.length; i++){
			var folderName = folderList_ary[i];

			// create HTML
			var thumbHtml_str = '';
			thumbHtml_str += '<li class="thumb">';
			thumbHtml_str += ' 	<a href="' + folderName + '" target="_blank">';
			thumbHtml_str += '				<div class="folderImage"></div>';
			// thumbHtml_str += '		</div>';
			thumbHtml_str += '			<p class="folderName">' + folderName + '</p>';
			thumbHtml_str += '		</a>';
			thumbHtml_str += '</li>';

			// add this thumbnail to the new html
			innerHtml_str += thumbHtml_str;
		}// for each folder
		
		
		
		// add files
		for(var i=0; i<fileList_ary.length; i++){
			var fileName = fileList_ary[i];
			var thumbPath = fileName;

			// create HTML
			var thumbHtml_str = '';
			thumbHtml_str += '<li class="thumb">';
			thumbHtml_str += '	<a href="' + fileName + '" target="_blank">';
			thumbHtml_str += '		<div class="thumbImageCrop">';
			thumbHtml_str += '			<img class="thumbImage" src="' + thumbPath + '">';
			thumbHtml_str += '		</div>';
			thumbHtml_str += '	</a>';
			thumbHtml_str += '	<a href="' + fileName + '" download>';
			thumbHtml_str += '		<p class="fileName">' + fileName + '</p>';
			thumbHtml_str += '	</a>';
			thumbHtml_str += '</li>';

			// add this thumbnail to the new html
			innerHtml_str += thumbHtml_str;
		}// for each file
	
		
		
		// populate the gallery
		gallery_elm.innerHTML = innerHtml_str;
	}// generateHtml()
	
	
	
	
}());
</script>
</body></html>

# Sure enough, it all works! And I only need to upload folders with pictures.
http://www.humbird0.com/gallery/subFolder/

title

# Okay so it turns out WinSCP has a feature called “keepuptodate” which watches a folder and automatically synchronizes it to a remote server when the contents change. Perfect! It won’t auto-compress my files. But then again, Dropbox didn’t do that either.

win-scp_auto-sync

# That’s how you would temporarily enable syncing. But if you want to do this automatically, you can use a script instead.

# I just tell my computer to automatically run this “start-gallery.cmd” file each time Windows boots, and everything is ready to go. The folder automatically syncs whenever I add files to it.

# Download start-gallery.cmd

start "" WinSCP.exe /script="start-gallery-script.txt"

# That CMD file just tells WinSCP run another file in the same folder called “start-gallery-script.txt” which has all the actual instructions.

# Download start-gallery-script.txt

open connection-name
option batch continue
option confirm off
option reconnecttime off
option echo on

keepuptodate -delete -filemask="*.jpg; *.jpeg; *.gif; *.png; *.svg; *.webp; *.mp4" "C:\local\path\to\folder" "/server/path/to/folder"

# Obviously you would change those folder paths for your setup, and you would replace “connection-name” with the name of the connection you saved in WinSCP for your website. WinSCP stays running in the background, watching your folder, with no DOS window visible.