1
0
mirror of https://github.com/lana-k/sqliteviz.git synced 2025-12-07 10:38:54 +08:00

change code structure

This commit is contained in:
lana-k
2021-05-04 14:13:58 +02:00
parent a07f2d3d99
commit cc483f4720
72 changed files with 297 additions and 311 deletions

View File

@@ -0,0 +1,51 @@
<template>
<div>
<div @click="colVisible = !colVisible" class="table-name">
<tree-chevron :expanded="colVisible"/>
{{ name }}
</div>
<div v-show="colVisible" class="columns">
<div v-for="(col, index) in columns" :key="index" class="column">
{{ col.name }}
<span class="column-type">{{ col.type }}</span>
</div>
</div>
</div>
</template>
<script>
import TreeChevron from '@/components/svg/treeChevron'
export default {
name: 'TableDescription',
components: { TreeChevron },
props: ['name', 'columns'],
data () {
return {
colVisible: false
}
}
}
</script>
<style scoped>
.table-name, .column {
margin-top: 11px;
}
.table-name:hover {
cursor: pointer;
}
.columns {
margin-left: 24px;
}
.column-type {
display: inline-block;
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;
text-transform: uppercase;
}
</style>

View File

@@ -0,0 +1,117 @@
<template>
<div id="schema-container">
<div id="schema-filter">
<text-field placeholder="Search table" width="100%" v-model="filter"/>
</div>
<div id="db">
<div @click="schemaVisible = !schemaVisible" class="db-name">
<tree-chevron :expanded="schemaVisible"/>
{{ dbName }}
</div>
<db-uploader id="db-edit" type="small" />
<export-icon tooltip="Export database" @click="exportToFile"/>
</div>
<div v-show="schemaVisible" class="schema">
<table-description
v-for="table in schema"
:key="table.name"
:name="table.name"
:columns="table.columns"
/>
</div>
</div>
</template>
<script>
import TableDescription from './TableDescription'
import TextField from '@/components/TextField'
import TreeChevron from '@/components/svg/treeChevron'
import DbUploader from '@/components/DbUploader'
import ExportIcon from '@/components/svg/export'
export default {
name: 'Schema',
components: {
TableDescription,
TextField,
TreeChevron,
DbUploader,
ExportIcon
},
data () {
return {
schemaVisible: true,
filter: null
}
},
computed: {
schema () {
if (!this.$store.state.schema) {
return []
}
return !this.filter
? this.$store.state.schema
: this.$store.state.schema.filter(
table => table.name.toUpperCase().indexOf(this.filter.toUpperCase()) !== -1
)
},
dbName () {
return this.$store.state.dbName
}
},
methods: {
exportToFile () {
this.$store.state.db.export(`${this.dbName}.sqlite`)
}
}
}
</script>
<style scoped>
#schema-container {
position: relative;
padding-bottom: 24px;
}
.schema {
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 {
color: var(--color-text-base);
font-size: 13px;
white-space: nowrap;
}
#db {
display: flex;
align-items: center;
margin-top: -5px;
padding: 0 12px;
}
.db-name {
cursor: pointer;
margin-right: 6px;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
flex-shrink: 0;
}
.db-name:hover .chevron-icon path,
>>> .table-name:hover .chevron-icon path {
fill: var(--color-gray-dark);
}
</style>

View File

@@ -0,0 +1,40 @@
import dereference from 'react-chart-editor/lib/lib/dereference'
export function getDataSourcesFromSqlResult (sqlResult) {
if (!sqlResult) {
return {}
}
const dataSorces = {}
const matrix = sqlResult.values
const [row] = matrix
const transposedMatrix = row.map((value, column) => matrix.map(row => row[column]))
sqlResult.columns.forEach((column, index) => {
dataSorces[column] = transposedMatrix[index]
})
return dataSorces
}
export function getOptionsFromDataSources (dataSources) {
return Object.keys(dataSources).map(name => ({
value: name,
label: name
}))
}
export function getChartStateForSave (state, dataSources) {
// we don't need to save the data, only settings
// so we modify state.data using dereference
const stateCopy = JSON.parse(JSON.stringify(state))
const emptySources = {}
for (const key in dataSources) {
emptySources[key] = []
}
dereference(stateCopy.data, emptySources)
return stateCopy
}
export default {
getDataSourcesFromSqlResult,
getOptionsFromDataSources,
getChartStateForSave
}

View File

