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

Tab refactor

This commit is contained in:
lana-k
2023-06-01 14:42:51 +02:00
parent db3dbdf993
commit 998e8d66f7
15 changed files with 810 additions and 499 deletions

View File

@@ -33,17 +33,16 @@ export default {
},
isTabNeedName (inquiryTab) {
const isFromScratch = !inquiryTab.initName
return inquiryTab.isPredefined || isFromScratch
return inquiryTab.isPredefined || !inquiryTab.name
},
save (inquiryTab, newName) {
const value = {
id: inquiryTab.isPredefined ? nanoid() : inquiryTab.id,
query: inquiryTab.query,
viewType: inquiryTab.$refs.dataView.mode,
viewOptions: inquiryTab.$refs.dataView.getOptionsForSave(),
name: newName || inquiryTab.initName
viewType: inquiryTab.dataView.mode,
viewOptions: inquiryTab.dataView.getOptionsForSave(),
name: newName || inquiryTab.name
}
// Get inquiries from local storage

58
src/lib/tab.js Normal file
View File

@@ -0,0 +1,58 @@
import { nanoid } from 'nanoid'
import time from '@/lib/utils/time'
import events from '@/lib/utils/events'
export default class Tab {
constructor (state, inquiry = {}) {
this.id = inquiry.id || nanoid()
this.name = inquiry.id ? inquiry.name : null
this.tempName = inquiry.name || (state.untitledLastIndex
? `Untitled ${state.untitledLastIndex}`
: 'Untitled')
this.query = inquiry.query
this.viewOptions = inquiry.viewOptions || undefined
this.isPredefined = inquiry.isPredefined
this.viewType = inquiry.viewType || 'chart'
this.result = null
this.isGettingResults = false
this.error = null
this.time = 0
this.layout = inquiry.layout || {
sqlEditor: 'above',
table: 'bottom',
dataView: 'hidden'
}
this.isSaved = !!inquiry.id
this.state = state
}
async execute () {
this.isGettingResults = true
this.result = null
this.error = null
const db = this.state.db
try {
const start = new Date()
this.result = await db.execute(this.query + ';')
this.time = time.getPeriod(start, new Date())
if (this.result && this.result.values) {
events.send('resultset.create',
this.result.values[this.result.columns[0]].length
)
}
events.send('query.run', parseFloat(this.time), { status: 'success' })
} catch (err) {
this.error = {
type: 'error',
message: err
}
events.send('query.run', 0, { status: 'error' })
}
db.refreshSchema()
this.isGettingResults = false
}
}

View File

@@ -1,32 +1,17 @@
import { nanoid } from 'nanoid'
import Tab from '@/lib/tab'
export default {
async addTab ({ state }, data) {
const tab = data ? JSON.parse(JSON.stringify(data)) : {}
// If no data then create a new blank one...
// No data.id means to create new tab, but not blank,
// e.g. with 'select * from csv_import' inquiry after csv import
if (!data || !data.id) {
tab.id = nanoid()
tab.name = null
tab.tempName = state.untitledLastIndex
? `Untitled ${state.untitledLastIndex}`
: 'Untitled'
tab.viewType = 'chart'
tab.viewOptions = undefined
tab.isSaved = false
} else {
tab.isSaved = true
}
// add new tab only if was not already opened
if (!state.tabs.some(openedTab => openedTab.id === tab.id)) {
async addTab ({ state }, inquiry = {}) {
// add new tab only if it was not already opened
if (!state.tabs.some(openedTab => openedTab.id === inquiry.id)) {
const tab = new Tab(state, JSON.parse(JSON.stringify(inquiry)))
state.tabs.push(tab)
if (!tab.name) {
state.untitledLastIndex += 1
}
return tab.id
}
return tab.id
return inquiry.id
}
}

View File

@@ -1,5 +1,3 @@
import Vue from 'vue'
export default {
setDb (state, db) {
if (state.db) {
@@ -8,8 +6,8 @@ export default {
state.db = db
},
updateTab (state, { index, name, id, query, viewType, viewOptions, isSaved }) {
const tab = state.tabs[index]
updateTab (state, { tab, newValues }) {
const { name, id, query, viewType, viewOptions, isSaved } = newValues
const oldId = tab.id
if (id && state.currentTabId === oldId) {
@@ -26,13 +24,12 @@ export default {
// Saved inquiry is not predefined
delete tab.isPredefined
}
Vue.set(state.tabs, index, tab)
},
deleteTab (state, index) {
deleteTab (state, tab) {
const index = state.tabs.indexOf(tab)
// If closing tab is the current opened
if (state.tabs[index].id === state.currentTabId) {
if (tab.id === state.currentTabId) {
if (index < state.tabs.length - 1) {
state.currentTabId = state.tabs[index + 1].id
} else if (index > 0) {
@@ -46,10 +43,12 @@ export default {
state.tabs.splice(index, 1)
},
setCurrentTabId (state, id) {
state.currentTabId = id
},
setCurrentTab (state, tab) {
state.currentTab = tab
try {
state.currentTabId = id
state.currentTab = state.tabs.find(tab => tab.id === id)
} catch (e) {
console.error('Can\'t open a tab id:' + id)
}
},
updatePredefinedInquiries (state, inquiries) {
state.predefinedInquiries = Array.isArray(inquiries) ? inquiries : [inquiries]

View File

@@ -338,12 +338,14 @@ export default {
storedInquiries.updateStorage(this.inquiries)
// update tab, if renamed inquiry is opened
const tabIndex = this.findTabIndex(processedInquiry.id)
if (tabIndex >= 0) {
const tab = this.$store.state.tabs
.find(tab => tab.id === processedInquiry.id)
if (tab) {
this.$store.commit('updateTab', {
index: tabIndex,
name: this.newName,
id: processedInquiry.id
tab,
newValues: {
name: this.newName
}
})
}
// hide dialog
@@ -367,9 +369,10 @@ export default {
this.inquiries.splice(this.processedInquiryIndex, 1)
// Close deleted inquiry tab if it was opened
const tabIndex = this.findTabIndex(this.processedInquiryId)
if (tabIndex >= 0) {
this.$store.commit('deleteTab', tabIndex)
const tab = this.$store.state.tabs
.find(tab => tab.id === this.processedInquiryId)
if (tab) {
this.$store.commit('deleteTab', tab)
}
// Clear checkbox
@@ -383,10 +386,12 @@ export default {
// Close deleted inquiries if it was opened
const tabs = this.$store.state.tabs
for (let i = tabs.length - 1; i >= 0; i--) {
let i = tabs.length - 1
while (i > -1) {
if (this.selectedInquiriesIds.has(tabs[i].id)) {
this.$store.commit('deleteTab', i)
this.$store.commit('deleteTab', tabs[i])
}
i--
}
// Clear checkboxes
@@ -395,9 +400,6 @@ export default {
this.selectedInquiriesCount = this.selectedInquiriesIds.size
storedInquiries.updateStorage(this.inquiries)
},
findTabIndex (id) {
return this.$store.state.tabs.findIndex(tab => tab.id === id)
},
exportToFile (inquiryList, fileName) {
storedInquiries.export(inquiryList, fileName)
},

View File

@@ -80,19 +80,10 @@ export default {
return this.$store.state.currentTab
},
isSaved () {
if (!this.currentInquiry) {
return false
}
const tabIndex = this.currentInquiry.tabIndex
const tab = this.$store.state.tabs[tabIndex]
return tab && tab.isSaved
return this.currentInquiry && this.currentInquiry.isSaved
},
isPredefined () {
if (this.currentInquiry) {
return this.currentInquiry.isPredefined
} else {
return false
}
return this.currentInquiry && this.currentInquiry.isPredefined
},
runDisabled () {
return this.currentInquiry && (!this.$store.state.db || !this.currentInquiry.query)
@@ -145,13 +136,15 @@ export default {
// Update tab in store
this.$store.commit('updateTab', {
index: this.currentInquiry.tabIndex,
name: value.name,
id: value.id,
query: value.query,
viewType: value.viewType,
viewOptions: value.viewOptions,
isSaved: true
tab: this.currentInquiry,
newValues: {
name: value.name,
id: value.id,
query: value.query,
viewType: value.viewType,
viewOptions: value.viewOptions,
isSaved: true
}
})
// Restore data:

View File

@@ -7,40 +7,40 @@
:after="{ size: 50, max: 100 }"
>
<template #left-pane>
<div :id="'above-' + tabIndex" class="above" />
<div :id="'above-' + tab.id" class="above" />
</template>
<template #right-pane>
<div :id="'bottom-'+ tabIndex" ref="bottomPane" class="bottomPane" />
<div :id="'bottom-'+ tab.id" ref="bottomPane" class="bottomPane" />
</template>
</splitpanes>
<div :id="'hidden-'+ tabIndex" class="hidden-part" />
<div :id="'hidden-'+ tab.id" class="hidden-part" />
<teleport :to="`#${layout.sqlEditor}-${tabIndex}`">
<teleport :to="`#${tab.layout.sqlEditor}-${tab.id}`">
<sql-editor
ref="sqlEditor"
v-model="query"
:is-getting-results="isGettingResults"
v-model="tab.query"
:is-getting-results="tab.isGettingResults"
@switchTo="onSwitchView('sqlEditor', $event)"
@run="execute"
@run="tab.execute()"
/>
</teleport>
<teleport :to="`#${layout.table}-${tabIndex}`">
<teleport :to="`#${tab.layout.table}-${tab.id}`">
<run-result
:result="result"
:is-getting-results="isGettingResults"
:error="error"
:time="time"
:result="tab.result"
:is-getting-results="tab.isGettingResults"
:error="tab.error"
:time="tab.time"
@switchTo="onSwitchView('table', $event)"
/>
</teleport>
<teleport :to="`#${layout.dataView}-${tabIndex}`">
<teleport :to="`#${tab.layout.dataView}-${tab.id}`">
<data-view
:data-source="(result && result.values) || null"
:init-options="initViewOptions"
:init-mode="initViewType"
:data-source="(tab.result && tab.result.values) || null"
:init-options="tab.viewOptions"
:init-mode="tab.viewType"
ref="dataView"
@switchTo="onSwitchView('dataView', $event)"
@update="onDataViewUpdate"
@@ -54,15 +54,15 @@ import Splitpanes from '@/components/Splitpanes'
import SqlEditor from './SqlEditor'
import DataView from './DataView'
import RunResult from './RunResult'
import time from '@/lib/utils/time'
import Teleport from 'vue2-teleport'
import events from '@/lib/utils/events'
export default {
name: 'Tab',
props: [
'id', 'initName', 'initQuery', 'initViewOptions', 'tabIndex', 'isPredefined', 'initViewType'
],
props: {
tab: Object
},
components: {
SqlEditor,
DataView,
@@ -70,23 +70,9 @@ export default {
Splitpanes,
Teleport
},
data () {
return {
query: this.initQuery,
result: null,
isGettingResults: false,
error: null,
time: 0,
layout: {
sqlEditor: 'above',
table: 'bottom',
dataView: 'hidden'
}
}
},
computed: {
isActive () {
return this.id === this.$store.state.currentTabId
return this.tab.id === this.$store.state.currentTabId
}
},
watch: {
@@ -94,54 +80,34 @@ export default {
immediate: true,
async handler () {
if (this.isActive) {
this.$store.commit('setCurrentTab', this)
await this.$nextTick()
this.$refs.sqlEditor.focus()
}
}
},
query () {
this.$store.commit('updateTab', { index: this.tabIndex, isSaved: false })
'tab.query' () {
this.$store.commit('updateTab', {
tab: this.tab,
newValues: { isSaved: false }
})
}
},
mounted () {
this.tab.dataView = this.$refs.dataView
},
methods: {
onSwitchView (from, to) {
const fromPosition = this.layout[from]
this.layout[from] = this.layout[to]
this.layout[to] = fromPosition
const fromPosition = this.tab.layout[from]
this.tab.layout[from] = this.tab.layout[to]
this.tab.layout[to] = fromPosition
events.send('inquiry.panel', null, { panel: to })
},
onDataViewUpdate () {
this.$store.commit('updateTab', { index: this.tabIndex, isSaved: false })
},
async execute () {
this.isGettingResults = true
this.result = null
this.error = null
const state = this.$store.state
try {
const start = new Date()
this.result = await state.db.execute(this.query + ';')
this.time = time.getPeriod(start, new Date())
if (this.result && this.result.values) {
events.send('resultset.create',
this.result.values[this.result.columns[0]].length
)
}
events.send('query.run', parseFloat(this.time), { status: 'success' })
} catch (err) {
this.error = {
type: 'error',
message: err
}
events.send('query.run', 0, { status: 'error' })
}
state.db.refreshSchema()
this.isGettingResults = false
this.$store.commit('updateTab', {
tab: this.tab,
newValues: { isSaved: false }
})
}
}
}

View File

@@ -5,7 +5,7 @@
v-for="(tab, index) in tabs"
:key="index"
@click="selectTab(tab.id)"
:class="[{'tab-selected': (tab.id === selectedIndex)}, 'tab']"
:class="[{'tab-selected': (tab.id === selectedTabId)}, 'tab']"
>
<div class="tab-name">
<span v-show="!tab.isSaved" class="star">*</span>
@@ -13,20 +13,14 @@
<span v-else class="tab-untitled">{{ tab.tempName }}</span>
</div>
<div>
<close-icon class="close-icon" :size="10" @click="beforeCloseTab(index)"/>
<close-icon class="close-icon" :size="10" @click="beforeCloseTab(tab)"/>
</div>
</div>
</div>
<tab
v-for="(tab, index) in tabs"
v-for="tab in tabs"
:key="tab.id"
:id="tab.id"
:init-name="tab.name"
:init-query="tab.query"
:init-view-options="tab.viewOptions"
:init-view-type="tab.viewType"
:is-predefined="tab.isPredefined"
:tab-index="index"
:tab="tab"
/>
<div v-show="tabs.length === 0" id="start-guide">
<span class="link" @click="$root.$emit('createNewInquiry')">Create</span>
@@ -38,25 +32,25 @@
<modal name="close-warn" classes="dialog" height="auto">
<div class="dialog-header">
Close tab {{
closingTabIndex !== null
? (tabs[closingTabIndex].name || `[${tabs[closingTabIndex].tempName}]`)
closingTab !== null
? (closingTab.name || `[${closingTab.tempName}]`)
: ''
}}
<close-icon @click="$modal.hide('close-warn')"/>
</div>
<div class="dialog-body">
You have unsaved changes. Save changes in {{
closingTabIndex !== null
? (tabs[closingTabIndex].name || `[${tabs[closingTabIndex].tempName}]`)
closingTab !== null
? (closingTab.name || `[${closingTab.tempName}]`)
: ''
}} before closing?
</div>
<div class="dialog-buttons-container">
<button class="secondary" @click="closeTab(closingTabIndex)">
<button class="secondary" @click="closeTab(closingTab)">
Close without saving
</button>
<button class="secondary" @click="$modal.hide('close-warn')">Cancel</button>
<button class="primary" @click="saveAndClose(closingTabIndex)">Save and close</button>
<button class="primary" @click="saveAndClose(closingTab)">Save and close</button>
</div>
</modal>
</div>
@@ -73,14 +67,14 @@ export default {
},
data () {
return {
closingTabIndex: null
closingTab: null
}
},
computed: {
tabs () {
return this.$store.state.tabs
},
selectedIndex () {
selectedTabId () {
return this.$store.state.currentTabId
}
},
@@ -97,25 +91,24 @@ export default {
selectTab (id) {
this.$store.commit('setCurrentTabId', id)
},
beforeCloseTab (index) {
this.closingTabIndex = index
if (!this.tabs[index].isSaved) {
beforeCloseTab (tab) {
this.closingTab = tab
if (!tab.isSaved) {
this.$modal.show('close-warn')
} else {
this.closeTab(index)
this.closeTab(tab)
}
},
closeTab (index) {
closeTab (tab) {
this.$modal.hide('close-warn')
this.closingTabIndex = null
this.$store.commit('deleteTab', index)
this.$store.commit('deleteTab', tab)
},
saveAndClose (index) {
saveAndClose (tab) {
this.$root.$on('inquirySaved', () => {
this.closeTab(index)
this.closeTab(tab)
this.$root.$off('inquirySaved')
})
this.selectTab(this.tabs[index].id)
this.selectTab(tab.id)
this.$modal.hide('close-warn')
this.$nextTick(() => {
this.$root.$emit('saveInquiry')