Files
sockeye/client/src/components/attachment-control.vue
2022-12-16 06:08:11 +00:00

388 lines
12 KiB
Vue

<template>
<div v-resize="onResize" class="mt-6">
<div>
<v-btn depressed tile @click="revealedClicked">
{{ $sock.t("Attachments")
}}<v-icon
right
v-text="reveal ? '$sockiEyeSlash' : '$sockiEye'"
></v-icon
></v-btn>
</div>
<template v-if="reveal">
<div>
<template v-if="readonly">
<!-- Note: this is just a copy of the inner part of read/write version below
with the action taken out -->
<div class="mt-4" :style="cardTextStyle()">
<span v-if="!hasFiles()" class="text-h4">{{
$sock.t("NoData")
}}</span>
<v-list three-line>
<v-list-item
v-for="item in displayList"
:key="item.id"
:href="item.url"
target="_blank"
>
<v-list-item-avatar>
<v-icon v-text="item.icon"></v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title>
<v-list-item-subtitle
v-text="item.info"
></v-list-item-subtitle>
<v-list-item-subtitle
v-text="item.notes"
></v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</div>
</template>
<template v-else>
<v-tabs v-model="tab" color="primary">
<v-tabs-slider></v-tabs-slider>
<v-tab key="list"><v-icon>$sockiFolder</v-icon></v-tab>
<v-tab key="attach"><v-icon>$sockiPaperclip</v-icon></v-tab>
<v-tabs-items v-model="tab">
<v-tab-item key="list">
<div
v-cloak
id="dropDiv"
class="mt-4"
:style="cardTextStyle()"
@drop.prevent="onDrop"
@dragover.prevent="onDragOver"
@dragleave="onDragEnd"
>
<span v-if="!hasFiles()">{{ $sock.t("DropFilesHere") }}</span>
<v-list three-line>
<v-list-item
v-for="item in displayList"
:key="item.id"
:href="item.url"
target="_blank"
>
<v-list-item-avatar>
<v-icon v-text="item.icon"></v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title
v-text="item.name"
></v-list-item-title>
<v-list-item-subtitle
v-text="item.info"
></v-list-item-subtitle>
<v-list-item-subtitle
v-text="item.notes"
></v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action>
<v-btn large icon @click="openEditMenu(item, $event)">
<v-icon>$sockiEdit</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</v-list>
</div>
<v-btn depressed tile class="mt-8" @click="revealedClicked">
{{ $sock.t("Attachments")
}}<v-icon
right
v-text="reveal ? '$sockiEyeSlash' : '$sockiEye'"
></v-icon
></v-btn>
</v-tab-item>
<v-tab-item key="attach">
<div class="mt-8">
<v-file-input
v-model="uploadFiles"
:label="$sock.t('AttachFile')"
prepend-icon="$sockiPaperclip"
multiple
chips
></v-file-input>
<v-text-field
v-model="notes"
:label="$sock.t('AttachmentNotes')"
></v-text-field>
<v-btn color="primary" text @click="upload">{{
$sock.t("Upload")
}}</v-btn>
</div>
</v-tab-item>
</v-tabs-items>
</v-tabs>
<v-menu
v-model="editMenu"
min-width="360"
:close-on-content-click="false"
offset-y
:position-x="menuX"
:position-y="menuY"
absolute
>
<v-card>
<v-card-title>{{ $sock.t("FileAttachment") }}</v-card-title>
<div class="ma-6">
<v-text-field
v-model="editName"
:label="$sock.t('AttachmentFileName')"
></v-text-field>
<v-text-field
v-model="editNotes"
:label="$sock.t('AttachmentNotes')"
></v-text-field>
</div>
<v-card-actions>
<v-btn text @click="remove()">
{{ $sock.t("Delete") }}
</v-btn>
<v-spacer v-if="!$vuetify.breakpoint.xs"></v-spacer>
<v-btn text @click="editMenu = false">{{
$sock.t("Cancel")
}}</v-btn>
<v-btn color="primary" text @click="saveEdit">{{
$sock.t("OK")
}}</v-btn>
</v-card-actions>
</v-card>
</v-menu>
</template>
</div>
</template>
</div>
</template>
<script>
export default {
props: {
sockType: { type: Number, default: null },
sockId: { type: Number, default: null },
readonly: Boolean
},
data() {
return {
reveal: false,
height: 300,
displayList: [],
notes: null,
tab: null,
uploadFiles: [],
editMenu: false,
menuX: 10,
menuY: 10,
editNotes: null,
editName: null,
editId: null
};
},
computed: {},
methods: {
revealedClicked() {
this.reveal = !this.reveal;
if (this.reveal) {
this.getList();
}
},
onResize() {
this.height = window.innerHeight * 0.8;
},
cardTextStyle() {
return "height: " + this.height + "px;overflow-y:auto;";
},
hasFiles() {
if (!this.displayList || this.displayList.length == 0) {
return false;
}
return true;
},
async upload() {
//similar code in wiki-control
const vm = this;
const fileData = [];
for (let i = 0; i < vm.uploadFiles.length; i++) {
let f = vm.uploadFiles[i];
fileData.push({ name: f.name, lastModified: f.lastModified });
}
const at = {
sockId: vm.sockId,
sockType: vm.sockType,
files: vm.uploadFiles,
fileData: JSON.stringify(fileData), //note this is required for an array or it will come to the server as a string [object,object]
notes: vm.notes ? vm.notes : ""
};
try {
const res = await window.$gz.api.uploadAttachment(at);
if (res.error) {
window.$gz.errorHandler.handleFormError(res.error);
} else {
vm.uploadFiles = [];
vm.updateDisplayList(res.data);
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error);
}
},
async remove() {
try {
if ((await window.$gz.dialog.confirmDelete()) !== true) {
return;
}
const res = await window.$gz.api.remove("attachment/" + this.editId);
if (res.error) {
window.$gz.errorHandler.handleFormError(res.error);
} else {
this.editMenu = false;
this.editName = null;
this.editNotes = null;
this.editId = null;
this.getList();
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error);
}
},
async getList() {
const vm = this;
try {
const res = await window.$gz.api.get(
"attachment/list?socktype=" + vm.sockType + "&ayaid=" + vm.sockId
);
if (res.error) {
window.$gz.errorHandler.handleFormError(res.error);
} else {
vm.updateDisplayList(res.data);
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error);
}
},
updateDisplayList(data) {
//{"data":[{"id":1,"concurrency":7733332,"contentType":"image/png","displayFileName":"Screen Shot 2020-01-09 at 10.50.24.png","lastModified":"0001-01-01T00:00:00Z","notes":"Here are notes"},{"id":4,"concurrency":7733354,"contentType":"text/plain","displayFileName":"TNT log file sockeye.txt","lastModified":"0001-01-01T00:00:00Z","notes":"Here are notes"},{"id":2,"concurrency":7733342,"contentType":"text/plain","displayFileName":"stack.txt","lastModified":"0001-01-01T00:00:00Z","notes":"Here are notes"},{"id":3,"concurrency":7733348,"contentType":"image/jpeg","displayFileName":"t2cx6sloffk41.jpg","lastModified":"0001-01-01T00:00:00Z","notes":"Here are notes"}]}
if (!data) {
data = [];
}
const timeZoneName = window.$gz.locale.getResolvedTimeZoneName();
const languageName = window.$gz.locale.getResolvedLanguage();
const hour12 = window.$gz.store.state.userOptions.hour12;
const ret = [];
for (let i = 0; i < data.length; i++) {
const o = data[i];
ret.push({
id: o.id,
concurrency: o.concurrency,
url: window.$gz.api.attachmentDownloadUrl(o.id, o.contentType),
name: o.displayFileName,
info: `${window.$gz.locale.utcDateToShortDateAndTimeLocalized(
o.lastModified,
timeZoneName,
languageName,
hour12
)} ${o.attachedByUser} ${window.$gz.locale.humanFileSize(
o.size,
languageName,
false,
2
)}`,
notes: o.notes ? o.notes : "",
icon: window.$gz.util.iconForFile(o.displayFileName, o.contentType)
});
}
this.displayList = ret;
},
openEditMenu(item, e) {
e.preventDefault();
this.editMenu = false;
this.editName = item.name;
this.editNotes = item.notes;
this.editId = item.id;
this.menuX = e.clientX;
this.menuY = e.clientY;
this.$nextTick(() => {
this.editMenu = true;
});
},
async saveEdit() {
const vm = this;
if (!vm.editName) {
return;
}
let o = null;
let i = 0;
for (i = 0; i < vm.displayList.length; i++) {
if (vm.displayList[i].id == vm.editId) {
o = vm.displayList[i];
break;
}
}
if (o.name == vm.editName && o.notes == vm.editNotes) {
return;
}
const p = {
concurrency: o.concurrency,
displayFileName: vm.editName,
notes: vm.editNotes
};
try {
const res = await window.$gz.api.upsert("attachment/" + vm.editId, p);
if (res.error) {
window.$gz.errorHandler.handleFormError(res.error);
} else {
vm.editMenu = false;
vm.editName = null;
vm.editNotes = null;
vm.editId = null;
//due to reactivity issues
vm.updateDisplayList(res.data);
}
} catch (error) {
window.$gz.errorHandler.handleFormError(error);
}
},
onDrop(ev) {
dropDiv.style.border = "none";
dropDiv = null;
//handle file drop
var files = Array.from(ev.dataTransfer.files);
if (files.length > 0) {
this.uploadFiles = files;
this.upload();
}
},
onDragOver() {
if (!dropDiv) {
dropDiv = document.getElementById("dropDiv");
}
dropDiv.style.border = "4px dashed #00ff00";
},
onDragEnd() {
dropDiv.style.border = "none";
dropDiv = null;
}
//-----
}
};
let dropDiv = null;
</script>