diff --git a/client/components/cards/attachments.css b/client/components/cards/attachments.css index d2c392eda..267627d50 100644 --- a/client/components/cards/attachments.css +++ b/client/components/cards/attachments.css @@ -1,8 +1,3 @@ -.slide { - /* swipebox slide background gradient of black to blue, so that back SVG images are visible */ - background: rgb(2,0,36); - background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 14%, rgba(0,212,255,1) 100%); -} .attachment-upload { text-align: center; font-weight: bold; @@ -83,29 +78,56 @@ top: 48px; /* height of the navbar */ left: 0; z-index: 9999 !important; - background: rgba(13,13,13,0.9); + background: rgba(13,13,13,0.95); } #viewer-container { - position: relative; - width: 100%; + display: flex; + flex-direction: row; + justify-content: space-between; height: 100%; } +#viewer-top-bar { + display: flex; + flex-direction: row; + justify-content: space-between; + width: 100%; + padding: 16px; +} +#attachment-name { + color: white; + font-size: 1.5em; + max-width: calc(100% - 50px); /* Make sure the name does not overlap the close button */ +} #viewer-close { color:white; cursor: pointer; font-size: 4em; top: 0; - right: 16px; + right: 8px; position: absolute; - padding: 20 20; } -#viewer-container { - text-align: center; +.attachment-arrow { + font-size: 4em; + color:white; + cursor: pointer; + align-self: center; + margin: 0 20px; +} +#image-viewer { + background: + repeating-conic-gradient(#808080 0% 25%, transparent 0% 50%) + 50% / 20px 20px; /* Checkerboard background for transparent images */ + max-width: 100%; } #pdf-viewer { width: 40vw; height: 100vh; } +#txt-viewer{ + background-color: white; + width: 40vw; + height: 100vh; +} .pdf-preview-error { margin-top: 20vh; display: block; @@ -120,8 +142,32 @@ } @media screen and (max-width: 800px) { + #viewer-container { + display: block; + } + .attachment-arrow{ + position: absolute; + bottom: 2.2em; + font-size: 1.6em; + padding: 16px; + } + #prev-attachment{ + left: 0; + } + #next-attachment{ + right: 0; + } #pdf-viewer { - width: 100vh; + width: 100%; + height: calc(100vh - 155px); /* Full height - height of top and bottom bars */ + } + #txt-viewer { + width: 100%; + height: calc(100vh - 155px); /* Full height - height of top and bottom bars */ + } + #audio-viewer { + margin-top: 20%; + width: 100%; } .attachment-thumbnail-container { width: 100px; diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade index e70b7295e..01a2a0bb7 100644 --- a/client/components/cards/attachments.jade +++ b/client/components/cards/attachments.jade @@ -32,11 +32,21 @@ template(name="attachmentDeletePopup") template(name="attachmentViewer") #viewer-overlay.hidden - #viewer-container - object#pdf-viewer(type="application/pdf") - span.pdf-preview-error {{_ 'preview-pdf-not-supported' }} + #viewer-top-bar + span#attachment-name a#viewer-close.fa.fa-times-thin + #viewer-container + i.fa.fa-chevron-left.attachment-arrow#prev-attachment + #viewer-content + img#image-viewer.hidden + video#video-viewer.hidden(controls="true") + audio#audio-viewer.hidden(controls="true") + object#pdf-viewer.hidden(type="application/pdf") + span.pdf-preview-error {{_ 'preview-pdf-not-supported' }} + object#txt-viewer.hidden(type="text/plain") + i.fa.fa-chevron-right.attachment-arrow#next-attachment + template(name="attachmentGallery") .attachment-gallery @@ -47,7 +57,7 @@ template(name="attachmentGallery") each attachments .attachment-item - .attachment-thumbnail-container(href="{{link}}" class="{{#if isImage}}swipebox{{/if}} {{#if $eq extension 'pdf'}}pdf{{/if}}") + .attachment-thumbnail-container.open-preview(data-attachment-id="{{_id}}" data-card-id="{{ meta.cardId }}") if link if(isImage) img.attachment-thumbnail(src="{{link}}" title="{{sanitize name}}") diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js index 32efa2203..f4918e0f7 100644 --- a/client/components/cards/attachments.js +++ b/client/components/cards/attachments.js @@ -4,11 +4,19 @@ import DOMPurify from 'dompurify'; const filesize = require('filesize'); const prettyMilliseconds = require('pretty-ms'); +// We store current card ID and the ID of currently opened attachment in a +// global var. This is used so that we know what's the next attachment to open +// when the user clicks on the prev/next button in the attachment viewer. +let cardId = null; +let openAttachmentId = null; + Template.attachmentGallery.events({ - 'click .pdf'(event) { - let link = $(event.currentTarget).attr("href"); - $("#pdf-viewer").attr("data", link); - $("#viewer-overlay").removeClass("hidden"); + 'click .open-preview'(event) { + + openAttachmentId = $(event.currentTarget).attr("data-attachment-id"); + cardId = $(event.currentTarget).attr("data-card-id"); + + openAttachmentViewer(openAttachmentId); }, 'click .js-add-attachment': Popup.open('cardAttachments'), // If we let this event bubble, FlowRouter will handle it and empty the page @@ -24,13 +32,129 @@ Template.attachmentGallery.events({ }), }); +function getNextAttachmentId(currentAttachmentId) { + const attachments = Attachments.find({'meta.cardId': cardId}).get(); + + let i = 0; + for (; i < attachments.length; i++) { + if (attachments[i]._id === currentAttachmentId) { + break; + } + } + return attachments[(i + 1 + attachments.length) % attachments.length]._id; +} + +function getPrevAttachmentId(currentAttachmentId) { + const attachments = Attachments.find({'meta.cardId': cardId}).get(); + + let i = 0; + for (; i < attachments.length; i++) { + if (attachments[i]._id === currentAttachmentId) { + break; + } + } + return attachments[(i - 1 + attachments.length) % attachments.length]._id; +} + +function openAttachmentViewer(attachmentId){ + + const attachment = Attachments.findOne({_id: attachmentId}); + + $("#attachment-name").text(attachment.name); + + // IMPORTANT: if you ever add a new viewer, make sure you also implement + // cleanup in the closeAttachmentViewer() function + switch(true){ + case (attachment.isImage): + $("#image-viewer").attr("src", attachment.link()); + $("#image-viewer").removeClass("hidden"); + break; + case (attachment.isPDF): + $("#pdf-viewer").attr("data", attachment.link()); + $("#pdf-viewer").removeClass("hidden"); + break; + case (attachment.isVideo): + // We have to create a new DOM element and append it to the video + // element, otherwise the video won't load + let videoSource = document.createElement('source'); + videoSource.setAttribute('src', attachment.link()); + $("#video-viewer").append(videoSource); + + $("#video-viewer").removeClass("hidden"); + break; + case (attachment.isAudio): + // We have to create a new DOM element and append it to the audio + // element, otherwise the audio won't load + let audioSource = document.createElement('source'); + audioSource.setAttribute('src', attachment.link()); + $("#audio-viewer").append(audioSource); + + $("#audio-viewer").removeClass("hidden"); + break; + case (attachment.isText): + case (attachment.isJSON): + $("#txt-viewer").attr("data", attachment.link()); + $("#txt-viewer").removeClass("hidden"); + break; + } + + $("#viewer-overlay").removeClass("hidden"); +} + +function closeAttachmentViewer() { + $("#viewer-overlay").addClass("hidden"); + + // We need to reset the viewers to avoid showing previous attachments + $("#image-viewer").attr("src", ""); + $("#image-viewer").addClass("hidden"); + + $("#pdf-viewer").attr("data", ""); + $("#pdf-viewer").addClass("hidden"); + + $("#txt-viewer").attr("data", ""); + $("#txt-viewer").addClass("hidden"); + + $("#video-viewer").get(0).pause(); // Stop playback + $("#video-viewer").get(0).currentTime = 0; + $("#video-viewer").empty(); + $("#video-viewer").addClass("hidden"); + + $("#audio-viewer").get(0).pause(); // Stop playback + $("#audio-viewer").get(0).currentTime = 0; + $("#audio-viewer").empty(); + $("#audio-viewer").addClass("hidden"); +} + Template.attachmentViewer.events({ 'click #viewer-container'(event) { - $("#viewer-overlay").addClass("hidden"); + + // Make sure the click was on #viewer-container and not on any of its children + if(event.target !== event.currentTarget) return; + + closeAttachmentViewer(); }, - 'click #viewer-close'(event) { - $("#viewer-overlay").addClass("hidden"); + 'click #viewer-content'(event) { + + // Make sure the click was on #viewer-content and not on any of its children + if(event.target !== event.currentTarget) return; + + closeAttachmentViewer(); }, + 'click #viewer-close'() { + closeAttachmentViewer(); + }, + 'click #next-attachment'(event) { + closeAttachmentViewer() + const id = getNextAttachmentId(openAttachmentId); + openAttachmentId = id; + openAttachmentViewer(id); + }, + 'click #prev-attachment'(event) { + closeAttachmentViewer() + const id = getPrevAttachmentId(openAttachmentId); + openAttachmentId = id; + openAttachmentViewer(id); + } }); Template.attachmentGallery.helpers({