mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 18:18:53 +08:00
343 lines
8.8 KiB
Vue
343 lines
8.8 KiB
Vue
<template>
|
|
<div>
|
|
<div id="my-queries-content">
|
|
<div id="my-queries-toolbar">
|
|
<div id="toolbar-buttons">
|
|
<input
|
|
ref="importFile"
|
|
type="file"
|
|
accept=".json"
|
|
id="import-file"
|
|
@change="importQueries"
|
|
/>
|
|
<button class="toolbar">
|
|
<label for="import-file">
|
|
Import
|
|
</label>
|
|
</button>
|
|
<button class="toolbar" v-show="selectedQueries.length > 0">Export</button>
|
|
<button class="toolbar" v-show="selectedQueries.length > 0">Delete</button>
|
|
</div>
|
|
<div id="toolbar-search">
|
|
<text-field placeholder="Search query by name" width="300px"/>
|
|
</div>
|
|
</div>
|
|
<div class="rounded-bg">
|
|
<div class="header-container">
|
|
<div>
|
|
<div class="fixed-header" ref="name-th">
|
|
Name
|
|
</div>
|
|
<div class="fixed-header">
|
|
Created at
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div
|
|
class="table-container"
|
|
ref="table-container"
|
|
>
|
|
<table ref="table">
|
|
<tbody>
|
|
<tr v-for="(query, index) in queries" :key="query.id" @click="openQuery(index)">
|
|
<td ref="name-td">
|
|
{{ query.name }}
|
|
</td>
|
|
<td>
|
|
<div class="second-column">
|
|
<div class="date-container">{{ query.createdAt | date }}</div>
|
|
<div class="icons-container">
|
|
<rename-icon @click="showRenameDialog(index)" />
|
|
<copy-icon @click="duplicateQuery(index)"/>
|
|
<export-icon @click="exportQuery(index)"/>
|
|
<delete-icon @click="showDeleteDialog(index)"/>
|
|
</div>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!--Rename Query dialog -->
|
|
<modal name="rename" classes="dialog" height="auto">
|
|
<div class="dialog-header">
|
|
Rename query
|
|
<close-icon @click="$modal.hide('rename')"/>
|
|
</div>
|
|
<div class="dialog-body">
|
|
<text-field
|
|
label="New query name"
|
|
:error-msg="errorMsg"
|
|
v-model="newName"
|
|
width="100%"
|
|
/>
|
|
</div>
|
|
<div class="dialog-buttons-container">
|
|
<button class="secondary" @click="$modal.hide('rename')">Cancel</button>
|
|
<button class="primary" @click="renameQuery">Rename</button>
|
|
</div>
|
|
</modal>
|
|
|
|
<!--Delete Query dialog -->
|
|
<modal name="delete" classes="dialog" height="auto">
|
|
<div class="dialog-header">
|
|
Delete query
|
|
<close-icon @click="$modal.hide('delete')"/>
|
|
</div>
|
|
<div
|
|
v-if="
|
|
currentQueryIndex !== null
|
|
&& currentQueryIndex >= 0
|
|
&& currentQueryIndex < queries.length
|
|
"
|
|
class="dialog-body"
|
|
>
|
|
Are you sure you want to delete
|
|
"{{ queries[currentQueryIndex].name }}"?
|
|
</div>
|
|
<div class="dialog-buttons-container">
|
|
<button class="secondary" @click="$modal.hide('delete')">Cancel</button>
|
|
<button class="primary" @click="deleteQuery">Delete</button>
|
|
</div>
|
|
</modal>
|
|
<a ref="downloader" />
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
import RenameIcon from '@/components/svg/rename'
|
|
import CopyIcon from '@/components/svg/copy'
|
|
import ExportIcon from '@/components/svg/export'
|
|
import DeleteIcon from '@/components/svg/delete'
|
|
import CloseIcon from '@/components/svg/close'
|
|
import TextField from '@/components/TextField'
|
|
import { nanoid } from 'nanoid'
|
|
|
|
export default {
|
|
name: 'MyQueries',
|
|
components: {
|
|
RenameIcon,
|
|
CopyIcon,
|
|
ExportIcon,
|
|
DeleteIcon,
|
|
CloseIcon,
|
|
TextField
|
|
},
|
|
data () {
|
|
return {
|
|
queries: [],
|
|
newName: null,
|
|
currentQueryIndex: null,
|
|
errorMsg: null,
|
|
selectedQueries: []
|
|
}
|
|
},
|
|
created () {
|
|
this.queries = JSON.parse(localStorage.getItem('myQueries'))
|
|
},
|
|
mounted () {
|
|
new ResizeObserver(this.calcNameWidth).observe(this.$refs.table)
|
|
this.calcNameWidth()
|
|
},
|
|
filters: {
|
|
date (value) {
|
|
if (!value) {
|
|
return ''
|
|
}
|
|
const dateOptions = { year: 'numeric', month: 'long', day: 'numeric' }
|
|
const timeOptions = {
|
|
hour12: false,
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
}
|
|
return new Date(value).toLocaleDateString('en-GB', dateOptions) + ' ' +
|
|
new Date(value).toLocaleTimeString('en-GB', timeOptions)
|
|
}
|
|
},
|
|
methods: {
|
|
calcNameWidth () {
|
|
this.$refs['name-th'].style = `width: ${this.$refs['name-td'][0].offsetWidth}px`
|
|
},
|
|
openQuery (index) {
|
|
const tab = this.queries[index]
|
|
tab.isUnsaved = false
|
|
this.$store.commit('addTab', tab)
|
|
this.$store.commit('setCurrentTabId', tab.id)
|
|
this.$router.push('/editor')
|
|
},
|
|
showRenameDialog (index) {
|
|
this.errorMsg = null
|
|
this.currentQueryIndex = index
|
|
this.newName = this.queries[this.currentQueryIndex].name
|
|
this.$modal.show('rename')
|
|
},
|
|
renameQuery () {
|
|
if (!this.newName) {
|
|
this.errorMsg = 'Query name can\'t be empty'
|
|
return
|
|
}
|
|
const currentQuery = this.queries[this.currentQueryIndex]
|
|
currentQuery.name = this.newName
|
|
this.$set(this.queries, this.currentQueryIndex, currentQuery)
|
|
this.$modal.hide('rename')
|
|
this.saveQueriesInLocalStorage()
|
|
const tabIndex = this.findTabIndex(currentQuery.id)
|
|
if (tabIndex >= 0) {
|
|
this.$store.commit('updateTabName', { index: tabIndex, newName: this.newName })
|
|
}
|
|
},
|
|
duplicateQuery (index) {
|
|
const newQuery = JSON.parse(JSON.stringify(this.queries[index]))
|
|
newQuery.name = newQuery.name + ' Copy'
|
|
newQuery.id = nanoid()
|
|
newQuery.createdAt = new Date()
|
|
this.queries.push(newQuery)
|
|
this.saveQueriesInLocalStorage()
|
|
},
|
|
showDeleteDialog (index) {
|
|
this.currentQueryIndex = index
|
|
this.$modal.show('delete')
|
|
},
|
|
deleteQuery () {
|
|
this.$modal.hide('delete')
|
|
const id = this.queries[this.currentQueryIndex].id
|
|
this.queries.splice(this.currentQueryIndex, 1)
|
|
this.saveQueriesInLocalStorage()
|
|
const tabIndex = this.findTabIndex(id)
|
|
if (tabIndex >= 0) {
|
|
this.$store.commit('deleteTab', tabIndex)
|
|
}
|
|
},
|
|
findTabIndex (id) {
|
|
return this.$store.state.tabs.findIndex(tab => tab.id === id)
|
|
},
|
|
exportQuery (index) {
|
|
this.currentQueryIndex = index
|
|
const downloader = this.$refs.downloader
|
|
const currentQuery = JSON.parse(JSON.stringify(this.queries[this.currentQueryIndex]))
|
|
delete currentQuery.id
|
|
delete currentQuery.createdAt
|
|
const json = JSON.stringify(currentQuery)
|
|
const blob = new Blob([json], { type: 'octet/stream' })
|
|
const url = window.URL.createObjectURL(blob)
|
|
downloader.href = url
|
|
downloader.download = `${currentQuery.name}.json`
|
|
downloader.click()
|
|
window.URL.revokeObjectURL(url)
|
|
},
|
|
importQueries () {
|
|
const file = this.$refs.importFile.files[0]
|
|
const reader = new FileReader()
|
|
reader.onload = () => {
|
|
let importedQueries = JSON.parse(event.target.result)
|
|
|
|
if (!Array.isArray(importedQueries)) {
|
|
importedQueries = [importedQueries]
|
|
}
|
|
|
|
importedQueries.forEach(query => {
|
|
query.id = nanoid()
|
|
query.createdAt = new Date()
|
|
})
|
|
|
|
this.queries = this.queries.concat(importedQueries)
|
|
this.saveQueriesInLocalStorage()
|
|
this.$refs.importFile.value = null
|
|
}
|
|
reader.readAsText(file)
|
|
},
|
|
saveQueriesInLocalStorage () {
|
|
localStorage.setItem('myQueries', JSON.stringify(this.queries))
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
#my-queries-content {
|
|
padding: 52px;
|
|
}
|
|
|
|
#my-queries-toolbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
margin-bottom: 18px;
|
|
margin: 0 auto 8px;
|
|
max-width: 1500px;
|
|
width: 100%;
|
|
}
|
|
|
|
.rounded-bg {
|
|
margin: 0 auto;
|
|
max-width: 1500px;
|
|
width: 100%;
|
|
}
|
|
table {
|
|
margin-top: 0;
|
|
}
|
|
tbody tr td {
|
|
overflow: hidden;
|
|
min-width: 0;
|
|
text-overflow: ellipsis;
|
|
padding: 0 24px;
|
|
line-height: 40px;
|
|
}
|
|
|
|
tbody tr td:first-child {
|
|
width: 70%;
|
|
max-width: 0;
|
|
}
|
|
tbody tr td:last-child {
|
|
width: 30%;
|
|
max-width: 0;
|
|
}
|
|
|
|
tbody tr:hover td {
|
|
cursor: pointer;
|
|
}
|
|
|
|
tbody tr:hover td {
|
|
color: var(--color-text-active);
|
|
}
|
|
|
|
.second-column {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
width: 100%;
|
|
max-width: 100%;
|
|
}
|
|
|
|
.icons-container {
|
|
display: none;
|
|
}
|
|
.date-container {
|
|
flex-shrink: 1;
|
|
min-width: 0;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
tbody tr:hover .icons-container {
|
|
display: block;
|
|
}
|
|
.dialog input {
|
|
width: 100%;
|
|
}
|
|
a, #import-file {
|
|
display: none;
|
|
}
|
|
button.toolbar {
|
|
margin-right: 16px;
|
|
}
|
|
button label {
|
|
display: block;
|
|
line-height: 36px;
|
|
}
|
|
button label:hover {
|
|
cursor: pointer;
|
|
}
|
|
</style>
|