Better attachment viewer

This commit is contained in:
Vid Smole 2023-07-06 20:35:49 +02:00
parent 7ef3bba9f7
commit 7a98445370
No known key found for this signature in database
GPG key ID: 85348A78371C84EB
3 changed files with 204 additions and 24 deletions

View file

@ -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;

View file

@ -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}}")

View file

@ -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-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({