aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarvin Borner2019-04-15 00:10:50 +0200
committerMarvin Borner2019-04-15 00:10:50 +0200
commit8067db686d646be9ea09bbd25d87b6ceab073607 (patch)
treea56073c108d7491219736743dd0bf54f0af81a9a
parent4b8dc3a21087c57ef7eb36f199cd68b55132ca77 (diff)
Added image previews
-rw-r--r--src/main/kotlin/App.kt3
-rw-r--r--src/main/resources/css/files.css10
-rw-r--r--src/main/resources/js/files.js60
-rw-r--r--src/main/resources/js/imagePreview.js663
-rw-r--r--src/main/resources/views/files.rocker.html26
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>