mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 18:18:53 +08:00
add predefined queries
This commit is contained in:
47
public/queries.json
Normal file
47
public/queries.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"query": "select * from invoices",
|
||||||
|
"chart": {
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"type": "scatter",
|
||||||
|
"mode": "lines",
|
||||||
|
"x": null,
|
||||||
|
"xsrc": "InvoiceId",
|
||||||
|
"meta": {
|
||||||
|
"columnNames": {
|
||||||
|
"x": "InvoiceId",
|
||||||
|
"y": "Total"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": null,
|
||||||
|
"ysrc": "Total"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"layout": {
|
||||||
|
"xaxis": {
|
||||||
|
"range": [
|
||||||
|
1,
|
||||||
|
412
|
||||||
|
],
|
||||||
|
"autorange": true,
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"yaxis": {
|
||||||
|
"range": [
|
||||||
|
-0.39166666666666683,
|
||||||
|
27.241666666666667
|
||||||
|
],
|
||||||
|
"autorange": true,
|
||||||
|
"type": "linear"
|
||||||
|
},
|
||||||
|
"autosize": true,
|
||||||
|
"mapbox": {
|
||||||
|
"style": "open-street-map"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"frames": []
|
||||||
|
},
|
||||||
|
"name": "Invoices",
|
||||||
|
"id": "ieZfcITwDUTADwOmQlYyL",
|
||||||
|
"createdAt": "2020-11-03T14:17:49.524Z"
|
||||||
|
}
|
||||||
3
src/assets/images/info.svg
Normal file
3
src/assets/images/info.svg
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path d="M11 9H13V7H11V9ZM12 20C7.59 20 4 16.41 4 12C4 7.59 7.59 4 12 4C16.41 4 20 7.59 20 12C20 16.41 16.41 20 12 20ZM12 2C10.6868 2 9.38642 2.25866 8.17317 2.7612C6.95991 3.26375 5.85752 4.00035 4.92893 4.92893C3.05357 6.8043 2 9.34784 2 12C2 14.6522 3.05357 17.1957 4.92893 19.0711C5.85752 19.9997 6.95991 20.7362 8.17317 21.2388C9.38642 21.7413 10.6868 22 12 22C14.6522 22 17.1957 20.9464 19.0711 19.0711C20.9464 17.1957 22 14.6522 22 12C22 10.6868 21.7413 9.38642 21.2388 8.17317C20.7362 6.95991 19.9997 5.85752 19.0711 4.92893C18.1425 4.00035 17.0401 3.26375 15.8268 2.7612C14.6136 2.25866 13.3132 2 12 2V2ZM11 17H13V11H11V17Z" fill="#A2B1C6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 754 B |
@@ -35,11 +35,6 @@ export default {
|
|||||||
checked: this.init
|
checked: this.init
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
|
||||||
checked () {
|
|
||||||
this.$emit('change', this.checked)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
methods: {
|
||||||
onClick () {
|
onClick () {
|
||||||
this.checked = !this.checked
|
this.checked = !this.checked
|
||||||
|
|||||||
@@ -8,21 +8,70 @@
|
|||||||
<button
|
<button
|
||||||
v-if="$store.state.tabs.length > 0"
|
v-if="$store.state.tabs.length > 0"
|
||||||
class="primary"
|
class="primary"
|
||||||
:disabled="$store.state.currentTab && !$store.state.currentTab.isUnsaved"
|
:disabled="currentQuery && !currentQuery.isUnsaved"
|
||||||
@click="saveQuery"
|
@click="checkQueryBeforeSave"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
<button class="primary" @click="createNewQuery">Create</button>
|
<button class="primary" @click="createNewQuery">Create</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!--Save Query dialog -->
|
||||||
|
<modal name="save" classes="dialog" height="auto">
|
||||||
|
<div class="dialog-header">
|
||||||
|
Save query
|
||||||
|
<close-icon @click="$modal.hide('save')"/>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<div v-show="isPredefined" id="save-note">
|
||||||
|
<img :src="require('@/assets/images/info.svg')">
|
||||||
|
Note: Predefined queries can't be edited.
|
||||||
|
That's why your modifications will be saved as a new query. Enter the name for it.
|
||||||
|
</div>
|
||||||
|
<text-field
|
||||||
|
label="Query name"
|
||||||
|
:error-msg="errorMsg"
|
||||||
|
v-model="name"
|
||||||
|
width="100%"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-buttons-container">
|
||||||
|
<button class="secondary" @click="$modal.hide('save')">Cancel</button>
|
||||||
|
<button class="primary" @click="saveQuery">Save</button>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from 'nanoid'
|
||||||
|
import TextField from '@/components/TextField'
|
||||||
|
import CloseIcon from '@/components/svg/close'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MainMenu',
|
name: 'MainMenu',
|
||||||
|
components: {
|
||||||
|
TextField,
|
||||||
|
CloseIcon
|
||||||
|
},
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
errorMsg: null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
currentQuery () {
|
||||||
|
return this.$store.state.currentTab
|
||||||
|
},
|
||||||
|
isPredefined () {
|
||||||
|
if (this.currentQuery) {
|
||||||
|
return this.currentQuery.isPredefined
|
||||||
|
} else {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
created () {
|
created () {
|
||||||
this.$root.$on('createNewQuery', this.createNewQuery)
|
this.$root.$on('createNewQuery', this.createNewQuery)
|
||||||
},
|
},
|
||||||
@@ -38,38 +87,69 @@ export default {
|
|||||||
}
|
}
|
||||||
this.$store.commit('addTab', tab)
|
this.$store.commit('addTab', tab)
|
||||||
this.$store.commit('setCurrentTabId', tab.id)
|
this.$store.commit('setCurrentTabId', tab.id)
|
||||||
this.$store.commit('updateUntitledLastIndex')
|
},
|
||||||
|
checkQueryBeforeSave () {
|
||||||
|
this.errorMsg = null
|
||||||
|
const isFromScratch = !this.currentQuery.initName
|
||||||
|
|
||||||
|
if (isFromScratch || this.isPredefined) {
|
||||||
|
this.$modal.show('save')
|
||||||
|
} else {
|
||||||
|
this.saveQuery()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
saveQuery () {
|
saveQuery () {
|
||||||
const currentQuery = this.$store.state.currentTab
|
const isFromScratch = !this.currentQuery.initName
|
||||||
const isFromScratch = !this.$store.state.currentTab.initName
|
if ((isFromScratch || this.isPredefined) && !this.name) {
|
||||||
|
this.errorMsg = 'Query name can\'t be empty'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const dataSet = this.currentQuery.result
|
||||||
|
const tabView = this.currentQuery.view
|
||||||
|
// Prepare query
|
||||||
const value = {
|
const value = {
|
||||||
id: currentQuery.id,
|
id: this.isPredefined ? nanoid() : this.currentQuery.id,
|
||||||
query: currentQuery.query,
|
query: this.currentQuery.query,
|
||||||
chart: currentQuery.getChartSatateForSave()
|
chart: this.currentQuery.getChartSatateForSave(),
|
||||||
}
|
name: (!this.isPredefined && this.currentQuery.initName) || this.name,
|
||||||
|
createdAt: new Date()
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Save query
|
||||||
let myQueries = JSON.parse(localStorage.getItem('myQueries'))
|
let myQueries = JSON.parse(localStorage.getItem('myQueries'))
|
||||||
if (!myQueries) {
|
if (!myQueries) {
|
||||||
myQueries = [value]
|
myQueries = [value]
|
||||||
} else if (isFromScratch) {
|
} else if (isFromScratch || this.isPredefined) {
|
||||||
myQueries.push(value)
|
myQueries.push(value)
|
||||||
} else {
|
} else {
|
||||||
const queryIndex = myQueries.findIndex(query => query.id === currentQuery.id)
|
const queryIndex = myQueries.findIndex(query => query.id === this.currentQuery.id)
|
||||||
value.createdAt = myQueries[queryIndex].createdAt
|
value.createdAt = myQueries[queryIndex].createdAt
|
||||||
myQueries[queryIndex] = value
|
myQueries[queryIndex] = value
|
||||||
}
|
}
|
||||||
localStorage.setItem('myQueries', JSON.stringify(myQueries))
|
localStorage.setItem('myQueries', JSON.stringify(myQueries))
|
||||||
currentQuery.isUnsaved = false
|
|
||||||
|
// Update tab
|
||||||
|
this.$store.commit('updateTab', {
|
||||||
|
index: this.currentQuery.tabIndex,
|
||||||
|
name: value.name,
|
||||||
|
id: value.id,
|
||||||
|
query: value.query,
|
||||||
|
chart: value.chart,
|
||||||
|
isUnsaved: false
|
||||||
|
})
|
||||||
|
|
||||||
|
// Restore data:
|
||||||
|
// e.g. if we save predefined query the tab will be created again
|
||||||
|
// (because of new id) and
|
||||||
|
// it will be without sql result and has default view - table.
|
||||||
|
// That's why we need to restore data and view
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.currentQuery.result = dataSet
|
||||||
|
this.currentQuery.view = tabView
|
||||||
|
})
|
||||||
|
|
||||||
|
// Hide dialog
|
||||||
|
this.$modal.hide('save')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -89,6 +169,7 @@ nav {
|
|||||||
left: 0;
|
left: 0;
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
padding: 0 52px;
|
padding: 0 52px;
|
||||||
|
z-index: 999;
|
||||||
}
|
}
|
||||||
a {
|
a {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
@@ -103,4 +184,13 @@ a.router-link-active {
|
|||||||
button {
|
button {
|
||||||
margin-left: 16px;
|
margin-left: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#save-note {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
#save-note img {
|
||||||
|
margin: -3px 6px 0 0;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
<table ref="table">
|
<table ref="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th v-for="(th,index) in data.columns" :key="index" ref="th">
|
<th v-for="(th,index) in dataSet.columns" :key="index" ref="th">
|
||||||
<div class="cell-data" :style="cellStyle">{{ th }}</div>
|
<div class="cell-data" :style="cellStyle">{{ th }}</div>
|
||||||
</th>
|
</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="table-footer">
|
<div class="table-footer">
|
||||||
<div class="table-footer-count">
|
<div class="table-footer-count">
|
||||||
{{ data.values.length}} {{data.values.length === 1 ? 'row' : 'rows'}} retrieved
|
{{ dataSet.values.length}} {{dataSet.values.length === 1 ? 'row' : 'rows'}} retrieved
|
||||||
</div>
|
</div>
|
||||||
<pager v-show="pageCount > 1" :page-count="pageCount" v-model="currentPage" />
|
<pager v-show="pageCount > 1" :page-count="pageCount" v-model="currentPage" />
|
||||||
</div>
|
</div>
|
||||||
@@ -52,17 +52,18 @@ import Pager from '@/components/Pager'
|
|||||||
export default {
|
export default {
|
||||||
name: 'SqlTable',
|
name: 'SqlTable',
|
||||||
components: { Pager },
|
components: { Pager },
|
||||||
props: ['data', 'height'],
|
props: ['dataSet', 'height'],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
header: null,
|
header: null,
|
||||||
tableWidth: null,
|
tableWidth: null,
|
||||||
currentPage: 1
|
currentPage: 1,
|
||||||
|
resizeObserver: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
cellStyle () {
|
cellStyle () {
|
||||||
const eq = this.tableWidth / this.data.columns.length
|
const eq = this.tableWidth / this.dataSet.columns.length
|
||||||
|
|
||||||
return { maxWidth: `${Math.max(eq, 100)}px` }
|
return { maxWidth: `${Math.max(eq, 100)}px` }
|
||||||
},
|
},
|
||||||
@@ -70,11 +71,11 @@ export default {
|
|||||||
return Math.max(Math.floor(this.height / 40), 20)
|
return Math.max(Math.floor(this.height / 40), 20)
|
||||||
},
|
},
|
||||||
pageCount () {
|
pageCount () {
|
||||||
return Math.ceil(this.data.values.length / this.pageSize)
|
return Math.ceil(this.dataSet.values.length / this.pageSize)
|
||||||
},
|
},
|
||||||
currentPageData () {
|
currentPageData () {
|
||||||
const start = (this.currentPage - 1) * this.pageSize
|
const start = (this.currentPage - 1) * this.pageSize
|
||||||
return this.data.values.slice(start, start + this.pageSize)
|
return this.dataSet.values.slice(start, start + this.pageSize)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -94,12 +95,16 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
new ResizeObserver(this.calculateHeadersWidth).observe(this.$refs.table)
|
this.resizeObserver = new ResizeObserver(this.calculateHeadersWidth)
|
||||||
|
this.resizeObserver.observe(this.$refs.table)
|
||||||
this.calculateHeadersWidth()
|
this.calculateHeadersWidth()
|
||||||
},
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.resizeObserver.unobserve(this.$refs.table)
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentPageData: 'calculateHeadersWidth',
|
currentPageData: 'calculateHeadersWidth',
|
||||||
data () {
|
dataSet () {
|
||||||
this.currentPage = 1
|
this.currentPage = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@
|
|||||||
<div v-show="error" class="table-preview error">
|
<div v-show="error" class="table-preview error">
|
||||||
{{ error }}
|
{{ error }}
|
||||||
</div>
|
</div>
|
||||||
<sql-table v-if="result" :data="result" :height="tableViewHeight" />
|
<sql-table v-if="result" :data-set="result" :height="tableViewHeight" />
|
||||||
</div>
|
</div>
|
||||||
<chart
|
<chart
|
||||||
:visible="view === 'chart'"
|
:visible="view === 'chart'"
|
||||||
@@ -59,8 +59,8 @@ import ViewSwitcher from '@/components/ViewSwitcher'
|
|||||||
import Chart from '@/components/Chart'
|
import Chart from '@/components/Chart'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'TabContent',
|
name: 'Tab',
|
||||||
props: ['id', 'initName', 'initQuery', 'initChart', 'tabIndex'],
|
props: ['id', 'initName', 'initQuery', 'initChart', 'tabIndex', 'isPredefined'],
|
||||||
components: {
|
components: {
|
||||||
SqlEditor,
|
SqlEditor,
|
||||||
SqlTable,
|
SqlTable,
|
||||||
@@ -76,7 +76,8 @@ export default {
|
|||||||
tableViewHeight: 0,
|
tableViewHeight: 0,
|
||||||
isUnsaved: !this.initName,
|
isUnsaved: !this.initName,
|
||||||
isGettingResults: false,
|
isGettingResults: false,
|
||||||
error: null
|
error: null,
|
||||||
|
resizeObserver: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -88,9 +89,13 @@ export default {
|
|||||||
this.$store.commit('setCurrentTab', this)
|
this.$store.commit('setCurrentTab', this)
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
new ResizeObserver(this.handleResize).observe(this.$refs.bottomPane)
|
this.resizeObserver = new ResizeObserver(this.handleResize)
|
||||||
|
this.resizeObserver.observe(this.$refs.bottomPane)
|
||||||
this.calculateTableHeight()
|
this.calculateTableHeight()
|
||||||
},
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.resizeObserver.unobserve(this.$refs.bottomPane)
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
isActive () {
|
isActive () {
|
||||||
if (this.isActive) {
|
if (this.isActive) {
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
<div id="tabs__header" v-if="tabs.length > 0">
|
<div id="tabs__header" v-if="tabs.length > 0">
|
||||||
<div
|
<div
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
:key="tab.id"
|
:key="index"
|
||||||
@click="selectTab(tab.id)"
|
@click="selectTab(tab.id)"
|
||||||
:class="[{'tab__selected': (tab.id === selectedIndex)}, 'tab']"
|
:class="[{'tab__selected': (tab.id === selectedIndex)}, 'tab']"
|
||||||
>
|
>
|
||||||
@@ -29,13 +29,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<tab-content
|
<tab
|
||||||
v-for="(tab, index) in tabs"
|
v-for="(tab, index) in tabs"
|
||||||
:key="tab.id"
|
:key="tab.id"
|
||||||
:id="tab.id"
|
:id="tab.id"
|
||||||
:init-name="tab.name"
|
:init-name="tab.name"
|
||||||
:init-query="tab.query"
|
:init-query="tab.query"
|
||||||
:init-chart="tab.chart"
|
:init-chart="tab.chart"
|
||||||
|
:is-predefined="tab.isPredefined"
|
||||||
:tab-index="index"
|
:tab-index="index"
|
||||||
/>
|
/>
|
||||||
<div v-if="tabs.length === 0" id="start-guide">
|
<div v-if="tabs.length === 0" id="start-guide">
|
||||||
@@ -47,11 +48,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TabContent from '@/components/TabContent'
|
import Tab from '@/components/Tab'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
TabContent
|
Tab
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
@@ -153,5 +154,6 @@ export default {
|
|||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.icon {
|
.icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: 0 0 0 12px;
|
margin: 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon:hover path {
|
.icon:hover path {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default {
|
|||||||
<style scoped>
|
<style scoped>
|
||||||
.icon {
|
.icon {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: 0 12px 0 6px;
|
margin: 0 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon:hover path {
|
.icon:hover path {
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ export default new Vuex.Store({
|
|||||||
tabs: [],
|
tabs: [],
|
||||||
currentTab: null,
|
currentTab: null,
|
||||||
currentTabId: null,
|
currentTabId: null,
|
||||||
untitledLastIndex: 0
|
untitledLastIndex: 0,
|
||||||
|
predefinedQueries: []
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
saveSchema (state, schema) {
|
saveSchema (state, schema) {
|
||||||
@@ -25,14 +26,29 @@ export default new Vuex.Store({
|
|||||||
},
|
},
|
||||||
addTab (state, tab) {
|
addTab (state, tab) {
|
||||||
state.tabs.push(tab)
|
state.tabs.push(tab)
|
||||||
|
|
||||||
|
if (!tab.name) {
|
||||||
|
state.untitledLastIndex += 1
|
||||||
|
}
|
||||||
},
|
},
|
||||||
updateTabName (state, { index, newName }) {
|
updateTab (state, { index, name, id, query, chart, isUnsaved }) {
|
||||||
const tab = state.tabs[index]
|
const tab = state.tabs[index]
|
||||||
tab.name = newName
|
const oldId = tab.id
|
||||||
|
|
||||||
|
if (state.currentTabId === oldId) {
|
||||||
|
state.currentTabId = id
|
||||||
|
}
|
||||||
|
|
||||||
|
tab.id = id
|
||||||
|
if (name) { tab.name = name }
|
||||||
|
if (query) { tab.query = query }
|
||||||
|
if (chart) { tab.chart = chart }
|
||||||
|
if (isUnsaved !== undefined) { tab.isUnsaved = isUnsaved }
|
||||||
|
delete tab.isPredefined
|
||||||
|
|
||||||
Vue.set(state.tabs, index, tab)
|
Vue.set(state.tabs, index, tab)
|
||||||
},
|
},
|
||||||
updateTabState (state, { index, newValue }) {
|
updateTabState (state, { index, newValue }) {
|
||||||
console.log(index, newValue)
|
|
||||||
const tab = state.tabs[index]
|
const tab = state.tabs[index]
|
||||||
tab.isUnsaved = newValue
|
tab.isUnsaved = newValue
|
||||||
Vue.set(state.tabs, index, tab)
|
Vue.set(state.tabs, index, tab)
|
||||||
@@ -45,6 +61,7 @@ export default new Vuex.Store({
|
|||||||
state.currentTabId = state.tabs[index - 1].id
|
state.currentTabId = state.tabs[index - 1].id
|
||||||
} else {
|
} else {
|
||||||
state.currentTabId = null
|
state.currentTabId = null
|
||||||
|
state.currentTab = null
|
||||||
state.untitledLastIndex = 0
|
state.untitledLastIndex = 0
|
||||||
}
|
}
|
||||||
state.tabs.splice(index, 1)
|
state.tabs.splice(index, 1)
|
||||||
@@ -55,12 +72,14 @@ export default new Vuex.Store({
|
|||||||
setCurrentTab (state, tab) {
|
setCurrentTab (state, tab) {
|
||||||
state.currentTab = tab
|
state.currentTab = tab
|
||||||
},
|
},
|
||||||
updateUntitledLastIndex (state) {
|
updatePredefinedQueries (state, queries) {
|
||||||
state.untitledLastIndex += 1
|
if (Array.isArray(queries)) {
|
||||||
|
state.predefinedQueries = queries
|
||||||
|
} else {
|
||||||
|
state.predefinedQueries = [queries]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
},
|
|
||||||
modules: {
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,7 +13,35 @@ import '@/assets/styles/scrollbars.css'
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MainView',
|
name: 'MainView',
|
||||||
components: { MainMenu }
|
components: { MainMenu },
|
||||||
|
created () {
|
||||||
|
this.readPredefinedQueries()
|
||||||
|
.then(queries => {
|
||||||
|
this.$store.commit('updatePredefinedQueries', queries)
|
||||||
|
})
|
||||||
|
.catch(console.error)
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
readPredefinedQueries () {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const xhr = new XMLHttpRequest()
|
||||||
|
xhr.open('GET', './queries.json')
|
||||||
|
xhr.onload = () => {
|
||||||
|
if (xhr.readyState === 4) {
|
||||||
|
if (xhr.status === 200) {
|
||||||
|
resolve(JSON.parse(xhr.responseText))
|
||||||
|
} else {
|
||||||
|
reject(xhr.statusText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xhr.onerror = () => {
|
||||||
|
reject(xhr.statusText)
|
||||||
|
}
|
||||||
|
xhr.send()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="toolbar"
|
class="toolbar"
|
||||||
v-show="selectedQueriesCount > 0"
|
v-show="selectedNotPredefinedCount > 0"
|
||||||
@click="showDeleteDialog(selectedQueriesIds)"
|
@click="showDeleteDialog(selectedQueriesIds)"
|
||||||
>
|
>
|
||||||
Delete
|
Delete
|
||||||
@@ -52,25 +52,31 @@
|
|||||||
>
|
>
|
||||||
<table ref="table">
|
<table ref="table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(query, index) in showedQueries" :key="query.id" @click="openQuery(index)">
|
<tr
|
||||||
|
v-for="(query, index) in showedQueries"
|
||||||
|
:key="query.id"
|
||||||
|
:class="{ 'predefined': query.isPredefined }"
|
||||||
|
@click="openQuery(index)"
|
||||||
|
>
|
||||||
<td ref="name-td">
|
<td ref="name-td">
|
||||||
<div class="cell-data">
|
<div class="cell-data">
|
||||||
<check-box
|
<check-box
|
||||||
ref="rowCheckBox"
|
ref="rowCheckBox"
|
||||||
:init="selectAll || selectedQueriesIds.has(query.id)"
|
:init="selectAll || selectedQueriesIds.has(query.id)"
|
||||||
@change="toggleRow($event, query.id)"
|
@click="toggleRow($event, query.id)"
|
||||||
/>
|
/>
|
||||||
<div class="name">{{ query.name }}</div>
|
<div class="name">{{ query.name }}</div>
|
||||||
|
<div class="badge">Predefined</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="second-column">
|
<div class="second-column">
|
||||||
<div class="date-container">{{ query.createdAt | date }}</div>
|
<div class="date-container">{{ query.createdAt | date }}</div>
|
||||||
<div class="icons-container">
|
<div class="icons-container">
|
||||||
<rename-icon @click="showRenameDialog(query.id)" />
|
<rename-icon v-if="!query.isPredefined" @click="showRenameDialog(query.id)" />
|
||||||
<copy-icon @click="duplicateQuery(index)"/>
|
<copy-icon @click="duplicateQuery(index)"/>
|
||||||
<export-icon @click="exportQuery(index)"/>
|
<export-icon @click="exportQuery(index)"/>
|
||||||
<delete-icon @click="showDeleteDialog(query.id)"/>
|
<delete-icon v-if="!query.isPredefined" @click="showDeleteDialog(query.id)"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -119,9 +125,13 @@
|
|||||||
>
|
>
|
||||||
Are you sure you want to delete
|
Are you sure you want to delete
|
||||||
{{ deleteGroup
|
{{ deleteGroup
|
||||||
? `${selectedQueriesCount} ${selectedQueriesCount > 1 ? 'queries' : 'query'}`
|
? `${selectedNotPredefinedCount} ${selectedNotPredefinedCount > 1 ? 'queries' : 'query'}`
|
||||||
: `"${queries[currentQueryIndex].name}"`
|
: `"${queries[currentQueryIndex].name}"`
|
||||||
}}?
|
}}?
|
||||||
|
<div v-show="selectedQueriesCount > selectedNotPredefinedCount" id="note">
|
||||||
|
<img :src="require('@/assets/images/info.svg')">
|
||||||
|
Note: Predefined queries you've selected won't be deleted
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-buttons-container">
|
<div class="dialog-buttons-container">
|
||||||
<button class="secondary" @click="$modal.hide('delete')">Cancel</button>
|
<button class="secondary" @click="$modal.hide('delete')">Cancel</button>
|
||||||
@@ -162,29 +172,49 @@ export default {
|
|||||||
errorMsg: null,
|
errorMsg: null,
|
||||||
selectedQueriesIds: new Set(),
|
selectedQueriesIds: new Set(),
|
||||||
selectedQueriesCount: 0,
|
selectedQueriesCount: 0,
|
||||||
|
selectedNotPredefinedCount: 0,
|
||||||
selectAll: false,
|
selectAll: false,
|
||||||
deleteGroup: false
|
deleteGroup: false,
|
||||||
|
resizeObserver: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
predefinedQueries () {
|
||||||
|
return this.$store.state.predefinedQueries.map(query => {
|
||||||
|
query.isPredefined = true
|
||||||
|
return query
|
||||||
|
})
|
||||||
|
},
|
||||||
|
predefinedQueriesIds () {
|
||||||
|
return new Set(this.predefinedQueries.map(query => query.id))
|
||||||
|
},
|
||||||
showedQueries () {
|
showedQueries () {
|
||||||
if (!this.filter) {
|
let showedQueries = this.allQueries
|
||||||
return this.queries
|
if (this.filter) {
|
||||||
} else {
|
showedQueries = showedQueries.filter(
|
||||||
return this.queries.filter(query => query.name.toUpperCase().indexOf(this.filter.toUpperCase()) >= 0)
|
query => query.name.toUpperCase().indexOf(this.filter.toUpperCase()) >= 0
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
return showedQueries
|
||||||
|
},
|
||||||
|
allQueries () {
|
||||||
|
return this.predefinedQueries.concat(this.queries)
|
||||||
},
|
},
|
||||||
currentQueryIndex () {
|
currentQueryIndex () {
|
||||||
return this.queries.findIndex(query => query.id === this.currentQueryId)
|
return this.queries.findIndex(query => query.id === this.currentQueryId)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
this.queries = JSON.parse(localStorage.getItem('myQueries'))
|
this.queries = JSON.parse(localStorage.getItem('myQueries')) || []
|
||||||
},
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
new ResizeObserver(this.calcNameWidth).observe(this.$refs.table)
|
this.resizeObserver = new ResizeObserver(this.calcNameWidth)
|
||||||
|
this.resizeObserver.observe(this.$refs.table)
|
||||||
this.calcNameWidth()
|
this.calcNameWidth()
|
||||||
},
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.resizeObserver.unobserve(this.$refs.table)
|
||||||
|
},
|
||||||
filters: {
|
filters: {
|
||||||
date (value) {
|
date (value) {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
@@ -205,7 +235,7 @@ export default {
|
|||||||
this.$refs['name-th'].style = `width: ${this.$refs['name-td'][0].offsetWidth}px`
|
this.$refs['name-th'].style = `width: ${this.$refs['name-td'][0].offsetWidth}px`
|
||||||
},
|
},
|
||||||
openQuery (index) {
|
openQuery (index) {
|
||||||
const tab = this.showedQueries[index]
|
const tab = JSON.parse(JSON.stringify(this.showedQueries[index]))
|
||||||
tab.isUnsaved = false
|
tab.isUnsaved = false
|
||||||
this.$store.commit('addTab', tab)
|
this.$store.commit('addTab', tab)
|
||||||
this.$store.commit('setCurrentTabId', tab.id)
|
this.$store.commit('setCurrentTabId', tab.id)
|
||||||
@@ -225,23 +255,33 @@ export default {
|
|||||||
const currentQuery = this.queries[this.currentQueryIndex]
|
const currentQuery = this.queries[this.currentQueryIndex]
|
||||||
currentQuery.name = this.newName
|
currentQuery.name = this.newName
|
||||||
this.$set(this.queries, this.currentQueryIndex, currentQuery)
|
this.$set(this.queries, this.currentQueryIndex, currentQuery)
|
||||||
this.$modal.hide('rename')
|
|
||||||
|
// update queries in local storage
|
||||||
this.saveQueriesInLocalStorage()
|
this.saveQueriesInLocalStorage()
|
||||||
|
|
||||||
|
// update tab, if renamed query is opened
|
||||||
const tabIndex = this.findTabIndex(currentQuery.id)
|
const tabIndex = this.findTabIndex(currentQuery.id)
|
||||||
if (tabIndex >= 0) {
|
if (tabIndex >= 0) {
|
||||||
this.$store.commit('updateTabName', { index: tabIndex, newName: this.newName })
|
this.$store.commit('updateTab', {
|
||||||
|
index: tabIndex,
|
||||||
|
name: this.newName,
|
||||||
|
id: currentQuery.id
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
// hide dialog
|
||||||
|
this.$modal.hide('rename')
|
||||||
},
|
},
|
||||||
duplicateQuery (index) {
|
duplicateQuery (index) {
|
||||||
const newQuery = JSON.parse(JSON.stringify(this.showedQueries[index]))
|
const newQuery = JSON.parse(JSON.stringify(this.showedQueries[index]))
|
||||||
newQuery.name = newQuery.name + ' Copy'
|
newQuery.name = newQuery.name + ' Copy'
|
||||||
newQuery.id = nanoid()
|
newQuery.id = nanoid()
|
||||||
newQuery.createdAt = new Date()
|
newQuery.createdAt = new Date()
|
||||||
this.queries.push(newQuery)
|
delete newQuery.isPredefined
|
||||||
if (this.selectAll) {
|
if (this.selectAll) {
|
||||||
this.selectedQueriesIds.add(newQuery.id)
|
this.selectedQueriesIds.add(newQuery.id)
|
||||||
this.selectedQueriesCount = this.selectedQueriesIds.size
|
this.selectedQueriesCount = this.selectedQueriesIds.size
|
||||||
}
|
}
|
||||||
|
this.queries.push(newQuery)
|
||||||
this.saveQueriesInLocalStorage()
|
this.saveQueriesInLocalStorage()
|
||||||
},
|
},
|
||||||
showDeleteDialog (id) {
|
showDeleteDialog (id) {
|
||||||
@@ -286,22 +326,18 @@ export default {
|
|||||||
|
|
||||||
// single operation
|
// single operation
|
||||||
if (typeof index === 'number') {
|
if (typeof index === 'number') {
|
||||||
console.log('single')
|
|
||||||
data = JSON.parse(JSON.stringify(this.showedQueries[index]))
|
data = JSON.parse(JSON.stringify(this.showedQueries[index]))
|
||||||
name = data.name
|
name = data.name
|
||||||
delete data.id
|
delete data.isPredefined
|
||||||
delete data.createdAt
|
|
||||||
} else {
|
} else {
|
||||||
// group operation
|
// group operation
|
||||||
data = this.selectAll
|
data = this.selectAll
|
||||||
? JSON.parse(JSON.stringify(this.queries))
|
? JSON.parse(JSON.stringify(this.allQueries))
|
||||||
: this.queries.filter(query => this.selectedQueriesIds.has(query.id))
|
: this.allQueries.filter(query => this.selectedQueriesIds.has(query.id))
|
||||||
name = 'My sqliteviz queries'
|
name = 'My sqliteviz queries'
|
||||||
data.forEach(query => {
|
data.forEach(query => delete query.isPredefined)
|
||||||
delete query.id
|
|
||||||
delete query.createdAt
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// export data to file
|
// export data to file
|
||||||
const downloader = this.$refs.downloader
|
const downloader = this.$refs.downloader
|
||||||
const json = JSON.stringify(data, null, 4)
|
const json = JSON.stringify(data, null, 4)
|
||||||
@@ -323,14 +359,19 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
importedQueries.forEach(query => {
|
importedQueries.forEach(query => {
|
||||||
query.id = nanoid()
|
const allQueriesIds = this.allQueries.map(query => query.id)
|
||||||
query.createdAt = new Date()
|
if (new Set(allQueriesIds).has(query.id)) {
|
||||||
if (this.selectAll) {
|
query.id = nanoid()
|
||||||
this.selectedQueriesIds.add(query.id)
|
|
||||||
this.selectedQueriesCount = this.selectedQueriesIds.size
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if (this.selectAll) {
|
||||||
|
importedQueries.forEach(query => {
|
||||||
|
this.selectedQueriesIds.add(query.id)
|
||||||
|
})
|
||||||
|
this.selectedQueriesCount = this.selectedQueriesIds.size
|
||||||
|
}
|
||||||
|
|
||||||
this.queries = this.queries.concat(importedQueries)
|
this.queries = this.queries.concat(importedQueries)
|
||||||
this.saveQueriesInLocalStorage()
|
this.saveQueriesInLocalStorage()
|
||||||
this.$refs.importFile.value = null
|
this.$refs.importFile.value = null
|
||||||
@@ -343,18 +384,30 @@ export default {
|
|||||||
toggleSelectAll (checked) {
|
toggleSelectAll (checked) {
|
||||||
this.selectAll = checked
|
this.selectAll = checked
|
||||||
this.$refs.rowCheckBox.forEach(item => { item.checked = checked })
|
this.$refs.rowCheckBox.forEach(item => { item.checked = checked })
|
||||||
this.selectedQueriesIds = checked ? new Set(this.queries.map(query => query.id)) : new Set()
|
|
||||||
|
this.selectedQueriesIds = checked
|
||||||
|
? new Set(this.allQueries.map(query => query.id))
|
||||||
|
: new Set()
|
||||||
|
|
||||||
this.selectedQueriesCount = this.selectedQueriesIds.size
|
this.selectedQueriesCount = this.selectedQueriesIds.size
|
||||||
|
this.selectedNotPredefinedCount = checked ? this.queries.length : 0
|
||||||
},
|
},
|
||||||
toggleRow (checked, id) {
|
toggleRow (checked, id) {
|
||||||
|
const isPredefined = this.predefinedQueriesIds.has(id)
|
||||||
if (checked) {
|
if (checked) {
|
||||||
this.selectedQueriesIds.add(id)
|
this.selectedQueriesIds.add(id)
|
||||||
|
if (!isPredefined) {
|
||||||
|
this.selectedNotPredefinedCount += 1
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.selectedQueriesIds.size === this.queries.length) {
|
if (this.selectedQueriesIds.size === this.allQueries.length) {
|
||||||
this.$refs.mainCheckBox.checked = false
|
this.$refs.mainCheckBox.checked = false
|
||||||
this.selectAll = false
|
this.selectAll = false
|
||||||
}
|
}
|
||||||
this.selectedQueriesIds.delete(id)
|
this.selectedQueriesIds.delete(id)
|
||||||
|
if (!isPredefined) {
|
||||||
|
this.selectedNotPredefinedCount -= 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.selectedQueriesCount = this.selectedQueriesIds.size
|
this.selectedQueriesCount = this.selectedQueriesIds.size
|
||||||
}
|
}
|
||||||
@@ -413,6 +466,7 @@ tbody .cell-data {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
tbody .cell-data div.name {
|
tbody .cell-data div.name {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
@@ -438,6 +492,7 @@ tbody tr:hover td {
|
|||||||
|
|
||||||
.icons-container {
|
.icons-container {
|
||||||
display: none;
|
display: none;
|
||||||
|
margin-right: -12px;
|
||||||
}
|
}
|
||||||
.date-container {
|
.date-container {
|
||||||
flex-shrink: 1;
|
flex-shrink: 1;
|
||||||
@@ -457,11 +512,34 @@ a, #import-file {
|
|||||||
button.toolbar {
|
button.toolbar {
|
||||||
margin-right: 16px;
|
margin-right: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button label {
|
button label {
|
||||||
display: block;
|
display: block;
|
||||||
line-height: 36px;
|
line-height: 36px;
|
||||||
}
|
}
|
||||||
|
|
||||||
button label:hover {
|
button label:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.badge {
|
||||||
|
display: none;
|
||||||
|
background-color: var(--color-gray-light-4);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
padding: 2px 6px;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: normal;
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr.predefined:hover .badge {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
#note {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
#note img {
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user