1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2025-12-06 18:18:53 +08:00

#115 record and row navigator

This commit is contained in:
lana-k
2023-10-28 22:51:28 +02:00
parent 07d31dbfe9
commit 735e4ec7f6
5 changed files with 311 additions and 2 deletions

View File

@@ -0,0 +1,20 @@
<template>
<svg
width="28"
height="27"
viewBox="0 0 28 27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.9475 8.33625L12.7838 13.5L17.9475 18.6638L16.35 20.25L9.60001
13.5L16.35 6.75L17.9475 8.33625Z"
fill="#506784"
/>
</svg>
</template>
<script>
export default {
}
</script>

View File

@@ -0,0 +1,26 @@
<template>
<svg
width="27"
height="27"
viewBox="0 0 27 27"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M17.3474 8.33625L12.1837 13.5L17.3474 18.6638L15.7499 20.25L8.99991
13.5L15.7499 6.75L17.3474 8.33625Z"
fill="#506784"
/>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M7.19995 19.8L7.19995 7.20001H9.19995V19.8H7.19995Z"
fill="#506784"
/>
</svg>
</template>
<script>
export default {
}
</script>

View File

@@ -0,0 +1,66 @@
<template>
<div class="record-navigator">
<icon-button
:disabled="value === 0"
tooltip="First row"
tooltip-position="top-left"
@click="$emit('input', 0)"
>
<edge-arrow-icon :disabled="false" />
</icon-button>
<icon-button
:disabled="value === 0"
tooltip="Previous row"
tooltip-position="top-left"
@click="$emit('input', value - 1)"
>
<arrow-icon :disabled="false" />
</icon-button>
<icon-button
:disabled="value === total - 1"
tooltip="Next row"
tooltip-position="top-left"
class="next-last"
@click="$emit('input', value + 1)"
>
<arrow-icon :disabled="false" />
</icon-button>
<icon-button
:disabled="value === total - 1"
tooltip="Last row"
tooltip-position="top-left"
class="next-last"
@click="$emit('input', total - 1)"
>
<edge-arrow-icon :disabled="false" />
</icon-button>
</div>
</template>
<script>
import IconButton from '@/components/IconButton'
import ArrowIcon from '@/components/svg/arrow'
import EdgeArrowIcon from '@/components/svg/edgeArrow'
export default {
components: {
IconButton,
ArrowIcon,
EdgeArrowIcon
},
props: {
value: Number,
total: Number
}
}
</script>
<style scoped>
.record-navigator {
display: flex;
}
.record-navigator .next-last {
transform: rotate(180deg);
}
</style>

View File

