mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 18:18:53 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16fab2d7a | ||
|
|
769c146d95 | ||
|
|
39d958de86 | ||
|
|
fbccb3d9be | ||
|
|
f25a4d5c07 | ||
|
|
805f2861aa | ||
|
|
9c6aae7c02 | ||
|
|
d7782733ed | ||
|
|
343dea6ba8 | ||
|
|
6a178a6436 | ||
|
|
dbc2e3d0f3 | ||
|
|
24e8e3e520 |
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -3,9 +3,7 @@ on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
branches:
|
||||
- master
|
||||
- '*'
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
40
src/assets/styles/dialogs.css
Normal file
40
src/assets/styles/dialogs.css
Normal 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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
63
src/components/TextField.vue
Normal file
63
src/components/TextField.vue
Normal 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>
|
||||
20
src/components/svg/close.vue
Normal file
20
src/components/svg/close.vue
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -3,12 +3,23 @@
|
||||
<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="selectedQueries.length > 0">Export</button>
|
||||
<button class="toolbar" v-show="selectedQueries.length > 0">Delete</button>
|
||||
</div>
|
||||
<div id="toolbar-search">
|
||||
<input type="text" placeholder="Search query by name"/>
|
||||
<text-field placeholder="Search query by name" width="300px"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="rounded-bg">
|
||||
@@ -36,10 +47,10 @@
|
||||
<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(index)" />
|
||||
<copy-icon @click="duplicateQuery(index)"/>
|
||||
<export-icon @click="exportQuery(index)"/>
|
||||
<delete-icon @click="showDeleteDialog(index)"/>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -49,14 +60,61 @@
|
||||
</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 query
|
||||
<close-icon @click="$modal.hide('delete')"/>
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
currentQueryIndex !== null
|
||||
&& currentQueryIndex >= 0
|
||||
&& currentQueryIndex < queries.length
|
||||
"
|
||||
class="dialog-body"
|
||||
>
|
||||
Are you sure you want to delete
|
||||
"{{ 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 { nanoid } from 'nanoid'
|
||||
|
||||
export default {
|
||||
name: 'MyQueries',
|
||||
@@ -64,11 +122,17 @@ export default {
|
||||
RenameIcon,
|
||||
CopyIcon,
|
||||
ExportIcon,
|
||||
DeleteIcon
|
||||
DeleteIcon,
|
||||
CloseIcon,
|
||||
TextField
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
queries: []
|
||||
queries: [],
|
||||
newName: null,
|
||||
currentQueryIndex: null,
|
||||
errorMsg: null,
|
||||
selectedQueries: []
|
||||
}
|
||||
},
|
||||
created () {
|
||||
@@ -103,6 +167,90 @@ export default {
|
||||
this.$store.commit('addTab', tab)
|
||||
this.$store.commit('setCurrentTabId', tab.id)
|
||||
this.$router.push('/editor')
|
||||
},
|
||||
showRenameDialog (index) {
|
||||
this.errorMsg = null
|
||||
this.currentQueryIndex = index
|
||||
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.queries[index]))
|
||||
newQuery.name = newQuery.name + ' Copy'
|
||||
newQuery.id = nanoid()
|
||||
newQuery.createdAt = new Date()
|
||||
this.queries.push(newQuery)
|
||||
this.saveQueriesInLocalStorage()
|
||||
},
|
||||
showDeleteDialog (index) {
|
||||
this.currentQueryIndex = index
|
||||
this.$modal.show('delete')
|
||||
},
|
||||
deleteQuery () {
|
||||
this.$modal.hide('delete')
|
||||
const id = this.queries[this.currentQueryIndex].id
|
||||
this.queries.splice(this.currentQueryIndex, 1)
|
||||
this.saveQueriesInLocalStorage()
|
||||
const tabIndex = this.findTabIndex(id)
|
||||
if (tabIndex >= 0) {
|
||||
this.$store.commit('deleteTab', tabIndex)
|
||||
}
|
||||
},
|
||||
findTabIndex (id) {
|
||||
return this.$store.state.tabs.findIndex(tab => tab.id === id)
|
||||
},
|
||||
exportQuery (index) {
|
||||
this.currentQueryIndex = index
|
||||
const downloader = this.$refs.downloader
|
||||
const currentQuery = JSON.parse(JSON.stringify(this.queries[this.currentQueryIndex]))
|
||||
delete currentQuery.id
|
||||
delete currentQuery.createdAt
|
||||
const json = JSON.stringify(currentQuery)
|
||||
const blob = new Blob([json], { type: 'octet/stream' })
|
||||
const url = window.URL.createObjectURL(blob)
|
||||
downloader.href = url
|
||||
downloader.download = `${currentQuery.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()
|
||||
})
|
||||
|
||||
this.queries = this.queries.concat(importedQueries)
|
||||
this.saveQueriesInLocalStorage()
|
||||
this.$refs.importFile.value = null
|
||||
}
|
||||
reader.readAsText(file)
|
||||
},
|
||||
saveQueriesInLocalStorage () {
|
||||
localStorage.setItem('myQueries', JSON.stringify(this.queries))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,10 +264,13 @@ 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%;
|
||||
@@ -172,4 +323,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>
|
||||
|
||||
Reference in New Issue
Block a user