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

13 Commits
0.3.0 ... 0.4.0

Author SHA1 Message Date
lana-k
4c685b6d7c move to dist 2020-11-10 21:37:32 +01:00
lana-k
a37edc501b create an archive 2020-11-10 21:33:35 +01:00
lana-k
46a9d1613b minor changes 2020-11-10 20:12:06 +01:00
lana-k
b08265fab9 minor changes 2020-11-10 20:05:40 +01:00
lana-k
f1b5f5e3c7 rename github action 2020-11-10 18:58:12 +01:00
lana-k
e08fdbecbc fix Create release action 2020-11-10 18:53:42 +01:00
lana-k
a5ba1cb821 add release.yml 2020-11-06 16:58:29 +01:00
lana-k
7da1cb36fa Add tooltips #9 2020-11-06 12:20:44 +01:00
lana-k
57c8fe5bc8 Add table filter in schema sidebar #7 2020-11-05 17:29:38 +01:00
lana-k
e7d3da8869 fade out in schema 2020-11-05 13:48:02 +01:00
lana-k
5dc80751c4 add padding to schema 2020-11-04 22:31:38 +01:00
lana-k
3e52dcac2c move run button to the menu 2020-11-04 22:18:04 +01:00
lana-k
1037185a6a add predefined queries 2020-11-04 19:13:27 +01:00
21 changed files with 624 additions and 180 deletions

View File

@@ -1,4 +1,4 @@
name: Deploy to GitHub Pages name: Deploy to GitHub Pages and create release
on: on:
workflow_dispatch: workflow_dispatch:
push: push:
@@ -7,10 +7,10 @@ on:
jobs: jobs:
deploy: deploy:
name: Deploy to GitHub Pages name: Deploy to GitHub Pages and create release
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@master - uses: actions/checkout@v2
- name: Use Node.js - name: Use Node.js
uses: actions/setup-node@v1 uses: actions/setup-node@v1
with: with:
@@ -20,6 +20,17 @@ jobs:
npm install npm install
npm run build npm run build
- name: Create archive
run: |
cd dist
zip -9 -r dist.zip . -x "js/*.map"
- name: Create release
uses: ncipollo/release-action@v1
with:
artifacts: "dist/dist.zip"
token: ${{ secrets.GITHUB_TOKEN }}
- name: Deploy 🚀 - name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@3.6.2 uses: JamesIves/github-pages-deploy-action@3.6.2
with: with:

47
public/queries.json Normal file
View 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"
}

View 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

View File

@@ -0,0 +1,15 @@
.icon-tooltip {
visibility: hidden;
background-color: rgba(80, 103, 132, 0.85);
color: #fff;
text-align: center;
font-size: 11px;
padding: 0 6px;
line-height: 19px;;
position: fixed;
z-index: 5;
height: 19px;
border-radius: var(--border-radius-medium);
white-space: nowrap;
z-index: 999;
}

View File