@@ -0,0 +1,98 @@
<template>
<div v-show="visible" class="chart-container">
<div class="warning chart-warning" v-show="!sqlResult && visible">
There is no data to build a chart. Run your sql query and make sure the result is not empty.
</div>
<PlotlyEditor
:data="state.data"
:layout="state.layout"
:frames="state.frames"
:config="{ editable: true, displaylogo: false }"
:dataSources="dataSources"
:dataSourceOptions="dataSourceOptions"
:plotly="plotly"
@onUpdate="update"
@onRender="go"
:useResizeHandler="true"
:debug="true"
:advancedTraceTypeSelector="true"
class="chart"
ref="plotlyEditor"
:style="{ height: !sqlResult ? 'calc(100% - 40px)' : '100%' }"
/>
</div>
</template>
<script>
import plotly from 'plotly.js/dist/plotly'
import 'react-chart-editor/lib/react-chart-editor.min.css'
import PlotlyEditor from 'react-chart-editor'
import chartHelper from './chartHelper'
import dereference from 'react-chart-editor/lib/lib/dereference'
export default {
name: 'Chart',
props: ['sqlResult', 'initChart', 'visible'],
components: {
PlotlyEditor
},
data () {
return {
plotly: plotly,
state: this.initChart || {
data: [],
layout: {},
frames: []
}
}
},
computed: {
dataSources () {
return chartHelper.getDataSourcesFromSqlResult(this.sqlResult)
},
dataSourceOptions () {
return chartHelper.getOptionsFromDataSources(this.dataSources)
}
},
watch: {
dataSources () {
// we need to update state.data in order to update the graph
// https://github.com/plotly/react-chart-editor/issues/948
dereference(this.state.data, this.dataSources)
}
},
methods: {
go (data, layout, frames) {
// TODO: check changes and enable Save button if needed
},
update (data, layout, frames) {
this.state = { data, layout, frames }
this.$emit('update')
},
getChartStateForSave () {
return chartHelper.getChartStateForSave(this.state, this.dataSources)
}
}
}
</script>
<style scoped>
.chart-container {
height: calc(100% - 89px);
}
.chart-warning {
height: 40px;
line-height: 40px;
}
.chart {
border-top: 1px solid var(--color-border);
min-height: 242px;
}
>>> .editor_controls .sidebar__item:before {
width: 0;
}
</style>

View File

@@ -0,0 +1,45 @@
import CM from 'codemirror'
import 'codemirror/addon/hint/show-hint.js'
import 'codemirror/addon/hint/sql-hint.js'
import store from '@/store'
export function getHints (cm, options) {
const token = cm.getTokenAt(cm.getCursor()).string.toUpperCase()
const result = CM.hint.sql(cm, options)
// Don't show the hint if there is only one option
// and the token is already completed with this option
if (result.list.length === 1 && result.list[0].text.toUpperCase() === token) {
result.list = []
}
return result
}
const hintOptions = {
get tables () {
const tables = {}
if (store.state.schema) {
store.state.schema.forEach(table => {
tables[table.name] = table.columns.map(column => column.name)
})
}
return tables
},
get defaultTable () {
const schema = store.state.schema
return schema && schema.length === 1 ? schema[0].name : null
},
completeSingle: false,
completeOnSingleClick: true,
alignWithWord: false
}
export default function showHint (editor) {
// Don't show autocomplete after a space or semicolon or in string literals
const token = editor.getTokenAt(editor.getCursor())
const ch = token.string.slice(-1)
const tokenType = token.type
if (tokenType === 'string' || !ch || ch === ' ' || ch === ';') {
return
}
CM.showHint(editor, getHints, hintOptions)
}

View File

