Browse Source

Better attachment viewer

Vid Smole 1 year ago
parent
commit
7a98445370

+ 59 - 13
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;

+ 14 - 4
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}}")

+ 131 - 7
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 <source> 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 <source> 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-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'(event) {
-    $("#viewer-overlay").addClass("hidden");
+  '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({