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:
@@ -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
58
src/lib/tab.js
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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)
|
||||
},
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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 }
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user