@@ -0,0 +1,65 @@
<template>
<div class="codemirror-container">
<codemirror v-model="query" :options="cmOptions" @changes="onChange" />
</div>
</template>
<script>
import showHint from './hint'
import { debounce } from 'debounce'
import { codemirror } from 'vue-codemirror'
import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/sql/sql.js'
import 'codemirror/theme/neo.css'
import 'codemirror/addon/hint/show-hint.css'
import 'codemirror/addon/display/autorefresh.js'
export default {
name: 'SqlEditor',
props: ['value'],
components: { codemirror },
data () {
return {
query: this.value,
cmOptions: {
// codemirror options
tabSize: 4,
mode: 'text/x-mysql',
theme: 'neo',
lineNumbers: true,
line: true,
autofocus: true,
autoRefresh: true
}
}
},
watch: {
query () {
this.$emit('input', this.query)
}
},
methods: {
onChange: debounce(showHint, 400)
}
}
</script>
<style scoped>
.codemirror-container {
flex-grow: 1;
min-height: 0;
}
>>> .vue-codemirror {
height: 100%;
max-height: 100%;
}
>>> .CodeMirror {
height: 100%;
max-height: 100%;
}
>>> .CodeMirror-cursor {
width: 1px;
background: var(--color-text-base);
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<div class="view-switcher">
<div
:class="['table-mode', {'active-mode': view === 'table'}]"
@click="$emit('update:view','table')"
>
Table
</div>
<div
:class="['chart-mode', {'active-mode': view === 'chart'}]"
@click="$emit('update:view','chart')"
>
Chart
</div>
</div>
</template>
<script>
export default {
name: 'ViewSwitcher',
props: ['view']
}
</script>
<style scoped>
.view-switcher {
height: 28px;
display: flex;
padding: 30px;
justify-content: center;
}
.view-switcher div {
height: 100%;
width: 136px;
box-sizing: border-box;
line-height: 28px;
font-size: 12px;
cursor: pointer;
background: var(--color-white);
border: 1px solid var(--color-border);
color: var(--color-text-base);
text-align: center;
font-weight: 400;
}
.view-switcher div:hover {
background-color: var(--color-bg-light);
color: var(--color-text-active);
}
.view-switcher div.active-mode {
background: var(--color-accent);
border: 1px solid var(--color-accent-shade);
color: var(--color-text-light);
text-shadow: var(--shadow);
z-index: 1;
font-weight: 600;
}
.view-switcher div.active-mode:hover {
background: var(--color-accent-shade);
}
.table-mode {
border-radius: var(--border-radius-medium) 0 0 var(--border-radius-medium);
}
.chart-mode {
margin-left: -1px;
border-radius: 0 var(--border-radius-medium) var(--border-radius-medium) 0;
}
</style>

View File

@@ -0,0 +1,193 @@
<template>
<div class="tab-content-container" v-show="isActive">
<splitpanes
class="query-results-splitter"
horizontal
:before="{ size: 50, max: 100 }"
:after="{ size: 50, max: 100 }"
>
<template #left-pane>
<div class="query-editor">
<sql-editor v-model="query" />
</div>
</template>
<template #right-pane>
<div id="bottomPane" ref="bottomPane">
<view-switcher :view.sync="view" />
<div v-show="view === 'table'" class="table-view">
<div
v-show="result === null && !isGettingResults && !error"
class="table-preview result-before"
>
Run your query and get results here
</div>
<div v-show="isGettingResults" class="table-preview result-in-progress">
Fetching results...
</div>
<div
v-show="result === undefined && !isGettingResults && !error"
class="table-preview result-empty"
>
No rows retrieved according to your query
</div>
<div v-show="error" class="table-preview error">
{{ error }}
</div>
<sql-table v-if="result" :data-set="result" :height="tableViewHeight" />
</div>
<chart
:visible="view === 'chart'"
:sql-result="result"
:init-chart="initChart"
ref="chart"
@update="$store.commit('updateTab', { index: tabIndex, isUnsaved: true })"
/>
</div>
</template>
</splitpanes>
</div>
</template>
<script>
import SqlTable from '@/components/SqlTable'
import Splitpanes from '@/components/Splitpanes'
import SqlEditor from './SqlEditor'
import ViewSwitcher from './ViewSwitcher'
import Chart from './Chart'
export default {
name: 'Tab',
props: ['id', 'initName', 'initQuery', 'initChart', 'tabIndex', 'isPredefined'],
components: {
SqlEditor,
SqlTable,
Splitpanes,
ViewSwitcher,
Chart
},
data () {
return {
query: this.initQuery,
result: null,
view: 'table',
tableViewHeight: 0,
isGettingResults: false,
error: null,
resizeObserver: null
}
},
computed: {
isActive () {
return this.id === this.$store.state.currentTabId
}
},
created () {
this.$store.commit('setCurrentTab', this)
},
mounted () {
this.resizeObserver = new ResizeObserver(this.handleResize)
this.resizeObserver.observe(this.$refs.bottomPane)
this.calculateTableHeight()
},
beforeDestroy () {
this.resizeObserver.unobserve(this.$refs.bottomPane)
},
watch: {
isActive () {
if (this.isActive) {
this.$store.commit('setCurrentTab', this)
}
},
query () {
this.$store.commit('updateTab', { index: this.tabIndex, isUnsaved: true })
}
},
methods: {
// Run a command in the database
async execute () {
this.isGettingResults = true
this.result = null
this.error = null
const state = this.$store.state
try {
this.result = await state.db.execute(this.query + ';')
const schema = await state.db.getSchema(state.dbName)
this.$store.commit('saveSchema', schema)
} catch (err) {
this.error = err
}
this.isGettingResults = false
},
handleResize () {
if (this.view === 'chart') {
// hack react-chart editor: hidden and show in order to make the graph resize
this.view = 'not chart'
this.$nextTick(() => {
this.view = 'chart'
})
}
this.calculateTableHeight()
},
calculateTableHeight () {
const bottomPane = this.$refs.bottomPane
// 88 - view swittcher height
// 42 - table footer width
// 30 - desirable space after the table
// 5 - padding-bottom of rounded table container
// 40 - height of table header
const freeSpace = bottomPane.offsetHeight - 88 - 42 - 30 - 5 - 40
this.tableViewHeight = freeSpace - (freeSpace % 40)
}
}
}
</script>
<style scoped>
.tab-content-container {
background-color: var(--color-white);
border-top: 1px solid var(--color-border-light);
margin-top: -1px;
}
#bottomPane {
height: 100%;
background-color: var(--color-bg-light);
}
.query-results-splitter {
height: calc(100vh - 104px);
background-color: var(--color-bg-light);
}
.query-editor {
display: flex;
flex-direction: column;
height: 100%;
max-height: 100%;
box-sizing: border-box;
min-height: 190px;
}
.table-view {
margin: 0 52px;
height: calc(100% - 88px);
position: relative;
}
.table-preview {
position: absolute;
top: 40%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--color-text-base);
font-size: 13px;
}
.table-preview.error {
color: var(--color-text-error);
}
.table-preview.error::first-letter {
text-transform: capitalize;
}
</style>

