1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2026-02-04 15:38:55 +08:00
Files
sqliteviz/src/components/RunResult/Record/index.vue
2026-01-15 21:53:12 +01:00

229 lines
5.4 KiB
Vue

<template>
<div class="record-view">
<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 class="column-cell" :title="col">
{{ col }}
</th>
<td
:key="index"
:data-col="index"
:data-row="currentRowIndex"
:data-isNull="isNull(getCellValue(col))"
:data-isBlob="isBlob(getCellValue(col))"
:aria-selected="false"
@click="onCellClick"
>
<div class="cell-data">
{{ getCellText(col) }}
</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'
import { nextTick } from 'vue'
export default {
components: { RowNavigator },
props: {
dataSet: Object,
time: String,
rowIndex: { type: Number, default: 0 },
selectedColumnIndex: Number
},
emits: ['updateSelectedCell'],
data() {
return {
selectedCellElement: null,
currentRowIndex: this.rowIndex
}
},
computed: {
columns() {
return this.dataSet.columns
},
rowCount() {
return this.dataSet.values[this.columns[0]].length
}
},
watch: {
async currentRowIndex() {
await nextTick()
if (this.selectedCellElement) {
const previouslySelected = this.selectedCellElement
this.selectCell(null)
this.selectCell(previouslySelected)
}
}
},
mounted() {
const col = this.selectedColumnIndex
const row = this.currentRowIndex
const cell = this.$refs.table.querySelector(
`td[data-col="${col}"][data-row="${row}"]`
)
if (cell) {
this.selectCell(cell)
}
},
methods: {
isBlob(value) {
return value && ArrayBuffer.isView(value)
},
isNull(value) {
return value === null
},
getCellValue(col) {
return this.dataSet.values[col][this.currentRowIndex]
},
getCellText(col) {
const value = this.getCellValue(col)
if (this.isNull(value)) {
return 'NULL'
}
if (this.isBlob(value)) {
return 'BLOB'
}
return value
},
onTableKeydown(e) {
const keyCodeMap = {
38: 'up',
40: 'down'
}
if (
!this.selectedCellElement ||
!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'), false)
},
selectCell(cell, scrollTo = true) {
if (!cell) {
if (this.selectedCellElement) {
this.selectedCellElement.ariaSelected = 'false'
}
this.selectedCellElement = cell
} else 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
}
if (this.selectedCellElement && scrollTo) {
this.selectedCellElement.scrollIntoView()
this.selectedCellElement
.closest('.table-container')
.scrollTo({ left: 0 })
}
this.$emit('updateSelectedCell', this.selectedCellElement)
},
moveFocusInTable(initialCell, direction) {
const currentColIndex = +initialCell.dataset.col
const newColIndex =
direction === 'up' ? currentColIndex - 1 : currentColIndex + 1
const newCell = this.$refs.table.querySelector(
`td[data-col="${newColIndex}"][data-row="${this.currentRowIndex}"]`
)
if (newCell) {
this.selectCell(newCell)
}
}
}
}
</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);
text-align: left;
}
.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);
border-right: 1px solid var(--color-border-light);
overflow: hidden;
text-overflow: ellipsis;
text-align: left;
}
.table-footer {
align-items: center;
}
.record-view {
display: flex;
flex-direction: column;
height: 100%;
}
.table-container {
flex-grow: 1;
overflow: auto;
}
.column-cell {
max-width: 150px;
width: 0;
}
</style>