@@ -1,10 +1,9 @@
<template> <template>
<div class="chart-container"> <div v-show="visible" class="chart-container">
<div class="chart-worning" v-show="!sqlResult && visible"> <div class="chart-worning" v-show="!sqlResult && visible">
There is no data to build a chart. Run your sql query and make sure the result is not empty. There is no data to build a chart. Run your sql query and make sure the result is not empty.
</div> </div>
<PlotlyEditor <PlotlyEditor
v-show="visible"
:data="state.data" :data="state.data"
:layout="state.layout" :layout="state.layout"
:frames="state.frames" :frames="state.frames"
@@ -106,6 +105,7 @@ export default {
} }
.chart { .chart {
border-top: 1px solid var(--color-border); border-top: 1px solid var(--color-border);
min-height: 242px;
} }
>>> .editor_controls .sidebar__item:before { >>> .editor_controls .sidebar__item:before {

View File

@@ -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

View File

@@ -8,21 +8,78 @@
<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 && !$store.state.schema || !currentQuery.query"
@click="saveQuery" @click="currentQuery.execute"
>
Run
</button>
<button
v-if="$store.state.tabs.length > 0"
class="primary"
:disabled="currentQuery && !currentQuery.isUnsaved"
@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 +95,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 +177,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 +192,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>

View File

@@ -1,10 +1,12 @@
<template> <template>
<div> <div id="schema-container">
<text-field placeholder="Search table" width="100%"/> <div id="schema-filter">
<text-field placeholder="Search table" width="100%" v-model="filter"/>
</div>
<div id="db"> <div id="db">
<div @click="schemaVisible = !schemaVisible" class="db-name"> <div @click="schemaVisible = !schemaVisible" class="db-name">
<svg <svg
:style="{transform: schemaVisible ? 'rotate(90deg)' : 'rotate(0)'}" :style="{ transform: schemaVisible ? 'rotate(90deg)' : 'rotate(0)' }"
class="chevron-icon" class="chevron-icon"
width="9" width="9"
height="9" height="9"
@@ -22,25 +24,14 @@
<div class="db-edit"> <div class="db-edit">
<input type="file" id="actual-btn" ref="dbfile" hidden @change="changeDb"/> <input type="file" id="actual-btn" ref="dbfile" hidden @change="changeDb"/>
<label for="actual-btn"> <label for="actual-btn">
<svg <change-db-icon />
class="db-edit-icon"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M3 10.5V12.75C3 14.25 5.2875 15.54 8.25 15.75V13.5825L8.3475 13.5C5.34 13.32 3 12.045 3 10.5ZM9 9.75C5.685 9.75 3 8.4075 3 6.75V9C3 10.6575 5.685 12 9 12C9.2925 12 9.5775 12 9.87 12L12.75 9.09C11.55 9.54 10.2825 9.75 9 9.75ZM9 2.25C5.685 2.25 3 3.5925 3 5.25C3 6.9075 5.685 8.25 9 8.25C12.315 8.25 15 6.9075 15 5.25C15 3.5925 12.315 2.25 9 2.25ZM15.75 8.3475C15.6375 8.3475 15.5325 8.3925 15.4575 8.475L14.7075 9.225L16.245 10.725L16.995 9.975C17.1525 9.825 17.16 9.57 16.995 9.3975L16.065 8.475C15.99 8.3925 15.885 8.3475 15.78 8.3475H15.75ZM14.28 9.66L9.75 14.205V15.75H11.295L15.84 11.1975L14.28 9.66Z"
fill="#A2B1C6"/>
</svg>
</label> </label>
<span class="db-edit-tooltip">Change database</span>
</div> </div>
</div> </div>
<div v-show="schemaVisible" class="schema"> <div v-if="schemaVisible" class="schema">
<table-description <table-description
v-for="(table, index) in schema" v-for="table in schema"
:key="index" :key="table[0]"
:name="table[0]" :name="table[0]"
:sql="table[1]" :sql="table[1]"
/> />
@@ -51,18 +42,32 @@
<script> <script>
import TableDescription from '@/components/TableDescription' import TableDescription from '@/components/TableDescription'
import TextField from '@/components/TextField' import TextField from '@/components/TextField'
import ChangeDbIcon from '@/components/svg/changeDb'
export default { export default {
name: 'Schema', name: 'Schema',
components: { TableDescription, TextField }, components: {
TableDescription,
TextField,
ChangeDbIcon
},
data () { data () {
return { return {
schemaVisible: true schemaVisible: true,
filter: null
} }
}, },
computed: { computed: {
schema () { schema () {
return this.$store.state.schema if (!this.$store.state.schema) {
return []
}
return !this.filter
? this.$store.state.schema
: this.$store.state.schema.filter(
table => table[0].toUpperCase().indexOf(this.filter.toUpperCase()) !== -1
)
}, },
dbName () { dbName () {
return this.$store.state.dbName return this.$store.state.dbName
@@ -77,8 +82,25 @@ export default {
</script> </script>
<style scoped> <style scoped>
#schema-container {
position: relative;
padding-bottom: 24px;
}
.schema { .schema {
margin-left: 12px; margin-left: 12px;
padding: 0 12px;
}
#schema-filter {
padding: 32px 12px;
position: sticky;
position: -webkit-sticky;
top: 0;
width: 100%;
height: 100px;
box-sizing: border-box;
background-image: linear-gradient(white 73%, transparent);;
z-index: 2;
} }
.schema, .db-name { .schema, .db-name {
color: var(--color-text-base); color: var(--color-text-base);
@@ -88,6 +110,8 @@ export default {
#db { #db {
display: flex; display: flex;
align-items: center; align-items: center;
margin-top: -5px;
padding: 0 12px;
} }
.db-name { .db-name {
@@ -101,36 +125,4 @@ export default {
>>> .table-name:hover .chevron-icon path { >>> .table-name:hover .chevron-icon path {
fill: #506784; fill: #506784;
} }
.db-edit {
position: relative;
}
.db-edit-icon {
display: block;
}
.db-edit-icon:hover path{
fill: var(--color-accent);
cursor: pointer;
}
.db-edit-tooltip {
visibility: hidden;
background-color: rgba(80, 103, 132, 0.75);
color: #fff;
text-align: center;
font-size: 11px;
padding: 0 6px;
line-height: 19px;;
position: absolute;
z-index: 5;
height: 19px;
left: 24px;
top: -12px;
border-radius: var(--border-radius-medium);
white-space: nowrap;
}
.db-edit:hover .db-edit-tooltip {
visibility: visible;
}
</style> </style>

View File

@@ -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
} }
} }

View File

@@ -3,21 +3,12 @@
<splitpanes <splitpanes
class="query-results-splitter" class="query-results-splitter"
horizontal horizontal
:before="{ size: 50, max: 50 }" :before="{ size: 50, max: 70 }"
:after="{ size: 50, max: 100 }" :after="{ size: 50, max: 100 }"
> >
<template #left-pane> <template #left-pane>
<div class="query-editor"> <div class="query-editor">
<sql-editor v-model="query" /> <sql-editor v-model="query" />
<div class="run-btn-container">
<button
class="primary run-btn"
@click="execute"
:disabled="!$store.state.schema || !query"
>
Run
</button>
</div>
</div> </div>
</template> </template>
<template #right-pane> <template #right-pane>
@@ -36,7 +27,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 +50,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 +67,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 +80,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) {
@@ -166,12 +162,8 @@ export default {
background-color: var(--color-bg-light); background-color: var(--color-bg-light);
} }
.run-btn {
margin-top: 24px;
}
.query-editor { .query-editor {
padding: 52px 52px 24px; padding: 52px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
height: 100%; height: 100%;
@@ -180,10 +172,6 @@ export default {
min-height: 190px; min-height: 190px;
} }
.run-btn-container {
text-align: right;
}
.table-view { .table-view {
margin: 0 52px; margin: 0 52px;
height: calc(100% - 88px); height: calc(100% - 88px);

View File

@@ -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>

View File

@@ -0,0 +1,41 @@
<template>
<div>
<svg
class="db-edit-icon"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@mouseover="showTooltip"
@mouseout="$set(tooltipStyle, 'visibility', 'hidden')"
>
<path
d="M3 10.5V12.75C3 14.25 5.2875 15.54 8.25 15.75V13.5825L8.3475 13.5C5.34 13.32 3 12.045 3 10.5ZM9 9.75C5.685 9.75 3 8.4075 3 6.75V9C3 10.6575 5.685 12 9 12C9.2925 12 9.5775 12 9.87 12L12.75 9.09C11.55 9.54 10.2825 9.75 9 9.75ZM9 2.25C5.685 2.25 3 3.5925 3 5.25C3 6.9075 5.685 8.25 9 8.25C12.315 8.25 15 6.9075 15 5.25C15 3.5925 12.315 2.25 9 2.25ZM15.75 8.3475C15.6375 8.3475 15.5325 8.3925 15.4575 8.475L14.7075 9.225L16.245 10.725L16.995 9.975C17.1525 9.825 17.16 9.57 16.995 9.3975L16.065 8.475C15.99 8.3925 15.885 8.3475 15.78 8.3475H15.75ZM14.28 9.66L9.75 14.205V15.75H11.295L15.84 11.1975L14.28 9.66Z"
fill="#A2B1C6"
/>
</svg>
<span class="icon-tooltip" :style="tooltipStyle">
Change database
</span>
</div>
</template>
<script>
import tooltipMixin from '@/mixins/tooltips'
export default {
name: 'changeDbIcon',
mixins: [tooltipMixin]
}
</script>
<style scoped>
.db-edit-icon {
display: block;
cursor: pointer;
}
.db-edit-icon:hover path {
fill: var(--color-accent);
}
</style>

View File

@@ -1,12 +1,33 @@
<template> <template>
<svg @click.stop="$emit('click')" class="icon" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <span>
<path d="M14.25 15.75H6V5.25H14.25V15.75ZM14.25 3.75H6C5.60218 3.75 5.22064 3.90804 4.93934 4.18934C4.65804 4.47064 4.5 4.85218 4.5 5.25V15.75C4.5 16.1478 4.65804 16.5294 4.93934 16.8107C5.22064 17.092 5.60218 17.25 6 17.25H14.25C14.6478 17.25 15.0294 17.092 15.3107 16.8107C15.592 16.5294 15.75 16.1478 15.75 15.75V5.25C15.75 4.85218 15.592 4.47064 15.3107 4.18934C15.0294 3.90804 14.6478 3.75 14.25 3.75ZM12 0.75H3C2.60218 0.75 2.22064 0.908035 1.93934 1.18934C1.65804 1.47064 1.5 1.85218 1.5 2.25V12.75H3V2.25H12V0.75Z" fill="#A2B1C6"/> <svg
class="icon"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@click.stop="$emit('click')"
@mouseover="showTooltip"
@mouseout="$set(tooltipStyle, 'visibility', 'hidden')"
>
<path
d="M14.25 15.75H6V5.25H14.25V15.75ZM14.25 3.75H6C5.60218 3.75 5.22064 3.90804 4.93934 4.18934C4.65804 4.47064 4.5 4.85218 4.5 5.25V15.75C4.5 16.1478 4.65804 16.5294 4.93934 16.8107C5.22064 17.092 5.60218 17.25 6 17.25H14.25C14.6478 17.25 15.0294 17.092 15.3107 16.8107C15.592 16.5294 15.75 16.1478 15.75 15.75V5.25C15.75 4.85218 15.592 4.47064 15.3107 4.18934C15.0294 3.90804 14.6478 3.75 14.25 3.75ZM12 0.75H3C2.60218 0.75 2.22064 0.908035 1.93934 1.18934C1.65804 1.47064 1.5 1.85218 1.5 2.25V12.75H3V2.25H12V0.75Z"
fill="#A2B1C6"
/>
</svg> </svg>
<span class="icon-tooltip" :style="tooltipStyle">
Duplicate query
</span>
</span>
</template> </template>
<script> <script>
import tooltipMixin from '@/mixins/tooltips'
export default { export default {
name: 'CopyIcon' name: 'CopyIcon',
mixins: [tooltipMixin]
} }
</script> </script>

View File

@@ -1,19 +1,40 @@
<template> <template>
<svg @click.stop="$emit('click')" class="icon" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <span>
<path d="M6.75 2.25V3H3V4.5H3.75V14.25C3.75 14.6478 3.90804 15.0294 4.18934 15.3107C4.47064 15.592 4.85218 15.75 5.25 15.75H12.75C13.1478 15.75 13.5294 15.592 13.8107 15.3107C14.092 15.0294 14.25 14.6478 14.25 14.25V4.5H15V3H11.25V2.25H6.75ZM5.25 4.5H12.75V14.25H5.25V4.5ZM6.75 6V12.75H8.25V6H6.75ZM9.75 6V12.75H11.25V6H9.75Z" fill="#A2B1C6"/> <svg
class="icon"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@click.stop="$emit('click')"
@mouseover="showTooltip"
@mouseout="$set(tooltipStyle, 'visibility', 'hidden')"
>
<path
d="M6.75 2.25V3H3V4.5H3.75V14.25C3.75 14.6478 3.90804 15.0294 4.18934 15.3107C4.47064 15.592 4.85218 15.75 5.25 15.75H12.75C13.1478 15.75 13.5294 15.592 13.8107 15.3107C14.092 15.0294 14.25 14.6478 14.25 14.25V4.5H15V3H11.25V2.25H6.75ZM5.25 4.5H12.75V14.25H5.25V4.5ZM6.75 6V12.75H8.25V6H6.75ZM9.75 6V12.75H11.25V6H9.75Z"
fill="#A2B1C6"
/>
</svg> </svg>
<span class="icon-tooltip" :style="tooltipStyle">
Delete query
</span>
</span>
</template> </template>
<script> <script>
import tooltipMixin from '@/mixins/tooltips'
export default { export default {
name: 'DeleteIcon' name: 'DeleteIcon',
mixins: [tooltipMixin]
} }
</script> </script>
<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 {

View File

@@ -1,12 +1,33 @@
<template> <template>
<svg @click.stop="$emit('click')" class="icon" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <span>
<path d="M10.5 1.5H4.5C3.675 1.5 3 2.175 3 3V15C3 15.825 3.675 16.5 4.5 16.5H13.5C14.325 16.5 15 15.825 15 15V6L10.5 1.5ZM13.5 15H4.5V3H9.75V6.75H13.5V15ZM12 8.25V13.575L10.425 12L8.325 14.1L6.225 12L8.325 9.9L6.675 8.25H12Z" fill="#A2B1C6"/> <svg
</svg> class="icon"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@click.stop="$emit('click')"
@mouseover="showTooltip"
@mouseout="$set(tooltipStyle, 'visibility', 'hidden')"
>
<path
d="M10.5 1.5H4.5C3.675 1.5 3 2.175 3 3V15C3 15.825 3.675 16.5 4.5 16.5H13.5C14.325 16.5 15 15.825 15 15V6L10.5 1.5ZM13.5 15H4.5V3H9.75V6.75H13.5V15ZM12 8.25V13.575L10.425 12L8.325 14.1L6.225 12L8.325 9.9L6.675 8.25H12Z"
fill="#A2B1C6"
/>
</svg>
<span class="icon-tooltip" :style="tooltipStyle">
Export query to file
</span>
</span>
</template> </template>
<script> <script>
import tooltipMixin from '@/mixins/tooltips'
export default { export default {
name: 'ExportIcon' name: 'ExportIcon',
mixins: [tooltipMixin]
} }
</script> </script>

View File

@@ -1,19 +1,40 @@
<template> <template>
<svg @click.stop="$emit('click')" class="icon" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg"> <span>
<path d="M10.545 6.75L11.25 7.455L4.44 14.25H3.75V13.56L10.545 6.75ZM13.245 2.25C13.0575 2.25 12.8625 2.325 12.72 2.4675L11.3475 3.84L14.16 6.6525L15.5325 5.28C15.825 4.9875 15.825 4.5 15.5325 4.2225L13.7775 2.4675C13.6275 2.3175 13.44 2.25 13.245 2.25ZM10.545 4.6425L2.25 12.9375V15.75H5.0625L13.3575 7.455L10.545 4.6425Z" fill="#A2B1C6"/> <svg
class="icon"
width="18"
height="18"
viewBox="0 0 18 18"
fill="none"
xmlns="http://www.w3.org/2000/svg"
@click.stop="$emit('click')"
@mouseover="showTooltip"
@mouseout="$set(tooltipStyle, 'visibility', 'hidden')"
>
<path
d="M10.545 6.75L11.25 7.455L4.44 14.25H3.75V13.56L10.545 6.75ZM13.245 2.25C13.0575 2.25 12.8625 2.325 12.72 2.4675L11.3475 3.84L14.16 6.6525L15.5325 5.28C15.825 4.9875 15.825 4.5 15.5325 4.2225L13.7775 2.4675C13.6275 2.3175 13.44 2.25 13.245 2.25ZM10.545 4.6425L2.25 12.9375V15.75H5.0625L13.3575 7.455L10.545 4.6425Z"
fill="#A2B1C6"
/>
</svg> </svg>
<span class="icon-tooltip" :style="tooltipStyle">
Rename query
</span>
</span>
</template> </template>
<script> <script>
import tooltipMixin from '@/mixins/tooltips'
export default { export default {
name: 'RenameIcon' name: 'RenameIcon',
mixins: [tooltipMixin]
} }
</script> </script>
<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 {

View File

@@ -10,6 +10,7 @@ import '@/assets/styles/variables.css'
import '@/assets/styles/buttons.css' import '@/assets/styles/buttons.css'
import '@/assets/styles/tables.css' import '@/assets/styles/tables.css'
import '@/assets/styles/dialogs.css' import '@/assets/styles/dialogs.css'
import '@/assets/styles/tooltips.css'
Vue.use(VuePlugin) Vue.use(VuePlugin)
Vue.use(VModal) Vue.use(VModal)

17
src/mixins/tooltips.js Normal file
View File

@@ -0,0 +1,17 @@
export default {
data () {
return {
tooltipStyle: {}
}
},
methods: {
showTooltip (e) {
this.tooltipStyle = {
visibility: 'visible',
position: 'fixed',
top: e.clientY - 12 + 'px',
left: e.clientX + 12 + 'px'
}
}
}
}

View File

@@ -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: {
} }
}) })

View File

@@ -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>

View File

@@ -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,41 @@
> >
<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"
@mouseover="showTooltip"
@mouseout="$set(tooltipStyle, 'visibility', 'hidden')"
>
Predefined
<span class="icon-tooltip" :style="tooltipStyle">
Predefined queries come from the server.
These queries cant be deleted or renamed.
</span>
</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 +135,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>
@@ -140,6 +160,7 @@ import DeleteIcon from '@/components/svg/delete'
import CloseIcon from '@/components/svg/close' import CloseIcon from '@/components/svg/close'
import TextField from '@/components/TextField' import TextField from '@/components/TextField'
import CheckBox from '@/components/CheckBox' import CheckBox from '@/components/CheckBox'
import tooltipMixin from '@/mixins/tooltips'
import { nanoid } from 'nanoid' import { nanoid } from 'nanoid'
export default { export default {
@@ -153,6 +174,7 @@ export default {
TextField, TextField,
CheckBox CheckBox
}, },
mixins: [tooltipMixin],
data () { data () {
return { return {
queries: [], queries: [],
@@ -162,29 +184,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 +247,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 +267,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 +338,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 +371,19 @@ export default {
} }
importedQueries.forEach(query => { importedQueries.forEach(query => {
const allQueriesIds = this.allQueries.map(query => query.id)
if (new Set(allQueriesIds).has(query.id)) {
query.id = nanoid() query.id = nanoid()
query.createdAt = new Date()
if (this.selectAll) {
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 +396,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 +478,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 +504,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 +524,42 @@ 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;
}
.icon-tooltip {
display: block;
width: 149px;
white-space: normal;
height: auto;
line-height: normal;
padding: 6px;
}
</style> </style>