mirror of
https://github.com/lana-k/sqliteviz.git
synced 2026-03-24 15:06:17 +08:00
#133 Add node data viewer
This commit is contained in:
@@ -1,222 +0,0 @@
|
||||
<template>
|
||||
<div class="value-viewer">
|
||||
<div class="value-viewer-toolbar">
|
||||
<button
|
||||
v-for="format in formats"
|
||||
:key="format.value"
|
||||
type="button"
|
||||
:aria-selected="currentFormat === format.value"
|
||||
:class="format.value"
|
||||
@click="currentFormat = format.value"
|
||||
>
|
||||
{{ format.text }}
|
||||
</button>
|
||||
|
||||
<button type="button" class="copy" @click="copyToClipboard">Copy</button>
|
||||
<button
|
||||
type="button"
|
||||
class="line-wrap"
|
||||
:aria-selected="lineWrapping === true"
|
||||
@click="lineWrapping = !lineWrapping"
|
||||
>
|
||||
Line wrap
|
||||
</button>
|
||||
</div>
|
||||
<div class="value-body">
|
||||
<codemirror
|
||||
v-if="currentFormat === 'json' && formattedJson"
|
||||
:value="formattedJson"
|
||||
:options="cmOptions"
|
||||
class="json-value original-style"
|
||||
/>
|
||||
<pre
|
||||
v-if="currentFormat === 'text'"
|
||||
:class="[
|
||||
'text-value',
|
||||
{ 'meta-value': isNull || isBlob },
|
||||
{ 'line-wrap': lineWrapping }
|
||||
]"
|
||||
>{{ cellText }}</pre
|
||||
>
|
||||
<logs
|
||||
v-if="messages && messages.length > 0"
|
||||
:messages="messages"
|
||||
class="messages"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Codemirror from 'codemirror-editor-vue3'
|
||||
import 'codemirror/lib/codemirror.css'
|
||||
import 'codemirror/mode/javascript/javascript.js'
|
||||
import 'codemirror/addon/fold/foldcode.js'
|
||||
import 'codemirror/addon/fold/foldgutter.js'
|
||||
import 'codemirror/addon/fold/foldgutter.css'
|
||||
import 'codemirror/addon/fold/brace-fold.js'
|
||||
import 'codemirror/theme/neo.css'
|
||||
import cIo from '@/lib/utils/clipboardIo'
|
||||
import Logs from '@/components/Common/Logs'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Codemirror,
|
||||
Logs
|
||||
},
|
||||
props: {
|
||||
cellValue: [String, Number, Uint8Array]
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
formats: [
|
||||
{ text: 'Text', value: 'text' },
|
||||
{ text: 'JSON', value: 'json' }
|
||||
],
|
||||
currentFormat: 'text',
|
||||
lineWrapping: false,
|
||||
formattedJson: '',
|
||||
messages: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cmOptions() {
|
||||
return {
|
||||
tabSize: 4,
|
||||
mode: { name: 'javascript', json: true },
|
||||
theme: 'neo',
|
||||
lineNumbers: true,
|
||||
line: true,
|
||||
lineWrapping: this.lineWrapping,
|
||||
foldGutter: true,
|
||||
gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'],
|
||||
readOnly: true
|
||||
}
|
||||
},
|
||||
isBlob() {
|
||||
return this.cellValue && ArrayBuffer.isView(this.cellValue)
|
||||
},
|
||||
isNull() {
|
||||
return this.cellValue === null
|
||||
},
|
||||
cellText() {
|
||||
const value = this.cellValue
|
||||
if (this.isNull) {
|
||||
return 'NULL'
|
||||
}
|
||||
if (this.isBlob) {
|
||||
return 'BLOB'
|
||||
}
|
||||
return value
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
currentFormat() {
|
||||
this.messages = []
|
||||
this.formattedJson = ''
|
||||
if (this.currentFormat === 'json') {
|
||||
this.formatJson(this.cellValue)
|
||||
}
|
||||
},
|
||||
cellValue() {
|
||||
this.messages = []
|
||||
if (this.currentFormat === 'json') {
|
||||
this.formatJson(this.cellValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatJson(jsonStr) {
|
||||
try {
|
||||
this.formattedJson = JSON.stringify(JSON.parse(jsonStr), null, 4)
|
||||
} catch (e) {
|
||||
this.formattedJson = ''
|
||||
this.messages = [
|
||||
{
|
||||
type: 'error',
|
||||
message: "Can't parse JSON."
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
copyToClipboard() {
|
||||
cIo.copyText(
|
||||
this.currentFormat === 'json' ? this.formattedJson : this.cellValue,
|
||||
'The value is copied to clipboard.'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.value-viewer {
|
||||
background-color: var(--color-white);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.value-viewer-toolbar {
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
}
|
||||
.value-body {
|
||||
flex-grow: 1;
|
||||
overflow: auto;
|
||||
}
|
||||
.text-value {
|
||||
padding: 0 8px;
|
||||
margin: 0;
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.json-value {
|
||||
margin-top: -4px;
|
||||
}
|
||||
|
||||
.text-value.meta-value {
|
||||
font-style: italic;
|
||||
color: var(--color-text-light-2);
|
||||
}
|
||||
|
||||
.text-value.line-wrap {
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.messages {
|
||||
margin: 0 8px;
|
||||
}
|
||||
|
||||
.value-viewer-toolbar button {
|
||||
font-size: 10px;
|
||||
height: 20px;
|
||||
padding: 0 8px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--color-text-base);
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
|
||||
.value-viewer-toolbar button:hover {
|
||||
background-color: var(--color-bg-light);
|
||||
}
|
||||
|
||||
.value-viewer-toolbar button[aria-selected='true'] {
|
||||
color: var(--color-accent);
|
||||
}
|
||||
|
||||
:deep(.codemirror-container) {
|
||||
display: block;
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
:deep(.CodeMirror) {
|
||||
height: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
:deep(.CodeMirror-cursor) {
|
||||
width: 1px;
|
||||
background: var(--color-text-base);
|
||||
}
|
||||
</style>
|
||||
@@ -1,40 +1,66 @@
|
||||
<template>
|
||||
<div ref="runResultPanel" class="run-result-panel">
|
||||
<component
|
||||
:is="viewValuePanelVisible ? 'splitpanes' : 'div'"
|
||||
<splitpanes
|
||||
:before="{ size: 50, max: 100 }"
|
||||
:after="{ size: 50, max: 100 }"
|
||||
:after="{ size: 50, max: 100, hidden: !viewValuePanelVisible }"
|
||||
:default="{ before: 50, after: 50 }"
|
||||
class="run-result-panel-content"
|
||||
>
|
||||
<template #left-pane>
|
||||
<div
|
||||
:id="'run-result-left-pane-' + tab.id"
|
||||
class="result-set-container"
|
||||
/>
|
||||
</template>
|
||||
<div
|
||||
:id="'run-result-result-set-' + tab.id"
|
||||
class="result-set-container"
|
||||
/>
|
||||
<template v-if="viewValuePanelVisible" #right-pane>
|
||||
<div class="value-viewer-container">
|
||||
<value-viewer
|
||||
v-show="selectedCell"
|
||||
:cellValue="
|
||||
selectedCell
|
||||
? result.values[result.columns[selectedCell.dataset.col]][
|
||||
selectedCell.dataset.row
|
||||
]
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
<div v-show="!selectedCell" class="table-preview">
|
||||
No cell selected to view
|
||||
<div class="result-set-container">
|
||||
<div
|
||||
v-show="result === null && !isGettingResults && !error"
|
||||
class="table-preview result-before"
|
||||
>
|
||||
Run your query and get results here
|
||||
</div>
|
||||
<div v-if="isGettingResults" class="table-preview result-in-progress">
|
||||
<loading-indicator :size="30" />
|
||||
Fetching results...
|
||||
</div>
|
||||
<div
|
||||
v-show="result === undefined && !isGettingResults && !error"
|
||||
class="table-preview result-empty"
|
||||
>
|
||||
No rows retrieved according to your query
|
||||
</div>
|
||||
<logs v-if="error" :messages="[error]" />
|
||||
<sql-table
|
||||
v-if="result && !viewRecord"
|
||||
:data-set="result"
|
||||
:time="time"
|
||||
:pageSize="pageSize"
|
||||
:page="defaultPage"
|
||||
:selectedCellCoordinates="defaultSelectedCell"
|
||||
class="straight"
|
||||
@update-selected-cell="onUpdateSelectedCell"
|
||||
/>
|
||||
|
||||
<record
|
||||
v-if="result && viewRecord"
|
||||
ref="recordView"
|
||||
:data-set="result"
|
||||
:time="time"
|
||||
:selectedColumnIndex="selectedCell ? +selectedCell.dataset.col : 0"
|
||||
:rowIndex="selectedCell ? +selectedCell.dataset.row : 0"
|
||||
@update-selected-cell="onUpdateSelectedCell"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</component>
|
||||
<template v-if="viewValuePanelVisible" #right-pane>
|
||||
<value-viewer
|
||||
:empty="!selectedCell"
|
||||
empty-message="No cell selected to view"
|
||||
:cellValue="
|
||||
selectedCell
|
||||
? result.values[result.columns[selectedCell.dataset.col]][
|
||||
selectedCell.dataset.row
|
||||
]
|
||||
: ''
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
</splitpanes>
|
||||
|
||||
<side-tool-bar panel="table" @switch-to="$emit('switchTo', $event)">
|
||||
<icon-button
|
||||
@@ -89,48 +115,6 @@
|
||||
@action="copyToClipboard"
|
||||
@cancel="cancelCopy"
|
||||
/>
|
||||
|
||||
<teleport defer :to="resultSetTeleportTarget" :disabled="!enableTeleport">
|
||||
<div>
|
||||
<div
|
||||
v-show="result === null && !isGettingResults && !error"
|
||||
class="table-preview result-before"
|
||||
>
|
||||
Run your query and get results here
|
||||
</div>
|
||||
<div v-if="isGettingResults" class="table-preview result-in-progress">
|
||||
<loading-indicator :size="30" />
|
||||
Fetching results...
|
||||
</div>
|
||||
<div
|
||||
v-show="result === undefined && !isGettingResults && !error"
|
||||
class="table-preview result-empty"
|
||||
>
|
||||
No rows retrieved according to your query
|
||||
</div>
|
||||
<logs v-if="error" :messages="[error]" />
|
||||
<sql-table
|
||||
v-if="result && !viewRecord"
|
||||
:data-set="result"
|
||||
:time="time"
|
||||
:pageSize="pageSize"
|
||||
:page="defaultPage"
|
||||
:selectedCellCoordinates="defaultSelectedCell"
|
||||
class="straight"
|
||||
@update-selected-cell="onUpdateSelectedCell"
|
||||
/>
|
||||
|
||||
<record
|
||||
v-if="result && viewRecord"
|
||||
ref="recordView"
|
||||
:data-set="result"
|
||||
:time="time"
|
||||
:selectedColumnIndex="selectedCell ? +selectedCell.dataset.col : 0"
|
||||
:rowIndex="selectedCell ? +selectedCell.dataset.row : 0"
|
||||
@update-selected-cell="onUpdateSelectedCell"
|
||||
/>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -151,7 +135,7 @@ import cIo from '@/lib/utils/clipboardIo'
|
||||
import time from '@/lib/utils/time'
|
||||
import loadingDialog from '@/components/Common/LoadingDialog'
|
||||
import events from '@/lib/utils/events'
|
||||
import ValueViewer from './ValueViewer'
|
||||
import ValueViewer from '@/components/ValueViewer'
|
||||
import Record from './Record/index.vue'
|
||||
|
||||
export default {
|
||||
@@ -172,7 +156,6 @@ export default {
|
||||
Splitpanes
|
||||
},
|
||||
props: {
|
||||
tab: Object,
|
||||
result: Object,
|
||||
isGettingResults: Boolean,
|
||||
error: Object,
|
||||
@@ -194,20 +177,6 @@ export default {
|
||||
showLoadingDialog: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
resultSetTeleportTarget() {
|
||||
if (!this.enableTeleport) {
|
||||
return undefined
|
||||
}
|
||||
const base = `#${
|
||||
this.viewValuePanelVisible
|
||||
? 'run-result-left-pane'
|
||||
: 'run-result-result-set'
|
||||
}`
|
||||
const tabIdPostfix = `-${this.tab.id}`
|
||||
return base + tabIdPostfix
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
result() {
|
||||
this.defaultSelectedCell = null
|
||||
@@ -332,19 +301,12 @@ export default {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.result-set-container,
|
||||
.result-set-container > div {
|
||||
.result-set-container {
|
||||
position: relative;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.value-viewer-container {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--color-white);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.table-preview {
|
||||
position: absolute;
|
||||
|
||||
Reference in New Issue
Block a user