Changeset View
Changeset View
Standalone View
Standalone View
src/resources/vue/File/List.vue
- This file was added.
<template> | |||||
<div class="container"> | |||||
<div class="card" id="files"> | |||||
<div class="card-body"> | |||||
<div class="card-title"> | |||||
{{ $t('dashboard.files') }} | |||||
<small><sup class="badge bg-primary">{{ $t('dashboard.beta') }}</sup></small> | |||||
<div id="drop-area" class="file-drop-area float-end"> | |||||
<svg-icon icon="upload"></svg-icon> Click or drop file(s) here | |||||
</div> | |||||
</div> | |||||
<div class="card-text"> | |||||
<ul class="nav nav-tabs mt-3" role="tablist"> | |||||
<li class="nav-item"> | |||||
<a class="nav-link active" id="tab-library" href="#library" role="tab" aria-controls="library" aria-selected="true" @click="$root.tab"> | |||||
Your library | |||||
</a> | |||||
</li> | |||||
<li class="nav-item"> | |||||
<a class="nav-link" id="tab-shared" href="#shared" role="tab" aria-controls="shared" aria-selected="false"> | |||||
Shared with you | |||||
</a> | |||||
</li> | |||||
</ul> | |||||
<div class="tab-content"> | |||||
<div class="tab-pane show active" id="library" role="tabpanel" aria-labelledby="tab-library"> | |||||
<div class="mb-2 mt-3 d-flex"> | |||||
<list-search :placeholder="$t('file.search')" :on-search="searchUsers"></list-search> | |||||
</div> | |||||
<table class="table table-sm table-hover"> | |||||
<thead> | |||||
<tr> | |||||
<th scope="col">{{ $t('form.name') }}</th> | |||||
<th scope="col">{{ $t('form.size') }}</th> | |||||
<th scope="col"></th> | |||||
</tr> | |||||
</thead> | |||||
<tbody> | |||||
<tr v-for="file in files" :key="file.id" @click="$root.clickRecord"> | |||||
<td> | |||||
<svg-icon icon="file" :title="file.mimetype"></svg-icon> | |||||
{{ file.name }} | |||||
</td> | |||||
<td> | |||||
{{ api.sizeText(file.size) }} | |||||
</td> | |||||
<td class="buttons"> | |||||
<btn class="button-share p-0 ms-1" @click="fileShare(file)" icon="share-alt-square" :title="$t('btn.share')"></btn> | |||||
<btn class="button-download p-0 ms-1" @click="fileDownload(file)" icon="download" :title="$t('btn.download')"></btn> | |||||
<btn class="button-delete text-danger p-0 ms-1" @click="fileDelete(file)" icon="trash-alt" :title="$t('btn.delete')"></btn> | |||||
</td> | |||||
</tr> | |||||
</tbody> | |||||
<list-foot :colspan="3" :text="$t('file.list-empty')"></list-foot> | |||||
</table> | |||||
<list-more v-if="hasMore" :on-click="loadFiles"></list-more> | |||||
</div> | |||||
<div class="tab-pane" id="shared" role="tabpanel" aria-labelledby="tab-shared"> | |||||
<div class="mb-2 mt-3 d-flex"> | |||||
<list-search :placeholder="$t('file.search')" :on-search="searchShares"></list-search> | |||||
</div> | |||||
<table class="table table-sm table-hover"> | |||||
<thead> | |||||
<tr> | |||||
<th scope="col">{{ $t('form.name') }}</th> | |||||
<th scope="col">{{ $t('form.size') }}</th> | |||||
<th scope="col"></th> | |||||
</tr> | |||||
</thead> | |||||
<tbody> | |||||
<tr v-for="file in shares" :key="file.id" @click="$root.clickRecord"> | |||||
<td> | |||||
<svg-icon icon="file" :title="file.mimetype"></svg-icon> | |||||
{{ file.name }} | |||||
</td> | |||||
<td> | |||||
{{ api.sizeText(file.size) }} | |||||
</td> | |||||
<td class="buttons"> | |||||
<btn class="button-download p-0 ms-1" @click="fileDownload(file)" icon="download" :title="$t('btn.download')"></btn> | |||||
<btn v-if="false" class="button-delete text-danger p-0 ms-1" @click="fileDelete(file)" icon="trash-alt" :title="$t('btn.delete')"></btn> | |||||
</td> | |||||
</tr> | |||||
</tbody> | |||||
<list-foot :colspan="3" :text="$t('file.list-empty')"></list-foot> | |||||
</table> | |||||
<list-more v-if="hasMore" :on-click="loadShares"></list-more> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
<div id="share-dialog" class="modal" tabindex="-1" role="dialog"> | |||||
<div class="modal-dialog" role="document"> | |||||
<div class="modal-content"> | |||||
<div class="modal-header"> | |||||
<h5 class="modal-title">{{ $t('file.sharing') }}</h5> | |||||
<btn class="btn-close" data-bs-dismiss="modal" :aria-label="$t('btn.close')"></btn> | |||||
</div> | |||||
<div class="modal-body"> | |||||
<p>{{ $t('file.sharing-text') }}</p> | |||||
<acl-input id="acl" v-model="acl" :list="acl" :useronly="true" class="mb-1"></acl-input> | |||||
</div> | |||||
<div class="modal-footer"> | |||||
<btn class="btn-secondary modal-cancel" data-bs-dismiss="modal">{{ $t('btn.cancel') }}</btn> | |||||
<btn class="btn-primary modal-action" icon="check" @click="submitShares()">{{ $t('btn.submit') }}</btn> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</div> | |||||
</template> | |||||
<script> | |||||
import { Modal } from 'bootstrap' | |||||
import FileAPI from '../../js/files.js' | |||||
import AclInput from '../Widgets/AclInput' | |||||
import ListTools from '../Widgets/ListTools' | |||||
import { library } from '@fortawesome/fontawesome-svg-core' | |||||
import { faFile, faDownload, faShareAltSquare, faUpload } from '@fortawesome/free-solid-svg-icons' | |||||
library.add(faFile, faDownload, faShareAltSquare, faUpload) | |||||
export default { | |||||
components: { | |||||
AclInput | |||||
}, | |||||
mixins: [ ListTools ], | |||||
data() { | |||||
return { | |||||
acl: [], | |||||
api: {}, | |||||
file: null, | |||||
files: [], | |||||
shares: [] | |||||
} | |||||
}, | |||||
mounted() { | |||||
this.uploads = {} | |||||
this.api = new FileAPI({ | |||||
dropArea: '#drop-area', | |||||
eventHandler: this.eventHandler | |||||
}) | |||||
this.loadFiles({ init: true }) | |||||
$('#tab-shared').on('click', e => { | |||||
this.$root.tab(e) | |||||
this.loadShares({ init: true }) | |||||
}) | |||||
}, | |||||
methods: { | |||||
eventHandler(name, params) { | |||||
const camelCase = name.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.toUpperCase()) | |||||
const method = camelCase + 'Handler' | |||||
if (method in this) { | |||||
this[method](params) | |||||
} | |||||
}, | |||||
fileDelete(file) { | |||||
axios.delete('/api/v4/files/' + file.id) | |||||
.then(response => { | |||||
if (response.data.status == 'success') { | |||||
this.$toast.success(response.data.message) | |||||
// Refresh the list | |||||
this.loadFiles({ reset: true }) | |||||
} | |||||
}) | |||||
}, | |||||
fileDownload(file) { | |||||
// FIXME: This is not an appropriate method for big files. | |||||
// We need to implement something like a "unique download url" | |||||
// which then would be used to download a file without | |||||
// a need for the api authentication headers. | |||||
this.$root.downloadFile('/api/v4/files/' + file.id + '?download=1', file.name) | |||||
}, | |||||
fileShare(file) { | |||||
this.acl = [] | |||||
this.file = file | |||||
// Display the dialog | |||||
this.dialog = new Modal('#share-dialog') | |||||
this.dialog.show() | |||||
const form = $('#share-dialog .modal-body') | |||||
this.$root.addLoader(form) | |||||
axios.get('/api/v4/files/' + file.id + '/permissions') | |||||
.then(response => { | |||||
this.$root.removeLoader(form) | |||||
if (response.data.list) { | |||||
response.data.list.forEach(item => { | |||||
this.acl.push(`${item.user}, ${item.permissions}`) | |||||
}) | |||||
} | |||||
}) | |||||
.catch(error => { | |||||
this.$root.removeLoader(form) | |||||
}) | |||||
}, | |||||
loadFiles(params, shared) { | |||||
this.listSearch('files', '/api/v4/files', params) | |||||
}, | |||||
loadShares(params) { | |||||
if (!params) params = {} | |||||
params.get = { shared: 1 } | |||||
this.listSearch('shares', '/api/v4/files', params) | |||||
}, | |||||
searchFiles(search) { | |||||
this.loadFiles({ reset: true, search }) | |||||
}, | |||||
searchShares(search) { | |||||
this.loadShares({ reset: true, search }) | |||||
}, | |||||
submitShares() { | |||||
const post = { permissions: [] } | |||||
// acl's widget outout is different that we need here | |||||
this.acl.forEach(item => { | |||||
const entry = item.split(', ') | |||||
post.permissions.push({ | |||||
user: entry[0], | |||||
permissions: entry[1], | |||||
}) | |||||
}) | |||||
axios.post('/api/v4/files/' + this.file.id + '/permissions', post) | |||||
.then(response => { | |||||
this.dialog.hide(); | |||||
if (response.data.status == 'success') { | |||||
this.$toast.success(response.data.message) | |||||
} | |||||
}) | |||||
}, | |||||
uploadProgressHandler(params) { | |||||
// Note: There might be more than one event with completed=0 | |||||
// e.g. if you upload multiple files at once | |||||
if (params.completed == 0 && !(params.id in this.uploads)) { | |||||
// Create the toast message with progress bar | |||||
this.uploads[params.id] = this.$toast.message({ | |||||
icon: 'upload', | |||||
timeout: 24 * 60 * 60 * 60 * 1000, | |||||
title: this.$t('msg.uploading'), | |||||
msg: `${params.name} (${this.api.sizeText(params.total)})`, | |||||
progress: 0 | |||||
}) | |||||
} else if (params.id in this.uploads) { | |||||
if (params.completed == 100) { | |||||
this.uploads[params.id].delete() // close the toast message | |||||
delete this.uploads[params.id] | |||||
// TODO: Reloading the list is probably not the best solution | |||||
this.loadFiles({ reset: true }) | |||||
} else { | |||||
// update progress bar | |||||
this.uploads[params.id].updateProgress(params.completed) | |||||
} | |||||
} | |||||
} | |||||
} | |||||
} | |||||
</script> |