antares/src/renderer/components/WorkspaceQueryTableCell.vue

349 lines
10 KiB
Vue
Raw Normal View History

2020-06-26 18:14:16 +02:00
<template>
<div
v-if="field !== '_id'"
ref="cell"
class="td p-0"
2020-06-26 18:14:16 +02:00
tabindex="0"
2020-07-22 18:30:52 +02:00
@contextmenu.prevent="$emit('contextmenu', $event)"
2020-06-26 18:14:16 +02:00
>
<span
v-if="!isInlineEditor"
2020-06-26 18:14:16 +02:00
class="cell-content px-2"
:class="`${isNull(content)} type-${type}`"
2020-06-26 18:14:16 +02:00
@dblclick="editON"
2020-07-22 18:30:52 +02:00
>{{ content | typeFormat(type, precision) | cutText }}</span>
2020-07-05 16:06:56 +02:00
<template v-else>
<input
v-if="inputProps.mask"
ref="editField"
v-model="localContent"
v-mask="inputProps.mask"
:type="inputProps.type"
autofocus
class="editable-field px-2"
@blur="editOFF"
>
<input
v-else
ref="editField"
v-model="localContent"
:type="inputProps.type"
autofocus
class="editable-field px-2"
@blur="editOFF"
>
</template>
<ConfirmModal
v-if="isTextareaEditor"
size="medium"
@confirm="editOFF"
@hide="hideEditorModal"
>
<template :slot="'header'">
{{ $t('word.edit') }} "{{ field }}"
</template>
<div :slot="'body'">
<div class="mb-2">
<div>
<textarea
v-model="localContent"
class="form-input textarea-editor"
/>
</div>
2020-07-31 15:45:32 +02:00
<div class="editor-field-info">
<div><b>{{ $t('word.size') }}</b>: {{ localContent.length }}</div>
<div><b>{{ $t('word.type') }}</b>: {{ type.toUpperCase() }}</div>
</div>
</div>
</div>
</ConfirmModal>
2020-08-03 18:07:08 +02:00
<ConfirmModal
v-if="isBlobEditor"
@confirm="editOFF"
@hide="hideEditorModal"
>
<template :slot="'header'">
{{ $t('word.edit') }} "{{ field }}"
</template>
<div :slot="'body'">
<div class="mb-2">
<div>
<img
v-if="isImage"
:src="`data:${contentInfo.mime};base64, ${bufferToBase64(localContent)}`"
class="img-responsive p-centered"
>
<div v-if="contentInfo.size" class="editor-buttons mt-2">
<button class="btn btn-link btn-sm" @click="downloadFile">
<span>{{ $t('word.download') }}</span>
<i class="material-icons ml-1">file_download</i>
</button>
</div>
</div>
<div class="editor-field-info">
<div>
<b>{{ $t('word.size') }}</b>: {{ localContent.length | formatBytes }}<br>
<b>{{ $t('word.mimeType') }}</b>: {{ contentInfo.mime }}
</div>
<div><b>{{ $t('word.type') }}</b>: {{ type.toUpperCase() }}</div>
</div>
<div class="mt-3">
<label>{{ $t('message.uploadFile') }}</label>
<input
class="form-input"
type="file"
@change="filesChange($event)"
>
</div>
</div>
</div>
</ConfirmModal>
2020-06-26 18:14:16 +02:00
</div>
</template>
<script>
import moment from 'moment';
2020-08-03 18:07:08 +02:00
import { mimeFromHex, formatBytes, bufferToBase64 } from 'common/libs/utilities';
2020-06-26 18:14:16 +02:00
import hexToBinary from 'common/libs/hexToBinary';
2020-07-02 19:17:25 +02:00
import { mask } from 'vue-the-mask';
import ConfirmModal from '@/components/BaseConfirmModal';
2020-06-26 18:14:16 +02:00
export default {
name: 'WorkspaceQueryTableCell',
components: {
ConfirmModal
},
2020-06-26 18:14:16 +02:00
filters: {
2020-08-03 18:07:08 +02:00
formatBytes,
2020-07-22 18:30:52 +02:00
cutText (val) {
if (typeof val !== 'string') return val;
2020-07-24 13:26:56 +02:00
return val.length > 128 ? `${val.substring(0, 128)}[...]` : val;
2020-07-22 18:30:52 +02:00
},
2020-07-05 16:06:56 +02:00
typeFormat (val, type, precision) {
2020-06-26 18:14:16 +02:00
if (!val) return val;
switch (type) {
case 'char':
case 'varchar':
case 'text':
case 'mediumtext':
2020-07-22 18:30:52 +02:00
return val;
2020-07-02 19:17:25 +02:00
case 'date': {
return moment(val).isValid() ? moment(val).format('YYYY-MM-DD') : val;
}
2020-06-26 18:14:16 +02:00
case 'datetime':
2020-07-02 19:17:25 +02:00
case 'timestamp': {
2020-07-05 16:06:56 +02:00
let datePrecision = '';
for (let i = 0; i < precision; i++)
datePrecision += i === 0 ? '.S' : 'S';
return moment(val).isValid() ? moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`) : val;
2020-07-02 19:17:25 +02:00
}
2020-06-26 18:14:16 +02:00
case 'blob':
case 'mediumblob':
case 'longblob': {
const buff = Buffer.from(val);
if (!buff.length) return '';
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
return `${mimeFromHex(hex).mime} (${formatBytes(buff.length)})`;
}
case 'bit': {
const hex = Buffer.from(val).toString('hex');
return hexToBinary(hex);
}
default:
return val;
}
}
},
2020-07-02 19:17:25 +02:00
directives: {
mask
},
2020-06-26 18:14:16 +02:00
props: {
type: String,
field: String,
2020-07-05 16:06:56 +02:00
precision: [Number, null],
2020-06-26 18:14:16 +02:00
content: [String, Number, Object, Date, Uint8Array]
},
data () {
return {
isInlineEditor: false,
isTextareaEditor: false,
2020-08-03 18:07:08 +02:00
isBlobEditor: false,
localContent: null,
contentInfo: {
ext: '',
mime: '',
size: null
},
fileToUpload: null
2020-06-26 18:14:16 +02:00
};
},
computed: {
2020-07-02 19:17:25 +02:00
inputProps () {
2020-06-26 18:14:16 +02:00
switch (this.type) {
case 'char':
case 'varchar':
case 'text':
case 'mediumtext':
case 'longtext':
2020-07-02 19:17:25 +02:00
return { type: 'text', mask: false };
2020-06-26 18:14:16 +02:00
case 'int':
case 'tinyint':
case 'smallint':
case 'mediumint':
case 'bigint':
2020-07-02 19:17:25 +02:00
return { type: 'number', mask: false };
2020-06-26 18:14:16 +02:00
case 'date':
2020-07-02 19:17:25 +02:00
return { type: 'text', mask: '####-##-##' };
2020-06-26 18:14:16 +02:00
case 'datetime':
2020-07-05 16:06:56 +02:00
case 'timestamp': {
let datetimeMask = '####-##-## ##:##:##';
for (let i = 0; i < this.precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: datetimeMask };
}
2020-06-26 18:14:16 +02:00
case 'blob':
case 'mediumblob':
case 'longblob':
case 'bit':
2020-07-05 16:06:56 +02:00
return { type: 'file', mask: false };
2020-06-26 18:14:16 +02:00
default:
2020-06-28 15:31:16 +02:00
return 'hidden';
2020-06-26 18:14:16 +02:00
}
2020-08-03 18:07:08 +02:00
},
isImage () {
return ['gif', 'jpg', 'png'].includes(this.contentInfo.ext);
2020-06-26 18:14:16 +02:00
}
},
methods: {
isNull (value) {
return value === null ? ' is-null' : '';
},
2020-08-03 18:07:08 +02:00
bufferToBase64 (val) {
return bufferToBase64(val);
},
2020-06-26 18:14:16 +02:00
editON () {
switch (this.type) {
2020-08-03 18:07:08 +02:00
// Large text editor
case 'text':
case 'mediumtext':
case 'longtext':
this.isTextareaEditor = true;
2020-08-03 18:07:08 +02:00
this.localContent = this.$options.filters.typeFormat(this.content, this.type);
break;
2020-08-03 18:07:08 +02:00
// File fields editor
case 'blob':
case 'mediumblob':
case 'longblob':
this.isBlobEditor = true;
this.localContent = this.content ? this.content : '';
this.fileToUpload = null;
2020-06-26 18:14:16 +02:00
2020-08-03 18:07:08 +02:00
if (this.content !== null) {
const buff = Buffer.from(this.localContent);
if (buff.length) {
const hex = buff.toString('hex').substring(0, 8).toUpperCase();
const { ext, mime } = mimeFromHex(hex);
this.contentInfo = {
ext,
mime,
size: this.localContent.length
};
}
}
break;
// Inline editable fields
default:
this.localContent = this.$options.filters.typeFormat(this.content, this.type);
this.$nextTick(() => { // Focus on input
this.$refs.cell.blur();
this.$nextTick(() => this.$refs.editField.focus());
});
this.isInlineEditor = true;
break;
}
2020-06-26 18:14:16 +02:00
},
editOFF () {
this.isInlineEditor = false;
2020-08-03 18:07:08 +02:00
let content;
if (!['blob', 'mediumblob', 'longblob'].includes(this.type)) {
if (this.localContent === this.$options.filters.typeFormat(this.content, this.type)) return;// If not changed
content = this.localContent;
}
else { // Handle file upload
if (!this.fileToUpload) return;
content = this.fileToUpload.file.path;
}
2020-06-26 18:14:16 +02:00
2020-08-03 18:07:08 +02:00
this.$emit('updateField', {
field: this.field,
type: this.type,
content
});
},
hideEditorModal () {
this.isTextareaEditor = false;
2020-08-03 18:07:08 +02:00
this.isBlobEditor = false;
},
downloadFile () {
const downloadLink = document.createElement('a');
downloadLink.href = `data:${this.contentInfo.mime};base64, ${bufferToBase64(this.localContent)}`;
downloadLink.setAttribute('download', `${this.field}.${this.contentInfo.ext}`);
document.body.appendChild(downloadLink);
downloadLink.click();
downloadLink.remove();
},
filesChange (event) {
const { files } = event.target;
if (!files.length) return;
this.fileToUpload = { name: files[0].name, file: files[0] };
2020-06-26 18:14:16 +02:00
}
}
};
</script>
<style lang="scss">
2020-07-31 18:16:28 +02:00
.editable-field {
margin: 0;
border: none;
line-height: 1;
width: 100%;
2020-06-26 18:14:16 +02:00
}
2020-07-31 18:16:28 +02:00
.cell-content {
display: block;
min-height: 0.8rem;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
2020-06-26 18:14:16 +02:00
}
2020-07-31 18:16:28 +02:00
.textarea-editor {
height: 50vh !important;
}
2020-07-31 15:45:32 +02:00
2020-07-31 18:16:28 +02:00
.editor-field-info {
margin-top: 0.6rem;
display: flex;
justify-content: space-between;
2020-08-03 18:07:08 +02:00
white-space: normal;
}
.editor-buttons {
display: flex;
justify-content: center;
.btn {
display: flex;
align-items: center;
}
2020-07-31 15:45:32 +02:00
}
2020-06-26 18:14:16 +02:00
</style>