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" :style="movableSplitterStyle"
/> />
<div <div
v-show="!before.hidden"
ref="left" ref="left"
class="splitpanes-pane" class="splitpanes-pane"
:size="paneBefore.size"
max-size="30"
:style="styles.before" :style="styles.before"
> >
<slot name="left-pane" /> <slot name="left-pane" />
</div> </div>
<!-- Splitter start--> <!-- Splitter start-->
<div <div
v-show="!before.hidden && !after.hidden"
class="splitpanes-splitter" class="splitpanes-splitter"
@mousedown="bindEvents" @mousedown="bindEvents"
@touchstart="bindEvents" @touchstart="bindEvents"
@@ -64,7 +64,12 @@
</div> </div>
</div> </div>
<!-- splitter end --> <!-- 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" /> <slot name="right-pane" />
</div> </div>
</div> </div>
@@ -114,10 +119,12 @@ export default {
styles() { styles() {
return { return {
before: { before: {
[this.horizontal ? 'height' : 'width']: `${this.paneBefore.size}%` [this.horizontal ? 'height' : 'width']:
`${this.after.hidden ? 100 : this.paneBefore.size}%`
}, },
after: { 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]" :initOptions="initOptionsByMode[mode]"
:data-sources="dataSource" :data-sources="dataSource"
:showViewSettings="showViewSettings" :showViewSettings="showViewSettings"
:showValueViewer="viewValuePanelVisible"
@loading-image-completed="loadingImage = false" @loading-image-completed="loadingImage = false"
@update="$emit('update')" @update="$emit('update')"
/> />
@@ -56,6 +57,16 @@
<settings-icon /> <settings-icon />
</icon-button> </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" /> <div class="side-tool-bar-divider" />
<icon-button <icon-button
@@ -126,6 +137,7 @@ import HtmlIcon from '@/components/svg/html'
import ExportToSvgIcon from '@/components/svg/exportToSvg' import ExportToSvgIcon from '@/components/svg/exportToSvg'
import PngIcon from '@/components/svg/png' import PngIcon from '@/components/svg/png'
import ClipboardIcon from '@/components/svg/clipboard' import ClipboardIcon from '@/components/svg/clipboard'
import ViewCellValueIcon from '@/components/svg/viewCellValue'
import cIo from '@/lib/utils/clipboardIo' import cIo from '@/lib/utils/clipboardIo'
import loadingDialog from '@/components/Common/LoadingDialog.vue' import loadingDialog from '@/components/Common/LoadingDialog.vue'
import time from '@/lib/utils/time' import time from '@/lib/utils/time'
@@ -144,6 +156,7 @@ export default {
GraphIcon, GraphIcon,
SettingsIcon, SettingsIcon,
ExportToSvgIcon, ExportToSvgIcon,
ViewCellValueIcon,
PngIcon, PngIcon,
HtmlIcon, HtmlIcon,
ClipboardIcon, ClipboardIcon,
@@ -172,7 +185,8 @@ export default {
graph: this.initMode === 'graph' ? this.initOptions : null graph: this.initMode === 'graph' ? this.initOptions : null
}, },
showLoadingDialog: false, showLoadingDialog: false,
showViewSettings: true showViewSettings: true,
viewValuePanelVisible: false
} }
}, },
computed: { computed: {

View File

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

View File

@@ -1,26 +1,56 @@
<template> <template>
<div ref="runResultPanel" class="run-result-panel"> <div ref="runResultPanel" class="run-result-panel">
<component <splitpanes
:is="viewValuePanelVisible ? 'splitpanes' : 'div'"
:before="{ size: 50, max: 100 }" :before="{ size: 50, max: 100 }"
:after="{ size: 50, max: 100 }" :after="{ size: 50, max: 100, hidden: !viewValuePanelVisible }"
:default="{ before: 50, after: 50 }" :default="{ before: 50, after: 50 }"
class="run-result-panel-content" class="run-result-panel-content"
> >
<template #left-pane> <template #left-pane>
<div class="result-set-container">
<div <div
:id="'run-result-left-pane-' + tab.id" v-show="result === null && !isGettingResults && !error"
class="result-set-container" 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> </template>
<div
:id="'run-result-result-set-' + tab.id"
class="result-set-container"
/>
<template v-if="viewValuePanelVisible" #right-pane> <template v-if="viewValuePanelVisible" #right-pane>
<div class="value-viewer-container">
<value-viewer <value-viewer
v-show="selectedCell" :empty="!selectedCell"
empty-message="No cell selected to view"
:cellValue=" :cellValue="
selectedCell selectedCell
? result.values[result.columns[selectedCell.dataset.col]][ ? result.values[result.columns[selectedCell.dataset.col]][
@@ -29,12 +59,8 @@
: '' : ''
" "
/> />
<div v-show="!selectedCell" class="table-preview">
No cell selected to view
</div>
</div>
</template> </template>
</component> </splitpanes>
<side-tool-bar panel="table" @switch-to="$emit('switchTo', $event)"> <side-tool-bar panel="table" @switch-to="$emit('switchTo', $event)">
<icon-button <icon-button
@@ -89,48 +115,6 @@
@action="copyToClipboard" @action="copyToClipboard"
@cancel="cancelCopy" @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> </div>
</template> </template>
@@ -151,7 +135,7 @@ import cIo from '@/lib/utils/clipboardIo'
import time from '@/lib/utils/time' import time from '@/lib/utils/time'
import loadingDialog from '@/components/Common/LoadingDialog' import loadingDialog from '@/components/Common/LoadingDialog'
import events from '@/lib/utils/events' import events from '@/lib/utils/events'
import ValueViewer from './ValueViewer' import ValueViewer from '@/components/ValueViewer'
import Record from './Record/index.vue' import Record from './Record/index.vue'
export default { export default {
@@ -172,7 +156,6 @@ export default {
Splitpanes Splitpanes
}, },
props: { props: {
tab: Object,
result: Object, result: Object,
isGettingResults: Boolean, isGettingResults: Boolean,
error: Object, error: Object,
@@ -194,20 +177,6 @@ export default {
showLoadingDialog: false 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: { watch: {
result() { result() {
this.defaultSelectedCell = null this.defaultSelectedCell = null
@@ -332,19 +301,12 @@ export default {
width: 0; width: 0;
} }
.result-set-container, .result-set-container {
.result-set-container > div {
position: relative; position: relative;
height: 100%; height: 100%;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
} }
.value-viewer-container {
height: 100%;
width: 100%;
background-color: var(--color-white);
position: relative;
}
.table-preview { .table-preview {
position: absolute; position: absolute;

View File

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

View File

@@ -1,5 +1,6 @@
<template> <template>
<div class="value-viewer"> <div class="value-viewer">
<template v-if="!empty">
<div class="value-viewer-toolbar"> <div class="value-viewer-toolbar">
<button <button
v-for="format in formats" v-for="format in formats"
@@ -12,7 +13,9 @@
{{ format.text }} {{ format.text }}
</button> </button>
<button type="button" class="copy" @click="copyToClipboard">Copy</button> <button type="button" class="copy" @click="copyToClipboard">
Copy
</button>
<button <button
type="button" type="button"
class="line-wrap" class="line-wrap"
@@ -44,6 +47,11 @@
class="messages" class="messages"
/> />
</div> </div>
</template>
<div v-show="empty" class="empty-message">
{{ emptyMessage }}
</div>
</div> </div>
</template> </template>
@@ -65,7 +73,9 @@ export default {
Logs Logs
}, },
props: { props: {
cellValue: [String, Number, Uint8Array] cellValue: [String, Number, Uint8Array],
empty: Boolean,
emptyMessage: String
}, },
data() { data() {
return { return {
@@ -153,8 +163,10 @@ export default {
.value-viewer { .value-viewer {
background-color: var(--color-white); background-color: var(--color-white);
height: 100%; height: 100%;
width: 100%;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
position: relative;
} }
.value-viewer-toolbar { .value-viewer-toolbar {
display: flex; display: flex;
@@ -219,4 +231,14 @@ export default {
width: 1px; width: 1px;
background: var(--color-text-base); 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> </style>