View File

@@ -0,0 +1,195 @@
<template>
<div id="tabs">
<div id="tabs-header" v-if="tabs.length > 0">
<div
v-for="(tab, index) in tabs"
:key="index"
@click="selectTab(tab.id)"
:class="[{'tab-selected': (tab.id === selectedIndex)}, 'tab']"
>
<div class="tab-name">
<span v-show="tab.isUnsaved" class="star">*</span>
<span v-if="tab.name">{{ tab.name }}</span>
<span v-else class="tab-untitled">{{ tab.tempName }}</span>
</div>
<div>
<close-icon class="close-icon" :size="10" @click="beforeCloseTab(index)"/>
</div>
</div>
</div>
<tab
v-for="(tab, index) in tabs"
:key="tab.id"
:id="tab.id"
:init-name="tab.name"
:init-query="tab.query"
:init-chart="tab.chart"
:is-predefined="tab.isPredefined"
:tab-index="index"
/>
<div v-show="tabs.length === 0" id="start-guide">
<span class="link" @click="$root.$emit('createNewQuery')">Create</span>
a new query from scratch or open the one from
<router-link class="link" to="/my-queries">My queries</router-link>
</div>
<!--Close tab warning dialog -->
<modal name="close-warn" classes="dialog" height="auto">
<div class="dialog-header">
Close tab {{
closingTabIndex !== null
? (tabs[closingTabIndex].name || `[${tabs[closingTabIndex].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}]`)
: ''
}} before closing?
</div>
<div class="dialog-buttons-container">
<button class="secondary" @click="closeTab(closingTabIndex)">
Close without saving
</button>
<button class="secondary" @click="$modal.hide('close-warn')">Cancel</button>
<button class="primary" @click="saveAndClose(closingTabIndex)">Save and close</button>
</div>
</modal>
</div>
</template>
<script>
import Tab from './Tab'
import CloseIcon from '@/components/svg/close'
export default {
components: {
Tab,
CloseIcon
},
data () {
return {
closingTabIndex: null
}
},
computed: {
tabs () {
return this.$store.state.tabs
},
selectedIndex () {
return this.$store.state.currentTabId
}
},
created () {
window.addEventListener('beforeunload', this.leavingSqliteviz)
},
methods: {
leavingSqliteviz (event) {
if (this.tabs.some(tab => tab.isUnsaved)) {
event.preventDefault()
event.returnValue = ''
}
},
selectTab (id) {
this.$store.commit('setCurrentTabId', id)
},
beforeCloseTab (index) {
this.closingTabIndex = index
if (this.tabs[index].isUnsaved) {
this.$modal.show('close-warn')
} else {
this.closeTab(index)
}
},
closeTab (index) {
this.$modal.hide('close-warn')
this.closingTabIndex = null
this.$store.commit('deleteTab', index)
},
saveAndClose (index) {
this.$root.$on('querySaved', () => {
this.closeTab(index)
this.$root.$off('querySaved')
})
this.selectTab(this.tabs[index].id)
this.$modal.hide('close-warn')
this.$nextTick(() => {
this.$root.$emit('saveQuery')
})
}
}
}
</script>
<style>
#tabs {
position: relative;
height: 100%;
background-color: var(--color-bg-light);
}
#tabs-header {
display: flex;
margin: 0;
max-width: 100%;
overflow: hidden;
}
#tabs-header .tab {
height: 36px;
background-color: var(--color-bg-light);
border-right: 1px solid var(--color-border-light);
border-bottom: 1px solid var(--color-border-light);
line-height: 36px;
font-size: 14px;
color: var(--color-text-base);
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;
}
#tabs-header .tab-selected {
color: var(--color-text-active);
font-weight: 600;
border-bottom: none;
background-color: var(--color-white);
}
#tabs-header .tab-selected:hover {
cursor: default;
}
.close-icon {
margin-left: 5px;
}
#start-guide {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: var(--color-text-base);
font-size: 14px;
text-align: center;
}
.link {
color: var(--color-accent);
text-decoration: none;
cursor: pointer;
white-space: nowrap;
}
</style>

