Merge branch 'feature-preview'

This commit is contained in:
Christoph Wiechert
2017-05-08 14:23:55 +02:00
4 changed files with 127 additions and 16 deletions

View File

@@ -36,14 +36,27 @@
td(style='width: 60px') td(style='width: 60px')
file-icon(:file='file') file-icon(:file='file')
td td
p div.pull-right
clipboard.pull-right(:value='host + file.url', @change='copied(file, $event)', title='Copy to clipboard', style='margin-left: 5px') i.fa.fa-check.text-success(v-show='file.downloaded')
clipboard.btn.btn-sm.btn-default(:value='host + file.url', @change='copied(file, $event)', title='Copy to clipboard', style='margin: 0 5px')
a a
i.fa.fa-fw.fa-copy i.fa.fa-fw.fa-copy
i.fa.fa-check.text-success.pull-right(v-show='file.downloaded') a.btn.btn-sm.btn-default(title="preview", @click.prevent.stop="preview=file", v-if="getPreviewType(file)")
strong {{ file.metadata.name }} i.fa.fa-fw.fa-eye
small(v-if="Number.isFinite(file.size)") ({{ humanFileSize(file.size) }}) p
strong {{ file.metadata.name }}
small(v-if="Number.isFinite(file.size)", style="margin-left:15px") ({{ humanFileSize(file.size) }})
p {{ file.metadata.comment }} p {{ file.metadata.comment }}
modal(v-if="preview", @close="preview=false", :has-header="true")
h4(slot="header") {{preview.metadata.name}}
div(slot="body")
div(v-if="getPreviewType(preview) === 'image'", style="text-align:center")
img(:src="preview.url", style="max-width: 100%; height:auto")
div(v-if="getPreviewType(preview) === 'text'")
pre {{ previewText }}
p(v-if="getPreviewType(preview) === false", style="text-align:center")
strong.text-danger No preview available
</template> </template>
@@ -55,11 +68,11 @@
import FileIcon from './common/FileIcon.vue'; import FileIcon from './common/FileIcon.vue';
import Clipboard from './common/Clipboard.vue'; import Clipboard from './common/Clipboard.vue';
import Modal from './common/Modal.vue';
export default { export default {
name: 'app', name: 'app',
components: { FileIcon, Clipboard }, components: { FileIcon, Clipboard, Modal },
data () { data () {
return { return {
files: [], files: [],
@@ -69,7 +82,17 @@
password: '', password: '',
content: '', content: '',
error: '', error: '',
host: document.location.protocol + '//' + document.location.host host: document.location.protocol + '//' + document.location.host,
config: {},
preview: false,
previewText: ''
}
},
watch: {
preview: function(preview, old) {
if(this.getPreviewType(preview) !== 'text' || preview === old) return;
this.getPreviewText();
} }
}, },
@@ -80,6 +103,29 @@
}, },
methods: { methods: {
getPreviewType(file) {
if(!file || !file.metadata.type) return false;
if(file.metadata.retention === 'one-time') return false;
// no preview for files size > 2MB
if(file.size > this.config.maxPreviewSize) return false;
if(file.metadata.type.startsWith('image/')) return 'image';
else if(file.metadata.type.startsWith('text/')) return 'text';
return false;
},
getPreviewText() {
this.previewText = '';
const xhr = new XMLHttpRequest();
xhr.open('GET', '//' + document.location.host + this.preview.url);
xhr.onload = () => {
if(xhr.status === 200) {
this.previewText = xhr.responseText
} else {
this.previewText = `${xhr.status} ${xhr.statusText}: ${xhr.responseText}`;
}
};
xhr.send();
},
download(file) { download(file) {
if(file.downloaded && file.metadata.retention === 'one-time') { if(file.downloaded && file.metadata.retention === 'one-time') {
alert('One-Time Download: File is not available anymore.'); alert('One-Time Download: File is not available anymore.');
@@ -150,7 +196,9 @@
xhr.onload = () => { xhr.onload = () => {
if(xhr.status === 200) { if(xhr.status === 200) {
try { try {
this.files = JSON.parse(xhr.responseText).map(f => { let data = JSON.parse(xhr.responseText);
this.config = data.config;
this.files = data.items.map(f => {
if(typeof f !== 'object') { if(typeof f !== 'object') {
this.needsPassword = true; this.needsPassword = true;
return f; return f;

56
app/src/common/Modal.vue Normal file
View File

@@ -0,0 +1,56 @@
<template lang="pug">
.modal.fade.in.background-darken(ref='modal', style="display:block", tabindex='-1', role='dialog', @click.self='close()', @keyup.esc='close()')
.modal-dialog.modal-lg(role='document')
.modal-content
.modal-header(v-if='hasHeader')
button.close(type='button', data-dismiss='modal', aria-label='Close', @click='close()')
span(aria-hidden='true') &times;
h4.modal-title
slot(name='header') Modal
.modal-body
slot(name='body') Body
.modal-footer(v-if='hasFooter')
slot(name='footer')
</template>
<script>
export default {
props: {
hasHeader: {
type: Boolean,
default: false
},
hasFooter: {
type: Boolean,
default: false
}
},
data() {
return {
}
},
mounted() {
this.$nextTick(function() {
this.$refs.modal.focus();
});
},
methods: {
close() {
this.$emit('close');
}
}
}
</script>
<style>
.background-darken {
background: rgba(0, 0, 0, 0.3);
}
.modal.in {
overflow-x: auto;
overflow-y: scroll;
}
</style>

View File

@@ -22,6 +22,8 @@ const config = {
4838400: "8 Weeks" 4838400: "8 Weeks"
}, },
defaultRetention: 604800, defaultRetention: 604800,
// maximum file-size for previews in byte
maxPreviewSize: Math.pow(2,20) * 2, // 2MB
mailTemplate: 'mailto:?subject=File Transfer&body=You can download the files here: %%URL%%', mailTemplate: 'mailto:?subject=File Transfer&body=You can download the files here: %%URL%%',
// see https://github.com/expressjs/morgan // see https://github.com/expressjs/morgan
// set to false to disable logging // set to false to disable logging

View File

@@ -60,14 +60,19 @@ app.get('/:sid', (req, res, next) => {
const sid = req.params.sid.substr(0, req.params.sid.length-5); const sid = req.params.sid.substr(0, req.params.sid.length-5);
if(!db.get(sid)) return res.status(404).end(); if(!db.get(sid)) return res.status(404).end();
res.json(db.get(sid).map(data => { res.json({
const item = Object.assign(data, {url: `/files/${sid}++${data.key}`}); items: db.get(sid).map(data => {
if(item.metadata.password) { const item = Object.assign(data, {url: `/files/${sid}++${data.key}`});
return AES.encrypt(JSON.stringify(data), item.metadata.password).toString(); if(item.metadata.password) {
} else { return AES.encrypt(JSON.stringify(data), item.metadata.password).toString();
return item; } else {
return item;
}
}),
config: {
maxPreviewSize: config.maxPreviewSize
} }
})); });
} else { } else {
if(!db.get(req.params.sid)) return next(); if(!db.get(req.params.sid)) return next();
res.sendFile(path.join(__dirname, '../public/html/download.html')); res.sendFile(path.join(__dirname, '../public/html/download.html'));