mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 10:08:52 +08:00
adding and closing tabs; saving of queries
This commit is contained in:
3
src/assets/images/close.svg
Normal file
3
src/assets/images/close.svg
Normal file
@@ -0,0 +1,3 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14 12.59L8.41 7L14 1.41Z" fill="#A2B1C6"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 232 B |
@@ -28,5 +28,9 @@ button.primary:disabled {
|
||||
color: var(--color-text-light-2);
|
||||
text-shadow: none;
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
button.primary:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,6 @@
|
||||
--color-text-light: var(--color-white);
|
||||
--color-text-light-2: var(--color-gray-medium);
|
||||
--color-text-base: var(--color-gray-dark);
|
||||
--color-text-medium: var(--color-gray-medium);
|
||||
--color-text-active: var(--color-blue-dark-2);
|
||||
|
||||
--shadow: 0 1px 2px rgba(42, 63, 95, 0.7);
|
||||
|
||||
@@ -5,15 +5,68 @@
|
||||
<router-link to="/my-queries">My queries</router-link>
|
||||
</div>
|
||||
<div>
|
||||
<button class="primary" disabled>Save</button>
|
||||
<button class="primary">Create</button>
|
||||
<button
|
||||
v-if="$store.state.tabs.length > 0"
|
||||
class="primary"
|
||||
:disabled="!$store.state.currentTab.isUnsaved"
|
||||
@click="saveQuery"
|
||||
>
|
||||
Save
|
||||
</button>
|
||||
<button class="primary" @click="createNewQuery">Create</button>
|
||||
</div>
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MainMenu'
|
||||
name: 'MainMenu',
|
||||
methods: {
|
||||
createNewQuery () {
|
||||
const tab = {
|
||||
id: Number(new Date()),
|
||||
name: this.$store.state.untitledLastIndex === 3 ? 'Very good query' : null,
|
||||
tempName: this.$store.state.untitledLastIndex
|
||||
? `Untitled ${this.$store.state.untitledLastIndex}`
|
||||
: 'Untitled',
|
||||
isUnsaved: true
|
||||
}
|
||||
this.$store.commit('addTab', tab)
|
||||
this.$store.commit('setCurrentTabId', tab.id)
|
||||
this.$store.commit('updateUntitledLastIndex')
|
||||
},
|
||||
saveQuery () {
|
||||
const currentQuery = this.$store.state.currentTab
|
||||
const isFromScratch = !this.$store.state.currentTab.initName
|
||||
const value = {
|
||||
id: currentQuery.id,
|
||||
query: currentQuery.query
|
||||
// TODO: save plotly settings
|
||||
}
|
||||
|
||||
if (isFromScratch) {
|
||||
value.name = prompt('query name')
|
||||
// TODO: create dialog
|
||||
this.$store.commit('updateTabName', { index: currentQuery.tabIndex, newName: value.name })
|
||||
value.createdAt = new Date()
|
||||
} else {
|
||||
value.name = currentQuery.initName
|
||||
}
|
||||
|
||||
let myQueries = JSON.parse(localStorage.getItem('myQueries'))
|
||||
if (!myQueries) {
|
||||
myQueries = [value]
|
||||
} else if (isFromScratch) {
|
||||
myQueries.push(value)
|
||||
} else {
|
||||
const queryIndex = myQueries.findIndex(query => query.id === currentQuery.id)
|
||||
value.createdAt = myQueries[queryIndex].createdAt
|
||||
myQueries[queryIndex] = value
|
||||
}
|
||||
localStorage.setItem('myQueries', JSON.stringify(myQueries))
|
||||
currentQuery.isUnsaved = false
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style scoped>
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<div slot="left-pane" class="query-editor">
|
||||
<div class="codemirror-container">
|
||||
<codemirror v-model="code" :options="cmOptions" @changes="onCmChange" />
|
||||
<codemirror v-model="query" :options="cmOptions" @changes="onCmChange" ref="codemirror" />
|
||||
</div>
|
||||
<div class="run-btn-container">
|
||||
<button class="primary run-btn" @click="execEditorContents">Run</button>
|
||||
@@ -59,7 +59,7 @@ import 'codemirror/addon/hint/sql-hint.js'
|
||||
|
||||
export default {
|
||||
name: 'TabContent',
|
||||
props: ['name', 'isActive'],
|
||||
props: ['id', 'initName', 'initQuery', 'initPlotly', 'tabIndex'],
|
||||
components: {
|
||||
codemirror,
|
||||
SqlTable,
|
||||
@@ -74,7 +74,7 @@ export default {
|
||||
layout: {},
|
||||
frames: []
|
||||
},
|
||||
code: 'select * from albums',
|
||||
query: 'select * from albums',
|
||||
cmOptions: {
|
||||
// codemirror options
|
||||
tabSize: 4,
|
||||
@@ -86,10 +86,14 @@ export default {
|
||||
result: null,
|
||||
view: 'table',
|
||||
tableViewHeight: 0,
|
||||
worker: this.$store.state.worker
|
||||
worker: this.$store.state.worker,
|
||||
isUnsaved: !this.name
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isActive () {
|
||||
return this.id === this.$store.state.currentTabId
|
||||
},
|
||||
dataSources () {
|
||||
if (!this.result) {
|
||||
return {}
|
||||
@@ -110,13 +114,30 @@ export default {
|
||||
}))
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$store.commit('setCurrentTab', this)
|
||||
},
|
||||
mounted () {
|
||||
new ResizeObserver(this.calculateTableHeight).observe(this.$refs.bottomPane)
|
||||
this.calculateTableHeight()
|
||||
},
|
||||
watch: {
|
||||
isActive () {
|
||||
if (this.isActive) {
|
||||
this.$store.commit('setCurrentTab', this)
|
||||
}
|
||||
},
|
||||
query () {
|
||||
this.isUnsaved = true
|
||||
},
|
||||
isUnsaved () {
|
||||
this.$store.commit('updateTabState', { index: this.tabIndex, newValue: this.isUnsaved })
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
update (data, layout, frames) {
|
||||
this.state = { data, layout, frames }
|
||||
this.isUnsaved = true
|
||||
console.log(this.state)
|
||||
},
|
||||
onCmChange (editor) {
|
||||
@@ -154,7 +175,7 @@ export default {
|
||||
// this.$refs.output.textContent = 'Fetching results...'
|
||||
},
|
||||
execEditorContents () {
|
||||
this.execute(this.code + ';')
|
||||
this.execute(this.query + ';')
|
||||
},
|
||||
calculateTableHeight () {
|
||||
const bottomPane = this.$refs.bottomPane
|
||||
|
||||
@@ -2,19 +2,39 @@
|
||||
<div>
|
||||
<div id="tabs__header">
|
||||
<div
|
||||
v-for="tab in tabs"
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.id"
|
||||
@click="selectTab(tab.id)"
|
||||
:class='{"tab__selected": (tab.id === selectedIndex)}'
|
||||
:class="[{'tab__selected': (tab.id === selectedIndex)}, 'tab']"
|
||||
>
|
||||
{{ tab.name }}
|
||||
<div class="tab-name">
|
||||
<span v-show="tab.isUnsaved">*</span>
|
||||
<span v-if="tab.name">{{ tab.name }}</span>
|
||||
<span v-else class="tab-untitled">{{ tab.tempName }}</span>
|
||||
</div>
|
||||
<div>
|
||||
<svg
|
||||
class="close-icon"
|
||||
@click.stop="closeTab(index)"
|
||||
width="10"
|
||||
height="10"
|
||||
viewBox="0 0 14 14"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14 12.59L8.41 7L14 1.41Z"
|
||||
fill="#A2B1C6"/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tab-content
|
||||
v-for="tab in tabs"
|
||||
v-for="(tab, index) in tabs"
|
||||
:key="tab.id"
|
||||
:is-active="tab.isActive"
|
||||
:name="tab.name"
|
||||
:id="tab.id"
|
||||
:init-name="tab.name"
|
||||
:tab-index="index"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@@ -28,19 +48,22 @@ export default {
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
selectedIndex: 0,
|
||||
tabs: [
|
||||
{ id: 1, name: 'New query', isActive: true },
|
||||
{ id: 2, name: 'New query 2', isActive: false }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
tabs () {
|
||||
return this.$store.state.tabs
|
||||
},
|
||||
selectedIndex () {
|
||||
return this.$store.state.currentTabId
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectTab (id) {
|
||||
this.selectedIndex = id
|
||||
this.tabs.forEach(tab => {
|
||||
tab.isActive = (tab.id === id)
|
||||
})
|
||||
this.$store.commit('setCurrentTabId', id)
|
||||
},
|
||||
closeTab (index) {
|
||||
this.$store.commit('deleteTab', index)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,8 +73,10 @@ export default {
|
||||
#tabs__header {
|
||||
display: flex;
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
#tabs__header div {
|
||||
#tabs__header .tab {
|
||||
height: 36px;
|
||||
background-color: var(--color-bg-light);
|
||||
border-right: 1px solid var(--color-border-light);
|
||||
@@ -62,7 +87,18 @@ export default {
|
||||
padding: 0 12px;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
max-width: 200px;
|
||||
display: flex;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
#tabs__header .tab-name {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
flex-shrink: 1;
|
||||
}
|
||||
|
||||
#tabs__header div:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
@@ -84,4 +120,13 @@ export default {
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.close-icon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.close-icon:hover path {
|
||||
fill: var(--color-text-base);
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -8,7 +8,11 @@ export default new Vuex.Store({
|
||||
schema: null,
|
||||
dbFile: null,
|
||||
dbName: null,
|
||||
worker: new Worker('/js/worker.sql-wasm.js')
|
||||
worker: new Worker('/js/worker.sql-wasm.js'),
|
||||
tabs: [],
|
||||
currentTab: null,
|
||||
currentTabId: null,
|
||||
untitledLastIndex: 0
|
||||
},
|
||||
mutations: {
|
||||
saveSchema (state, schema) {
|
||||
@@ -19,6 +23,41 @@ export default new Vuex.Store({
|
||||
},
|
||||
saveDbName (state, name) {
|
||||
state.dbName = name
|
||||
},
|
||||
addTab (state, tab) {
|
||||
state.tabs.push(tab)
|
||||
},
|
||||
updateTabName (state, { index, newName }) {
|
||||
const tab = state.tabs[index]
|
||||
tab.name = newName
|
||||
Vue.set(state.tabs, index, tab)
|
||||
},
|
||||
updateTabState (state, { index, newValue }) {
|
||||
console.log(index, newValue)
|
||||
const tab = state.tabs[index]
|
||||
tab.isUnsaved = newValue
|
||||
Vue.set(state.tabs, index, tab)
|
||||
},
|
||||
deleteTab (state, index) {
|
||||
if (state.tabs[index].id !== state.currentTabId) {
|
||||
} else if (index < state.tabs.length - 1) {
|
||||
state.currentTabId = state.tabs[index + 1].id
|
||||
} else if (index > 0) {
|
||||
state.currentTabId = state.tabs[index - 1].id
|
||||
} else {
|
||||
state.currentTabId = null
|
||||
state.untitledLastIndex = 0
|
||||
}
|
||||
state.tabs.splice(index, 1)
|
||||
},
|
||||
setCurrentTabId (state, id) {
|
||||
state.currentTabId = id
|
||||
},
|
||||
setCurrentTab (state, tab) {
|
||||
state.currentTab = tab
|
||||
},
|
||||
updateUntitledLastIndex (state) {
|
||||
state.untitledLastIndex += 1
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
||||
Reference in New Issue
Block a user