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

18 Commits
0.0.1 ... 0.2.0

Author SHA1 Message Date
lana-k
3e5e4b29c1 add group delete feature 2020-10-20 14:53:08 +02:00
lana-k
71c70e0232 group export 2020-10-19 21:24:31 +02:00
lana-k
8f49c0509f add filter to my queries 2020-10-19 12:37:12 +02:00
lana-k
5e29a051b2 hover style for checkboxes 2020-10-17 14:10:17 +02:00
lana-k
8b76258260 add checkboxes 2020-10-17 14:07:43 +02:00
lana-k
518270e1f5 fix build 2020-10-15 20:48:54 +02:00
lana-k
f16fab2d7a hide Export and Delete (group functions) 2020-10-15 20:29:42 +02:00
lana-k
769c146d95 add import queries feature 2020-10-15 20:27:35 +02:00
lana-k
39d958de86 add export to file feature 2020-10-13 21:46:15 +02:00
lana-k
fbccb3d9be add Delete query feature 2020-10-13 21:13:47 +02:00
lana-k
f25a4d5c07 duplicate feature 2020-10-13 20:05:18 +02:00
lana-k
805f2861aa finish rename feature 2020-10-13 19:57:07 +02:00
lana-k
9c6aae7c02 add draft of Rename Query dialog 2020-10-12 22:49:52 +02:00
lana-k
d7782733ed stop propagating click on icons 2020-10-12 22:49:28 +02:00
lana-k
343dea6ba8 add close icon 2020-10-12 22:49:09 +02:00
lana-k
6a178a6436 style for secondary button 2020-10-12 22:48:34 +02:00
lana-k
dbc2e3d0f3 add VModal 2020-10-12 22:48:10 +02:00
lana-k
24e8e3e520 Update main.yml 2020-10-12 13:51:45 +02:00
20 changed files with 600 additions and 67 deletions

View File

@@ -3,9 +3,7 @@ on:
workflow_dispatch:
push:
tags:
- '*'
branches:
- master
- '*'
jobs:
deploy:

13
package-lock.json generated
View File

