#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.
#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.
<!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.
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.
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.
#Icons
# The folders are SVG’s that are inlined into the CSS using:
url("data:image/svg+xml;utf8, ... ")
#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?
# 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.
.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”
# 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.
#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.
# 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.
#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 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.
# .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.
# 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.
# 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.
<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>
<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/
# 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.
# 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.
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.
#
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.