This commit is contained in:
387
client/src/components/attachment-control.vue
Normal file
387
client/src/components/attachment-control.vue
Normal file
@@ -0,0 +1,387 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user