0x2620-jpg/js/jpg.js

767 lines
28 KiB
JavaScript

/*
2010 0x2620.org GPL v3
TODO: migrate to ox.js
(& sorry for the mess)
*/
$(function() {
if ($.browser.msie) {
alert("Please use Firefox, Safari or Chrome.");
return;
}
var $body = $("body"),
$document = $(document),
$window = $(window),
$album = $("#album"),
json,
users = [],
albums = {},
split = location.hash.substr(1).replace("_", " ").split("/"),
user = split[0] || "S",
album = split[1] || "Beirut",
photoid = split[2] || "",
hash = photoid ? [user, album, photoid].join("/") : [user, album].join("/"),
windowWidth = $window.width(),
windowHeight = $window.height() - 24,
windowRatio = windowWidth / windowHeight,
photo = -1,
photos = [],
thumbnails = [],
playInterval,
buttonTimeout,
$layer,
spinner;
location.hash = hash;
$.each(["Loading", "Close", "Previous", "Play", "Pause", "Next", "ZoomIn", "ZoomOut"], function(i, name) {
$("<img>").attr({
src: "png/symbol" + name + ".png"
});
});
function Album(username, albumname) {
var that = this,
data = json[username][albumname];
that.show = function() {
playInterval && $("#play").trigger("click");
$("#zoomin").attr("src") == "png/symbolZoomOut.png" && $("#zoomin").trigger("click");
$(".photo") && $("#close").trigger("click");
$album.empty();
album = albumname;
photos = [];
photo = -1;
$.each(data, function(i, image) {
photos[i] = image,
thumbnails[i] = new Thumbnail(i).show();
});
$("<br/>").attr({clear: "all"}).appendTo($album);
$("<div>")
.css({
height: "24px"
})
.appendTo($album);
$interface = $("<div>")
.attr({
id: "interface"
})
.appendTo($album);
$layer = $("<div>")
.attr({
id: "layer"
})
.appendTo($album);
setTimeout(function() {
location.hash = [user, album.replace(" ", "_")].join("/");
}, 500);
return that;
}
return that;
}
function Button(photo, name, tooltip) {
var that = this,
$div = $("<div>")
.addClass("button")
.mouseenter(showTooltip)
.mouseleave(hideTooltip)
.click(function() {
hideTooltip();
photo.click(name);
}),
$img = $("<img>")
.addClass("button")
.attr({
id: name.toLowerCase().replace(" ", ""),
src: "png/symbol" + name.replace(" ", "") + ".png",
})
.appendTo($div),
$tooltip;
function hideTooltip() {
$tooltip.animate({
opacity: 0
}, 50, function() {
$tooltip.remove();
});
}
function showTooltip() {
$tooltip.html(that.tooltip).appendTo($body).animate({
opacity: 1
}, 50);
}
that.tooltip = tooltip;
that.show = function() {
$div.appendTo("#buttons");
$tooltip = $("<div>")
.addClass("tooltip")
.css({
left: ($div.offset().left - 52) + "px"
});
}
return that;
}
function Dialog(title, text, buttons) {
var that = this,
$div = $("<div>")
.addClass("background");
$dialog = $("<div>")
.addClass("dialog")
.appendTo($div);
$title = $("<div>")
.addClass("title")
.html(title)
.appendTo($dialog);
$text = $("<div>")
.addClass("text")
.html(text)
.appendTo($dialog);
$buttons = $("<div>")
.addClass("buttons")
.appendTo($dialog);
new Input("Close", function() {
that.close();
}, $buttons).show();
that.close = function() {
$div.animate({
opacity: 0
}, 250, function() {
$div.remove();
});
$dialog.animate({
opacity: 0
}, 250);
return that;
}
that.open = function() {
$div.appendTo($body).animate({
opacity: 1
}, 250);
$dialog.animate({
opacity: 1
}, 250);
return that;
}
return that;
}
function Input(name, fn, $parent) {
var that = this,
$parent = $parent || $("#right"),
$div = $("<div>")
.addClass("input")
.html(name)
.click(fn);
that.show = function() {
$parent.append($div);
return that;
}
return that;
}
function Map() {
var that = this,
offset = $(".original").offset(), // fixme: needed?
thumbnailWidth = 90 * photos[photo].width / photos[photo].height,
thumbnailHeight = 90,
boxWidth = thumbnailWidth / photos[photo].width * windowWidth,
boxHeight = thumbnailHeight / photos[photo].height * windowHeight,
$div = $("<div>")
.addClass("map")
.css({
width: thumbnailWidth + "px"
}),
$img = $("<img>")
.addClass("map")
.attr({
src: photos[photo].file.replace("/IMG", "/90/IMG"),
})
.css({
width: thumbnailWidth + "px"
})
.appendTo($div),
$interface = $("<div>")
.addClass("interface")
.css({
width: thumbnailWidth + "px"
})
.click(function(e) {
var offset = $div.offset(),
left = Math.min(Math.max(e.clientX - offset.left - boxWidth / 2 - 2, 0), thumbnailWidth - boxWidth),
top = Math.min(Math.max(e.clientY - offset.top + $body.scrollTop() - boxHeight / 2 - 2, 0), thumbnailHeight - boxHeight);
$(".original").animate({
left: -(left * photos[photo].width / thumbnailWidth) + "px",
top: -(top * photos[photo].height / thumbnailHeight) + "px"
}, 250, function() {
that.update(true);
});
})
.appendTo($div);
$box = $("<div>")
.addClass("box")
.css({
width: boxWidth + "px",
height: boxHeight + "px"
})
.mousedown(function(e) {
var left = parseInt($box.css("left")) + 2,
top = parseInt($box.css("top")) + 2,
x = e.clientX,
y = e.clientY;
$window.mousemove(function(e) {
left = Math.min(Math.max(left + e.clientX - x, 0), thumbnailWidth - boxWidth);
top = Math.min(Math.max(top + e.clientY - y, 0), thumbnailHeight - boxHeight);
x = e.clientX;
y = e.clientY;
$(".original").css({
left: -(left * photos[photo].width / thumbnailWidth) + "px",
top: -(top * photos[photo].height / thumbnailHeight) + "px"
});
that.update();
});
$window.mouseup(function() {
$window.unbind("mousemove");
});
})
.appendTo($div);
that.show = function() {
$div.appendTo($album);
that.update();
return that;
}
that.update = function(animate) {
var left = -parseInt($(".original").css("left")) * thumbnailWidth / photos[photo].width - 2,
top = -parseInt($(".original").css("top")) * thumbnailHeight / photos[photo].height - 2;
if (animate) {
$box.animate({
left: left + "px",
top: top + "px"
}, 250);
} else {
$box.css({
left: left + "px",
top: top + "px"
});
}
}
return that;
}
function Photo(i) {
var that = this,
image = photos[i],
id = image.file.substr(-8, 4),
title = image.file.substr(4, 8), // fixme: needed?
zoomedIn = false,
keepVisible = false,
$buttons = $("<div>")
.attr({
id: "buttons"
})
.mouseenter(function() {
//console.log("mouseenter")
clearTimeout(buttonTimeout);
keepVisible = true;
})
.mouseleave(function() {
buttonTimeout = setTimeout(hideButtons, 2500);
keepVisible = false;
})
.appendTo($body),
map;
$img = $("<img>")
.addClass("photo")
.attr({
src: image.file.replace("/IMG", "/90/IMG"),
});
$.each([
["Close", "CLOSE [ESCAPE]"],
["Previous", "PREVIOUS [LEFT]"],
["Play", "PLAY/PAUSE [SPACE]"],
["Next", "NEXT [RIGHT]"],
["Zoom In", "ZOOM [=/-]"]
], function(i, button) {
new Button(that, button[0], button[1]).show();
});
function getOriginalCSS(i) {
return {
width: photos[i].width + "px",
height: photos[i].height + "px"
};
}
function getPhotoCSS(i) {
var size = getSize(i);
return {
left: ((windowWidth - size.width) / 2) + "px",
top: ((windowHeight - size.height) / 2) + "px",
width: size.width + "px",
height: size.height + "px"
};
}
function getSize(i) {
var photoRatio = photos[i].width / photos[i].height;
return {
width: windowRatio > photoRatio ? windowHeight * photoRatio : windowWidth,
height: windowRatio > photoRatio ? windowHeight : windowWidth / photoRatio
};
}
function hideButtons() {
return;
//console.log("hideButtons")
$("#buttons").animate({
opacity: 0
}, 250);
}
function showButtons() {
//console.log("showButtons")
clearTimeout(buttonTimeout);
$("#buttons").animate({
opacity: 1
}, 250);
buttonTimeout = setTimeout(hideButtons, 2500);
}
that.click = function(button) {
//console.log("click " + button)
if (button == "Close") {
that.close();
} else if (button == "Previous") {
photo = photo == 0 ? photos.length - 1 : photo - 1;
that.show(photo);
playInterval && updateInterval();
} else if (button == "Play") {
if (!playInterval) {
$("#play").attr({
src: "png/symbolPause.png"
});
playInterval = setInterval(function() {
$("#next").trigger("click");
}, 5000);
setTimeout(function() {
$("#next").trigger("click");
}, 500);
} else {
$("#play").attr({
src: "png/symbolPlay.png"
});
clearInterval(playInterval);
playInterval = 0;
}
} else if (button == "Next") {
photo = photo == photos.length - 1 ? 0 : photo + 1;
that.show(photo);
playInterval && updateInterval();
} else if (button == "Zoom In") {
var left = (photos[photo].width - windowWidth) / -2,
top = (photos[photo].height - windowHeight) / -2,
x, y;
if (!zoomedIn) {
zoomedIn = true;
spinner = new Spinner().start();
var $img_ = $("<img>")
.attr({
src: photos[photo].file
})
.load(function() {
$img.attr({
src: photos[photo].file
});
spinner.stop();
});
$img
.animate({
width: photos[photo].width,
height: photos[photo].height,
left: left,
top: top
}, 250, function() {
$("#zoomin").attr({
src: "png/symbolZoomOut.png"
});
$img.addClass("original");
$interface
.show()
.mousedown(function(e) {
left = parseInt($img.css("left"));
top = parseInt($img.css("top"));
x = e.clientX;
y = e.clientY;
$window.mousemove(function(e) {
left = Math.max(Math.min(left + e.clientX - x, 0), windowWidth - photos[photo].width);
top = Math.max(Math.min(top + e.clientY - y, 0), windowHeight - photos[photo].height);
x = e.clientX;
y = e.clientY;
$img.css({
left: left + "px",
top: top + "px"
});
map.update();
});
$window.mouseup(function() {
$window.unbind("mousemove");
});
});
map = new Map().show();
});
} else {
zoomedIn = false;
$(".map").remove();
$interface.hide();
spinner = new Spinner().start();
var $img_ = $("<img>")
.attr({
src: photos[photo].file.replace("/IMG", "/720/IMG")
})
.load(function() {
$img.attr({
src: photos[photo].file.replace("/IMG", "/720/IMG")
});
spinner.stop();
});
$img.animate(getPhotoCSS(photo), 250, function() {
$("#zoomin").attr({
src: "png/symbolZoomIn.png"
});
});
}
}
function updateInterval() {
clearInterval(playInterval);
playInterval = setInterval(function() {
$("#next").trigger("click");
}, 5000);
}
return that;
}
that.close = function() {
var thumbnailPosition = thumbnails[photo].position();
if (playInterval) {
$("#play").trigger("click");
}
$("#buttons").remove();
$(".map").remove();
$img.animate({
left: thumbnailPosition.left,
top: thumbnailPosition.top,
width: thumbnailPosition.width,
height: thumbnailPosition.height
}, 250, function() {
location.hash = [user, album].join("/");
$img.unbind("mouseenter");
$img.unbind("mouseleave");
$img.unbind("mousemove");
$img.remove();
});
$layer.animate({
opacity: 0
}, 250, function() {
$layer.hide();
});
spinner.stop();
$interface.hide();
$document.unbind("keydown");
return that;
}
that.open = function() {
var thumbnailPosition = thumbnails[i].position();
$img.css({
left: thumbnailPosition.left,
top: thumbnailPosition.top,
width: thumbnailPosition.width,
height: thumbnailPosition.height
})
.appendTo($album)
.animate(getPhotoCSS(i), 250, function() {
location.hash = [user, album, id].join("/");
spinner = new Spinner().start();
var $img_ = $("<img>")
.attr({
src: photos[i].file.replace("/IMG", "/720/IMG")
})
.load(function() {
$img.attr({
src: photos[i].file.replace("/IMG", "/720/IMG")
});
spinner.stop();
});
/*
$img.mouseenter(showButtons)
.mouseleave(hideButtons)
.mousemove(function() {
if (!keepVisible) {
showButtons();
}
});
*/
showButtons();
$document.keydown(function(e) {
//console.log(e.keyCode);
var d = 0;
if (photo > -1) {
if (e.keyCode == 27) {
$("#close").trigger("click");
} else if (e.keyCode == 32) {
showButtons();
$("#play").trigger("click");
} else if (e.keyCode == 37) {
showButtons();
$("#previous").trigger("click");
} else if (e.keyCode == 39) {
showButtons();
$("#next").trigger("click");
} else if (e.keyCode == 187) {
showButtons();
!zoomedIn && $("#zoomin").trigger("click");
} else if (e.keyCode == 189) {
showButtons();
zoomedIn && $("#zoomin").trigger("click");
}
}
});
});
$layer.show().animate({
opacity: 1
}, 250);
photo = i;
return that;
}
that.remove = function() {
$img.remove();
return that;
}
that.show = function(i) {
var $imgOld = $img,
offset = $img.offset(),
size = {
width: $img.width(),
height: $img.height()
};
$img = $("<img>")
.addClass(zoomedIn ? "original" : "photo")
.attr({
src: photos[i].file.replace("/IMG", "/90/IMG"),
})
.css(zoomedIn ? $.extend(getOriginalCSS(i), {
left: offset.left + "px",
top: offset.top + "px"
}) : getPhotoCSS(i))
.css({
opacity: 0
})
.appendTo($album)
.animate({
opacity: 1
}, playInterval ? 500 : 250, function() {
location.hash = [user, album, photos[i].file.substr(-8, 4)].join("/");
$imgOld.remove();
photo = i;
if (zoomedIn) {
if (photos[i].width / size.width == size.width && photos[i].height == size.height) {
$("img.map")
.attr({
src: photos[photo].file.replace("/IMG", "/90/IMG"),
});
} else {
$img.css({
left: offset.left * (photos[i].width - windowWidth) / (size.width - windowWidth),
top: offset.top * (photos[i].height - windowHeight) / (size.height - windowHeight)
});
$("div.map").remove();
map = new Map().show();
}
}
});
spinner = new Spinner().start();
var $img_ = $("<img>")
.attr({
src: photos[photo].file.replace("/IMG", "/720/IMG")
})
.load(function() {
$img.attr({
src: photos[photo].file.replace("/IMG", "/720/IMG")
});
if (zoomedIn) {
$img_ = $("<img>")
.attr({
src: photos[photo].file
})
.load(function() {
$img.attr({
src: photos[photo].file
});
spinner.stop();
});
} else {
spinner.stop();
}
});
return that;
}
return that;
}
function Select(name, options, selected) {
var that = this,
$div = $("<div>")
.addClass("select");
$val = $("<div>")
.html(name + ": " + selected)
.appendTo($div);
$select = $("<select>")
.change(function() {
var val = $select.val()
$val.html(name + ": " + val);
new Album(user, val).show();
})
.appendTo($div);
$.each(options, function(i, option) {
var $option = $("<option>")
.html(option)
.appendTo($select);
option == selected && $option.attr({
selected: "selected"
});
});
that.show = function() {
$("#left").append($div);
return that;
}
return that;
}
function Spinner() {
var that = this,
$div = $("<div>")
.addClass("spinner")
.css({
left: ((windowWidth - 16 - 24) / 2) + "px",
top: ((windowHeight - 24) / 2) + "px",
}),
$img = $("<img>")
.addClass("spinner")
.attr({
src: "png/symbolLoading.png"
})
.appendTo($div),
deg = 0,
interval;
function update() {
$img.css({
MozTransform: "rotate(" + deg + "deg)",
WebkitTransform: "rotate(" + deg + "deg)"
});
}
that.start = function() {
var spinner_ = $(".spinner");
if (spinner_) {
spinner_.remove();
}
deg = 0;
$div.appendTo($album).animate({
opacity: 1
}, 250);
interval = setInterval(function() {
deg = (deg + 30) % 360;
update();
}, 83);
return that;
};
that.stop = function() {
$div.animate({
opacity: 0
}, 250, function() {
$div.remove();
clearTimeout(interval);
deg = 0;
});
return that;
}
return that;
}
function Thumbnail(i) {
var that = this,
image = photos[i],
width = image.width * 90 / image.height,
marginLeft = 16 + Math.floor((135 - width) / 2),
marginRight = 16 + Math.ceil((135 - width) / 2),
$img = $("<img>")
.addClass("thumbnail loading")
.attr({
id: image.file.substr(-8, 4),
src: image.file.replace("/IMG", "/90/IMG"),
})
.css({
width: width + "px",
marginLeft: marginLeft + "px",
marginRight: marginRight + "px"
})
.click(function() {
new Photo(i).open();
})
.load(function() {
$(this).removeClass("loading");
});
that.position = function() {
var offset = $img.offset();
return {
left: offset.left + 4,
top: offset.top + 4 - $body.scrollTop(),
width: $img.width(),
height: $img.height()
};
}
that.show = function() {
$img.appendTo($album);
return that;
}
return that;
}
$.getJSON("json/jpg.json", function(data) {
json = data;
$.each(data, function(username, albumnames) {
users.push(username);
albums[username] = [];
$.each(albumnames, function(albumname, photos) {
albums[username].push(albumname);
});
});
new Select("User", users, user).show();
new Select("Album", albums[user], album).show();
new Input("About: Photos", function() {
new Dialog("About: Photos", "2005-2010 Robert Luxemburg<br/><br/>Creative Commons Attribution Non-Commercial Share Alike 3.0").open();
}).show();
new Input("About: Software", function() {
new Dialog("About: Software", "2010 Robert Luxemburg<br/><br/>GNU General Public License 3.0<br/><br/><a href=\"zip/jpg.zip\">Download</a> (0.1.0, 2010-05-05, 150 KB)").open();
}).show();
new Album(user, album).show();
photoid && setTimeout(function() {
$("#" + photoid).trigger("click")
}, 500);
//$body.append("<br clear=\"all\"/>");
//$body.append("5DM2 - GPL V3 - <a href=\"5DM2.zip\">Download</a>");
});
});