File: trnpix/_thumbspage/2015-florida.JPG.html
<!DOCTYPE HTML> <html> <head> <!-- Generated 2024-11-27 @17:19:16, by thumbspage 2.3: learning-python.com/thumbspage.html --> <!-- Be Unicode and mobile friendly (and in the first 1k bytes here) --> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Context: thumbspage.py generates viewer pages from this template file - a combination of HTML, CSS, and JavaScript. Python expands this for each image, replacing all its 'percent(uppercase)s' keys. Edit with _care_ - some of this code is both subtle and fragile. This file's Unicode encoding defaults to UTF-8 in user_configs.py. Recent noteworthy mods here: [2.3] Note button+popup, up-swipe=Note, popup colors and opacity. [2.3] Drop hrefs in toolbar, more tooltips, tooltips on by default. [2.2] Add swipe gestures for touch screens, and optional tooltips. [2.1] Use underline font for Auto button when slideshow is on. [2.1] Use underline for Full for parity (but fullscreen obvious). [2.0] Add slideshow via Auto toggle, one-page fullscreen via Full. [2.0] Cut Raw (it's now image tap); iOS Chrome history bug fixed. [2.0] Use custom dialog for 1.7's info popup (on filename tap). [1.7] Add generation date+version above, and info popup display. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ --> <!-- Plus analytics code, custom styles, etc. (replace me) --> <!-- Anonymous analytics to prioritize work, enabled in online resources only. Automatically inserted at publish time by insert-analytics.py. --> <!-- 1) Universal Analytics tag (custom): stops collecting data on Jul-1-2023 --> <SCRIPT> // Start async JS-file fetch, if not already cached (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) })(window,document,'script','//www.google-analytics.com/analytics.js','ga'); // Queue actions to run in order after async JS-file fetch finished ga('create', 'UA-52579036-1', 'auto'); // Create tracker object (and queue) ga('set', 'anonymizeIp', true); // Anonymize IP addr (&aip) [Jun-2019] ga('send', 'pageview'); // Send page-view event now </SCRIPT> <!-- 2) Google Analytics 4 tag: added to site Oct-2022 (okay to keep UA tag) --> <!-- Google tag (gtag.js) --> <script async src="https://www.googletagmanager.com/gtag/js?id=G-J8CTEZHX3L"></script> <script> window.dataLayer = window.dataLayer || []; function gtag(){dataLayer.push(arguments);} gtag('js', new Date()); gtag('config', 'G-J8CTEZHX3L'); </script> <!-- End analytics insert --> <!-- ==================================================================================== CSS: style HTML elements by id, class, or element type ==================================================================================== --> <style type="text/css"> /* in head (or elsewhere per HTML 5.2), type optional */ /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Context: this is CSS in HTML expanded and generated by Python. The all-upper names here are Python dict-key replacement targets. The JavaScript ahead uses both HTML attributes and CSS properties, forming a nest of cross-language coupling. This file is close to incomprehensible if embedded in the .py script itself. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ /* For all elements in doc (unless redef, includes load note) */ * { font-family: Arial, Helvetica, sans-serif; background-color: black; /* bg is entire background */ color: white; /* text, not border or no-JS note */ } /* Don't display scrollbars (else Chrome on Windows/Linux may, briefly) */ body { overflow: hidden; /* added late in 1.X, but no other display impact anywhere */ } /* Don't upscale text in landscape mode on iOS Safari */ @media screen and (max-device-width: 640px) { html { -webkit-text-size-adjust: 100%; /* webkit browsers (old chrome too) */ } } /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Navigation toolbar and its buttons (bottom of page) */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ .navdiv { width: 100%; /* span page: else no scrolls */ position: fixed; /* anchor to page bottom */ bottom: 0; overflow-x: auto; /* auto-scroll on viewport overflow, iff min-width */ left: 4px; /* use more empty space on left [2.0] */ } .navtable { width: 100%; /* span page: else won't fill entire page */ border-collapse: collapse; table-layout: auto; /* 'fixed' less uniform? + fails on .optional [2.0] */ min-width: 150px; /* usable min: else no scrolls [2.0] */ } .navlink { display: block; /* buttons; nicer links through ad hoc css? */ text-decoration: none; /* no link colors or underlines */ text-align: center; /* how did this work without this before [2.3]? */ /*min-width: 3em;*/ /* this changes layout slightly for the worse */ } /* Add horizontal padding for large fonts in Android Firefox */ /* This also ensures space on right, despite new min-width [2.0] */ .navtable td { padding-right: 10px; /* for all <td> nested in a class=navtable */ } /* [2.0] Optional toolbar buttons: not if no JS, or disabled in configs */ .optional { display: none; /* initially HIDDEN (changed on load if JS + enabled) */ } /* [1.7] Filename+image cursor change on desktop, in lieu of hover effects */ .cursorpointer { cursor: pointer; } /* [1.7] But no cursor change if no JS, for no-op widgets: image, filename */ </style> <noscript> <style> .cursorpointer { cursor: auto; } </style> </noscript> <style> /* See also "Dead/olde/abandoned" code ahead for toolbar history */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Message posted when JavaScript disabled (top of page) */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ .nojsnote { text-align: center; font-style: italic; margin-top: 0px; /* other: text-shadow */ color: red; /* allow user configs */ /*color: red;*/ /* no: may be BGCOLOR */ } /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Filename-related styles (top of page) */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ .filename { text-align: center; margin-top: 4px; margin-bottom: 10px; } .overflowscroll { /* also auto-scroll via this style in <div> */ overflow-x: auto; /* used by JS warning too, but wraps at ' ' */ } /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Image-related styles (middle of page) */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ img { max-width: 100%; /* for before onload resize: else Chrome starts.. */ max-height: 100%; /* fullsize, and botches sizes on Android later; */ display: block; /* allow centering sans <p>; [kill bottom space] */ /*vertical-align: text-bottom; /* [didn't work fully, but bottom space now moot] */ } #theimg { border: thin solid white; /* not 1px, else Chrome may not draw (see .py) */ margin-left: auto; /* center-align image, but horizontally only */ margin-right: auto; /* set bdcolor=bgcolor to hide image border */ } /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Dead/olde/abandoned navigation toolbar code (delete me soon) */ /*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/ /* Switch toolbar button's font effects on mouseover as a visual indicator */ /* [1.7] PUNT: drop hover, because it's broken on mobile browsers, and may still appear in mobile landscape mode's larger screens; but force cursor=pointer for an indicator on desktops; see UserGuide.html#1.7. */ /*--DEFUNCT .navlink:hover { -- [1.7] punt; mobile browsers get stuck 'on' text-decoration: underline; -- was: desktop browsers only (mobiles may botch) font-style: italic; } .navlink:active { font-style: italic; } DEFUNCT--*/ /* Smaller mobile devices: scale up toolbar links and no hover */ /* [1.7] PUNT: abandon former toolbar button scale up here, because it allows less space for images, and can lead to button text running together on small screens when font size is set very high by users. */ /*--DEFUNCT @media screen and (max-device-width: 640px) { .navtable { height: 1.25em; -- [1.7] punt on scale up for touch (per above) } -- was: for 0..640 pixel screens only .navlink { font-size: 1.25em; -- [1.7] punt on scale up for touch (per above) } .navlink:hover { -- [1.7] punt: abandon hover altogether (per earlier) text-decoration: none; -- else underline/italic may get stuck on font-style: normal; } } DEFUNCT--*/ </style> <!-- Iff JavaScript not enabled: use former 1.5 CSS scaling --> <noscript> <style> #theimg { width: 100%; height: auto; } </style> </noscript> <!-- There's more CSS ahead (info+note popups): search for "<style" --> <!-- ==================================================================================== JavaScript: dynamically scale HTML image element per DOM view size (etc.) ==================================================================================== --> <script type="text/javascript"> /* ok anywhere in page, type optional */ /* ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Context: thumbspage is now a four-language "stew" - Python code generates HTML with embedded CSS for styling and the JavaScript here for dynamic image scaling - the last two of which regularly vary per browser. Web coding has become an interoperability nightmare. Getting the comments right alone is brutal; handling the many browser-specific quirks seems the stuff of madness... None of the following code is used if JavaScript is disabled. ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ */ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // TOUCH SWIPES //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /* ---------------------------------------------------------------------- [2.2] Update: image-viewer pages now support left/right and up/down swipe gestures. Left/right swipes trigger previous- and next-image events (like the Prev/Next menu buttons) per config; down invokes the image-info dialog (like filename taps); and up opens raw image view (like image tap) where supported. Touch events are simple but glitchy (swipes to raw-view pages aren't supported on Chrome); work on both mobiles/smartphones and Windows PCs with touchscreens; and are no-ops on non-touch displays. Simple taps, browser gestures (e.g., pinch/spread zoom, back/forward swipe), and mice still work. [2.3] Up-swipe is now changed to be the same as a new Note toolbar button tap, if Notes are enabled via config and .note presence. Else, up-swipe is as in 2.2, including the browser-specific support levels. ---------------------------------------------------------------------- */ // state of one-touch gestures we care about var touchStartX = null; var touchStartY = null; function onTouchStart(event) { //-------------------------------------------------------------- // Finger down: save screen-position state in globals. // These are used in the move and end event handlers. // Assume a swipe is starting; move may abandon it. // event's touches[] and targetTouches[] have no pageX/Y. //-------------------------------------------------------------- // use only first touch within image's area touchStartX = event.changedTouches[0].pageX; touchStartY = event.changedTouches[0].pageY; } function onTouchMove(event) { //-------------------------------------------------------------- // Finger move: see if it's still a one-touch event. // Some browsers may do pinch/zoom, back/forward, etc. // To allow pinch/spread gestures: clear swipe's state // if any 2+ touch events fire between start and end. //-------------------------------------------------------------- // don't do this: it prevents pinch/zoom // event.preventDefault(); if (event.touches.length > 1) { // abandon 2+ point gestures: pinch/spread touchStartX = null; touchStartY = null; } } function onTouchEnd(event) { //-------------------------------------------------------------- // Finger up: classify the swipe, and trigger page action. // The LRSWIPESPERBUTTONS config mods the meaning of left/right // swipes: true=order of Prev/Next in toolbar; false='natural'. // // Horizontal|vertical direction is determined by the dimension // with the most change. This also must ignore motions below // a pixel-distance threshold, else simple taps may trigger swipe // actions (and even both in some cases). A 25-pixel threshold // seems adequate for both finger and more-precise stylus. // // Caveat: up-swipe is enabled only for browsers which support // it today. Chrome and its derivatives (Opera and Edge) do not. // In all browsers, [window.location.href = 'url'] works fine in // tap/click handlers. In the unsupporting browsers, though, the // same code in touch handlers like this frequently fails to add // the calling page to history, such that Back doesn't return to // the gallery's viewer page. Assigning to just window.location // or using .assign() or a setTimeout() callback doesn't help. // // Hence: rather than penalizing all browsers, up-swipe is coded // to work on Firefox (all platforms), iOS (all browsers), and // Samsung Browser on Android. Other touch browsers get alerts. // This will have to be revisited if Chrome ever fixes its bug; // UPSWIPEONALL can be used to test/enable up-swipe everywhere. // // [2.3] the preceding caveat is moot if Note popups are enabled // via config and .note presence - up-swipe is then the same as a // Note tap, not raw-image view (and image tap is still raw view). // Up-swipe to Note is in-page, and so supported on all browsers. //-------------------------------------------------------------- // ignore 2+ point gestures: pinch/spread if (touchStartX == null) { return; } // use only first touch within image's area var touchEndX = event.changedTouches[0].pageX; var touchEndY = event.changedTouches[0].pageY; // how far was the motion, in pixels? var diffX = touchStartX - touchEndX; // horizontal var diffY = touchStartY - touchEndY; // vertical // to see the distances // alert('hori, vert: ' + diffX + ', ' + diffY); // ignore if not far enough to be a swipe: tap var threshold = 25; if (Math.abs(diffX) < threshold && Math.abs(diffY) < threshold) { return; } // disable any other touch events: we're all in event.preventDefault(); // which changed most? if (Math.abs(diffX) > Math.abs(diffY)) { // horizontal swipe if (touchStartX > touchEndX) { // left swipe if (false) // prev image onNavClick('2014-pyref5e-book.jpg.html'); else // next image onNavClick('2015-florida2.JPG.html'); } else { // right swipe if (false) // next image onNavClick('2015-florida2.JPG.html'); else // prev image onNavClick('2014-pyref5e-book.jpg.html'); } } else { // vertical swipe if (touchStartY < touchEndY) { // down swipe: image info popup imageInfoDialog(); } else { // up swipe: Note popup [2.3], or raw-image view [2.2] if (true) showNote(); else if (runningInFirefox() || runningInSamsung() || runningOniOS() || false) // where currently supported window.location.href = "../2015-florida.JPG"; else // punt with notice alert('Use tap instead of up-swipe on this browser.'); } } // moot if start or move always precede end, but... touchStartX = null; touchStartY = null; } document.addEventListener('DOMContentLoaded', function() { //-------------------------------------------------------------- // Register touch-event handlers on image, after image created. // Don't register on whole window: wrongly generates on buttons. // A load callback avoids moving this past the image build code. //-------------------------------------------------------------- // the image now exists var theimg = document.getElementById('theimg'); // this coding worked on mobile but not Windows; hmm // theimg.ontouchstart = onTouchStart; // this coding works universally theimg.addEventListener('touchstart', onTouchStart); theimg.addEventListener('touchmove', onTouchMove); theimg.addEventListener('touchend', onTouchEnd); }); //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // SLIDESHOW //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /* ---------------------------------------------------------------------- [2.1] Updates: 1) The Auto button is now changed to underlined font as an 'on' indicator, whenever a slideshow is active. Else it can be hard to tell the state of the show, especially when first started, and after coming back from another page later. Italics alone seems too subtle (and makes other buttons shift noticeably on Android), bold changes the toolbar's layout jarringly, and colors are arbitrary via configs. 2) Prototyped but did not use half the normal delay (showDelayMS / 2) for the page on which Auto is first tapped to start a show. This page is already open and perhaps already viewed, and the full delay might make the show seem inoperable. However, the new Auto font change (1) provides ample indication that the show is on; and assuming that the first page has been viewed seems too presumptuous - it may have been jumped to from the index in order to start a show there immediately. ---------------------------------------------------------------------- */ // Global delay for timer loop, per-gallery configurable var showDelayMS = 5000; // Values have to be strings: ternary op inconvenient var toggleKey = 'thumbspageAuto'; var toggleOn = 'show-timer-on', toggleOff = 'show-timer-off'; // Cancel id, global to this page var showTimerID; // Auto button HTML element, for font change [2.1] var autoBtn; // No-op or alert or console.log var trace = function(msg) {}; function hasSessionStore() { /* ------------------------------------------------------------------ Return true iff HTML5 session storage is present and enabled. Very old browsers (e.g., IE7-) don't support the API, and some browsers may allow users to disable session storage manually. This seems overkill, but allows for browser-specific disables. ------------------------------------------------------------------ */ try { sessionStorage.setItem('testkey', 'testval'); return (sessionStorage.getItem('testkey') == 'testval'); } catch (err) { return false; } } /* ------------------------------------------------------------------ Factor out common start/stop code, shared by button-click handler (in two state-retention flavors), and initial page-show handler. Global autoBtn has been fetched by autoContinue() post Auto build. Update: don't use italics - it causes buttons to shift noticeably on Android, and underline is enough (and looks more like a link). ------------------------------------------------------------------ */ function startSlideShow(delay) { //autoBtn.style['font-style'] = "italic"; autoBtn.style['text-decoration'] = "underline"; showTimerID = setTimeout(function() {onNavClick('2015-florida2.JPG.html');}, delay); } function stopSlideShow() { //autoBtn.style['font-style'] = "normal"; autoBtn.style['text-decoration'] = "none"; clearTimeout(showTimerID); } function onAutoClick() { /* ------------------------------------------------------------------ [2.0] On toolbar Auto button clicks: toggle per-tab, cross-page slideshow state, and schedule or clear timer event to flip image. This is essentially automatic Next clicks, run on timer events. The cross-page toggle's state is per-tab, and used by both the current and following viewer pages. The slideshow persists until cancelled by a toggle or the gallery is exited, and is restarted on gallery returns (in most contexts). To loop: exiting a viewer page stops the timer automatically, but new pages reschedule the timer on load if the toggle is on. Subject to the vagaries of browser back-forward caches, but it works on all browsers tested. Auto requires JavaScript. About cross-page state in serverless pages: this uses window.name, because it's simple and cannot be disabled. Alternatives: client-side cookies, and HTML5 stores (window.sessionStorage.{getItem('key'), setItem('key', 'val')}). Coding note: unlike window.name, page-defined attributes (e.g., window.autoToggle) do not persist across pages: Auto switches pages once, but the attribute is 'undefined' in the next page. UPDATE, Jul-18-20: this now uses HTML5 session storage, not window.name. Else, state fails in Safari (only) when viewing galleries locally/offline (only). The new scheme works the same as window.name, which is still used as a fallback where session storage is unsupported or disabled. More: UserGuide.html#_20C. Caveat: with this fix, local-gallery slideshows work on Safari, but stop (and rarely even crash) when returning with Back from another page in some contexts (e.g., from remote); see ahead. [2.1] Change Auto button font to underline/normal for slideshow on/off; this happens in the factored utility functions above. [2.1] Do _not_ use half the normal delay for the page on which Auto is first clicked to start the show; this is too presumptuous. ------------------------------------------------------------------ */ if (hasSessionStore()) { // use HTML5 cross-page session storage where available (newer, safari ok) trace('onAutoClick: using session storage'); // if set and on: clear flag, cancel next timer event if (sessionStorage.getItem(toggleKey) == toggleOn) { sessionStorage.setItem(toggleKey, toggleOff); stopSlideShow(); } // if off or not set: start loop (rescheduled by next page) else { sessionStorage.setItem(toggleKey, toggleOn); startSlideShow(showDelayMS); } } else { // use cross-page window.name as a fallback (original, Safari fail) trace('onAutoClick: using window.name'); // if set and on: clear flag, cancel next timer event if (window.name == toggleOn) { window.name = toggleOff; stopSlideShow(); } // if off or not set: start loop (rescheduled by next page) else if (window.name == toggleOff || window.name == '') { window.name = toggleOn; startSlideShow(showDelayMS); } } } function autoContinue(event) { /* ------------------------------------------------------------------ On page load: reset timer if show on, but not if off or not set (any 'falsy'). Run on first open, but also any navigation entry, and might be run on back-forward cache restores (Safari issue?). [2.1] Change Auto button to underline font if slideshow is active. [2.1] The next page (and thereafter) always uses the full delay. [2.1] Now called just after Auto built so font change unnoticeable. ------------------------------------------------------------------ */ var reschedule; trace('autoContinue'); // assume it's built by now autoBtn = document.getElementById("autoBtn"); // global // on Safari Back stop, deferred: 'null::false' persisted = event ? event.persisted : 'none'; trace(sessionStorage.getItem(toggleKey) +':'+ window.name +':'+ persisted); if (hasSessionStore()) { reschedule = (sessionStorage.getItem(toggleKey) == toggleOn); // HTML5 storage } else { reschedule = (window.name == toggleOn); // else fallback } if (reschedule) { trace('reschedule'); startSlideShow(showDelayMS); } else { trace('NOT rescheduled'); // off, or Safari Back stop } } /* ------------------------------------------------------------------------------------ [2.1] The call to autoContinue() has been moved to end of this file, so it occurs immediately after the Auto button is built. It cannot be called here because the button must exist for its font to be changed, and the pageshow event-handler code below made the button's font change noticeable on some browsers/platforms (a timer event is another option, but would similarly flash and is heuristic). [2.0] Safari (only) work-around attempt: run timer reschedule code in a pageshow load-time event, not immediately. This was harmless in other browsers; for Safari, it tried to both restore the show on a next page or Back return, and avoid Safari Back-failure popup messages: "... Operation not permitted" (NSPOSIXErrorDomain:1)". DID NOT HELP: a Safari glitch changes the Auto toggle to null in HTML5 session state after a Back from remote; see UserGuide.html#moresafarijunk for the whole story. ------------------------------------------------------------------------------------ */ /*--DEFUNCT try { // callback trace('run timer reschedule deferred'); window.addEventListener('pageshow', autoContinue); // when widgets built } catch (err) { // immediate trace('run timer reschedule immediately'); autoContinue(null); // run now in older browsers? (alt: window.onpageshow) } DEFUNCT--*/ // original: font-change fails here, because autoBtn is still null here // autoContinue(null); // run on page load immediately everywhere // now called near end of file as soon as Auto built, so font change not visible // document.addEventListener('DOMContentLoaded'....) not used: need to run asap //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // FULLSCREEN //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /* ---------------------------------------------------------------------- [2.1] Update: the Full button is changed to underline font as an 'on' indicator, whenever fullscreen has been activated by Full. This is done only for parity with Auto: fullscreen is obvious, and lasts for just one page as currently implemented (better == major redesign). Unlike Auto, all Full font changes happen in an event handler, because a fullscreen request can be denied and raise an exception in an async 'promise,' and users may cancel Full without pressing Full (e.g., via an Escape key). On browsers that don't support this event (e.g., some Safari), Full's font will simply never morph. Users may also manually turn a different fullscreen mode on and off outside the page's control at any time with shortcut keys or similar, but the page might not receive an event for, and be unable to cancel, that fullscreen mode. ---------------------------------------------------------------------- */ /* ---------------------------------------------------------------------- FULLSCREEN CAVEAT JavaScript-initiated fullscreen mode is limited to a single page, and does nothing on some browsers - including Safari and Chrome on iOS. It's unclear whether this is useful as a blowup/zoom feature, or just a "stupid browser trick" in thumbspage galleries that wastes UI space. Hence, the Full toolbar button that invokes code here can be disabled on a per-gallery basis via a build setting in user_configs.py. Users may be better served by browsers that have manual fullscreen options, which persist across page changes and span Auto slideshows (e.g., try F11 on Windows, control-command-f on Mac OS, Hide Toolbar on iOS 13+, and Opera 63+ on Android). Manual fullscreen doesn't work everywhere, but it likely beats thumbspage's Full where it does. For more about this feature, see the User Guide's coverage: - UserGuide.html#fullscreenmanual (manual options) - UserGuide.html#fullscreen20 (2.0 release docs) - UserGuide.html#_dnG (initial feature proposal) Coding note: these alternatives were also tried (forced scrolls, and "Add to home screen" support), but as coded did not help; either more is required, or they are old and fully trounced hacks: - window.scrollTo(0,1); (this may require bogus space) - <meta name="mobile-web-app-capable" content="yes"> - <meta name="apple-mobile-web-app-capable" content="yes"> [2.2] Some mobile browsers now popup a temporary but annoying message every time JavaScript fullscreen is entered, just as many do on PCs. Which makes it even less useful; see Opera's landscape fullscreen. ---------------------------------------------------------------------- */ // // The fullscreen API has been, well, fluid... // https://developer.mozilla.org/en-US/docs/Web/API/Fullscreen_API // Some cases below are untested; the standard calls are now widespread. // function openFullscreen() { var de = document.documentElement; // documentElement: <html> // request fullscreen if (de.requestFullscreen) { // "standard" (undefined: false) de.requestFullscreen(); } else if (de.webkitRequestFullscreen) { // some Chrome, Safari, Opera de.webkitRequestFullscreen(); } else if (de.mozRequestFullScreen) { // some Firefox de.mozRequestFullScreen(); } else if (de.msRequestFullscreen) { // some IE/Edge de.msRequestFullscreen(); } // if it worked: change font in fullscreen-change handler } function closeFullscreen() { // documentElement not required here if (document.exitFullscreen) { // "standard" (undefined: false) document.exitFullscreen(); } else if (document.webkitExitFullscreen) { // some Chrome, Safari, Opera document.webkitExitFullscreen(); } else if (document.mozCancelFullScreen) { // some Firefox document.mozCancelFullScreen(); } else if (document.msExitFullscreen) { // some IE/Edge document.msExitFullscreen(); } // if it worked: change font in fullscreen-change handler } function isFullscreen() { // any may be undefined: false in JS return document.fullscreenElement || // "standard" document.webkitFullscreenElement || // some Chrome, Safari, Opera document.mozFullScreenElement || // some Firefox document.msFullscreenElement; // some IE/Edge } function onFullClick() { /* ------------------------------------------------------------------ [2.0] On toolbar Full button taps: toggle one-page fullscreen on or off. Fullscreen persists until cancelled by a Full toggle, user action, or page exit, and it doesn't work in some browsers. This requires JavaScript (and perhaps a horizontal toolbar scroll on smaller displays). It doesn't need to use cross-page state, because the fullscreen API has state queries. This could track on/off state manually with HTML5 storage, but that might also require catching (nonportable?) callbacks on user-invoked changes. ------------------------------------------------------------------ */ if (!isFullscreen()) { openFullscreen(); } else { closeFullscreen(); } } function onFullscreenChange() { /* ------------------------------------------------------------------ [2.1] Change Full's font as a perhaps-redundant visual indicator, whenever fullscreen state is changed by the Full button itself, or the Full button's fullscreen is otherwise cancelled by a user. The fullBtn element is already built here, because we're in a user-event callback. Run by an event handler, because fullscreen requests are async and can fail with an uncaught exception, and a Full may be cancelled without pressing Full (e.g., an Escape key). This won't be invoked for user-initiated fullscreen mode (which weirdly differs from page/JavaScript-initiated fullscreen), but it is called when users cancel a Full without pressing Full. Update: don't use italics - it causes buttons to shift noticeably on Android, and underline is enough (and looks more like a link). ------------------------------------------------------------------ */ var fullBtn = document.getElementById("fullBtn"); if (isFullscreen()) { //fullBtn.style['font-style'] = "italic"; fullBtn.style['text-decoration'] = "underline"; } else { //fullBtn.style['font-style'] = "normal"; fullBtn.style['text-decoration'] = "none"; } } // // register for fullscreen on/off changes invoked by page // document.documentElement.onfullscreenchange = onFullscreenChange; /* ---------------------------------------------------------------------- Caveat: we can't force fullscreen for a next page here, even in a deferred pageshow event, because it must be requested only from a short-running, user-initiated event handler (per Chrome's console error message, the API can be initiated only by a user gesture). Hence, it lasts for one page only, where it works at all, and won't persist for Auto slideshows. It's useful anyhow as a zoom, but disable as desired, and use browser/platform-specific fullscreen if available. A "real" fullscreen would require morphing a single persistent page, and more thumbspage redesign than its use cases currently warrant, and might sacrifice linkable viewer-page URLs for individual images. For now, users can increase page size manually in most browsers. [2.2] JavaScript fullscreen also fails in touch events (see above). Which would be okay if all browsers had multi-page fullscreen modes. ---------------------------------------------------------------------- */ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // PLATFORM //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /* These are all really bad ideas; why hasn't this been standardized? */ function runningOniOS() { /* ------------------------------------------------------------------ More heinous web hacks; MS says iPhone in IE11 too. Use a JS regexp on an unreliable but widely used string. A generally bad idea, but often the only option today. ------------------------------------------------------------------ */ // alert(navigator.userAgent); return /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; // hack! } function runningInSafariOniOS() { /* ------------------------------------------------------------------ More heinous web hacks; Chrome says Safari on iOS too, and all iOS browsers must use the same Apple code base. Use a JS regexp on an unreliable but widely used string. These tests matter; why haven't they been standardized? Was unused; formerly employed to allow for more vertical cruft on landscape+iOS+Safari only (but failed: use 1.5): safariFudge = (runningOniOS() && runningInSafari() && viewportsize.width > viewportsize.height) ? 50 : 0; [1.7] May-2020: Now used for Safari+iOS+landscape CSS-scaling legacy option (but UNUSED by default, per user_configs.py). This new, renamed coding works only for Safari on iOS, not desktop, but suffices... though its !others.test() coding means that any not in others are Safari (where others means {Chrome+iOS, Firefox+iOS, the new Edge, UC Browser}); yuck. ------------------------------------------------------------------ */ // this doesn't work today (! list incomplete?) // return /^((?!chrome|android).)*safari/i.test(navigator.userAgent); // hack too! // close, but not quite right (see above) var vendor = navigator.vendor; var uagent = navigator.userAgent; return runningOniOS() && vendor && vendor.indexOf('Apple') > -1 && // Apple code base uagent && !/CriOS|FxiOS|Edg|UCBrowser/.test(uagent); // not other browsers } function runningInChromeOniOS() { /* ------------------------------------------------------------------ Ditto: used for history-destacking disable on Next/Prev onclick. [2.0] This is now UNUSED by default too: the bug has been fixed. ------------------------------------------------------------------ */ return navigator.userAgent.match('CriOS'); // also hack! } function runningInSafari() { /* ------------------------------------------------------------------ Same: add to work around Safari-only Back fails for Auto [2.0]. UNUSED: Back fail is a session-state bug in Safari (see ahead). Coding note: some browsers 'spoof' a Safari string in userAgent. ------------------------------------------------------------------ */ // this doesn't work today... // return window.safari !== undefined; // also also hack! var vendor = navigator.vendor; var uagent = navigator.userAgent; return vendor && vendor.indexOf('Apple') > -1 && // Apple code base uagent && !/CriOS|FxiOS|Edg|UCBrowser/.test(uagent); // not other browsers } function runningInFirefox() { // [2.2] added for Chrome post-touch Back bug return navigator.userAgent.toLowerCase().indexOf('firefox') != -1; } function runningInSamsung() { // [2.2] added for Chrome post-touch Back bug return navigator.userAgent.match(/SamsungBrowser/i); // i=caseinsens, notfound:null } // to test /* console.log(navigator.userAgent); console.log('runningInSafari: ' + (runningInSafari() ? 'yes' : 'no')); console.log('runningInSafariOniOS: ' + (runningInSafariOniOS() ? 'yes' : 'no')); */ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // NAVIGATION //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ function onNavClick(page) { /* ------------------------------------------------------------------ When opening another viewer page with the Prev or Next links, simply replace this page with the target, to avoid adding this soon-to-be-prior viewer page to the browser's history stack. The net effect is that viewer pages are never added to browser history, except for just one when navigating away from the gallery. This way, the browser's Back skips over the entire gallery navigation session, returning immediately to the gallery entry point: the index, or other direct viewer-page link source. Otherwise, N image views may require N Back clicks to return to pre-gallery pages (if there are any, and Index-page context is not enough). This was much worse than making Back skip all nav steps, given that viewer pages' Prev and Next do similar work. The Index link still goes to the gallery's index page regardless of the entry point - as it should, for direct viewer-page links. Moreover, after navigating away from the gallery, browser Back and Forward clicks return to the last viewer page viewed (only). Relies on the DOM location.replace() redirect-sans-history call, which might fail if classified as unsecure (crossing domains?). This could use <button>s, but keeping original <a>s appearance. history.go(-N) + counter stacks may work, but can't catch Back. onclick() here fires before target's onload(): see "Loading...". Former typo: "return True;" is exception (but href used anyhow). CAVEAT: this history destacking feature is disabled for Chrome on iOS (only), because location.replace() fails in that browser (only!). If used, Back clicks redisplay the _same_ page N times for N image views, which is worse than allowing pages to be stacked. thumbspage.py's version 1.6 notes have more details. No coding work-around could be found: go(-1), timer, paths, ... UPDATE [1.7]: this is still broken as of May 2020's iOS Chrome 75; if enabled, the .replace() here now silently stacks all pages. UPDATE [2.0]: since iOS Chrome's current behavior is no different than the work-around, the work-around bypass config switch now defaults to true; set it back to false if this ever becomes worse again. The new true will also pick up a future Chrome fix. UPDATE [2.0]: the iOS Chrome history bug has been fixed, as of June 2020's Chrome 83 (and perhaps earlier). The true setting of the config switch adopts the fix automatically, and thumbspage navigation pages are no longer stacked or retraced in iOS Chrome. Hence, this issue is closed; its code below is retained as info. UPDATE [2.2]: see also onTouchEnd() above for a related issue, plus https://developer.mozilla.org/docs/Web/API/Window/location. ------------------------------------------------------------------ */ // absolute URL not required, and URL-encoded filenames okay // var loc = window.location; // var url = loc.protocol + '//' + loc.host + ... + page; // no longer used: browser-bug work-around, till fixed? if (runningInChromeOniOS() && ! true) { return true; // use URL in <a> (same as no-JS case) } // all other browsers try { window.location.replace(page); // goto this, don't add page to history } catch(err) { window.location.href = page; // work on errors, add to history (=assign()) } return false; // do not goto <a> href link (if one coded) } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // INFO POPUP //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ function imageInfoDialog() { /* ------------------------------------------------------------------ [1.7] Feb-2020: popup an image-info dialog when the image's filename at the top of the viewer page is clicked/tapped. This initially used a simple alert() call; it could instead build new HTML element objects, but that seems overkill in this context. The 'Image size' comes from the static .py (via Pillow); it's the same as the JS DOM's theimg.natural{Width, Height}. Display size alone is from JS DOM, and reflects the size requested in the last resizeImage() callback (extraneous, but interesting). This looks funky in generated pages, due to Py str replacements. [2.0] This now uses a custom modal 'dialog' - which is really a full-page overlay with opacity, plus a display box within it, all shown and hidden on demand. Most implementation code (CSS, HTML, JavaScript) is at end of file; this formats the message. Why go custom? Some browser vendors discourage alert(), and even treat it as a threat, asking users if they wish to silence it. Worse, mobiles format alert() text badly with wrapping, and iOS 13's Hide Toolbars can fully botch it with text outside the box. The custom dialog instead scrolls text vertically when needed. Bonus: the new dialog box inherits viewer-page bg/fg color settings in user_configs.py; respects user font-size choices; supports copy/paste; and does not kill Full fullscreen displays. [1.7] May-2020: now labels date as 'Digitized' for photo scans. [2.0] Jul-2020: now shows device|software line iff in Exif tags. [2.0] Jul-2020: use "Created" for unknown data, and "Nw x Mh". [2.0] Jul-2020: use a custom modal-dialog display (@end of file). [2.3] Apr-2022: bg/fg/bd colors may now be custom, as for Note. [2.3] Apr-2022: device text is arbitrary: now JS+HTML escaped. ------------------------------------------------------------------ */ var theimg = document.getElementById('theimg'); var domwide = theimg.width, domhigh = theimg.height; // info added here var popup = showInfo; // [2.0] alert() no more... see above/ahead // legacy: see ahead popup('Taken: 2015-08-23 @15:49:11\n' + // Py: Exif (origin date) 'Modified: 2020-03-05 @15:48:57\n' + // Py: OS 'File size: 583,417 bytes\n' + // Py: OS 'Image size: 2,073w x 1,555h\n' + // Py: Pillow (also in DOM) 'Display size: ' + domwide + 'w x ' + domhigh + 'h' + // JS: DOM (dynamic) '\nDevice: DSC-HX50V (SONY)') // Py: Exif (device|software?) return false; // don't try to follow a fake <a> href, if coded } // // [2.0] The info-popup implementation continues near the end of this file. // //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // IMAGE SCALING //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ function refitAspectRatio(trueWidth, trueHeight, // image size displayWidth, displayHeight, // display size stretchBeyondActual) { // expand if small? /* ------------------------------------------------------------------ Constrain image size to display area, retaining original aspect ratio. Use to shrink or enlarge images as needed to fit a new display size while showing the entire image. The math scales both sides per the smallest display ratio: trueWidth, trueHeight: width, height of actual/original source image (pixels) displayWidth, displayheight: maximum available width, height for image display (pixels) If 'stretch' is true, images may expand beyond their actual sizes to fit the display; if false, image maximum sizes are limited to actual size, to avoid blurring of small images. Returns an Object (see Python dict), with .width, .height. ------------------------------------------------------------------ */ var ratio; // per smallest-fit side ratio = Math.min(displayWidth / trueWidth, displayHeight / trueHeight); if (! stretchBeyondActual) { ratio = Math.min(ratio, 1.0); // don't expand beyond actual size } return {width: (trueWidth * ratio), height: (trueHeight * ratio)}; } function getViewportSize() { /* ------------------------------------------------------------------ Display size: yet another browser-specific mess... iOS reports available size differently (of course). A Math.max() might work here, but seems a bit iffy. These are often the same, but supported unevenly; in theory, client* sizes discount border and margin. ------------------------------------------------------------------ */ // to watch: // alert('window: ' + window.innerWidth + ' x ' + window.innerHeight); // alert('docelmt: ' + document.documentElement.clientWidth + // ' x ' + document.documentElement.clientHeight); if (! runningOniOS() && window.innerWidth !== undefined && window.innerHeight !== undefined) { return {width: window.innerWidth, height: window.innerHeight}; } else { return {width: document.documentElement.clientWidth, height: document.documentElement.clientHeight}; // portable much? } } function getUsedHeight() { /* ------------------------------------------------------------------ [1.7] Feb-2020: Return the total pixel height of all non-image elements on the page, used to deduce space left for the image. Though rare, the 110px fudge-factor constant formerly used to calculate image-area size could result in clipping of the image's bottom border and part of its content, when viewing very tall images and applying unusually large font settings. This was first seen for mobile portrait screenshots in Firefox on an S8+ smartphone, but could also happen on both desktop and mobile browsers if users selected larger fonts. This page's CSS font-size boost used for toolbar buttons on small screens made the clipping more likely, but user font/scaling settings alone could trigger it for some images and browsers. The new scheme deduces image space from space already taken, rather than relying on constants, or the undrawn image's space. The net effect now accommodates arbitrary user font settings; as a bonus, it also typically yields a larger image display. Caveat: this must still use a small constant size (fudgeExtra) to account for margins and the image border, but this seems to work universally (so far); alas, the DOM offsetHeight includes content+padding+border, but not margin, which is oddly complex. Coding Notes: - The <noscript> warning size is moot: never gets here to resize. - The result isn't cached, because font size can change any time. - This is coded defensively; browser variability is a nightmare. - See also elt.getBoundingClientRect(), a possible alternative. - Failed: elt.outerHeight(), getComputedStyle(elt)['marginTop']. - TBD: cache getElementById() results in global vars for speed? ------------------------------------------------------------------ */ // locals var debug = false; // show alerts? var defaultHeight = 110; // prior-constant fallback, prefudge var fudgeExtra = 12; // for margins + image border var usedHeight; try { var titleElement = document.getElementById('thetitle'); var toolbarElement = document.getElementById('thetoolbar'); var titleHeight = titleElement.offsetHeight; var toolbarHeight = toolbarElement.offsetHeight; if (titleHeight != 0 && toolbarHeight != 0) { usedHeight = titleHeight + toolbarHeight; } else { usedHeight = defaultHeight; if (debug) alert('usedHeight defaulted: zero element size'); } } catch (err) { usedHeight = defaultHeight; if (debug) alert('usedHeight defaulted: size fetch failed'); } usedHeight += fudgeExtra; if (debug) alert('getUsedHeight: ' + usedHeight); return usedHeight; } function setLoadingDisplay() { /* ------------------------------------------------------------------ To minimize 'flash' hide the image during initial download or load and post an indicator message. The message is erased and the image is unhidden when the image is sized. Otherwise, the only indicator of progress is the browser's busy indicator. The <P> isn't coded in HTML - else it could not be erased if JavaScript is disabled. This uses a global variable that can be reference later (though it might instead set a fetchable DOM id with loadingIndicator.id = 'theid'). ------------------------------------------------------------------ */ // hide the image temp document.getElementById('theimg').style.visibility = 'hidden'; // post a temp <P> indicator loadingIndicator = document.createElement('P'); // no 'var' means global loadingIndicator.style.fontStyle = 'italic'; // global means window loadingIndicator.style.textAlign = 'center'; loadingIndicator.innerHTML = 'Loading...'; // var text = document.createTextNode('Loading...'); // innerHTML is enough // loadingIndicator.appendChild(text); document.getElementById('thetitle').appendChild(loadingIndicator); } function resizeImage() { /* ------------------------------------------------------------------ Run on initial page load, and again on each window resize. As of 1.6, viewer pages use JS here to rescale the page's image to the current display area, retaining the image's original aspect ratio. CSS alone cannot preserve aspect ratio on all window resizes; when used, images can overflow containers and run off-screen, requiring scrolls to view. This emulates native display well on all desktop browsers, and scales images to the available space on mobile browsers. Exception: iOS landscape falls back on 1.5's CSS scrolled scaling, because no way could be found to do better (TBD; Safari munges sizes and UI, and it is most of iOS traffic). 1.5 CSS scaling is also used when JavaScript is disabled. [1.7] Update May-2020: JavaScript scaling is now also used for all iOS browsers in landscape mode. This works well on all non-Safari iOS browsers (and matches their Android behavior); is better on Safari in iOS < 13 (image bottom is easier to view); and works perfectly in iOS Safari if users enable the new toolbar-hiding option added in iOS 13. Hence, JS scaling is now used everywhere by default, when JS is enabled. As a legacy option, 1.5's CSS-based display can still be used for landscape in iOS Safari, because its toolbars mar pages sans the iOS 13 fix. Scaling to the full viewport size is partly heuristic: the constants here work everywhere tested, but may need tweaking; alas, no universal way to get a scaling-area size was found. Tried but failed: a <div> with display:flex + <canvas>/Image(). [1.7] Update Feb-2020: the available image height is now deduced from the sizes of other page elements instead of constants, to accommodate font settings; see getUsedHeight(). Some Python % replacements here could be dynamic JavaScript. Caution: browser settings can have big side effects - Android Chrome's "Force enable zoom" may break same "Raw" displays. Note: the DOM's theimg.natural{Width, Height} here is original in-file image size, and the same as the {ORIGWIDE, ORIGHIGH} fetched from Pillow and expanded in imageInfoDialog() by .py. ------------------------------------------------------------------ */ // erase message paragraph loadingIndicator.style.display = 'none'; // HTML element: via DOM, per <img> tag var theimg = document.getElementById('theimg'); // new display area size for image scaling var viewportsize = getViewportSize(); // [1.7] calc size of other page elements var usedheight = getUsedHeight(); // [1.7] landscape on iOS in Safari: use legacy CSS display? if (false && runningInSafariOniOS() && viewportsize.width > viewportsize.height) { // sizes and UI munged: use version 1.5 CSS scaling theimg.style.width = '100%'; theimg.style.height = 'auto'; } else { // erase any iOS landscape settings theimg.style.width = null; theimg.style.height = null; // calc new scaled size (less borders, title+toolbar+cruft) // [1.7] usedheight was 110, now actual space used var refitsize = refitAspectRatio( theimg.naturalWidth, theimg.naturalHeight, viewportsize.width - 18, viewportsize.height - usedheight, false); // change in DOM/page: redraws theimg.width = refitsize.width; // see also .style.cssText('width:n;') theimg.height = refitsize.height; // see also .style.height=n (inline CSS) } // show now, minimizing initial flash theimg.style.visibility = 'visible'; } // // Register resize callback handler now // Or: <body onresize="resizeImage()" onload="resizeImage()"> // window.addEventListener('resize', resizeImage); // There's more JS ahead (info popup, etc.): search for "<script" </script> <!-- ==================================================================================== Back to HTML: layout, inline CSS styles, CSS/JavaScript fodder/linkage ==================================================================================== --> <title>2015-florida.JPG</title> </head> <!-- initial sizing, else opens fullsize ("...code..." = func) --> <body onload="resizeImage();"> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- JS NOTE: iff JavaScript not enabled, alert user --> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- [2.3] JS is now required, as hrefs for Prev/Next/Index were dropped to --> <!-- kill URL popups; otoh, viewers were largely broken without it before; --> <!-- 2.3 also omits Note/Auto/Full btns if no JS, but keeps other (layout); --> <noscript> <div class=overflowscroll> <p class=nojsnote> This page is best viewed with JavaScript enabled </p></div> </noscript> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- FILENAME: possibly long, <h1> takes away too much image space --> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- [1.7] popup mod/taken dates + size info dialog on filename click/tap --> <!-- use same hover style as bottom buttons for indicator and consistency --> <div id=thetitle class=overflowscroll> <!-- already scrolled [2.0] --> <p class=filename> <!-- with <p>+<span>, android chrome (only!) selects text and prompts for a copy --> <!-- instead, use simple <a> + onclick for JS use only + manual cursor (no href) --> <a class="navlink cursorpointer" onclick="return imageInfoDialog();" title="View image info"> 2015-florida.JPG </a> </p></div> <!--DEFUNCT <span style="cursor: pointer;" onclick='alert("...")'> 2015-florida.JPG </span> DEFUNCT--> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- IMAGE: (re)scaled to fit window/viewport, clickable for raw --> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- [1.7] make image click/tap same as Raw (view fullsize) for convenience; --> <!-- especially useful for smaller image displays of most mobile landscapes; --> <!-- doesn't use a simple href, because the mouseover url popup is too busy; --> <!-- [2.2] image tap still works as before with image-swipe touch gestures; --> <img id=theimg src="../2015-florida.JPG" class=cursorpointer alt="Full image" title="View raw image" onclick='window.location.href = "../2015-florida.JPG"'> <!-- iff JavaScript enabled, hide now to minimize initial flash, show indicator --> <script> // // on page load // setLoadingDisplay(); // to test: // setTimeout(setLoadingDisplay, 2000); // func, msec // setTimeout(resizeImage, 4000); </script> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- BUTTONS: small but scrolled to avoid mobile-viewport overflow --> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- [1.7] large user fonts could run together: avoid by dropping CSS scale-up --> <!-- this, plus the new used-space calcs, yield slightly larger image displays --> <!-- [2.0] toolbar buttons now scroll, so overflow seems a largely moot point --> <!-- [2.2] add optional tooltip popups to Auto, Full, images, and filename --> <!-- [2.3] add optional Note button, drop hrefs, more tooltips, delete buttons --> <div id=thetoolbar class=navdiv> <!-- [1.7] id for space --> <p> <table class=navtable id=toolbartable> <tr> <!-- hrefs used if JS is disabled (or ios-chrome browser bug work-around used); --> <!-- it may be nice to drop the URL popups on mouseover, but hrefs trigger them; --> <!-- [2.3] Dropped all hrefs here (e.g., href="2014-pyref5e-book.jpg.html", href="#slideshow"): they are no longer used as fallbacks for Prev/Next if JS, Index is now a func, and tooltips are default. This avoids obtrusive URL popups on mouseover, which are now redundant with tips. It also requires JS for Prev/Next/Index, but viewer pages were already unusable without it. The 2.3 code renders identically to 2.2, when no Note button is present. 2.3 also changed hardcoded 'index.html' to use INDEX config: name may differ per build. --> <td><a class=navlink title="Go to previous image" onclick="return onNavClick('2014-pyref5e-book.jpg.html');">Prev</a></td> <td><a class=navlink title="Go to next image" onclick="return onNavClick('2015-florida2.JPG.html');">Next</a></td> <td><a class=navlink title="Go to thumbnails page" onclick="window.location.href='../index.html'; return false;">Index</a></td> <td><a class="navlink optional" id=noteBtn title="View image description" onclick="showNote(); return false">Note</a></td> <!-- [2.3] new --> <td><a class="navlink optional" id=autoBtn title="Toggle slideshow" onclick="onAutoClick(); return false">Auto</a></td> <!-- [2.0] new --> <td><a class="navlink optional" id=fullBtn title="Toggle one-page fullscreen" onclick="onFullClick(); return false">Full</a></td> <!-- [2.0] new (lesser) --> </tr></table> </p> <!-- the /p adds space below links bar, oddly: don't remove! --> </div> <script> // // reschedule Auto timer event on the new page, and change // Auto's font to underline now so change is not noticeable; // autoContinue(null); // // show Auto button now, if JS is enabled (else it's a no-op) // document.getElementById("autoBtn").style.display = "block"; // // [2.3] show Note button if JS && feature enabled via config+files // if (true) { document.getElementById("noteBtn").style.display = "block"; } else { // delete noteBtn, else throws off spacing, especially on mobile document.getElementById("toolbartable").rows[0].deleteCell(3); } // // show optional Full button now iff JS and config enabled // if (true) { document.getElementById("fullBtn").style.display = "block"; } else { // [2.3] ditto for spacing, now (4) if Note was deleted, else (5) document.getElementById("toolbartable").rows[0].deleteCell(true ? 5 : 4); } </script> <!--DEFUNCT [2.0] raw display is now image-tap or down-swipe (see also filename-tap info) <td><a class=navlink href="../2015-florida.JPG">Raw</a></td> DEFUNCT--> <!-- POPUP OVERLAYS --> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- INFO POPUP: custom modal dialog, via div overlay+opacity [2.0] --> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!--''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' CSS: why not in <head>? - OK per HTML 5.2, OK in all browsers tested, this is really a new page def, and code proximity is much better here. Dialog-box colors are from bg/fg user settings for entire viewer page. Page or text scrolls may be needed for mobile and/or very large fonts. Parts of the following are also used for the Note display in [2.3]. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''--> <style> /* Modal-dialog display: full page, background */ .InfoDisplay { display: none; /* hidden initially */ position: fixed; /* stay in place */ z-index: 1; /* open div on top: overlay */ left: 0; /* fill entire page */ top: 0; width: 100%; /* full width+height */ height: 100%; overflow: auto; /* enable full-page vertical scrolls, if ever? */ /* [2.3 now customizable */ background-color: rgb(0, 0, 0); /* fallback color: blank bg */ background-color: rgba(0, 0, 0, 0.45); /* black, with opacity: dim bg */ } /* Modal-dialog box: text + button content on page */ .InfoBox { margin: 8px auto; /* top+bottom, right+left; offset from top, centered */ padding: 4px; /* top offset can't be much on mobile */ padding-left: 20px; /* margin=outside, padding=inside */ border: thin solid; /* #888 color nice, unless same as bg */ width: 80%; /* up to this much of the overlay/page */ max-width: 400px; /* but not too wide on desktop or landscape */ border-radius: 6px; /* rounded borders (for the kids) */ /* [2.3] colorize dialog per configs */ color: wheat; /* foreground: text (not OK) */ background-color: black; /* background: full widget (not OK) */ border-color: white; /* dialog (and OK button) border */ } /* Modal-dialog message: preformatted text in box */ .InfoText { overflow: auto; /* enable text-area horizontal scrolls, if needed */ /* [2.3] inherit colors from InfoBox dom parent, not page or 'initial' */ color: inherit; background-color: inherit; } /* Modal-dialog close: OK button in box */ .InfoClose { display: block; margin: auto; /* centered in box */ margin-bottom: 6px; /* but hug the box bottom */ /* [2.2] customize to make larger on iOS */ padding: 2px 8px 2px 8px; font-size: 0.8em; /* [2.3] use page's bg/fg colors, but inherit border from InfoBox dom parent */ border-color: inherit; border-radius: 6px; /* round corners to match popup, while we're at it */ } .InfoClose:hover, .InfoClose:focus { cursor: pointer; /* nothing fancy: mobiles may botch hover */ } </style> <!--''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' HTML: make elements that use CSS and are referenced in JavaScript. Build a div that overlays entire page, to be shown/hidden in demand. This essentially defines a new page, but it looks like a popup box. Fails for big fonts: <span id=infoClose class=InfoClose>×</span> ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''--> <!-- Modal dialog display: full-page container --> <div id=infoDisplay class=InfoDisplay> <!-- Modal dialog box: text + close content --> <div class=InfoBox> <pre id=infoText class=InfoText>Replace me on opens...</pre> <button id=infoClose class=InfoClose> OK </button> </div> </div> <!--''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' JavaScript: must be after HTML to cache elements in globals on load (or: run these from an onload event handler for DOM-element access). showInfo() is called on filename tap from imageInfoDialog() above, which formats the message text, and documents this dialog further. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''--> <script type="text/javascript"> /* ok anywhere in page, type optional */ // Get the full modal display <div> in a global var infodisplay = document.getElementById('infoDisplay'); // Get the text-message element to be replaced var infotext = document.getElementById('infoText'); // Get the OK-button element that closes the display // or document.getElementsByClassName('InfoClose')[0] var infoclose = document.getElementById('infoClose'); // On OK click/tap, close entire modal overlay infoclose.onclick = function() { infodisplay.style.display = 'none'; // hide/close display } // On clicks outside the box, close entire modal overlay window.addEventListener('click', function(event) { // // not window.onclick = function(){}: also for Note overlay // but iOS still doesn't fire this with the new coding... // if (event.target == infodisplay) { // if tap bg, not box infodisplay.style.display = 'none'; } }); // On filename clicks, open the modal display (called above, formerly alert()) function showInfo(message) { infotext.innerHTML = message; infodisplay.style.display = 'block'; // show/popup display } </script> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!-- NOTE POPUP: custom modal dialog, via div overlay+opacity [2.3] --> <!--~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~--> <!--''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' CSS: largely reused from Info popup above - see it for more intro docs. Dialog-box colors are from bg/fg user settings for entire viewer page. Page or text scrolls may be needed for mobile and/or very large fonts. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''--> <style> /* Modal-dialog display: full page, background */ /* Reuse .InfoDisplay for overlay: identical CSS */ /* Modal-dialog box: text + button content on page */ .NoteBox { /* margin=outside, padding=inside */ padding: 4px 20px 4px 20px; /* top, right, bottom, left */ border: thin solid; /* #888 color nice, unless same as bg */ border-radius: 6px; /* rounded borders (for the kids) */ position: absolute; /* anchor to page bottom (for up-swipe) */ bottom: 8px; left: 15%; /* box is what's left, large=good for mobile */ right: 15%; /* e.g., 15 percent leaves 70 percent for box */ max-height: 100%; /* overflow works on sized block elms like <p> */ overflow: auto; /* vscroll notes too wordy for the page */ /* [2.3] colorize dialog per configs: border doesn't work in a mixin class here */ color: wheat; /* foreground: text (not OK) */ background-color: black; /* background: full widget (not OK) */ border-color: white; /* dialog (and OK button) border */ } /* Punt: no longer centered on page -> [max-width: 600px;] */ /* Punt: not needed and fails on IE<9 -> [transform: translate(-25%, 0);] */ /* Punt: not centered -> [margin: 8px 20% 8px 20%; min-width: 53.5%;] */ /* Modal-dialog message: collapsed <p> text in box */ .NoteText { /* != InfoText: hscroll irrelevant here for <p>, and vscroll via NoteBox div */ /* [2.3] inherit colors from NoteBox dom parent, not page or 'initial' */ color: inherit; background-color: inherit; } /* Modal-dialog close: OK button in box */ /* Reuse .InfoClose for OK: identical CSS, but inherits bd color from NoteBox here */ </style> <!--''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' HTML: make elements that use CSS and are referenced in JavaScript. Build a div that overlays entire page, to be shown/hidden in demand. This essentially defines a new page, but it looks like a popup box. The <p> doesn't support paragraph breaks: use <br><br> in the insert. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''--> <!-- Modal dialog display: full-page container --> <div id=noteDisplay class=InfoDisplay> <!-- Modal dialog box: text + close content --> <div class=NoteBox> <p id=noteText class=NoteText>Replace me on opens...</p> <button id=noteClose class=InfoClose> OK </button> </div> </div> <!--''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''' JavaScript: must be after HTML to cache elements in globals on load (or: run these from an onload event handler for DOM-element access). showNote() is called on Note tap and from up-swipe handler above. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''--> <script type="text/javascript"> /* ok anywhere in page, type optional */ // Nit: this code is pointless but harmless for note-less galleries // (i.e., !ENABLENOTES); adding a nesting level seems pointless too // Get the full modal display <div> in a global var notedisplay = document.getElementById('noteDisplay'); // Get the text-message element to be replaced var notetext = document.getElementById('noteText'); // Get the OK-button element that closes the display var noteclose = document.getElementById('noteClose'); // The note's file's content from Python, Unicode decoded and JS+HTML escaped var notecontent = 'Now we're in 2015 after the 5th Edition books were wrapped\ up, and a second training restart is afoot. This and the next are\ from a class held at NASA's Kennedy Space Center in Florida, for\ structural engineers.'; // Distinguish if no real note: don't waste users' time; noteBtn may be deleted! if (true && !notecontent) { document.getElementById("noteBtn").style["text-decoration"] = "line-through"; } // On OK click/tap, close entire modal overlay noteclose.onclick = function() { notedisplay.style.display = 'none'; // hide/close display } // On clicks outside the box, close entire modal overlay window.addEventListener('click', function(event) { // // not window.onclick = function(){}: also for Info overlay // if (event.target == notedisplay) { // if tap bg, not box notedisplay.style.display = 'none'; } }); // On Note and up-swipe, open the modal display function showNote() { if (!notecontent) noteText.innerHTML = '(No note)'; else notetext.innerHTML = notecontent; // text from imagename.note file notedisplay.style.display = 'block'; // show/popup display (inline- same) } </script> </body> </html>