View File

@@ -23,8 +23,8 @@
<script>
import Splitpanes from '@/components/Splitpanes'
import Schema from '@/components/Schema'
import Tabs from '@/components/Tabs'
import Schema from './Schema'
import Tabs from './Tabs'
import DbUploader from '@/components/DbUploader'
export default {

240
src/views/Main/MainMenu.vue Normal file
View File

@@ -0,0 +1,240 @@
<template>
<nav>
<div>
<router-link to="/editor">Editor</router-link>
<router-link to="/my-queries">My queries</router-link>
</div>
<div>
<button
id="run-btn"
v-if="currentQuery && $route.path === '/editor'"
class="primary"
:disabled="runDisabled"
@click="currentQuery.execute"
>
Run
</button>
<button
id="save-btn"
v-show="currentQuery && $route.path === '/editor'"
class="primary"
:disabled="!isUnsaved"
@click="checkQueryBeforeSave"
>
Save
</button>
<button
id="create-btn"
class="primary"
@click="createNewQuery"
>
Create
</button>
</div>
<!--Save Query dialog -->
<modal name="save" classes="dialog" height="auto">
<div class="dialog-header">
Save query
<close-icon @click="cancelSave"/>
</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="cancelSave">Cancel</button>
<button class="primary" @click="saveQuery">Save</button>
</div>
</modal>
</nav>
</template>
<script>
import TextField from '@/components/TextField'
import CloseIcon from '@/components/svg/close'
import storedQueries from '@/lib/storedQueries'
export default {
name: 'MainMenu',
components: {
TextField,
CloseIcon
},
data () {
return {
name: '',
errorMsg: null
}
},
computed: {
currentQuery () {
return this.$store.state.currentTab
},
isUnsaved () {
if (!this.currentQuery) {
return false
}
const tabIndex = this.currentQuery.tabIndex
const tab = this.$store.state.tabs[tabIndex]
return tab && tab.isUnsaved
},
isPredefined () {
if (this.currentQuery) {
return this.currentQuery.isPredefined
} else {
return false
}
},
runDisabled () {
return this.currentQuery && (!this.$store.state.schema || !this.currentQuery.query)
}
},
created () {
this.$root.$on('createNewQuery', this.createNewQuery)
this.$root.$on('saveQuery', this.checkQueryBeforeSave)
document.addEventListener('keydown', this._keyListener)
},
beforeDestroy () {
document.removeEventListener('keydown', this._keyListener)
},
methods: {
createNewQuery () {
this.$store.dispatch('addTab').then(id => {
this.$store.commit('setCurrentTabId', id)
if (this.$route.path !== '/editor') {
this.$router.push('/editor')
}
})
},
cancelSave () {
this.$modal.hide('save')
this.$root.$off('querySaved')
},
checkQueryBeforeSave () {
this.errorMsg = null
this.name = ''
if (storedQueries.isTabNeedName(this.currentQuery)) {
this.$modal.show('save')
} else {
this.saveQuery()
}
},
saveQuery () {
const isNeedName = storedQueries.isTabNeedName(this.currentQuery)
if (isNeedName && !this.name) {
this.errorMsg = 'Query name can\'t be empty'
return
}
const dataSet = this.currentQuery.result
const tabView = this.currentQuery.view
// Save query
const value = storedQueries.save(this.currentQuery, this.name)
// Update tab in store
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')
// Signal about saving
this.$root.$emit('querySaved')
},
_keyListener (e) {
if (this.$route.path === '/editor') {
// Run query Ctrl+R or Ctrl+Enter
if ((e.key === 'r' || e.key === 'Enter') && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
if (!this.runDisabled) {
this.currentQuery.execute()
}
return
}
// Save query Ctrl+S
if (e.key === 's' && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
if (this.isUnsaved) {
this.checkQueryBeforeSave()
}
return
}
}
// New (blank) query Ctrl+B
if (e.key === 'b' && (e.ctrlKey || e.metaKey)) {
e.preventDefault()
this.createNewQuery()
}
}
}
}
</script>
<style scoped>
nav {
height: 68px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--color-bg-light);
border-bottom: 1px solid var(--color-border-light);
box-shadow: var(--shadow-1);
box-sizing: border-box;
position: fixed;
top: 0;
left: 0;
width: 100vw;
padding: 0 52px;
z-index: 999;
}
a {
font-size: 18px;
color: var(--color-text-base);
text-transform: none;
text-decoration: none;
margin-right: 28px;
}
a.router-link-active {
color: var(--color-accent);
}
button {
margin-left: 16px;
}
#save-note {
margin-bottom: 24px;
display: flex;
align-items: flex-start;
}
#save-note img {
margin: -3px 6px 0 0;
}
</style>

