<!DOCTYPE htm> <!-- Copyright (C) 2021 Alexander Rosenberg - This file is AGPL v3. See LICENSE file for more information --> <html> <?php require 'options.php'; require 'library.php'; require 'Album.php'; if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != 'HEAD') { http_response_code(406); display_error_message('Request method not supported'); exit(0); } if (!isset($_GET['album']) || !in_array($_GET['album'], $LIBRARY_INFO['albums'])) { http_response_code(404); display_error_message('No album specified'); exit(0); } $album = new Album($_GET['album']); if (!$album->is_valid()) { http_response_code(404); display_error_message('Album not found'); exit(0); } if (count($album->get_images()) == 0) { http_response_code(400); display_error_message('Album empty'); exit(0); } $image = 0; if (isset($_GET['image'])) { $image = $_GET['image']; if (!ctype_digit($image)) { http_response_code(404); display_error_message('Image not found in album'); exit(0); } else if ($image >= count($album->get_images())) { $image = count($album->get_images()) - 1; } } ?> <head> <title><?php echo htmlspecialchars($album->get_title() . ' - ' . $LIBRARY_INFO['name'], ENT_QUOTES | ENT_HTML5); ?></title> <link rel="stylesheet" href="global.css" /> <link rel="stylesheet" href="view.css" /> <link rel="stylesheet" href="Album.css" /> <link rel="icon" type="image/svg" href="favicon.svg" /> </head> <body> <a id="close-button" href="/">X</a> <div id="info-button-wrapper"> <a id="download-button" title="Download image" download><img id="download-button-image" src="download-button.svg" /></a> <div id="info-button" title="Image info" tabindex="0" onclick="show_metadata_window()">i</div> <img id="size-button" title="Toggle HD view" tabindex="0" onclick="toggle_size()" src="sd.svg" /> <img id="expand-button" title="Toggle sidebar" tabindex="0" onclick="toggle_expand()" src="expand-button.svg" /> </div> <div id="album-title"><?php echo htmlspecialchars($album->get_title(), ENT_QUOTES | ENT_HTML5); ?></div> <div id="sidebar-wrapper"> <div id="sidebar"> <?php foreach ($album->get_images() as $i) { echo $album->get_image_entry($i); } ?> </div> </div> <div id="image-and-info-wrapper"> <div id="main-image-wrapper"> <img class="main-viewer" id="main-image" onload="media_load_handler()" /> <video style="display: none;" class="main-viewer" id="main-video" onload="media_load_handler()" controls></video> <img style="display: none;" id="main-audio-image" src="music-icon.svg"/> <audio style="display: none;" class="main-viewer" id="main-audio" onload="media_load_handler()" controls></audio> <div id="loading-overlay"> <div id="loading-text">Loading...</div> </div> </div> <div id="image-nav-wrapper"> <div id="prev-image-button" onclick="change_image(-1)" tabindex="0"><</div> <div id="current-image-text"><?php echo htmlspecialchars($image + 1 . ' / ' . count($album->get_images()), ENT_QUOTES | ENT_HTML5); ?></div> <div id="next-image-button" onclick="change_image(1)" tabindex="0">></div> </div> <div id="image-description"></div> </div> <div id="metadata-window"> <div id="close-metadata-window-button-wrapper"> <div id="close-metadata-window-button" onclick="hide_metadata_window()">X</div> </div> <div class="metadata-prop-wrapper"> <div class="metadata-prop-desc" id="date-prop-name">Date Taken:</div> <div class="metadata-prop-value" id="date-prop"></div> </div> <div class="metadata-prop-wrapper"> <div class="metadata-prop-desc" id="time-prop-name">Time Taken:</div> <div class="metadata-prop-value" id="time-prop"></div> </div> <div class="metadata-prop-wrapper"> <div class="metadata-prop-desc">File Name:</div> <div class="metadata-prop-value" id="file-name-prop"></div> </div> <div class="metadata-prop-wrapper"> <div class="metadata-prop-desc" id="type-prop-name">Image Type:</div> <div class="metadata-prop-value" id="image-type-prop"></div> </div> <div class="metadata-prop-wrapper"> <div class="metadata-prop-desc">File Size:</div> <div class="metadata-prop-value" id="file-size-prop"></div> </div> <div class="metadata-prop-wrapper" id="res-prop-wrapper"> <div class="metadata-prop-desc" id="res-prop-name">Image Size:</div> <div class="metadata-prop-value" id="image-size-prop"></div> </div> <div id="map-wrapper"> <iframe id="metadata-map" width="100%" frameborder="0" scrolling="no" marginheight="0" marginwidth="0" style="border: 1px solid black; height: 40vh;"></iframe> <div id="source-links"> <a class="metadata-link" id="view-map-link" target="_blank">View Larger Map</a> · <a class="metadata-link" target="_blank" href="https://www.gnu.org/licenses/agpl-3.0.en.html">Page License</a> · <a class="metadata-link" target="_blank" href="https://gitlab.com/zander671/art-museum">Page Source</a> </div> </div> </div> <script type="text/javascript"> var PAGE_SIZE = Math.floor((window.innerHeight * 0.8) / 75) * 2; const MAX_IMAGES = <?php echo count($album->get_images()); ?>; const MAP_BASE_URL = "https://www.openstreetmap.org/#map=16/"; var jump_count = 0; var current_image = <?php echo $image; ?>; var img_lat = 0; var img_lon = 0; var full_size = false; var enable_touch = false; var expanded = false; var album_title = document.getElementById('album-title'); var info_button = document.getElementById('info-button'); var info_button_wrapper = document.getElementById('info-button-wrapper'); var download_button_image = document.getElementById('download-button-image'); var expand_button = document.getElementById('expand-button'); var size_button = document.getElementById('size-button'); var album_title = document.getElementById('album-title'); var image_and_info_wrapper = document.getElementById('image-and-info-wrapper'); var main_image_wrapper = document.getElementById('main-image-wrapper'); var main_image = document.getElementById('main-image'); var main_video = document.getElementById('main-video'); var main_audio = document.getElementById('main-audio'); var main_audio_image = document.getElementById('main-audio-image'); var download_button = document.getElementById('download-button'); var sidebar = document.getElementById('sidebar'); var sidebar_wrapper = document.getElementById('sidebar-wrapper'); var prev_image_button = document.getElementById('prev-image-button'); var next_image_button = document.getElementById('next-image-button'); var current_image_text = document.getElementById('current-image-text'); var image_description = document.getElementById('image-description'); var date_prop_name = document.getElementById('date-prop-name'); var date_prop = document.getElementById('date-prop'); var time_prop_name = document.getElementById('time-prop-name'); var time_prop = document.getElementById('time-prop'); var file_name_prop = document.getElementById('file-name-prop'); var image_type_prop = document.getElementById('image-type-prop'); var file_size_prop = document.getElementById('file-size-prop'); var image_size_prop = document.getElementById('image-size-prop'); var map_wrapper = document.getElementById('map-wrapper'); var map_elem = document.getElementById('metadata-map'); var view_map_link = document.getElementById('view-map-link'); var metadata_window = document.getElementById('metadata-window'); var type_prop_name = document.getElementById('type-prop-name'); var res_prop_name = document.getElementById('res-prop-name'); var res_prop_wrapper = document.getElementById('res-prop-wrapper'); var loading_overlay = document.getElementById('loading-overlay'); /* the following 2 functions source: https://stackoverflow.com/questions/57776001/how-to-detect-ipad-pro-as-ipad-using-javascript */ function isIOS() { if (/iPad|iPhone|iPod/.test(navigator.platform)) { return true; } else { return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform); } } function isIpadOS() { return navigator.maxTouchPoints && navigator.maxTouchPoints > 2 && /MacIntel/.test(navigator.platform); } function show_metadata_window() { metadata_window.style.display = "initial"; } function hide_metadata_window() { set_map_location(img_lat, img_lon); metadata_window.style.display = "none"; } function get_map_source_string(lat, lon, zoom = 15) { return "https://www.openstreetmap.org/export/embed.html?bbox=" + lon + "," + lat + "," + lon + "," + lat + "&layer=mapnik&marker=" + lat + "," + lon; } function get_map_href_string(lat, lon, zoom = 15) { return "http://www.openstreetmap.org/?mlat=" + lat + "&mlon=" + lon + "&layers=M#map=" + zoom + "/" + lat + "/" + lon; } function set_map_location(lat, lon, zoom = 15) { map_elem.src = get_map_source_string(lat, lon, zoom); view_map_link.href = get_map_href_string(lat, lon, zoom); } function deg_to_dec(loc) { return (+loc[0]) + (loc[1] / 60) + (loc[2] / 3600); } function image_ready(ie) { return ie.complete; } // Main image must be fully loaded function update_image_metadata(ie) { if (!image_ready(ie)) { ie.onload = function() { update_image_metadata(ie) }; } else { date_prop_name.textContent = 'Date Taken: '; time_prop_name.textContent = 'Time Taken: '; type_prop_name.textContent = 'Image Type: '; res_prop_wrapper.style.display = 'flex'; res_prop_name.textContent = 'Image Size: '; image_description.textContent = ie.parentElement.getAttribute('data-desc'); var date = ie.parentElement.getAttribute('data-date'); if (date === '') { date = 'Unknown'; } var time = ie.parentElement.getAttribute('data-time'); if (time === '') { time = 'Unknown'; } date_prop.textContent = date; time_prop.textContent = time; var lat = ie.parentElement.getAttribute('data-lat'); var lon = ie.parentElement.getAttribute('data-lon'); if (lat === '' || lon === '') { map_wrapper.style.display = "none"; } else { img_lat = lat; img_lon = lon; set_map_location(img_lat, img_lon); map_wrapper.style.display = "initial"; } file_name_prop.textContent = ie.parentElement.getAttribute('data-name'); file_size_prop.textContent = ie.parentElement.getAttribute('data-size') + " B"; image_type_prop.textContent = ie.parentElement.getAttribute('data-type'); image_size_prop.textContent = ie.getAttribute('data-size'); } } function video_ready(ve) { return ve.readyState > 3; } // Main video must be fully loaded function update_video_metadata(ve) { if (!video_ready(ve)) { ve.onloadedmetadata = function () { update_video_metadata(ve) }; } else { date_prop_name.textContent = 'Date Recorded: '; time_prop_name.textContent = 'Time Recorded: '; type_prop_name.textContent = 'Video Type: '; res_prop_wrapper.style.display = 'flex'; res_prop_name.textContent = 'Video Size: '; image_description.textContent = ve.parentElement.getAttribute('data-desc'); var date = ve.parentElement.getAttribute('data-date'); if (date === '') { date = 'Unknown'; } var time = ve.parentElement.getAttribute('data-time'); if (time === '') { time = 'Unknown'; } date_prop.textContent = date; time_prop.textContent = time; var lat = ve.parentElement.getAttribute('data-lat'); var lon = ve.parentElement.getAttribute('data-lon'); if (lat === '' || lon === '') { map_wrapper.style.display = "none"; } else { img_lat = lat; img_lon = lon; set_map_location(img_lat, img_lon); map_wrapper.style.display = "initial"; } file_name_prop.textContent = ve.parentElement.getAttribute('data-name'); file_size_prop.textContent = ve.parentElement.getAttribute('data-size') + " B"; image_type_prop.textContent = ve.parentElement.getAttribute('data-type'); image_size_prop.textContent = ve.videoWidth + "x" + ve.videoHeight; } } function audio_ready(ae) { return ae.readyState > 2; } // Main video must be fully loaded function update_audio_metadata(ae) { if (!audio_ready(ae)) { ae.onloadedmetadata = function () { update_video_metadata(ae) }; } else { date_prop_name.textContent = "Date Recorded: "; time_prop_name.textContent = "Time Recorded: "; type_prop_name.textContent = 'Audio Type: '; res_prop_wrapper.style.display = 'none'; res_prop_name.textContent = 'Audio Duration: '; image_description.textContent = ae.parentElement.getAttribute('data-desc'); var date = ae.parentElement.getAttribute('data-date'); if (date === '') { date = 'Unknown'; } var time = ae.parentElement.getAttribute('data-time'); if (time === '') { time = 'Unknown'; } date_prop.textContent = date; time_prop.textContent = time; var lat = ae.parentElement.getAttribute('data-lat'); var lon = ae.parentElement.getAttribute('data-lon'); if (lat === '' || lon === '') { map_wrapper.style.display = "none"; } else { img_lat = lat; img_lon = lon; set_map_location(img_lat, img_lon); map_wrapper.style.display = "initial"; } file_name_prop.textContent = ae.parentElement.getAttribute('data-name'); file_size_prop.textContent = ae.parentElement.getAttribute('data-size') + " B"; image_type_prop.textContent = ae.parentElement.getAttribute('data-type'); } } function get_time_string(dur) { var h = Math.floor(dur / 3600); dur -= h * 3600; var m = Math.floor(dur / 60); dur -= m * 60; var s = Math.round(dur); if (!(h <= 0)) { return h + "h " + m + "m " + s + "s"; } else if (!(m <= 0)) { return m + "m " + s + "s"; } else { return s + "s"; } } // Handle resize stuff function windowResizeHandler() { if (!isIpadOS() && window.innerWidth < 1000) { document.querySelectorAll('.sidebar-image-wrapper').forEach((e) => { e.style.width = "30vw"; e.style.height = "30vw"; }); sidebar_wrapper.style.width = "100%"; main_image_wrapper.style.height = 'initial'; main_image_wrapper.style.marginTop = '2em'; metadata_window.style.width = "80vw"; map_elem.style.height = '60vh'; album_title.style.marginTop = '2em'; enable_touch = true; } else { document.querySelectorAll('.sidebar-image-wrapper').forEach((e) => { e.style.width = "10vw"; e.style.height = "10vw"; }); sidebar_wrapper.style.width = "initial"; main_image_wrapper.style.height = '76vh'; main_image_wrapper.style.marginTop = '0'; metadata_window.style.width = "40vw"; album_title.style.marginTop = '0'; map_elem.style.height = '40vh'; enable_touch = false; } info_button.style.width = info_button.offsetHeight + 'px'; download_button_image.style.width = info_button.offsetHeight + 'px'; download_button_image.style.height = info_button.offsetHeight + 'px'; download_button.style.display = "initial"; expand_button.style.width = info_button.offsetHeight + 'px'; expand_button.style.height = info_button.offsetHeight + 'px'; expand_button.style.display = "initial"; size_button.style.width = info_button.offsetHeight + 'px'; size_button.style.height = info_button.offsetHeight + 'px'; size_button.style.display = "initial"; info_button_wrapper.style.visibility = "initial"; } window.addEventListener("resize", windowResizeHandler, true); window.addEventListener("load", windowResizeHandler, true); function media_load_handler() { if (jump_count != 0) { set_main_image(current_image + jump_count); jump_count = 0; } else { loading_overlay.style.display = "none"; } } function change_image(ammount) { if (!video_ready(main_video) && !audio_ready(main_audio) && !image_ready(main_image)) { jump_count += ammount; } else { set_main_image(current_image + ammount); } } // Loading images function set_main_image(image, do_scroll = false, do_metadata = true) { loading_overlay.style.display = "revert"; if (image < 0 || image >= MAX_IMAGES) { return; } current_image = image; var ie = sidebar.children[image].firstChild; if (main_video.innerHTML !== "") { main_video.pause(); } else if (main_audio.innerHTML !== "") { main_audio.pause(); } main_video.innerHTML = ""; main_audio.innerHTML = ""; main_image.style.display = "none"; main_video.style.display = "none"; main_audio.style.display = "none"; main_audio_image.style.display = "none"; if (ie.getAttribute('data-audiotype') !== null) { var type = ie.getAttribute('data-audiotype'); var path = ie.getAttribute('data-audiopath'); main_audio.innerHTML = '<source src="' + path + '" type="' + type + '" />'; if (do_metadata) { update_audio_metadata(ie); } download_button.href = path; main_audio.load(); main_audio.style.display = "initial"; main_audio_image.style.display = "initial"; } else if (ie.getAttribute('data-videopath') !== null) { var ds = ie.getAttribute('data-videopath'); var dt = ie.getAttribute('data-type'); main_video.innerHTML = '<source src="' + ds + '" type="' + dt + '" />'; if (do_metadata) { update_video_metadata(ie); } download_button.href = ds; main_video.load(); main_video.style.display = "initial"; } else { var ds; if (full_size) { ds = 'albums/' + ie.getAttribute('data-src'); } else { ds = 'thumbnails/large/' + ie.getAttribute('data-src'); } main_image.src = ds; if (do_metadata) { update_image_metadata(ie); } download_button.href = ds; main_image.style.display = "initial"; } current_image_text.textContent = (image + 1) + ' / ' + MAX_IMAGES; if (image == 0) { prev_image_button.style.visibility = "hidden"; } else { prev_image_button.style.visibility = "visible"; } if (image == MAX_IMAGES - 1) { next_image_button.style.visibility = "hidden"; } else { next_image_button.style.visibility = "visible"; } window.history.replaceState('', '', '?album=<?php echo htmlspecialchars($_GET['album'], ENT_QUOTES | ENT_HTML5); ?>&image=' + image); if (do_scroll) { var w = sidebar.children[image]; w.scrollIntoView(true); } } function load_entry_media(elem) { if (elem.getAttribute('data-visible') !== null) { me = elem.firstChild; if (me.getAttribute('data-audiotype') !== null) { // Audio does not require work } else if (me instanceof HTMLImageElement) { // Image me.src = 'thumbnails/small/' + me.getAttribute('data-src'); } me.style.display = "initial"; } } function image_scroll_callback(entries, observer) { entries.forEach((entry) => { var elem = entry.target; if (entry.isIntersecting) { elem.setAttribute('data-visible', ''); setTimeout(load_entry_media, 250, elem); observer.unobserve(elem); } else { elem.removeAttribute('data-visible'); } }); } var io = new IntersectionObserver(image_scroll_callback, { root: sidebar }); for (i = 0; i < sidebar.children.length; ++i) { io.observe(sidebar.children[i]); } set_main_image(current_image, true); // Key press and swipe window.onkeydown = function (gfg) { if (gfg.keyCode == 37 /* left */) { set_main_image(current_image - 1, true); } else if (gfg.keyCode == 39 /* right */) { set_main_image(current_image + 1, true); } } var touchstartX = 0; var touchendX = 0; function handleSwipes() { if (enable_touch) { if (touchendX < touchstartX) { set_main_image(current_image + 1, true); } else if (touchendX > touchstartX) { set_main_image(current_image - 1, true); } main_image_wrapper.scrollIntoView(true); } } main_image_wrapper.addEventListener('touchstart', e => { touchstartX = e.changedTouches[0].screenX; }); main_image_wrapper.addEventListener('touchend', e => { touchendX = e.changedTouches[0].screenX; handleSwipes(); }); function toggle_expand() { if (expanded) { sidebar_wrapper.style.display = 'flex'; image_and_info_wrapper.style.width = '70vw'; image_and_info_wrapper.style.height = 'max-content'; image_and_info_wrapper.style.marginLeft = '2vw'; expand_button.src = 'expand-button.svg'; } else { sidebar_wrapper.style.display = 'none'; image_and_info_wrapper.style.width = '100%'; image_and_info_wrapper.style.height = '100%'; image_and_info_wrapper.style.marginLeft = '0'; expand_button.src = 'contract-button.svg'; } expanded = !expanded; } function toggle_size() { full_size = !full_size; if (full_size) { size_button.src = "hd.svg" } else { size_button.src = "sd.svg" } set_main_image(current_image, false, false); } </script> </body> </html>