@@ -0,0 +1,172 @@
<template>
<div>
<div class="table-container">
<table
ref="table"
class="sqliteviz-table"
tabindex="0"
@keydown="onTableKeydown"
>
<thead>
<tr>
<th/>
<th>
<div class="cell-data">
Row #{{ currentRowIndex + 1 }}
</div>
</th>
</tr>
</thead>
<tbody>
<tr v-for="(col, index) in columns" :key="index">
<th>{{ col }}</th>
<td
:data-col="1"
:data-row="index"
:key="index"
:aria-selected="false"
@click="onCellClick"
>
<div class="cell-data">
{{ dataSet.values[col][currentRowIndex] }}
</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="table-footer">
<div class="table-footer-count">
{{ rowCount }} {{rowCount === 1 ? 'row' : 'rows'}} retrieved
<span v-if="time">in {{ time }}</span>
</div>
<row-navigator v-model="currentRowIndex" :total="rowCount"/>
</div>
</div>
</template>
<script>
import RowNavigator from './RowNavigator.vue'
export default {
components: { RowNavigator },
props: {
dataSet: Object,
time: String,
pageSize: {
type: Number,
default: 20
},
rowIndex: { type: Number, default: 0 }
},
data () {
return {
currentPage: 1,
selectedCellElement: null,
currentRowIndex: this.rowIndex
}
},
computed: {
columns () {
return this.dataSet.columns
},
rowCount () {
return this.dataSet.values[this.columns[0]].length
},
pageCount () {
return Math.ceil(this.rowCount / this.pageSize)
},
currentPageData () {
const start = (this.currentPage - 1) * this.pageSize
let end = start + this.pageSize
if (end > this.rowCount - 1) {
end = this.rowCount - 1
}
return {
start,
end,
count: end - start + 1
}
}
},
methods: {
onTableKeydown (e) {
const keyCodeMap = {
38: 'up',
40: 'down'
}
if (!Object.keys(keyCodeMap).includes(e.keyCode.toString())) {
return
}
e.preventDefault()
this.moveFocusInTable(this.selectedCellElement, keyCodeMap[e.keyCode])
},
onCellClick (e) {
this.selectCell(e.target.closest('td'))
},
selectCell (cell) {
if (!cell.ariaSelected || cell.ariaSelected === 'false') {
if (this.selectedCellElement) {
this.selectedCellElement.ariaSelected = 'false'
}
cell.ariaSelected = 'true'
this.selectedCellElement = cell
} else {
cell.ariaSelected = 'false'
this.selectedCellElement = null
}
this.$emit('updateSelectedCell', this.selectedCellElement)
},
moveFocusInTable (initialCell, direction) {
const currentRowIndex = +initialCell.dataset.row
const newRowIndex = direction === 'up'
? currentRowIndex - 1
: currentRowIndex + 1
const newCell = this.$refs.table
.querySelector(`td[data-col="1"][data-row="${newRowIndex}"]`)
if (newCell) {
this.selectCell(newCell)
}
}
},
watch: {
dataSet () {
this.currentPage = 1
}
}
}
</script>
<style scoped>
table.sqliteviz-table:focus {
outline: none;
}
.sqliteviz-table tbody td:hover {
background-color: var(--color-bg-light-3);
}
.sqliteviz-table tbody td[aria-selected="true"] {
box-shadow:inset 0 0 0 1px var(--color-accent);
}
table.sqliteviz-table {
margin-top: 0;
}
.sqliteviz-table thead tr th {
border-bottom: 1px solid var(--color-border-light);
}
.sqliteviz-table tbody tr th {
font-size: 14px;
font-weight: 600;
box-sizing: border-box;
background-color: var(--color-bg-dark);
color: var(--color-text-light);
border-bottom: 1px solid var(--color-border-light);
overflow: hidden;
text-overflow: ellipsis;
}
</style>

View File

@@ -37,6 +37,15 @@
<clipboard-icon/> <clipboard-icon/>
</icon-button> </icon-button>
<icon-button
:disabled="!result"
tooltip="View record"
tooltip-position="top-left"
@click="toggleViewRecord"
>
<export-to-csv-icon/>
</icon-button>
<icon-button <icon-button
:disabled="!result" :disabled="!result"
tooltip="View value" tooltip="View value"
@@ -78,7 +87,16 @@
</div> </div>
<logs v-if="error" :messages="[error]"/> <logs v-if="error" :messages="[error]"/>
<sql-table <sql-table
v-if="result" v-if="result && !viewRecord"
:data-set="result"
:time="time"
:pageSize="pageSize"
class="straight"
@updateSelectedCell="onUpdateSelectedCell"
/>
<record
v-if="result && viewRecord"
:data-set="result" :data-set="result"
:time="time" :time="time"
:pageSize="pageSize" :pageSize="pageSize"
@@ -107,6 +125,7 @@ import loadingDialog from '@/components/LoadingDialog'
import events from '@/lib/utils/events' import events from '@/lib/utils/events'
import Teleport from 'vue2-teleport' import Teleport from 'vue2-teleport'
import ValueViewer from './ValueViewer' import ValueViewer from './ValueViewer'
import Record from './Record/index.vue'
export default { export default {
name: 'RunResult', name: 'RunResult',
@@ -125,7 +144,8 @@ export default {
dataToCopy: null, dataToCopy: null,
viewValuePanelVisible: false, viewValuePanelVisible: false,
selectedCell: null, selectedCell: null,
selectedCellValue: '' selectedCellValue: '',
viewRecord: false
} }
}, },
components: { components: {
@@ -138,6 +158,7 @@ export default {
ClipboardIcon, ClipboardIcon,
loadingDialog, loadingDialog,
ValueViewer, ValueViewer,
Record,
Splitpanes, Splitpanes,
Teleport Teleport
}, },
@@ -229,6 +250,10 @@ export default {
this.viewValuePanelVisible = !this.viewValuePanelVisible this.viewValuePanelVisible = !this.viewValuePanelVisible
}, },
toggleViewRecord () {
this.viewRecord = !this.viewRecord
},
onUpdateSelectedCell (e) { onUpdateSelectedCell (e) {
this.selectedCell = e this.selectedCell = e
this.selectedCellValue = this.selectedCell?.innerText this.selectedCellValue = this.selectedCell?.innerText