View File

@@ -141,16 +141,16 @@
</template>
<script>
import RenameIcon from '@/components/svg/rename'
import CopyIcon from '@/components/svg/copy'
import RenameIcon from './svg/rename'
import CopyIcon from './svg/copy'
import ExportIcon from '@/components/svg/export'
import DeleteIcon from '@/components/svg/delete'
import DeleteIcon from './svg/delete'
import CloseIcon from '@/components/svg/close'
import TextField from '@/components/TextField'
import CheckBox from '@/components/CheckBox'
import tooltipMixin from '@/mixins/tooltips'
import storedQueries from '@/storedQueries'
import fu from '@/file.utils'
import tooltipMixin from '@/tooltipMixin'
import storedQueries from '@/lib/storedQueries'
import fu from '@/lib/utils/fileIo'
export default {
name: 'MyQueries',

View File

@@ -0,0 +1,42 @@
<template>
<span>
<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="hideTooltip"
>
<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>
<span class="icon-tooltip" :style="tooltipStyle">
Duplicate query
</span>
</span>
</template>
<script>
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'CopyIcon',
mixins: [tooltipMixin]
}
</script>
<style scoped>
.icon {
display: block;
margin: 0 12px;
}
.icon:hover path {
fill: var(--color-accent);
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<span>
<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="hideTooltip"
>
<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>
<span class="icon-tooltip" :style="tooltipStyle">
Delete query
</span>
</span>
</template>
<script>
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'DeleteIcon',
mixins: [tooltipMixin]
}
</script>
<style scoped>
.icon {
display: block;
margin: 0 12px;
}
.icon:hover path {
fill: var(--color-accent);
}
</style>

View File

@@ -0,0 +1,43 @@
<template>
<span>
<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="hideTooltip"
>
<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>
<span class="icon-tooltip" :style="tooltipStyle">
Rename query
</span>
</span>
</template>
<script>
import tooltipMixin from '@/tooltipMixin'
export default {
name: 'RenameIcon',
mixins: [tooltipMixin]
}
</script>
<style scoped>
.icon {
display: block;
margin: 0 12px;
}
.icon:hover path {
fill: var(--color-accent);
}
</style>

View File

@@ -8,11 +8,11 @@
</template>
<script>
import MainMenu from '@/components/MainMenu'
import MainMenu from './MainMenu'
import '@/assets/styles/scrollbars.css'
export default {
name: 'MainView',
name: 'Main',
components: { MainMenu }
}
</script>

View File

@@ -14,7 +14,7 @@
import DbUploader from '@/components/DbUploader'
export default {
name: 'Home',
name: 'Welcome',
components: { DbUploader }
}
</script>