Source of “photos.js”
// Emacs settings: -*- mode: Fundamental; tab-width: 4; -*-
////////////////////////////////////////////////////////////////////////////
// //
// Andrew's Album Applications: photos.js //
// //
// Uses functions from common.js and crypto.js //
// //
// Copyright (c) 2004-2005, Andrew Birrell //
// //
////////////////////////////////////////////////////////////////////////////
//
// Global variables
//
var wSize; // window size
var mainSize = Object(); // size of main image display area
var iPhone = false; // fine tuning of the UI
var dropZone; // dropzone element
var reading; // page element to show while reading a page
var scaling; // page element to show current scale
var title; // title element
var parents; // element for parent links
var parentsTxt; // HTML for top-level parent link
var writing; // element for "writing" message
var editInner; // inside of edit dialog
var editTitle; // edit dialog title type-in element
var linkAnchor; // <A> in "Link" button
var btns = new Object(); // button details
var dlogs = new Object(); // pop-up dialogs
var dlogPositioned = new Object();
var thisPage = null; // attributes for current page
var user = \\\\"\\\\"; // default user for editing
var pwd = \\\\"\\\\"; // default password for editing
var jumpTarget = null; // if set, switch to here instead
var timer = null; // auto-play timer, for cancelling
var autoRoot = null; // root directory for auto-play
var preloadServer = false; // variant of auto-play to preload server cache
var interval = 6000; // auto-play timer interval
var timerScaleOn = null; // timer for showing "scaling" dlog
var timerScaleOff = null; // timer for hiding "scaling" dlog
var cacheP = new Cache(50); // cache of photo XML responses
var cacheF = new Cache(50); // cache of folder XML responses
var cacheHits = 0;
var cacheMisses = 0;
var parentFrag = // Template HTML fragment for parent link
'<a href="#<#PATH>" onclick="' +
"return doParent('<#PATH>')" +
'" title="Go back to "<#TITLE>""><#TITLE></a>';
var folderFrag = // Template HTML fragment for folder listing
'<td class=mini><a href="#<#PATH>" onclick="' +
"return jumpTo('<#PATH>')" +
'" title="Go to "<#TITLE>"">' +
'<img class=mini width="<#WIDTH>" height="<#HEIGHT>" ' +
'src="<#SRC>"></a></td>' +
'<td class=miniTitle><a href="#<#PATH>" onclick="' +
"return jumpTo('<#PATH>')" +
'" title="Go to "<#TITLE>""><#TITLE></a></td>';
var photoFrag = // Template HTML fragment for photo listing
'<a href="#<#PATH>" title="<#TITLE>" onclick="' +
"return jumpTo('<#PATH>')" +
'"><img class=thumb width="<#WIDTH>" height="<#HEIGHT>" ' +
'src="<#SRC>" style="margin-left: <#MARGINL>px; ' +
'margin-right: <#MARGINR>px; margin-top: <#MARGINT>px; ' +
'margin-bottom: <#MARGINB>px"></a> ';
var mainFrag = // Template HTML fragment for main screen
'<a href="<#RAW>" target="_new"' +
// ' title="Download the original full-scale image<#SIZE>"' +
'><img id=<#ID> ' +
'style="width: 1px; height: 1px" src="<#SRC>"></a>';
var scalingFrag =
'Image area is now<br><#XPX> x <#YPX> pixels';
//
// Dragging
//
var dragObject = null; // victim of the current drag, if any
var dragHighlight; // element whose class is changed during drag
var dragStartM = Object(); // initial mouse coordinates
var dragStartO; // initial object coordinates, doc relative
var dragSaveClass; // initial class of dragHighlight
var dragConstraint;
function dragStart(event, id, highlightId, highlightClass,
constraint) {
// Called on mousedown on the drag handle.
// Sets things up to drag the element named by "id".
// If "highlightId" is present (non-null), it's the name of an element
// whose className should be changed to "highlightClass" during the
// drag.
dragObject = document.getElementById(id);
if (dragObject) {
dragStartO = getElementPos(dragObject);
dragStartM.x = event.clientX;
dragStartM.y = event.clientY;
document.onmousemove = dragMove;
document.onmouseup = dragStop;
document.onmouseout = dragOut;
if (highlightId) {
dragHighlight = document.getElementById(highlightId);
} else {
dragHighlight = null;
}
if (dragHighlight) {
dragSaveClass = dragHighlight.className;
dragHighlight.className = highlightClass;
}
}
dragConstraint = constraint;
return false;
}
function dragMove(event) {
// Called on mouse movement (in IE, only if it happens in our window)
// Perform the drag
if (dragObject) {
if (!event) event = window.event; // IE versus the rest
var lMin = (dragConstraint ? dragConstraint.lMin : 0);
var lMax = (dragConstraint ? dragConstraint.lMax : 999999);
var tMin = (dragConstraint ? dragConstraint.tMin : 0);
var tMax = (dragConstraint ? dragConstraint.tMax : 999999);
var newLeft = Math.max(lMin, Math.min(lMax,
dragStartO.x + event.clientX - dragStartM.x));
var newTop = Math.max(tMin, Math.min(tMax,
dragStartO.y + event.clientY - dragStartM.y));
dragObject.style.left = "" + newLeft + "px";
dragObject.style.right = "auto";
dragObject.style.top = "" + newTop + "px";
dragObject.style.bottom = "auto";
if (dragConstraint && dragConstraint.draggedTo) {
dragConstraint.draggedTo(newLeft, newTop);
}
}
return false;
}
function dragStop(event) {
// Called on mouse up (in IE, only if it happens in our window)
if (dragObject) {
if (!event) event = window.event; // IE versus the rest
if (dragHighlight) dragHighlight.className = dragSaveClass;
dragObject = null;
document.onmousemove = null;
document.onmouseup = null;
document.onmouseout = null;
if (dragConstraint && dragConstraint.dragEnded) {
dragConstraint.dragEnded();
}
}
return false;
}
function dragOut(event) {
// Called on mouse leaving its target element
// We want to terminate the drag if the mouse leaves the window
// (it can transiently leave dragObject during fast dragging)
if (dragObject) {
if (!event) event = window.event; // IE versus the rest
if (!event.relatedTarget && !event.toElement) {
// It didn't go anywhere, so it must have left completely
dragStop(event);
}
}
return false;
}
//
// Button management
//
function Button(id, action, photoTip, folderTip) {
// Constructor for a Button object
this.element = document.getElementById(id);
if (this.element) {
this.txt = this.element.innerHTML;
this.photoHTML = '<a href="./" onclick="return ' +
action + '" title="' + photoTip + '">' + this.txt + '</a>';
this.folderHTML = '<a href="./" onclick="return ' +
action + '" title="' + folderTip + '">' + this.txt + '</a>';
}
this.disable();
}
Button.prototype.enable = function() {
// Enable a button
if (this.element) {
this.element.innerHTML =
(thisPage.isPhoto ? this.photoHTML : this.folderHTML);
}
this.enabled = true;
this.visible = true;
}
Button.prototype.disable = function() {
// Disable a button
if (this.element) {
this.element.innerHTML =
'<span class=disabled>' + this.txt + '</span>';
}
this.enabled = false;
this.visible = true;
}
Button.prototype.hide = function() {
// Remove button from the screen
if (this.element) this.element.innerHTML = "";
this.enabled = false;
this.visible = false;
}
//
// Progress bar animation and "reading" dialog
//
// This is purely for entertainment: it doesn't actually measure progress.
// It assumes that the bar widget should be sized within its parent.
//
var progressBarWidget = null; // the widget being scaled
// maximum width is the widget's parent
var progressBarTimer = null; // timer for reading progress bar
var progressBarW = 0; // reading progress bar state
function stepProgressBar() {
// Animate the readProgress widget
var maxWidth = progressBarWidget.parentNode.offsetWidth;
progressBarW += (progressBarW <= 20 ? 4 :
(progressBarW <= 40 ? 3 :
(progressBarW <= 60 ? 2 : 1)));
if (progressBarW > maxWidth-4) progressBarW = 0;
progressBarWidget.style.width = "" + progressBarW + "px";
}
function startProgressBar(widget) {
// Start a progress bar using the element named "widget"
progressBarWidget = document.getElementById(widget);
progressBarWidget.style.width = "4px"; // amount initially visible
progressBarW = 4;
progressBarTimer = setInterval(stepProgressBar, 500);
}
function stopProgressBar() {
// Stop animating the progress bar
if (progressBarTimer) clearInterval(progressBarTimer);
progressBarTimer = null;
}
function noteReading() {
// Show the "reading" dialog and disable buttons as appropriate
if (reading.style.display != 'block') {
reading.style.display = "block";
moveToCenter(reading, wSize);
startProgressBar("readingWidget");
btns.next.disable();
btns.prev.disable();
btns.skip.disable();
if (btns.auto.visible) btns.auto.disable();
btns.edit.disable();
if (dlogs.editForm.style.display == 'block') {
editTitle.blur();
editInner.style.visibility = "hidden";
}
}
}
function endOfReading() {
// enable/disable buttons as appropriate for current page,
// and dispense with the "reading" dialog
stopProgressBar();
reading.style.display = "none";
if (preloadServer) {
btns.next.disable();
btns.prev.disable();
btns.skip.disable();
btns.edit.disable();
} else {
if (thisPage.nextImage != "" || thisPage.isPhoto) {
btns.next.enable();
} else {
btns.next.disable();
}
if (thisPage.prevImage != "" || thisPage.isPhoto) {
btns.prev.enable();
} else {
btns.prev.disable();
}
if (thisPage.skipImage != "") {
btns.skip.enable();
} else {
btns.skip.disable();
}
btns.edit.enable();
}
if (timer) {
btns.auto.hide();
btns.pause.enable();
btns.resume.hide();
btns.stop.enable();
} else if (autoRoot) {
btns.auto.hide();
btns.pause.hide();
btns.resume.enable();
btns.stop.enable();
} else {
if (thisPage.autoImage != "") {
btns.auto.enable();
} else {
btns.auto.disable();
}
btns.pause.hide();
btns.resume.hide();
btns.stop.disable();
}
}
//
// Scaling the main image
//
function computeMainSize() {
// Compute appropriate values for current size of "td.main" element
// and adjust it in the DOM.
// NOTE: in Safari 1.3.1, td.main is at the top of the image, which
// can be below the top of the main table. (In IE 6, both return the
// same top value.)
var mainTable = document.getElementById("mainTable");
var main = document.getElementById("main");
var bottom = document.getElementById("bottomWrapper");
var mainPos = getElementPos(mainTable);
wSize = windowSize();
var h = wSize.y - mainPos.y - bottom.offsetHeight;
main.style.height = "" + h + "px";
mainSize.x = wSize.x;
mainSize.y = h;
}
function scaleMainImage() {
// Make the main image, if any, fit the "td.main" element
// Used for onresize events, and when loading the page
computeMainSize();
var mainImage = document.getElementById(thisPage.path);
if (mainImage) {
if (thisPage.isPhoto) {
var xScale = mainSize.x / thisPage.photoActualW;
var yScale = mainSize.y / thisPage.photoActualH;
// iPhone uses a viewscreen larger than the physicaly screen.
// So images will be scaled down again, so we allow a scale-up.
var scale = Math.min((iPhone ? 2 : 1),
Math.min(xScale, yScale));
mainImage.style.width = "" +
Math.round(thisPage.photoActualW * scale) + "px";
mainImage.style.height = "" +
Math.round(thisPage.photoActualH * scale) + "px";
} else {
// iPhone's gesture for scrolling a fixed-size inner DIV is
// awkward, and unknown by the vast majority of users. So on an
// iPhone we make the inner DIV height "auto". However, td.main
// has a fixed size too: if mainImage is less than that, the
// size from td.main applies, placing the bottom menu bar at
// the bottom of the screen; if mainImage is more than that,
// "auto" makes it expand td.main.
if (iPhone) {
mainImage.style.height = "auto";
} else {
mainImage.style.height = "" + mainSize.y + "px";
}
}
}
}
function scaleOn() {
timerScaleOn = null;
if (!iPhone) {
// Don't show scale on iPhone ... it happens too often, and isn't
// readable anyway.
scaling.style.display = 'block';
var temp = scalingFrag.replace(/<#XPX>/, mainSize.x);
temp = temp.replace(/<#YPX>/, mainSize.y);
scaling.innerHTML = temp;
moveToCenter(scaling, wSize);
if (timerScaleOff) clearTimeout(timerScaleOff);
timerScaleOff = window.setTimeout(scaleOff, 1000);
}
}
function scaleOff() {
if (scaling.style.display == 'block') scaling.style.display = 'none';
}
function doResize() {
// Respond to window resizing
scaleOff();
if (thisPage) {
scaleMainImage();
// Showing the "scaling" window during resize causes significant
// flicker in IE, so we defer it until dragging stops
if (timerScaleOn) clearTimeout(timerScaleOn);
timerScaleOn = window.setTimeout(scaleOn, 100);
}
}
//
// Prefetching Images
//
// We could get images pre-loaded by placing them all into some HTML
// and handing that to the browser. But then there'd be no way
// to cancel the load requests if we want to go somewhere else.
// By doing them sequentially ourselves, we get that control.
//
// This doesn't work on iPhone (v.2).
var imgQueue = null; // queue of images to preload
var imgQueueTail = null;
function initiateImageLoad() {
if (imgQueue) {
var prefetch = document.getElementById("prefetch");
prefetch.onload = stepImagePreload;
prefetch.src = imgQueue.src;
}
}
function stepImagePreload() {
// Current preload has completed; initiate next
imgQueue = imgQueue.next;
if (imgQueue) {
// execute next asynchronously, to avoid recursive overflow
setTimeout(initiateImageLoad, 10);
}
}
function enqueueImage(src) {
// enqueue an image for preloading
if (!iPhone) {
var queue = new Object();
queue.src = src;
queue.next = null;
if (imgQueue) {
imgQueueTail.next = queue;
} else {
imgQueue = queue;
initiateImageLoad();
}
imgQueueTail = queue;
}
}
function cancelImagePreload() {
// Dispense with the image preload queue
if (imgQueue) imgQueue.next = null;
}
//
// Page interpretation
//
function getTitle(node) {
// Return the value of the "title" child of given XML node
var titles = node.getElementsByTagName("title");
var child = titles[0].firstChild;
// The title is the child, but there's no child if the title was empty
return (child ? child.nodeValue : "");
}
function buildPage(responseXML) {
// Construct and return a page object based on the given XML
var doc = responseXML.documentElement;
newPage = new Object();
newPage.responseXML = responseXML;
newPage.path = doc.getAttribute("path");
newPage.title = getTitle(doc);
newPage.nextImage = doc.getAttribute("next");
newPage.prevImage = doc.getAttribute("prev");
newPage.skipImage = doc.getAttribute("skip");
var starLevel = doc.getAttribute("starLevel");
newPage.starLevel = (starLevel ? parseInt(starLevel) : null);
newPage.parents = ''; // parent links HTML
var i;
var parents = doc.getElementsByTagName("parent");
newPage.lastParent = "";
for (i = 0; i < parents.length; i++) {
var child = parents[i];
var chtitle = getTitle(child);
var chpath = child.getAttribute("path");
if (i == 0) {
chtitle = parentsTxt;
} else {
newPage.parents += ' > ';
}
var temp = parentFrag.replace(/<#PATH>/g, chpath);
temp = temp.replace(/<#TITLE>/g, htmlspecials(chtitle));
newPage.parents += temp;
newPage.lastParent = chpath;
newPage.autoRoot = chpath; // use last parent for photos
}
if (doc.nodeName == "photo") {
newPage.isPhoto = true;
newPage.autoImage = // enable auto iff next is in this folder
(newPage.autoRoot == "." ||
newPage.nextImage.indexOf(newPage.autoRoot) == 0 ?
newPage.nextImage : "");
var width = parseInt(doc.getAttribute("width"));
var height = parseInt(doc.getAttribute("height"));
var src = doc.getAttribute("src");
var raw = doc.getAttribute("raw");
var size = doc.getAttribute("size");
var date = doc.getAttribute("date");
var exposure = doc.getAttribute("exposure");
newPage.photoActualW = width;
newPage.photoActualH = height;
if (preloadServer) {
newPage.html = "<br><br><br><br><br><br><br><br>" +
"Preloading the server-side cache," +
" with no image display and no delays.<p>" +
"Click \"Stop\" to exit from this mode.";
} else {
var temp = mainFrag.replace(/<#SRC>/, src);
temp = temp.replace(/<#RAW>/, raw);
temp = temp.replace(/<#ID>/, newPage.path);
temp = temp.replace(/<#SIZE>/, "\n" + size);
newPage.html = temp;
}
newPage.commentary = (date ? htmlspecials(date) : " ") +
"<br>" +
(exposure ? htmlspecials(exposure) : " ")
} else {
// folder
newPage.isPhoto = false;
if (parents.length > 0) {
newPage.parents += ' > ';
}
newPage.parents += '<span class=disabled>' +
(parents.length == 0 ? parentsTxt :
htmlspecials(newPage.title)) +
'</span>';
newPage.autoImage = doc.getAttribute("first");
newPage.autoRoot = newPage.path; // use self for folders
content = '<div class=catalog id="' + newPage.path + '">';
var children = doc.getElementsByTagName("folder");
var folderCount = children.length;
var lCol = 0; // child index for left column
var rCol = Math.round((folderCount+0.5)/2);
var i;
for (i = 0; i < folderCount; i++) {
var sub;
if (i%2 == 0) {
content += (i == 0 ?
"<table class=catalog cellspacing=0>" :
"</tr>");
content += "<tr>";
sub = children[lCol]; lCol++;
} else {
sub = children[rCol]; rCol++;
}
var chpath = sub.getAttribute("path");
var chtitle = getTitle(sub);
var width = sub.getAttribute("width");
var height = sub.getAttribute("height");
var src = sub.getAttribute("src");
var temp = folderFrag.replace(/<#PATH>/g, chpath);
temp = temp.replace(/<#WIDTH>/, width);
temp = temp.replace(/<#HEIGHT>/, height);
temp = temp.replace(/<#SRC>/, src);
temp = temp.replace(/<#TITLE>/g, htmlspecials(chtitle));
content += temp;
}
if (i != 0) content += "</tr></table>";
var photoMaxW = parseInt(doc.getAttribute("photoMaxW"));
children = doc.getElementsByTagName("photo");
var photoCount = children.length;
for (i = 0; i < photoCount; i++) {
var sub = children[i];
var chpath = sub.getAttribute("path");
var chtitle = getTitle(sub);
var width = parseInt(sub.getAttribute("width"));
var height = parseInt(sub.getAttribute("height"));
var src = sub.getAttribute("src");
var temp = photoFrag.replace(/<#PATH>/g, chpath);
temp = temp.replace(/<#TITLE>/g, htmlspecials(chtitle));
temp = temp.replace(/<#WIDTH>/, width);
temp = temp.replace(/<#HEIGHT>/, height);
temp = temp.replace(/<#SRC>/, src);
var lMargin = 6 + Math.floor((photoMaxW-width)/2);
// Vertically, we can just use a fixed margin, because our style
// lays the images out with "vertical-align: center". This
// way rows are closer together when there are no portrait-mode
// photos.
temp = temp.replace(/<#MARGINL>/, lMargin);
temp = temp.replace(/<#MARGINR>/, 6+photoMaxW-(width+lMargin));
temp = temp.replace(/<#MARGINT>/, 4);
temp = temp.replace(/<#MARGINB>/, 0);
content += temp;
}
newPage.commentary =
(folderCount > 0 ? "" + folderCount + " folders" : " ") +
(folderCount > 0 && photoCount > 0 ? " and" : "") +
"<br>" +
(photoCount > 0 ? "" + photoCount + " photographs" : " ")
newPage.html = content + '<br> </div>';
}
return newPage;
}
function displayPage() {
// Update the display based on the current page attributes
setCookie("AndrewAlbumApp", thisPage.path);
var commentary = document.getElementById("commentary");
if (thisPage.commentary) {
commentary.innerHTML = thisPage.commentary;
} else {
commentary.innerHTML = " <br> ";
}
dropZone.innerHTML = thisPage.html;
window.scrollTo(0,0);
title.innerHTML = htmlspecials(thisPage.title);
parents.innerHTML = thisPage.parents;
showStarLevel();
editTitle.value = thisPage.title;
if (dlogs.editForm.style.display == 'block') {
editInner.style.visibility = "visible";
editTitle.focus();
editTitle.select();
}
scaleMainImage();
linkAnchor.href = '#' + thisPage.path;
endOfReading();
}
//
// Page access
//
var fetching = new Object(); // paths currently being fetched from server
function getXML(path, jumpTo) {
// Fetch XML for given path. If it's already in cache, return the XML;
// if it's already being fetched, do nothihng, otherwise initiate fetch
// "path" was urlencoded by the server-side script.
// Iff "jumpTo", arrange to display the page when it arrives.
var cached = cacheP.read(path);
if (!cached) cached = cacheF.read(path);
if (cached) {
if (jumpTo) jumpTarget = null; // cancel any in-progress jumpTarget
return cached;
}
if (jumpTo) {
jumpTarget = path;
noteReading();
}
if (!fetching[path]) {
var thisReq = new Object();
thisReq.requestedPath = path;
fetching[path] = thisReq;
get(thisReq, "photos.php?op=xml&path=" + path +
(iPhone ? "&pda=" : ""));
}
}
function loadPage(responseXML) {
// Load the given page's responseXML into the window.
// Also initiate page prefetching, and refresh auto-display timer
thisPage = buildPage(responseXML);
cancelImagePreload();
fetching = new Object(); // abandon previous page prefetching
displayPage();
if (thisPage.path == autoRoot) manual();
if (!preloadServer) {
// pre-fetch nearby pages
if (thisPage.autoImage != "") getXML(thisPage.autoImage, false);
if (thisPage.nextImage != "") getXML(thisPage.nextImage, false);
if (thisPage.lastParent != "") getXML(thisPage.lastParent, false);
}
if (timer) timer =
window.setTimeout(doNext, (preloadServer ? 10 : interval));
}
function handleFailure(thisReq) {
// Completion procedure called on failures
// Redisplay the current page (undo "showReading")
cancelImagePreload();
fetching = new Object();
if (thisPage) displayPage();
}
function handleResult(thisReq) {
// Completion procedure: interpret the result, assumed to be XML
var responseXML = thisReq.xmlhttp.responseXML;
if (!responseXML) alert(thisReq.xmlhttp.responseText);
var doc = responseXML.documentElement;
if (doc.nodeName == "save") {
// Result of editSave
var status = doc.getAttribute("status");
var path = doc.getAttribute("path");
if (thisReq.isPhoto) {
cacheP.flush(path);
} else {
cacheP = new Cache(50); // parent names might have changed
cacheF = new Cache(50);
}
editInner.style.visibility = "visible";
writing.style.display = 'none';
if (status == "ok") {
if (editStayOpen) {
doNext();
} else {
closeDlogs();
jumpTo(thisPage.path);
}
} else {
alert("Failed: " + status);
}
} else if (doc.nodeName == "photo" || doc.nodeName == "folder") {
// Interpret the XML response for a folder or image page
var path = doc.getAttribute("path");
var thisCache = (doc.nodeName == "photo" ? cacheP : cacheF);
thisCache.write(path, responseXML);
if (jumpTarget == thisReq.requestedPath) {
// Note that the server can return a page with a different
// path, for example if the one we requested doesn't exist
jumpTarget = null;
loadPage(responseXML);
} else if (fetching[path]) {
fetching[path] = false;
// enqueue preload requests for our images, if we're still OK
if (preloadServer) {
// don't preload any images
} else if (doc.nodeName == "photo") {
var src = doc.getAttribute("src");
enqueueImage(src);
} else { // folder
var children = doc.getElementsByTagName("folder");
for (var i = 0; i < children.length; i++) {
var sub = children[i];
var src = sub.getAttribute("src");
enqueueImage(src);
}
children = doc.getElementsByTagName("photo");
for (var i = 0; i < children.length; i++) {
var sub = children[i];
var src = sub.getAttribute("src");
enqueueImage(src);
}
}
}
} else {
alert('Unknown result, nodeName="' + doc.nodeName +'"');
alert(thisReq.xmlhttp.responseText);
handleFailure(thisReq);
}
}
function jumpTo(path) {
// Jump to given path
if (timer) clearTimeout(timer);
var cached = getXML(path, true);
if (cached) {
cacheHits++;
loadPage(cached);
} else {
cacheMisses++;
}
return false;
}
//
// The top-level subroutines, mostly invoked from the HTML page
//
function containedJump(candidate) {
// If candidate would move us outside of our folder (or autoRoot),
// jump to the bounding folder; otherwise jump to the candidate
var target = candidate;
var container = (autoRoot ? autoRoot :
(thisPage.isPhoto ? thisPage.lastParent : null));
if (container) {
if (container == ".") {
if (candidate == "") target = container;
} else {
if (candidate.indexOf(container) != 0) target = container;
}
}
if (target != "") jumpTo(target);
}
function doNext() {
// Perform the "next" operation.
containedJump(thisPage.nextImage);
return false;
}
function doPrev() {
containedJump(thisPage.prevImage);
return false;
}
function doSkip() {
containedJump(thisPage.skipImage);
return false;
}
function auto(event) {
// start automatic mode
autoRoot = thisPage.autoRoot;
preloadServer = (event.shiftKey ? true : false);
if (preloadServer) {
// flush cache so that we actually use the server-side
cacheP = new Cache(50);
cacheF = new Cache(50);
}
if (!timer) timer = window.setTimeout("jumpTo(thisPage.autoImage)", 50);
btns.auto.hide();
btns.pause.enable();
btns.resume.hide();
btns.stop.enable();
return false;
}
function pause() {
// suspend automatic mode without losing track of root
if (timer) clearTimeout(timer);
timer = null;
btns.auto.hide();
btns.pause.hide();
btns.resume.enable();
btns.stop.enable();
return false;
}
function resume() {
// continue after pause
if (!timer) timer = window.setTimeout(doNext, 50);
btns.auto.hide();
btns.pause.enable();
btns.resume.hide();
btns.stop.enable();
return false;
}
function manual() {
// stop automatic mode
if (timer) clearTimeout(timer);
timer = null;
var wasPreloading = preloadServer;
if (preloadServer) {
// flush cache so that future pre-fetches will pre-fetch images
cacheP = new Cache(50);
cacheF = new Cache(50);
}
autoRoot = null;
preloadServer = false;
btns.auto.enable();
btns.pause.hide();
btns.resume.hide();
btns.stop.disable();
if (wasPreloading && thisPage) jumpTo(thisPage.path);
return false;
}
function doParent(path) {
// Jump to given path, cancelling auto-play
if (autoRoot) manual();
jumpTo(path);
return false;
}
function up() {
// Go up one folder level
if (thisPage.lastParent != "") doParent(thisPage.lastParent);
return false;
}
function hideShowActions() {
// Hide or show the "actions" menu body
var body = document.getElementById("actionsBody");
var openClose = document.getElementById("openClose");
if (body.style.display == 'none') {
body.style.display = 'block';
openClose.src = 'open.gif';
} else {
body.style.display = 'none';
openClose.src = 'closed.gif';
}
return false;
}
function closeDlogs() {
// Close all dialog elements
for (var dlog in dlogs) {
var style = dlogs[dlog].style;
if (style.display != 'none') style.display = 'none';
}
return false;
}
function openDlog(dlogName) {
closeDlogs();
dlog = dlogs[dlogName];
dlog.style.display = 'block';
if (!dlogPositioned[dlogName]) {
moveToCenter(dlog, wSize);
dlogPositioned[dlogName] = true;
}
if (dlog == dlogs.editForm) {
if (timer) pause();
var editUser = document.getElementById("editUser");
var editPwd = document.getElementById("editPwd");
editUser.value = user;
editPwd.value = pwd;
if (user == "") {
editUser.focus();
} else if (pwd == "") {
editPwd.focus();
} else {
editTitle.focus();
}
}
return false;
}
var editStayOpen; // leave the dialog up after save completion
function editSave(stayOpen) {
editStayOpen = stayOpen;
user = document.getElementById("editUser").value;
pwd = document.getElementById("editPwd").value;
setCookie("pachyuser", user, null, "/");
setCookie("photopwd", pwd, null, "/", null, true);
var title = document.getElementById("editTitle").value;
editInner.style.visibility = "hidden";
writing.style.display = 'block';
if (thisPage.isPhoto) {
cacheP.flush(thisPage.path);
cacheF.flush(thisPage.lastParent);
} else {
cacheP = new Cache(50); // parent names might have changed
cacheF = new Cache(50);
}
var now = new Date();
var nowSec = Math.round(now.getTime() / 1000);
var hmac = md5HmacHex(utf8("AndrewAlbumApp\x00xmlSave\x00" +
user + "\x00" + nowSec + "\x00" + thisPage.path + "\x00" +
title),
utf8(pwd));
var saveRequest = new Object();
saveRequest.isPhoto = thisPage.isPhoto;
get(saveRequest,
"photos.php", "path=" + thisPage.path + "&op=xmlSave&title=" +
encodeURIComponent(title) +
"&user=" + encodeURIComponent(user) +
"&time=" + nowSec +
"&hmac=" + hmac);
return false;
}
function starNumber(star) {
var id = star.id;
return parseInt(id.substring(4));
}
function setStarDisplay(n, src) {
for (var i = 1; i <= n; i++) {
var x = document.getElementById("star" + i);
x.src = src;
}
for (var i = n+1; i <= 5; i++) {
var x = document.getElementById("star" + i);
x.src = "whiteStar.gif";
}
}
function showStarLevel() {
// display current star level, or hide the stars
var stars = document.getElementById("stars");
if (!thisPage || thisPage.starLevel == null) {
stars.style.display = "none";
} else {
setStarDisplay(thisPage.starLevel, "redStar.gif");
stars.style.display = "block";
}
}
function starOver(star) {
// mouse over a star
setStarDisplay(starNumber(star), "blueStar.gif");
}
function starClick(star) {
// mouse click on a star
var n = starNumber(star);
thisPage.starLevel = n;
thisPage.responseXML.documentElement.setAttribute("starLevel", n);
showStarLevel();
}
function init() {
if (!document.getElementById) {
alert('This browser has no "getElementById" support.' +
' Redirecting to the non-scripted page');
location.replace("noscript.php");
return;
}
dropZone = document.getElementById("dropZone");
reading = document.getElementById("reading");
scaling = document.getElementById("scaling");
title = document.getElementById("titleText");
title.innerHTML = "";
parents = document.getElementById("parents");
parentsTxt = parents.innerHTML;
parents.innerHTML = "";
showStarLevel();
dlogs.about = document.getElementById("about");
dlogs.details = document.getElementById("details");
dlogs.editForm = document.getElementById("editForm");
dlogs.link = document.getElementById("link");
writing = document.getElementById("writing");
editInner = document.getElementById("editInner");
editTitle = document.getElementById("editTitle");
linkAnchor = document.getElementById("linkAnchor");
btns.auto = new Button("auto", "auto(event)",
"Play all the photos in this folder, from here onward",
"Play all the photos in this folder, including " +
"those in its sub-folders");
btns.pause = new Button("pause", "pause()",
"Suspend the slideshow, remembering the starting position",
"I'm not here");
btns.pause.hide();
btns.resume = new Button("resume", "resume()",
"Resume the slideshow",
"I'm not here");
btns.resume.hide();
btns.stop = new Button("stop", "manual()",
"Stop the slideshow",
"I'm not here");
btns.prev = new Button("prev", "doPrev()",
"Move to the previous photo",
"Move to the previous folder");
btns.next = new Button("next", "doNext()",
"Move to the next photo",
"Move to the next folder, including sub-folders");
btns.skip = new Button("skip", "doSkip()",
"Skip the remainder of this folder",
"Move to the next folder, skipping this folder's sub-folders");
btns.edit = new Button("edit", "openDlog('editForm')",
"Edit this photo's title",
"Edit this folder's title");
iPhone = isIphone();
if (iPhone) interval += 1000; // compensate for no image pre-fetch
window.onresize = doResize;
var dest = "";
if (location.hash && location.hash != "") {
dest = location.hash.substring(1);
} else {
var prevPath = getCookie("AndrewAlbumApp");
if (prevPath) dest = prevPath;
}
user = getCookie("pachyuser");
if (!user) user = "";
dropZone.innerHTML = "";
jumpTo(dest);
// Enable the UI
document.getElementById("parentBtns").style.visibility = "visible";
document.getElementById("prevnextBtns").style.visibility = "visible";
document.getElementById("privBtns").style.visibility = "visible";
}
End of listing