1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2026-03-22 05:56:16 +08:00

#133 Add node data viewer

This commit is contained in:
lana-k
2026-01-25 22:15:48 +01:00
parent 4e5adc147f
commit dd30e17ff5
6 changed files with 180 additions and 158 deletions

View File

@@ -13,16 +13,16 @@
:style="movableSplitterStyle"
/>
<div
v-show="!before.hidden"
ref="left"
class="splitpanes-pane"
:size="paneBefore.size"
max-size="30"
:style="styles.before"
>
<slot name="left-pane" />
</div>
<!-- Splitter start-->
<div
v-show="!before.hidden && !after.hidden"
class="splitpanes-splitter"
@mousedown="bindEvents"
@touchstart="bindEvents"
@@ -64,7 +64,12 @@
</div>
</div>
<!-- splitter end -->
<div ref="right" class="splitpanes-pane" :style="styles.after">
<div
v-show="!after.hidden"
ref="right"
class="splitpanes-pane"
:style="styles.after"
>
<slot name="right-pane" />
</div>
</div>
@@ -114,10 +119,12 @@ export default {
styles() {
return {
before: {
[this.horizontal ? 'height' : 'width']: `${this.paneBefore.size}%`
[this.horizontal ? 'height' : 'width']:
`${this.after.hidden ? 100 : this.paneBefore.size}%`
},
after: {
[this.horizontal ? 'height' : 'width']: `${this.paneAfter.size}%`
[this.horizontal ? 'height' : 'width']:
`${this.before.hidden ? 100 : this.paneAfter.size}%`
}
}
},

View File

@@ -11,6 +11,7 @@
:initOptions="initOptionsByMode[mode]"
:data-sources="dataSource"
:showViewSettings="showViewSettings"
:showValueViewer="viewValuePanelVisible"
@loading-image-completed="loadingImage = false"
@update="$emit('update')"
/>
@@ -56,6 +57,16 @@
<settings-icon />
</icon-button>
<icon-button
ref="viewNodeValueBtn"
tooltip="View node"
tooltipPosition="top-left"
:active="viewValuePanelVisible"
@click="viewValuePanelVisible = !viewValuePanelVisible"
>
<view-cell-value-icon />
</icon-button>
<div class="side-tool-bar-divider" />
<icon-button
@@ -126,6 +137,7 @@ import HtmlIcon from '@/components/svg/html'
import ExportToSvgIcon from '@/components/svg/exportToSvg'
import PngIcon from '@/components/svg/png'
import ClipboardIcon from '@/components/svg/clipboard'
import ViewCellValueIcon from '@/components/svg/viewCellValue'
import cIo from '@/lib/utils/clipboardIo'
import loadingDialog from '@/components/Common/LoadingDialog.vue'
import time from '@/lib/utils/time'
@@ -144,6 +156,7 @@ export default {
GraphIcon,
SettingsIcon,
ExportToSvgIcon,
ViewCellValueIcon,
PngIcon,
HtmlIcon,
ClipboardIcon,
@@ -172,7 +185,8 @@ export default {
graph: this.initMode === 'graph' ? this.initOptions : null
},
showLoadingDialog: false,
showViewSettings: true
showViewSettings: true,
viewValuePanelVisible: false
}
},
computed: {

View File

@@ -13,21 +13,35 @@
documentation</a
>.
</div>
<div
<splitpanes
:before="{ size: 70, max: 100 }"
:after="{ size: 30, max: 50, hidden: !showValueViewer }"
:default="{ before: 70, after: 30 }"
class="graph"
:style="{
height:
!dataSources || !dataSourceIsValid ? 'calc(100% - 40px)' : '100%'
}"
>
<GraphEditor
ref="graphEditor"
:dataSources="dataSources"
:initOptions="initOptions"
:showViewSettings="showViewSettings"
@update="$emit('update')"
/>
</div>
<template #left-pane>
<div :style="{ height: '100%' }" ref="graphEditorContainer">
<GraphEditor
ref="graphEditor"
:dataSources="dataSources"
:initOptions="initOptions"
:showViewSettings="showViewSettings"
@update="$emit('update')"
/>
</div>
</template>
<template v-if="showValueViewer" #right-pane>
<value-viewer
:empty="!selectedNode"
empty-message="No node selected to view"
:cellValue="'{}'"
/>
</template>
</splitpanes>
</div>
</template>
@@ -35,17 +49,20 @@
import 'react-chart-editor/lib/react-chart-editor.css'
import GraphEditor from '@/components/Graph/GraphEditor.vue'
import { dataSourceIsValid } from '@/lib/graphHelper'
import ValueViewer from '@/components/ValueViewer'
import Splitpanes from '@/components/Common/Splitpanes'
export default {
name: 'Graph',
components: { GraphEditor },
components: { GraphEditor, ValueViewer, Splitpanes },
props: {
dataSources: Object,
initOptions: Object,
exportToPngEnabled: Boolean,
exportToSvgEnabled: Boolean,
exportToHtmlEnabled: Boolean,
showViewSettings: Boolean
showViewSettings: Boolean,
showValueViewer: Boolean
},
emits: [
'update:exportToSvgEnabled',
@@ -57,7 +74,8 @@ export default {
],
data() {
return {
resizeObserver: null
resizeObserver: null,
selectedNode: {}
}
},
computed: {
@@ -83,10 +101,10 @@ export default {
},
mounted() {
this.resizeObserver = new ResizeObserver(this.handleResize)
this.resizeObserver.observe(this.$refs.graphContainer)
this.resizeObserver.observe(this.$refs.graphEditorContainer)
},
beforeUnmount() {
this.resizeObserver.unobserve(this.$refs.graphContainer)
this.resizeObserver.unobserve(this.$refs.graphEditorContainer)
},
methods: {
getOptionsForSave() {
@@ -100,7 +118,7 @@ export default {
return this.$refs.graphEditor.prepareCopy()
},
async handleResize() {
const renderer = this.$refs.graphEditor.renderer
const renderer = this.$refs.graphEditor?.renderer
if (renderer) {
renderer.refresh()
renderer.getCamera().animatedReset({ duration: 600 })

View File

@@ -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;

View File

@@ -37,7 +37,6 @@
:disabled="!enableTeleport"
>
<run-result
:tab="tab"
:result="tab.result"
:isGettingResults="tab.isGettingResults"
:error="tab.error"

View File

@@ -1,48 +1,56 @@
<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>
<template v-if="!empty">
<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"
/>
<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>
</template>
<div v-show="empty" class="empty-message">
{{ emptyMessage }}
</div>
</div>
</template>
@@ -65,7 +73,9 @@ export default {
Logs
},
props: {
cellValue: [String, Number, Uint8Array]
cellValue: [String, Number, Uint8Array],
empty: Boolean,
emptyMessage: String
},
data() {
return {
@@ -153,8 +163,10 @@ export default {
.value-viewer {
background-color: var(--color-white);
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
position: relative;
}
.value-viewer-toolbar {
display: flex;
@@ -219,4 +231,14 @@ export default {
width: 1px;
background: var(--color-text-base);
}
.empty-message {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--color-text-base);
font-size: 13px;
text-align: center;
}
</style>