diff options
author | Marvin Borner | 2019-04-15 00:10:50 +0200 |
---|---|---|
committer | Marvin Borner | 2019-04-15 00:10:50 +0200 |
commit | 8067db686d646be9ea09bbd25d87b6ceab073607 (patch) | |
tree | a56073c108d7491219736743dd0bf54f0af81a9a | |
parent | 4b8dc3a21087c57ef7eb36f199cd68b55132ca77 (diff) |
Added image previews
-rw-r--r-- | src/main/kotlin/App.kt | 3 | ||||
-rw-r--r-- | src/main/resources/css/files.css | 10 | ||||
-rw-r--r-- | src/main/resources/js/files.js | 60 | ||||
-rw-r--r-- | src/main/resources/js/imagePreview.js | 663 | ||||
-rw-r--r-- | src/main/resources/views/files.rocker.html | 26 |
5 files changed, 739 insertions, 23 deletions
diff --git a/src/main/kotlin/App.kt b/src/main/kotlin/App.kt index 8b1de6d..fb93564 100644 --- a/src/main/kotlin/App.kt +++ b/src/main/kotlin/App.kt @@ -144,7 +144,8 @@ fun crawlFiles(ctx: Context) { arrayOf( if (File(filePath).isDirectory) "$fileName/" else fileName, humanReadableBytes(File(filePath).length()), - SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(File(filePath).lastModified()).toString() + SimpleDateFormat("MM/dd/yyyy HH:mm:ss").format(File(filePath).lastModified()).toString(), + if (File(filePath).isDirectory) "true" else isHumanReadable(filePath).toString() ) ) } diff --git a/src/main/resources/css/files.css b/src/main/resources/css/files.css index 3ca6792..272645f 100644 --- a/src/main/resources/css/files.css +++ b/src/main/resources/css/files.css @@ -14,10 +14,12 @@ table th, table td { text-align: center; } -a.filename { - color: black; - text-decoration: none; - margin-bottom: 5px;; +table tr { + cursor: pointer; +} + +table tr:hover { + background-color: #e8e8e8; } .drop { diff --git a/src/main/resources/js/files.js b/src/main/resources/js/files.js index 3cdf4fd..57d9950 100644 --- a/src/main/resources/js/files.js +++ b/src/main/resources/js/files.js @@ -1,5 +1,7 @@ const drop = document.getElementById("drop"); +setListeners(); + drop.addEventListener('dragover', e => { e.stopPropagation(); e.preventDefault(); @@ -7,7 +9,7 @@ drop.addEventListener('dragover', e => { drop.style.background = "rgba(12,99,250,0.3)"; }); -drop.addEventListener('dragleave', e => +drop.addEventListener('dragleave', () => drop.style.background = "white" ); @@ -18,20 +20,22 @@ drop.addEventListener('drop', e => { drop.style.background = "white"; const files = e.dataTransfer.files; - for (let i = 0; i < files.length; i++) { - let request = new XMLHttpRequest(); - let formData = new FormData(); + for (const file of files) { + const request = new XMLHttpRequest(); + const formData = new FormData(); // TODO: Consider using current date due to updated lastModified state at upload - const date = new Date(files[i].lastModified); - const lastModified = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; + const date = new Date(file.lastModified); const row = document.getElementById("table").insertRow(-1); - row.insertCell(0).innerHTML = `<a class="filename" href="${files[i].name}">${files[i].name}</a>`; - row.insertCell(1).innerHTML = `<a class="filename" href="${files[i].name}">${bytesToSize(files[i].size)}</a>`; - row.insertCell(2).innerHTML = `<a class="filename" href="${files[i].name}">${lastModified}</a>`; + row.setAttribute("data-href", file.name); + row.insertCell(0).innerHTML = file.name; + row.insertCell(1).innerHTML = bytesToSize(file.size); + row.insertCell(2).innerHTML = `${date.getMonth() + 1}/${date.getDate()}/${date.getFullYear()} ${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; + + //setListeners(); - formData.append("file", files[i]); + formData.append("file", file); request.open("POST", "/upload/" + path); request.send(formData); } @@ -43,3 +47,39 @@ drop.addEventListener('drop', e => { return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[i]; } }); + +function setListeners() { + // binary files + document.querySelectorAll("[data-path]").forEach(element => { + const images = ["jpg", "jpeg", "png"]; // TODO: Add other file types + + const filename = element.getAttribute("data-path"); + const extension = /(?:\.([^.]+))?$/.exec(filename)[1].toLowerCase(); + + if (images.indexOf(extension) > -1) { + element.setAttribute("data-bp", filename); + element.setAttribute("data-image", ""); + } + + element.addEventListener("click", () => { + if (images.indexOf(extension) === -1) window.location = filename; + }); + }); + + // images + document.querySelectorAll("[data-image]").forEach(element => { + element.addEventListener("click", image => { + BigPicture({ + el: image.currentTarget, + gallery: document.querySelectorAll("[data-image]") + }) + }); + }); + + // normal files + document.querySelectorAll("[data-href]").forEach(element => { + element.addEventListener("click", () => { + window.location = element.getAttribute("data-href"); + }) + }); +} diff --git a/src/main/resources/js/imagePreview.js b/src/main/resources/js/imagePreview.js new file mode 100644 index 0000000..0574e73 --- /dev/null +++ b/src/main/resources/js/imagePreview.js @@ -0,0 +1,663 @@ +// BigPicture.js | license MIT | henrygd.me/bigpicture +(function () { + const // assign window object to variable + global = window; + let // trigger element used to open popup + el, + // set to true after first interaction + initialized, + // container element holding html needed for script + container, + // currently active display element (image, video, youtube / vimeo iframe container) + displayElement, + // popup image element + displayImage, + // popup video element + displayVideo, + // popup audio element + displayAudio, + // container element to hold youtube / vimeo iframe + iframeContainer, + // iframe to hold youtube / vimeo player + iframeSiteVid, + // store requested image source + imgSrc, + // button that closes the container + closeButton, + // youtube / vimeo video id + siteVidID, + // keeps track of loading icon display state + isLoading, + // timeout to check video status while loading + checkMediaTimeout, + // loading icon element + loadingIcon, + // caption element + caption, + // caption content element + captionText, + // store caption content + captionContent, + // hide caption button element + captionHideButton, + // open state for container element + isOpen, + // gallery open state + galleryOpen, + // used during close animation to avoid triggering timeout twice + isClosing; + const // array of prev viewed image urls to check if cached before showing loading icon + imgCache = []; + let // store whether image requested is remote or local + remoteImage, + // store animation opening callbacks + animationStart, + animationEnd, + // gallery left / right icons + rightArrowBtn, + leftArrowBtn, + // position of gallery + galleryPosition, + // hold active gallery els / image src + galleryEls, + // counter element + galleryCounter, + // store images in gallery that are being loaded + preloadedImages = {}, + // whether device supports touch events + supportsTouch, + // options object + opts; + const // Save bytes in the minified version + doc = document, + appendEl = 'appendChild', + createEl = 'createElement', + removeEl = 'removeChild', + htmlInner = 'innerHTML', + pointerEventsAuto = 'pointer-events:auto', + cHeight = 'clientHeight', + cWidth = 'clientWidth', + listenFor = 'addEventListener', + timeout = global.setTimeout, + clearTimeout = global.clearTimeout; + + global.BigPicture = function (options) { + // initialize called on initial open to create elements / style / event handlers + initialized || initialize(); + + // clear currently loading stuff + if (isLoading) { + clearTimeout(checkMediaTimeout); + removeContainer() + } + + opts = options; + + // store video id if youtube / vimeo video is requested + siteVidID = options.ytSrc || options.vimeoSrc; + + // store optional callbacks + animationStart = options.animationStart; + animationEnd = options.animationEnd; + + // set trigger element + el = options.el; + + // wipe existing remoteImage state + remoteImage = false; + + // set caption if provided + captionContent = el.getAttribute('data-caption'); + + if (options.gallery) { + makeGallery(options.gallery) + } else if (siteVidID || options.iframeSrc) { + // if vimeo, youtube, or iframe video + toggleLoadingIcon(true); + displayElement = iframeContainer; + createIframe(); + } else if (options.imgSrc) { + // if remote image + remoteImage = true; + imgSrc = options.imgSrc; + !~imgCache.indexOf(imgSrc) && toggleLoadingIcon(true); + displayElement = displayImage; + displayElement.src = imgSrc + } else if (options.audio) { + // if direct video link + toggleLoadingIcon(true); + displayElement = displayAudio; + displayElement.src = options.audio; + checkMedia('audio file') + } else if (options.vidSrc) { + // if direct video link + toggleLoadingIcon(true); + makeVidSrc(options.vidSrc); + checkMedia('video') + } else { + // local image / background image already loaded on page + displayElement = displayImage; + // get img source or element background image + displayElement.src = + el.tagName === 'IMG' + ? el.src + : global + .getComputedStyle(el) + .backgroundImage.replace(/^url|[(|)|'|"]/g, '') + } + + // add container to page + container[appendEl](displayElement); + doc.body[appendEl](container) + }; + + // create all needed methods / store dom elements on first use + function initialize() { + let startX; + + // return close button elements + function createCloseButton(className) { + const el = doc[createEl]('button'); + el.className = className; + el[htmlInner] = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><path d="M28 24L47 5a3 3 0 1 0-4-4L24 20 5 1a3 3 0 1 0-4 4l19 19L1 43a3 3 0 1 0 4 4l19-19 19 19a3 3 0 0 0 4 0v-4L28 24z"/></svg>'; + return el + } + + function createArrowSymbol(direction, style) { + const el = doc[createEl]('button'); + el.className = 'bp-lr'; + el[htmlInner] = + '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 129 129" height="70" fill="#fff"><path d="M88.6 121.3c.8.8 1.8 1.2 2.9 1.2s2.1-.4 2.9-1.2a4.1 4.1 0 0 0 0-5.8l-51-51 51-51a4.1 4.1 0 0 0-5.8-5.8l-54 53.9a4.1 4.1 0 0 0 0 5.8l54 53.9z"/></svg>'; + changeCSS(el, style); + el.onclick = function (e) { + e.stopPropagation(); + updateGallery(direction) + }; + return el + } + + // add style - if you want to tweak, run through beautifier + const style = doc[createEl]('STYLE'); + style[htmlInner] = + '#bp_caption,#bp_container{bottom:0;left:0;right:0;position:fixed;opacity:0}#bp_container>*,#bp_loader{position:absolute;right:0;z-index:10}#bp_container,#bp_caption,#bp_container svg{pointer-events:none}#bp_container{top:0;z-index:9999;background:rgba(0,0,0,.7);opacity:0;transition:opacity .35s}#bp_loader{top:0;left:0;bottom:0;display:flex;margin:0;cursor:wait;z-index:9;background:0 0}#bp_loader svg{width:50%;max-width:300px;max-height:50%;margin:auto;animation:bpturn 1s infinite linear}#bp_aud,#bp_container img,#bp_sv,#bp_vid{user-select:none;max-height:96%;max-width:96%;top:0;bottom:0;left:0;margin:auto;box-shadow:0 0 3em rgba(0,0,0,.4);z-index:-1}#bp_sv{height:0;padding-bottom:54%;background-color:#000;width:96%}#bp_caption{font-size:.9em;padding:1.3em;background:rgba(15,15,15,.94);color:#fff;text-align:center;transition:opacity .3s}#bp_aud{width:650px;top:calc(50% - 20px);bottom:auto;box-shadow:none}#bp_count{left:0;right:auto;padding:14px;color:rgba(255,255,255,.7);font-size:22px;cursor:default}#bp_container button{position:absolute;border:0;outline:0;background:0 0;cursor:pointer;transition:all .1s}#bp_container>.bp-x{height:41px;width:41px;border-radius:100%;top:8px;right:14px;opacity:.8}#bp_container>.bp-x:focus,#bp_container>.bp-x:hover{background:rgba(255,255,255,.2)}.bp-x svg,.bp-xc svg{height:21px;width:20px;fill:#fff;vertical-align:top;}.bp-xc svg{width:16px}#bp_container .bp-xc{left:2%;bottom:100%;padding:9px 20px 7px;background:#d04444;border-radius:2px 2px 0 0;opacity:.85}#bp_container .bp-xc:focus,#bp_container .bp-xc:hover{opacity:1}.bp-lr{top:50%;top:calc(50% - 130px);padding:99px 0;width:6%;background:0 0;border:0;opacity:.4;transition:opacity .1s}.bp-lr:focus,.bp-lr:hover{opacity:.8}@keyframes bpf{50%{transform:translatex(15px)}100%{transform:none}}@keyframes bpl{50%{transform:translatex(-15px)}100%{transform:none}}@keyframes bpfl{0%{opacity:0;transform:translatex(70px)}100%{opacity:1;transform:none}}@keyframes bpfr{0%{opacity:0;transform:translatex(-70px)}100%{opacity:1;transform:none}}@keyframes bpfol{0%{opacity:1;transform:none}100%{opacity:0;transform:translatex(-70px)}}@keyframes bpfor{0%{opacity:1;transform:none}100%{opacity:0;transform:translatex(70px)}}@keyframes bpturn{0%{transform:none}100%{transform:rotate(360deg)}}@media (max-width:600px){.bp-lr{font-size:15vw}}@media (min-aspect-ratio:9/5){#bp_sv{height:98%;width:170.6vh;padding:0}}'; + doc.head[appendEl](style); + + // create container element + container = doc[createEl]('DIV'); + container.id = 'bp_container'; + container.onclick = close; + closeButton = createCloseButton('bp-x'); + container[appendEl](closeButton); + // gallery swipe listeners + if ('ontouchstart' in global) { + supportsTouch = true; + container.ontouchstart = function (e) { + startX = e.changedTouches[0].pageX + }; + container.ontouchmove = function (e) { + e.preventDefault() + }; + container.ontouchend = function (e) { + if (!galleryOpen) { + return + } + const touchobj = e.changedTouches[0]; + const distX = touchobj.pageX - startX; + // swipe right + distX < -30 && updateGallery(1); + // swipe left + distX > 30 && updateGallery(-1) + } + } + + // create display image element + displayImage = doc[createEl]('IMG'); + + // create display video element + displayVideo = doc[createEl]('VIDEO'); + displayVideo.id = 'bp_vid'; + displayVideo.setAttribute('playsinline', true); + displayVideo.controls = true; + displayVideo.loop = true; + + // create audio element + displayAudio = doc[createEl]("audio"); + displayAudio.id = "bp_aud"; + displayAudio.controls = true; + displayAudio.loop = true; + + // create gallery counter + galleryCounter = doc[createEl]('span'); + galleryCounter.id = 'bp_count'; + + // create caption elements + caption = doc[createEl]('DIV'); + caption.id = 'bp_caption'; + captionHideButton = createCloseButton('bp-xc'); + captionHideButton.onclick = toggleCaption.bind(null, false); + caption[appendEl](captionHideButton); + captionText = doc[createEl]('SPAN'); + caption[appendEl](captionText); + container[appendEl](caption); + + // left / right arrow icons + rightArrowBtn = createArrowSymbol(1, 'transform:scalex(-1)'); + leftArrowBtn = createArrowSymbol(-1, 'left:0;right:auto'); + + // create loading icon element + loadingIcon = doc[createEl]('DIV'); + loadingIcon.id = 'bp_loader'; + loadingIcon[htmlInner] = + '<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 32 32" fill="#fff" opacity=".8"><path d="M16 0a16 16 0 0 0 0 32 16 16 0 0 0 0-32m0 4a12 12 0 0 1 0 24 12 12 0 0 1 0-24" fill="#000" opacity=".5"/><path d="M16 0a16 16 0 0 1 16 16h-4A12 12 0 0 0 16 4z"/></svg>'; + // create youtube / vimeo container + iframeContainer = doc[createEl]('DIV'); + iframeContainer.id = 'bp_sv'; + + // create iframe to hold youtube / vimeo player + iframeSiteVid = doc[createEl]('IFRAME'); + iframeSiteVid.setAttribute('allowfullscreen', true); + iframeSiteVid.allow = 'autoplay; fullscreen'; + iframeSiteVid.onload = open; + changeCSS(iframeSiteVid, 'border:0;position:absolute;height:100%;width:100%;left:0;top:0'); + iframeContainer[appendEl](iframeSiteVid); + + // display image bindings for image load and error + displayImage.onload = open; + displayImage.onerror = open.bind(null, 'image'); + + // adjust loader position on window resize + global[listenFor]('resize', function () { + galleryOpen || (isLoading && toggleLoadingIcon(true)) + }); + + // close container on escape key press and arrow buttons for gallery + doc[listenFor]('keyup', function (e) { + const key = e.keyCode; + key === 27 && isOpen && close(container); + if (galleryOpen) { + key === 39 && updateGallery(1); + key === 37 && updateGallery(-1); + key === 38 && updateGallery(10); + key === 40 && updateGallery(-10) + } + }); + // prevent scrolling with arrow keys if gallery open + doc[listenFor]('keydown', function (e) { + const usedKeys = [37, 38, 39, 40]; + if (galleryOpen && ~usedKeys.indexOf(e.keyCode)) { + e.preventDefault() + } + }); + + // trap focus within container while open + doc[listenFor]( + 'focus', + function (e) { + if (isOpen && !container.contains(e.target)) { + e.stopPropagation(); + closeButton.focus() + } + }, + true + ); + + // all done + initialized = true + } + + // return transform style to make full size display el match trigger el size + function getRect() { + const rect = el.getBoundingClientRect(); + const leftOffset = rect.left - (container[cWidth] - rect.width) / 2; + const centerTop = rect.top - (container[cHeight] - rect.height) / 2; + const scaleWidth = el[cWidth] / displayElement[cWidth]; + const scaleHeight = el[cHeight] / displayElement[cHeight]; + return 'transform:translate3D(' + + leftOffset + + 'px, ' + + centerTop + + 'px, 0) scale3D(' + + scaleWidth + + ', ' + + scaleHeight + + ', 0)' + } + + function makeVidSrc(source) { + if (Array.isArray(source)) { + displayElement = displayVideo.cloneNode(); + source.forEach(function (src) { + const source = doc[createEl]('SOURCE'); + source.src = src; + source.type = 'video/' + src.match(/.(\w+)$/)[1]; + displayElement[appendEl](source) + }) + } else { + displayElement = displayVideo; + displayElement.src = source + } + } + + function makeGallery(gallery) { + if (Array.isArray(gallery)) { + // is array of images + galleryPosition = 0; + galleryEls = gallery; + captionContent = gallery[0].caption + } else { + // is element selector or nodelist + galleryEls = [].slice.call(typeof gallery === 'string' ? doc.querySelectorAll(gallery + ' [data-bp]') : gallery); + // find initial gallery position + const elIndex = galleryEls.indexOf(el); + galleryPosition = elIndex !== -1 ? elIndex : 0; + // make gallery object w/ els / src / caption + galleryEls = galleryEls.map(function (el) { + return { + el: el, + src: el.getAttribute('data-bp'), + caption: el.getAttribute('data-caption') + } + }) + } + // show loading icon if needed + remoteImage = true; + // set initial src to imgSrc so it will be cached in open func + imgSrc = galleryEls[galleryPosition].src; + !~imgCache.indexOf(imgSrc) && toggleLoadingIcon(true); + if (galleryEls.length > 1) { + // if length is greater than one, add gallery stuff + container[appendEl](galleryCounter); + galleryCounter[htmlInner] = galleryPosition + 1 + '/' + galleryEls.length; + if (!supportsTouch) { + // add arrows if device doesn't support touch + container[appendEl](rightArrowBtn); + container[appendEl](leftArrowBtn) + } + } else { + // gallery is one, just show without clutter + galleryEls = false + } + displayElement = displayImage; + // set initial image src + displayElement.src = imgSrc + } + + function updateGallery(movement) { + const galleryLength = galleryEls.length - 1; + let isEnd; + + // only allow one change at a time + if (isLoading) { + return + } + + // return if requesting out of range image + if (movement > 0) { + if (galleryPosition === galleryLength) { + isEnd = true + } + } else if (galleryPosition === 0) { + isEnd = true + } + if (isEnd) { + // if beginning or end of gallery, run end animation + changeCSS(displayImage, ''); + timeout(changeCSS, 9, displayImage, 'animation:' + (movement > 0 ? 'bpl' : 'bpf') + ' .3s;transition:transform .35s'); + return + } + + // normalize position + galleryPosition = Math.max( + 0, + Math.min(galleryPosition + movement, galleryLength) + ) + + // load images before and after for quicker scrolling through pictures + ;[galleryPosition - 1, galleryPosition, galleryPosition + 1].forEach( + function (position) { + // normalize position + position = Math.max(0, Math.min(position, galleryLength)); + // cancel if image has already been preloaded + if (preloadedImages[position]) return; + const src = galleryEls[position].src; + // create image for preloadedImages + const img = doc[createEl]('IMG'); + img[listenFor]('load', addToImgCache.bind(null, src)); + img.src = src; + preloadedImages[position] = img + } + ); + // if image is loaded, show it + if (preloadedImages[galleryPosition].complete) { + return changeGalleryImage(movement) + } + // if not, show loading icon and change when loaded + isLoading = true; + changeCSS(loadingIcon, 'opacity:.4;'); + container[appendEl](loadingIcon); + preloadedImages[galleryPosition].onload = function () { + galleryOpen && changeGalleryImage(movement) + }; + // if error, store error object in el array + preloadedImages[galleryPosition].onerror = function () { + galleryEls[galleryPosition] = { + error: 'Error loading image' + }; + galleryOpen && changeGalleryImage(movement) + } + } + + function changeGalleryImage(movement) { + if (isLoading) { + container[removeEl](loadingIcon); + isLoading = false + } + const activeEl = galleryEls[galleryPosition]; + if (activeEl.error) { + // show alert if error + alert(activeEl.error) + } else { + // add new image, animate images in and out w/ css animation + const oldimg = container.querySelector('img:last-of-type'); + displayImage = displayElement = preloadedImages[galleryPosition]; + changeCSS(displayImage, 'animation:' + (movement > 0 ? 'bpfl' : 'bpfr') + ' .35s;transition:transform .35s'); + changeCSS(oldimg, 'animation:' + (movement > 0 ? 'bpfol' : 'bpfor') + ' .35s both'); + container[appendEl](displayImage); + // update el for closing animation + if (activeEl.el) { + el = activeEl.el + } + } + // update counter + galleryCounter[htmlInner] = galleryPosition + 1 + '/' + galleryEls.length; + // show / hide caption + toggleCaption(galleryEls[galleryPosition].caption) + } + + // create video iframe + function createIframe() { + let url; + const prefix = 'https://'; + const suffix = 'autoplay=1'; + + // create appropriate url + if (opts.ytSrc) { + url = prefix + 'www.youtube.com/embed/' + siteVidID + '?html5=1&rel=0&playsinline=1&' + suffix; + } else if (opts.vimeoSrc) { + url = prefix + 'player.vimeo.com/video/' + siteVidID + '?' + suffix; + } else if (opts.iframeSrc) { + url = opts.iframeSrc; + } + + // set iframe src to url + iframeSiteVid.src = url; + } + + // timeout to check video status while loading + function checkMedia(errMsg) { + if (~[1, 4].indexOf(displayElement.readyState)) { + open(); + // short timeout to to make sure controls show in safari 11 + timeout(function () { + displayElement.play() + }, 99) + } else if (displayElement.error) open(errMsg); + else checkMediaTimeout = timeout(checkMedia, 35, errMsg) + } + + // hide / show loading icon + function toggleLoadingIcon(bool) { + // don't show loading icon if noLoader is specified + if (opts.noLoader) return; + // bool is true if we want to show icon, false if we want to remove + // change style to match trigger element dimensions if we want to show + bool && + changeCSS( + loadingIcon, + 'top:' + + el.offsetTop + + 'px;left:' + + el.offsetLeft + + 'px;height:' + + el[cHeight] + + 'px;width:' + + el[cWidth] + + 'px' + ); + // add or remove loader from DOM + el.parentElement[bool ? appendEl : removeEl](loadingIcon); + isLoading = bool + } + + // hide & show caption + function toggleCaption(captionContent) { + if (captionContent) { + captionText[htmlInner] = captionContent + } + changeCSS( + caption, + 'opacity:' + (captionContent ? '1;' + pointerEventsAuto : '0') + ) + } + + function addToImgCache(url) { + !~imgCache.indexOf(url) && imgCache.push(url) + } + + // animate open of image / video; display caption if needed + function open(err) { + // hide loading spinner + isLoading && toggleLoadingIcon(); + + // execute animationStart callback + animationStart && animationStart(); + + // check if we have an error string instead of normal event + if (typeof err === 'string') { + removeContainer(); + return opts.onError ? opts.onError() : alert('Error: The requested ' + err + ' could not be loaded.') + } + + // if remote image is loaded, add url to imgCache array + remoteImage && addToImgCache(imgSrc); + + // transform displayEl to match trigger el + changeCSS(displayElement, getRect()); + + // fade in container + changeCSS(container, 'opacity:1;' + pointerEventsAuto); + + // set animationEnd callback to run after animation ends (cleared if container closed) + animationEnd = timeout(animationEnd, 410); + + isOpen = true; + + galleryOpen = !!galleryEls; + + // enlarge displayEl, fade in caption if hasCaption + timeout(function () { + changeCSS(displayElement, 'transition:transform .35s;transform:none'); + captionContent && timeout(toggleCaption, 250, captionContent) + }, 60) + } + + // close active display element + function close(e) { + const target = e.target; + const clickEls = [ + caption, + captionHideButton, + displayVideo, + displayAudio, + captionText, + leftArrowBtn, + rightArrowBtn, + loadingIcon + ]; + + // blur to hide close button focus style + target && target.blur(); + + // don't close if one of the clickEls was clicked or container is already closing + if (isClosing || ~clickEls.indexOf(target)) { + return + } + + // animate closing + displayElement.style.cssText += getRect(); + changeCSS(container, pointerEventsAuto); + + // timeout to remove els from dom; use variable to avoid calling more than once + timeout(removeContainer, 350); + + // clear animationEnd timeout + clearTimeout(animationEnd); + + isOpen = false; + isClosing = true + } + + // remove container / display element from the DOM + function removeContainer() { + // remove container from DOM & clear inline style + doc.body[removeEl](container); + container[removeEl](displayElement); + changeCSS(container, '') + + // clear src of displayElement (or iframe if display el is iframe container) + ;(displayElement === iframeContainer + ? iframeSiteVid + : displayElement + ).removeAttribute('src'); + + // remove caption + toggleCaption(false); + + if (galleryOpen) { + // remove all gallery stuff + const images = container.querySelectorAll('img'); + for (let i = 0; i < images.length; i++) { + container[removeEl](images[i]) + } + isLoading && container[removeEl](loadingIcon); + container[removeEl](galleryCounter); + galleryOpen = galleryEls = false; + preloadedImages = {}; + supportsTouch || container[removeEl](rightArrowBtn); + supportsTouch || container[removeEl](leftArrowBtn); + // in case displayimage changed, we need to update event listeners + displayImage.onload = open; + displayImage.onerror = open.bind(null, 'image') + } + + // run close callback + opts.onClose && opts.onClose(); + + isClosing = isLoading = false + } + + // style helper functions + function changeCSS(element, newStyle) { + element.style.cssText = newStyle + } +})(); diff --git a/src/main/resources/views/files.rocker.html b/src/main/resources/views/files.rocker.html index 2c4a7ff..96676ed 100644 --- a/src/main/resources/views/files.rocker.html +++ b/src/main/resources/views/files.rocker.html @@ -7,6 +7,7 @@ @js => { <script>const path = "@path";</script> +<script src="/js/imagePreview.js"></script> <script src="/js/files.js"></script> } @@ -23,17 +24,26 @@ </thead> <tbody> - <tr> - <td><a class="filename" href="../">../</a></td> - <td><a class="filename" href="../"></a></td> - <td><a class="filename" href="../"></a></td> + <tr data-href="../"> + <td>../</td> + <td></td> + <td></td> </tr> + @for (String[] fileArray : files) { - <tr> - <td><a class="filename" href="@fileArray[0]">@fileArray[0]</a></td> - <td><a class="filename" href="@fileArray[0]">@fileArray[1]</a></td> - <td><a class="filename" href="@fileArray[0]">@fileArray[2]</a></td> + @if (fileArray[3] == "true") { + <tr data-href="@fileArray[0]"> + <td>@fileArray[0]</td> + <td>@fileArray[1]</td> + <td>@fileArray[2]</td> </tr> + } else { + <tr data-path="@fileArray[0]"> + <td>@fileArray[0]</td> + <td>@fileArray[1]</td> + <td>@fileArray[2]</td> + </tr> + } } </tbody> </table> |