Files
transfer/app/src/Download.vue
2017-11-21 13:25:01 +01:00

212 lines
6.9 KiB
Vue

<template lang="pug">
.download-app
a.btn.btn-sm.btn-info.btn-new-session(@click='newSession()', title='New Upload')
icon.fa-fw(name="cloud-upload")
span.hidden-xs new upload
.alert.alert-danger(v-show="error")
strong
icon.fa-fw(name="exclamation-triangle")
| {{ error }}
.well(v-if='needsPassword')
h3 Password
.form-group
input.form-control(type='password', v-model='password')
p.text-danger(v-show='passwordWrong')
strong Access denied!
|
button.btn.btn-primary(:disabled='password.length<1', @click='decrypt()')
icon.fa-fw(name="key")
| decrypt
.panel.panel-primary(v-if='!needsPassword')
.panel-heading
strong Files
div.pull-right.btn-group.btn-download-archive(v-if="downloadsAvailable")
a.btn.btn-sm.btn-default(@click="downloadAll('zip')", title="Archive download is not resumeable!")
icon.fa-fw(name="download")
| zip
a.btn.btn-sm.btn-default(@click="downloadAll('tar.gz')", title="Archive download is not resumeable!")
icon.fa-fw(name="download")
| tar.gz
.panel-body
table.table.table-hover.table-striped
tbody
tr(v-for='file in files', style='cursor: pointer', @click='download(file)')
td.file-icon
file-icon(:file='file')
td
div.pull-right.btn-group
clipboard.btn.btn-sm.btn-default(:value='host + file.url', @change='copied(file, $event)', title='Copy to clipboard')
a
icon(name="copy")
a.btn.btn-sm.btn-default(title="Preview", @click.prevent.stop="preview=file", v-if="file.previewType")
icon(name="eye")
i.pull-right.fa.fa-check.text-success.downloaded(v-show='file.downloaded')
p
strong {{ file.metadata.name }}
small.file-size(v-if="Number.isFinite(file.size)") ({{ humanFileSize(file.size) }})
p {{ file.metadata.comment }}
preview-modal(:preview="preview", :files="previewFiles", :max-size="config.maxPreviewSize", @close="preview=false")
</template>
<script>
"use strict";
import AES from 'crypto-js/aes';
import encUtf8 from 'crypto-js/enc-utf8';
import MD5 from 'crypto-js/md5';
import FileIcon from './common/FileIcon.vue';
import Clipboard from './common/Clipboard.vue';
import PreviewModal from './Download/PreviewModal.vue';
import 'vue-awesome/icons/cloud-upload';
import 'vue-awesome/icons/exclamation-triangle';
import 'vue-awesome/icons/copy';
import 'vue-awesome/icons/check';
import 'vue-awesome/icons/download';
import 'vue-awesome/icons/key';
import 'vue-awesome/icons/eye';
function getPreviewType(file, maxSize) {
if(!file || !file.metadata) return false;
if(file.metadata.retention === 'one-time') return false;
// no preview for files size > 2MB
if(file.size > maxSize) return false;
if(file.metadata.type && file.metadata.type.startsWith('image/')) return 'image';
else if(file.metadata.type && file.metadata.type.match(/(text\/|xml|json|javascript|x-sh)/)
|| file.metadata.name && file.metadata.name
.match(/\.(jsx|vue|sh|pug|less|scss|sass|c|h|conf|log|bat|cmd|lua|class|java|py|php|yml|sql|md)$/)) {
return 'text';
}
return false;
}
export default {
name: 'app',
components: { FileIcon, Clipboard, PreviewModal },
data () {
return {
files: [],
sid: document.location.pathname.substr(1),
passwordWrong: false,
needsPassword: false,
password: '',
content: '',
error: '',
host: document.location.protocol + '//' + document.location.host,
config: {},
preview: false
}
},
computed: {
downloadsAvailable: function() {
return this.files.filter(f => !f.downloaded || f.metadata.retention !== 'one-time').length > 0
},
previewFiles: function() {
return this.files.filter(f => !!f.previewType);
}
},
methods: {
download(file) {
if(file.downloaded && file.metadata.retention === 'one-time') {
alert('One-Time Download: File is not available anymore.');
return;
}
const aEl = document.createElement('a');
aEl.setAttribute('href', file.url);
aEl.setAttribute('download', file.metadata.name);
aEl.click();
file.downloaded = true;
},
downloadAll(format) {
document.location.href = document.location.protocol + '//' + document.location.host
+ '/files/' + this.sid + '++'
+ MD5(
this.files
.filter(f => !f.downloaded || f.metadata.retention !== 'one-time')
.map(f => f.key).join()
).toString() + '.' + format;
this.files.forEach(f => {
f.downloaded = true;
});
},
copied(file, $event) {
file.downloaded = $event === 'copied';
},
decrypt() {
this.passwordWrong = false;
this.files = this.files.map(item => {
if(typeof item === 'object') return item;
let f = AES.decrypt(item, this.password);
try {
f = JSON.parse(f.toString(encUtf8));
return Object.assign(f, {
downloaded: false,
previewType: getPreviewType(f, this.config.maxPreviewSize)
});
} catch(e) {
this.passwordWrong = true;
return item;
}
});
if(!this.passwordWrong) {
this.needsPassword = false;
this.password = '';
}
},
humanFileSize(fileSizeInBytes) {
let i = -1;
const byteUnits = [' kB', ' MB', ' GB', ' TB', 'PB', 'EB', 'ZB', 'YB'];
do {
fileSizeInBytes = fileSizeInBytes / 1024;
i++;
}
while(fileSizeInBytes > 1024);
return Math.max(fileSizeInBytes, 0.01).toFixed(2) + byteUnits[i];
},
newSession() {
document.location.href = '/';
}
},
beforeMount() {
const xhr = new XMLHttpRequest();
xhr.open('GET', '/' + this.sid + '.json');
xhr.onload = () => {
if(xhr.status === 200) {
try {
let data = JSON.parse(xhr.responseText);
this.config = data.config;
this.files = data.items.map(f => {
if(typeof f !== 'object') {
this.needsPassword = true;
return f;
}
return Object.assign(f, {
downloaded: false,
previewType: getPreviewType(f, this.config.maxPreviewSize)
});
});
} catch(e) {
this.error = e.toString();
}
} else {
this.error = `${xhr.status} ${xhr.statusText}: ${xhr.responseText}`;
}
};
xhr.send();
}
}
</script>