@@ -10672,6 +10672,11 @@
"thenify-all": "^1.0.0"
}
},
"nanoid": {
"version": "3.1.12",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.12.tgz",
"integrity": "sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A=="
},
"nanomatch": {
"version": "1.2.13",
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
@@ -15878,6 +15883,14 @@
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
"dev": true
},
"vue-js-modal": {
"version": "2.0.0-rc.6",
"resolved": "https://registry.npmjs.org/vue-js-modal/-/vue-js-modal-2.0.0-rc.6.tgz",
"integrity": "sha512-bJOm7Yhrl0ur/QyXjoC3gMMmE7UxiVEcS2rl8v9iPXIe9QLvjiCSZElSOvvyps8LNuG1X0rPifZGxI/CWKCFaw==",
"requires": {
"resize-observer-polyfill": "^1.5.1"
}
},
"vue-loader": {
"version": "15.9.3",
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz",

View File

@@ -4,13 +4,14 @@
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build": "NODE_OPTIONS=--max_old_space_size=4096 vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"lint": "vue-cli-service lint"
},
"dependencies": {
"codemirror": "^5.57.0",
"core-js": "^3.6.5",
"nanoid": "^3.1.12",
"plotly.js": "^1.54.6",
"react": "^16.13.1",
"react-chart-editor": "^0.41.7",
@@ -19,6 +20,7 @@
"sqlite-parser": "^1.0.1",
"vue": "^2.6.11",
"vue-codemirror": "^4.0.6",
"vue-js-modal": "^2.0.0-rc.6",
"vue-router": "^3.2.0",
"vuejs-paginate": "^2.1.0",
"vuera": "^0.2.7",

View File

@@ -0,0 +1,17 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="17" height="17" rx="2.5" fill="#119DFF" stroke="#0D76BF"/>
<g filter="url(#filter0_d)">
<path d="M15.75 5.25L6.75 14.25L2.625 10.125L3.6825 9.0675L6.75 12.1275L14.6925 4.1925L15.75 5.25Z" fill="white"/>
</g>
<defs>
<filter id="filter0_d" x="0.625" y="3.1925" width="17.125" height="14.0575" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.164706 0 0 0 0 0.247059 0 0 0 0 0.372549 0 0 0 0.7 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 981 B

View File

@@ -0,0 +1,17 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.5" y="0.5" width="17" height="17" rx="2.5" fill="#F3F6FA" stroke="#C8D4E3"/>
<g filter="url(#filter0_d)">
<path d="M15.75 5.24988L6.75 14.2499L2.625 10.1249L3.6825 9.06738L6.75 12.1274L14.6925 4.19238L15.75 5.24988Z" fill="#119DFF"/>
</g>
<defs>
<filter id="filter0_d" x="0.625" y="3.19238" width="17.125" height="14.0575" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="1"/>
<feGaussianBlur stdDeviation="1"/>
<feColorMatrix type="matrix" values="0 0 0 0 0.164706 0 0 0 0 0.247059 0 0 0 0 0.372549 0 0 0 0.45 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow" result="shape"/>
</filter>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 996 B

View File

@@ -1,3 +1,12 @@
button {
box-sizing: border-box;
height: 36px;
padding: 0 12px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
button:focus {
outline: none;
}
@@ -5,16 +14,10 @@ button:focus {
button.primary {
background: var(--color-accent);
border: 1px solid var(--color-accent-shade);
box-sizing: border-box;
border-radius: var(--border-radius-big);
height: 36px;
padding: 0 12px;
min-width: 83px;
color: var(--color-text-light);
text-shadow: var(--shadow);
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
button.primary:hover {
@@ -22,8 +25,6 @@ button.primary:hover {
border: 1px solid var(--color-accent-shade);
color: var(--color-text-light);
text-shadow: var(--shadow);
font-size: 14px;
font-weight: 600;
}
button.primary:disabled {
@@ -34,16 +35,24 @@ button.primary:disabled {
cursor: default;
}
button.secondary {
background: white;
border: 1px solid var(--color-border);
border-radius: var(--border-radius-big);
min-width: 83px;
color: var(--color-text-base);
}
button.secondary:hover {
border: 1px solid var(--color-text-light-2);
color: var(--color-text-active);
}
button.toolbar {
background: transparent;
border: none;
box-sizing: border-box;
height: 36px;
padding: 0 12px;
color: var(--color-text-base);
font-size: 14px;
font-weight: 600;
cursor: pointer;
padding: 0;
}
button.toolbar:hover {

View File

@@ -0,0 +1,40 @@
.dialog {
border-radius: var(--border-radius-big);
box-shadow: 0px 2px 9px rgba(80, 103, 132, 0.8);
}
.dialog-header {
height: 46px;
line-height: 46px;
padding: 0 22px 0 12px;
color: var(--color-text-base);
font-size: 16px;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.dialog-body {
min-height: 60px;
background-color: var(--color-bg-light);
padding: 24px;
border-top: 1px solid var(--color-border-light);
color: var(--color-text-base);
font-size: 13px;
}
.dialog-buttons-container {
display: flex;
justify-content: flex-end;
background-color: var(--color-bg-light);
padding: 24px;
}
.dialog-buttons-container button {
margin-left: 16px;
}
.vm--overlay {
background-color: rgba(162, 177, 198, 0.5);
}

View File

@@ -1,18 +0,0 @@
input[type="text"] {
background: var(--color-white);
border: 1px solid var(--color-border);
color: var(--color-text-base);
border-radius: var(--border-radius-medium-2);
height: 36px;
padding: 0 8px;
font-size: 12px;
}
input[type="text"]::placeholder {
color: var(--color-text-light-2);
}
input[type="text"]:focus {
outline: none;
}

View File

@@ -10,6 +10,7 @@
--color-blue-medium: #119DFF;
--color-blue-dark: #0D76BF;
--color-blue-dark-2: #2A3F5F;
--color-red: #EF553B;
@@ -25,6 +26,7 @@
--color-text-light-2: var(--color-gray-medium);
--color-text-base: var(--color-gray-dark);
--color-text-active: var(--color-blue-dark-2);
--color-text-error: var(--color-red);
--shadow: 0 1px 2px rgba(42, 63, 95, 0.7);

View File

@@ -0,0 +1,72 @@
<template>
<div class="checkbox-container" @click.stop="onClick">
<div v-show="!checked" class="unchecked" />
<img
v-show="checked && theme === 'accent'"
:src="require('@/assets/images/checkbox_checked.svg')"
/>
<img
v-show="checked && theme === 'light'"
:src="require('@/assets/images/checkbox_checked_light.svg')"
/>
</div>
</template>
<script>
export default {
name: 'checkBox',
props: {
theme: {
type: String,
required: false,
default: 'accent',
validator: (value) => {
return ['accent', 'light'].includes(value)
}
},
init: {
type: Boolean,
required: false,
default: false
}
},
data () {
return {
checked: this.init
}
},
watch: {
checked () {
this.$emit('change', this.checked)
}
},
methods: {
onClick () {
this.checked = !this.checked
this.$emit('click', this.checked)
}
}
}
</script>
<style scoped>
.checkbox-container {
display: inline-block;
cursor: pointer;
}
.unchecked {
width: 18px;
height: 18px;
background-color: white;
border: 1px solid var(--color-border);
border-radius: var(--border-radius-medium);
box-sizing: border-box;
}
.unchecked:hover {
background-color: var(--color-bg-light);
}
img {
display: block;
}
</style>

View File

@@ -19,12 +19,14 @@
</template>
<script>
import { nanoid } from 'nanoid'
export default {
name: 'MainMenu',
methods: {
createNewQuery () {
const tab = {
id: Number(new Date()),
id: nanoid(),
name: null,
tempName: this.$store.state.untitledLastIndex
? `Untitled ${this.$store.state.untitledLastIndex}`
@@ -69,6 +71,7 @@ export default {
}
}
</script>
<style scoped>
nav {
height: 68px;

View File

@@ -1,6 +1,6 @@
<template>
<div>
<input type="text" placeholder="Search table"/>
<text-field placeholder="Search table" width="100%"/>
<div id="db">
<div @click="schemaVisible = !schemaVisible" class="db-name">
<svg
@@ -50,10 +50,11 @@
<script>
import TableDescription from '@/components/TableDescription'
import TextField from '@/components/TextField'
export default {
name: 'Schema',
components: { TableDescription },
components: { TableDescription, TextField },
data () {
return {
schemaVisible: true,

View File

@@ -0,0 +1,63 @@
<template>
<div>
<div :class="['text-field-label', { error: errorMsg }]">{{ label }}</div>
<input
type="text"
:placeholder="placeholder"
:class="{ error: errorMsg }"
:style="{ width: width }"
:value="value"
@input="$emit('input', $event.target.value)"
/>
<div class="text-field-error">{{ errorMsg }}</div>
</div>
</template>
<script>
export default {
name: 'textField',
props: ['placeholder', 'label', 'errorMsg', 'value', 'width']
}
</script>
<style scoped>
input {
background: var(--color-white);
border: 1px solid var(--color-border);
color: var(--color-text-base);
border-radius: var(--border-radius-medium-2);
height: 36px;
padding: 0 8px;
font-size: 13px;
box-sizing: border-box;
}
input::placeholder {
color: var(--color-text-light-2);
}
input:focus {
outline: none;
}
input.error {
border-color: var(--color-text-error);
}
.text-field-label {
font-size: 12px;
color: var(--color-text-base);
padding-left: 8px;
margin-bottom: 2px;
}
.text-field-label.error {
color: var(--color-text-error);
}
.text-field-error {
color: var(--color-text-error);
font-size: 12px;
padding-left: 8px;
margin-top: 2px;
}
</style>

View File

@@ -0,0 +1,20 @@
<template>
<svg @click.stop="$emit('click')" class="icon" width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 1.41L12.59 0L7 5.59L1.41 0L0 1.41L5.59 7L0 12.59L1.41 14L7 8.41L12.59 14L14 12.59L8.41 7L14 1.41Z" fill="#A2B1C6"/>
</svg>
</template>
<script>
export default {
name: 'CloseIcon'
}
</script>
<style scoped>
.icon {
cursor: pointer;
}
.icon:hover path {
fill: var(--color-text-active);
}
</style>

View File

@@ -1,5 +1,5 @@
<template>
<svg class="icon" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<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">
<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>
</template>

View File

@@ -1,5 +1,5 @@
<template>
<svg class="icon" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<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">
<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>
</template>

View File

@@ -1,5 +1,5 @@
<template>
<svg class="icon" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<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">
<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>
</template>

View File

@@ -1,5 +1,5 @@
<template>
<svg class="icon" width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<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">
<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>
</template>

View File

@@ -3,13 +3,15 @@ import App from './App.vue'
import router from './router'
import store from './store'
import { VuePlugin } from 'vuera'
import VModal from 'vue-js-modal'
import '@/assets/styles/variables.css'
import '@/assets/styles/buttons.css'
import '@/assets/styles/textFields.css'
import '@/assets/styles/tables.css'
import '@/assets/styles/dialogs.css'
Vue.use(VuePlugin)
Vue.use(VModal)
Vue.config.productionTip = false

View File

@@ -3,19 +3,43 @@
<div id="my-queries-content">
<div id="my-queries-toolbar">
<div id="toolbar-buttons">
<button class="toolbar">Import</button>
<button class="toolbar">Export</button>
<button class="toolbar">Delete</button>
<input
ref="importFile"
type="file"
accept=".json"
id="import-file"
@change="importQueries"
/>
<button class="toolbar">
<label for="import-file">
Import
</label>
</button>
<button
class="toolbar"
v-show="selectedQueriesCount > 0"
@click="exportQuery(selectedQueriesIds)"
>
Export
</button>
<button
class="toolbar"
v-show="selectedQueriesCount > 0"
@click="showDeleteDialog(selectedQueriesIds)"
>
Delete
</button>
</div>
<div id="toolbar-search">
<input type="text" placeholder="Search query by name"/>
<text-field placeholder="Search query by name" width="300px" v-model="filter"/>
</div>
</div>
<div class="rounded-bg">
<div class="header-container">
<div>
<div class="fixed-header" ref="name-th">
Name
<check-box ref="mainCheckBox" theme="light" @click="toggleSelectAll"/>
<div class="name-th">Name</div>
</div>
<div class="fixed-header">
Created at
@@ -28,18 +52,25 @@
>
<table ref="table">
<tbody>
<tr v-for="(query, index) in queries" :key="query.id" @click="openQuery(index)">
<tr v-for="(query, index) in showedQueries" :key="query.id" @click="openQuery(index)">
<td ref="name-td">
{{ query.name }}
<div class="cell-data">
<check-box
ref="rowCheckBox"
:init="selectAll || selectedQueriesIds.has(query.id)"
@change="toggleRow($event, query.id)"
/>
<div class="name">{{ query.name }}</div>
</div>
</td>
<td>
<div class="second-column">
<div class="date-container">{{ query.createdAt | date }}</div>
<div class="icons-container">
<rename-icon />
<copy-icon />
<export-icon />
<delete-icon />
<rename-icon @click="showRenameDialog(query.id)" />
<copy-icon @click="duplicateQuery(index)"/>
<export-icon @click="exportQuery(index)"/>
<delete-icon @click="showDeleteDialog(query.id)"/>
</div>
</div>
</td>
@@ -49,14 +80,67 @@
</div>
</div>
</div>
<!--Rename Query dialog -->
<modal name="rename" classes="dialog" height="auto">
<div class="dialog-header">
Rename query
<close-icon @click="$modal.hide('rename')"/>
</div>
<div class="dialog-body">
<text-field
label="New query name"
:error-msg="errorMsg"
v-model="newName"
width="100%"
/>
</div>
<div class="dialog-buttons-container">
<button class="secondary" @click="$modal.hide('rename')">Cancel</button>
<button class="primary" @click="renameQuery">Rename</button>
</div>
</modal>
<!--Delete Query dialog -->
<modal name="delete" classes="dialog" height="auto">
<div class="dialog-header">
Delete {{ deleteGroup ? 'queries' : 'query' }}
<close-icon @click="$modal.hide('delete')"/>
</div>
<div
v-if="
deleteGroup || (
currentQueryIndex !== null
&& currentQueryIndex >= 0
&& currentQueryIndex < queries.length
)
"
class="dialog-body"
>
Are you sure you want to delete
{{ deleteGroup
? `${selectedQueriesCount} ${selectedQueriesCount > 1 ? 'queries' : 'query'}`
: `"${queries[currentQueryIndex].name}"`
}}?
</div>
<div class="dialog-buttons-container">
<button class="secondary" @click="$modal.hide('delete')">Cancel</button>
<button class="primary" @click="deleteQuery">Delete</button>
</div>
</modal>
<a ref="downloader" />
</div>
</template>
<script>
import RenameIcon from '@/components/svg/rename.vue'
import CopyIcon from '@/components/svg/copy.vue'
import ExportIcon from '@/components/svg/export.vue'
import DeleteIcon from '@/components/svg/delete.vue'
import RenameIcon from '@/components/svg/rename'
import CopyIcon from '@/components/svg/copy'
import ExportIcon from '@/components/svg/export'
import DeleteIcon from '@/components/svg/delete'
import CloseIcon from '@/components/svg/close'
import TextField from '@/components/TextField'
import CheckBox from '@/components/CheckBox'
import { nanoid } from 'nanoid'
export default {
name: 'MyQueries',
@@ -64,11 +148,34 @@ export default {
RenameIcon,
CopyIcon,
ExportIcon,
DeleteIcon
DeleteIcon,
CloseIcon,
TextField,
CheckBox
},
data () {
return {
queries: []
queries: [],
filter: null,
newName: null,
currentQueryId: null,
errorMsg: null,
selectedQueriesIds: new Set(),
selectedQueriesCount: 0,
selectAll: false,
deleteGroup: false
}
},
computed: {
showedQueries () {
if (!this.filter) {
return this.queries
} else {
return this.queries.filter(query => query.name.toUpperCase().indexOf(this.filter.toUpperCase()) >= 0)
}
},
currentQueryIndex () {
return this.queries.findIndex(query => query.id === this.currentQueryId)
}
},
created () {
@@ -98,11 +205,158 @@ export default {
this.$refs['name-th'].style = `width: ${this.$refs['name-td'][0].offsetWidth}px`
},
openQuery (index) {
const tab = this.queries[index]
const tab = this.showedQueries[index]
tab.isUnsaved = false
this.$store.commit('addTab', tab)
this.$store.commit('setCurrentTabId', tab.id)
this.$router.push('/editor')
},
showRenameDialog (id) {
this.errorMsg = null
this.currentQueryId = id
this.newName = this.queries[this.currentQueryIndex].name
this.$modal.show('rename')
},
renameQuery () {
if (!this.newName) {
this.errorMsg = 'Query name can\'t be empty'
return
}
const currentQuery = this.queries[this.currentQueryIndex]
currentQuery.name = this.newName
this.$set(this.queries, this.currentQueryIndex, currentQuery)
this.$modal.hide('rename')
this.saveQueriesInLocalStorage()
const tabIndex = this.findTabIndex(currentQuery.id)
if (tabIndex >= 0) {
this.$store.commit('updateTabName', { index: tabIndex, newName: this.newName })
}
},
duplicateQuery (index) {
const newQuery = JSON.parse(JSON.stringify(this.showedQueries[index]))
newQuery.name = newQuery.name + ' Copy'
newQuery.id = nanoid()
newQuery.createdAt = new Date()
this.queries.push(newQuery)
if (this.selectAll) {
this.selectedQueriesIds.add(newQuery.id)
this.selectedQueriesCount = this.selectedQueriesIds.size
}
this.saveQueriesInLocalStorage()
},
showDeleteDialog (id) {
this.deleteGroup = typeof id !== 'string'
if (!this.deleteGroup) {
this.currentQueryId = id
}
this.$modal.show('delete')
},
deleteQuery () {
this.$modal.hide('delete')
if (!this.deleteGroup) {
this.queries.splice(this.currentQueryIndex, 1)
const tabIndex = this.findTabIndex(this.currentQueryId)
if (tabIndex >= 0) {
this.$store.commit('deleteTab', tabIndex)
}
if (this.selectedQueriesIds.has(this.currentQueryId)) {
this.selectedQueriesIds.delete(this.currentQueryId)
}
} else {
this.queries = this.selectAll
? []
: this.queries.filter(query => !this.selectedQueriesIds.has(query.id))
const tabs = this.$store.state.tabs
for (let i = tabs.length - 1; i >= 0; i--) {
if (this.selectedQueriesIds.has(tabs[i].id)) {
this.$store.commit('deleteTab', i)
}
}
this.selectedQueriesIds.clear()
}
this.selectedQueriesCount = this.selectedQueriesIds.size
this.saveQueriesInLocalStorage()
},
findTabIndex (id) {
return this.$store.state.tabs.findIndex(tab => tab.id === id)
},
exportQuery (index) {
let data
let name
// single operation
if (typeof index === 'number') {
console.log('single')
data = JSON.parse(JSON.stringify(this.showedQueries[index]))
name = data.name
delete data.id
delete data.createdAt
} else {
// group operation
data = this.selectAll
? JSON.parse(JSON.stringify(this.queries))
: this.queries.filter(query => this.selectedQueriesIds.has(query.id))
name = 'My sqliteviz queries'
data.forEach(query => {
delete query.id
delete query.createdAt
})
}
// export data to file
const downloader = this.$refs.downloader
const json = JSON.stringify(data, null, 4)
const blob = new Blob([json], { type: 'octet/stream' })
const url = window.URL.createObjectURL(blob)
downloader.href = url
downloader.download = `${name}.json`
downloader.click()
window.URL.revokeObjectURL(url)
},
importQueries () {
const file = this.$refs.importFile.files[0]
const reader = new FileReader()
reader.onload = () => {
let importedQueries = JSON.parse(event.target.result)
if (!Array.isArray(importedQueries)) {
importedQueries = [importedQueries]
}
importedQueries.forEach(query => {
query.id = nanoid()
query.createdAt = new Date()
if (this.selectAll) {
this.selectedQueriesIds.add(query.id)
this.selectedQueriesCount = this.selectedQueriesIds.size
}
})
this.queries = this.queries.concat(importedQueries)
this.saveQueriesInLocalStorage()
this.$refs.importFile.value = null
}
reader.readAsText(file)
},
saveQueriesInLocalStorage () {
localStorage.setItem('myQueries', JSON.stringify(this.queries))
},
toggleSelectAll (checked) {
this.selectAll = checked
this.$refs.rowCheckBox.forEach(item => { item.checked = checked })
this.selectedQueriesIds = checked ? new Set(this.queries.map(query => query.id)) : new Set()
this.selectedQueriesCount = this.selectedQueriesIds.size
},
toggleRow (checked, id) {
if (checked) {
this.selectedQueriesIds.add(id)
} else {
if (this.selectedQueriesIds.size === this.queries.length) {
this.$refs.mainCheckBox.checked = false
this.selectAll = false
}
this.selectedQueriesIds.delete(id)
}
this.selectedQueriesCount = this.selectedQueriesIds.size
}
}
}
@@ -116,32 +370,54 @@ export default {
#my-queries-toolbar {
display: flex;
justify-content: space-between;
margin-bottom: 18px;
margin: 0 auto 8px;
max-width: 1500px;
width: 100%;
}
.rounded-bg,
#my-queries-toolbar {
.rounded-bg {
margin: 0 auto;
max-width: 1500px;
width: 100%;
}
.fixed-header:first-child {
display: flex;
align-items: center;
padding-left: 12px;
}
.fixed-header:first-child .name-th {
margin-left: 24px;
}
table {
margin-top: 0;
}
tbody tr td {
overflow: hidden;
min-width: 0;
text-overflow: ellipsis;
padding: 0 24px;
line-height: 40px;
}
tbody tr td:first-child {
width: 70%;
max-width: 0;
padding: 0 12px;
}
tbody tr td:last-child {
width: 30%;
max-width: 0;
padding: 0 24px;
}
tbody .cell-data {
display: flex;
align-items: center;
max-width: 100%;
}
tbody .cell-data div.name {
overflow: hidden;
text-overflow: ellipsis;
margin-left: 24px;
}
tbody tr:hover td {
@@ -172,4 +448,20 @@ tbody tr:hover td {
tbody tr:hover .icons-container {
display: block;
}
.dialog input {
width: 100%;
}
a, #import-file {
display: none;
}
button.toolbar {
margin-right: 16px;
}
button label {
display: block;
line-height: 36px;
}
button label:hover {
cursor: pointer;
}
</style>