mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-07 18:48:55 +08:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f16fab2d7a | ||
|
|
769c146d95 | ||
|
|
39d958de86 | ||
|
|
fbccb3d9be | ||
|
|
f25a4d5c07 | ||
|
|
805f2861aa | ||
|
|
9c6aae7c02 | ||
|
|
d7782733ed | ||
|
|
343dea6ba8 | ||
|
|
6a178a6436 | ||
|
|
dbc2e3d0f3 | ||
|
|
24e8e3e520 |
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@@ -4,8 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- '*'
|
- '*'
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy:
|
||||||
|
|||||||
13
package-lock.json
generated
13
package-lock.json
generated
@@ -10672,6 +10672,11 @@
|
|||||||
"thenify-all": "^1.0.0"
|
"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": {
|
"nanomatch": {
|
||||||
"version": "1.2.13",
|
"version": "1.2.13",
|
||||||
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
"resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz",
|
||||||
@@ -15878,6 +15883,14 @@
|
|||||||
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
|
"integrity": "sha512-BXq3jwIagosjgNVae6tkHzzIk6a8MHFtzAdwhnV5VlvPTFxDCvIttgSiHWjdGoTJvXtmRu5HacExfdarRcFhog==",
|
||||||
"dev": true
|
"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": {
|
"vue-loader": {
|
||||||
"version": "15.9.3",
|
"version": "15.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.9.3.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"codemirror": "^5.57.0",
|
"codemirror": "^5.57.0",
|
||||||
"core-js": "^3.6.5",
|
"core-js": "^3.6.5",
|
||||||
|
"nanoid": "^3.1.12",
|
||||||
"plotly.js": "^1.54.6",
|
"plotly.js": "^1.54.6",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
"react-chart-editor": "^0.41.7",
|
"react-chart-editor": "^0.41.7",
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
"sqlite-parser": "^1.0.1",
|
"sqlite-parser": "^1.0.1",
|
||||||
"vue": "^2.6.11",
|
"vue": "^2.6.11",
|
||||||
"vue-codemirror": "^4.0.6",
|
"vue-codemirror": "^4.0.6",
|
||||||
|
"vue-js-modal": "^2.0.0-rc.6",
|
||||||
"vue-router": "^3.2.0",
|
"vue-router": "^3.2.0",
|
||||||
"vuejs-paginate": "^2.1.0",
|
"vuejs-paginate": "^2.1.0",
|
||||||
"vuera": "^0.2.7",
|
"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 {
|
button:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
@@ -5,16 +14,10 @@ button:focus {
|
|||||||
button.primary {
|
button.primary {
|
||||||
background: var(--color-accent);
|
background: var(--color-accent);
|
||||||
border: 1px solid var(--color-accent-shade);
|
border: 1px solid var(--color-accent-shade);
|
||||||
box-sizing: border-box;
|
|
||||||
border-radius: var(--border-radius-big);
|
border-radius: var(--border-radius-big);
|
||||||
height: 36px;
|
|
||||||
padding: 0 12px;
|
|
||||||
min-width: 83px;
|
min-width: 83px;
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
text-shadow: var(--shadow);
|
text-shadow: var(--shadow);
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.primary:hover {
|
button.primary:hover {
|
||||||
@@ -22,8 +25,6 @@ button.primary:hover {
|
|||||||
border: 1px solid var(--color-accent-shade);
|
border: 1px solid var(--color-accent-shade);
|
||||||
color: var(--color-text-light);
|
color: var(--color-text-light);
|
||||||
text-shadow: var(--shadow);
|
text-shadow: var(--shadow);
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.primary:disabled {
|
button.primary:disabled {
|
||||||
@@ -34,16 +35,24 @@ button.primary:disabled {
|
|||||||
cursor: default;
|
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 {
|
button.toolbar {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
border: none;
|
border: none;
|
||||||
box-sizing: border-box;
|
|
||||||
height: 36px;
|
|
||||||
padding: 0 12px;
|
|
||||||
color: var(--color-text-base);
|
color: var(--color-text-base);
|
||||||
font-size: 14px;
|
padding: 0;
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button.toolbar:hover {
|
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-medium: #119DFF;
|
||||||
--color-blue-dark: #0D76BF;
|
--color-blue-dark: #0D76BF;
|
||||||
--color-blue-dark-2: #2A3F5F;
|
--color-blue-dark-2: #2A3F5F;
|
||||||
|
--color-red: #EF553B;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -25,6 +26,7 @@
|
|||||||
--color-text-light-2: var(--color-gray-medium);
|
--color-text-light-2: var(--color-gray-medium);
|
||||||
--color-text-base: var(--color-gray-dark);
|
--color-text-base: var(--color-gray-dark);
|
||||||
--color-text-active: var(--color-blue-dark-2);
|
--color-text-active: var(--color-blue-dark-2);
|
||||||
|
--color-text-error: var(--color-red);
|
||||||
|
|
||||||
--shadow: 0 1px 2px rgba(42, 63, 95, 0.7);
|
--shadow: 0 1px 2px rgba(42, 63, 95, 0.7);
|
||||||
|
|
||||||
|
|||||||
@@ -19,12 +19,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MainMenu',
|
name: 'MainMenu',
|
||||||
methods: {
|
methods: {
|
||||||
createNewQuery () {
|
createNewQuery () {
|
||||||
const tab = {
|
const tab = {
|
||||||
id: Number(new Date()),
|
id: nanoid(),
|
||||||
name: null,
|
name: null,
|
||||||
tempName: this.$store.state.untitledLastIndex
|
tempName: this.$store.state.untitledLastIndex
|
||||||
? `Untitled ${this.$store.state.untitledLastIndex}`
|
? `Untitled ${this.$store.state.untitledLastIndex}`
|
||||||
@@ -69,6 +71,7 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
nav {
|
nav {
|
||||||
height: 68px;
|
height: 68px;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<input type="text" placeholder="Search table"/>
|
<text-field placeholder="Search table" width="100%"/>
|
||||||
<div id="db">
|
<div id="db">
|
||||||
<div @click="schemaVisible = !schemaVisible" class="db-name">
|
<div @click="schemaVisible = !schemaVisible" class="db-name">
|
||||||
<svg
|
<svg
|
||||||
@@ -50,10 +50,11 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import TableDescription from '@/components/TableDescription'
|
import TableDescription from '@/components/TableDescription'
|
||||||
|
import TextField from '@/components/TextField'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Schema',
|
name: 'Schema',
|
||||||
components: { TableDescription },
|
components: { TableDescription, TextField },
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
schemaVisible: true,
|
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>
|
<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"/>
|
<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>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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"/>
|
<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>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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"/>
|
<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>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<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"/>
|
<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>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import App from './App.vue'
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import { VuePlugin } from 'vuera'
|
import { VuePlugin } from 'vuera'
|
||||||
|
import VModal from 'vue-js-modal'
|
||||||
|
|
||||||
import '@/assets/styles/variables.css'
|
import '@/assets/styles/variables.css'
|
||||||
import '@/assets/styles/buttons.css'
|
import '@/assets/styles/buttons.css'
|
||||||
import '@/assets/styles/textFields.css'
|
|
||||||
import '@/assets/styles/tables.css'
|
import '@/assets/styles/tables.css'
|
||||||
|
import '@/assets/styles/dialogs.css'
|
||||||
|
|
||||||
Vue.use(VuePlugin)
|
Vue.use(VuePlugin)
|
||||||
|
Vue.use(VModal)
|
||||||
|
|
||||||
Vue.config.productionTip = false
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,23 @@
|
|||||||
<div id="my-queries-content">
|
<div id="my-queries-content">
|
||||||
<div id="my-queries-toolbar">
|
<div id="my-queries-toolbar">
|
||||||
<div id="toolbar-buttons">
|
<div id="toolbar-buttons">
|
||||||
<button class="toolbar">Import</button>
|
<input
|
||||||
<button class="toolbar">Export</button>
|
ref="importFile"
|
||||||
<button class="toolbar">Delete</button>
|
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>
|
||||||
<div id="toolbar-search">
|
<div id="toolbar-search">
|
||||||
<input type="text" placeholder="Search query by name"/>
|
<text-field placeholder="Search query by name" width="300px"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="rounded-bg">
|
<div class="rounded-bg">
|
||||||
@@ -36,10 +47,10 @@
|
|||||||
<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 />
|
<rename-icon @click="showRenameDialog(index)" />
|
||||||
<copy-icon />
|
<copy-icon @click="duplicateQuery(index)"/>
|
||||||
<export-icon />
|
<export-icon @click="exportQuery(index)"/>
|
||||||
<delete-icon />
|
<delete-icon @click="showDeleteDialog(index)"/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
@@ -49,14 +60,61 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import RenameIcon from '@/components/svg/rename.vue'
|
import RenameIcon from '@/components/svg/rename'
|
||||||
import CopyIcon from '@/components/svg/copy.vue'
|
import CopyIcon from '@/components/svg/copy'
|
||||||
import ExportIcon from '@/components/svg/export.vue'
|
import ExportIcon from '@/components/svg/export'
|
||||||
import DeleteIcon from '@/components/svg/delete.vue'
|
import DeleteIcon from '@/components/svg/delete'
|
||||||
|
import CloseIcon from '@/components/svg/close'
|
||||||
|
import TextField from '@/components/TextField'
|
||||||
|
import { nanoid } from 'nanoid'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'MyQueries',
|
name: 'MyQueries',
|
||||||
@@ -64,11 +122,17 @@ export default {
|
|||||||
RenameIcon,
|
RenameIcon,
|
||||||
CopyIcon,
|
CopyIcon,
|
||||||
ExportIcon,
|
ExportIcon,
|
||||||
DeleteIcon
|
DeleteIcon,
|
||||||
|
CloseIcon,
|
||||||
|
TextField
|
||||||
},
|
},
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
queries: []
|
queries: [],
|
||||||
|
newName: null,
|
||||||
|
currentQueryIndex: null,
|
||||||
|
errorMsg: null,
|
||||||
|
selectedQueries: []
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
@@ -103,6 +167,90 @@ 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.$router.push('/editor')
|
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 {
|
#my-queries-toolbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
|
margin-bottom: 18px;
|
||||||
|
margin: 0 auto 8px;
|
||||||
|
max-width: 1500px;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rounded-bg,
|
.rounded-bg {
|
||||||
#my-queries-toolbar {
|
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
max-width: 1500px;
|
max-width: 1500px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@@ -172,4 +323,20 @@ tbody tr:hover td {
|
|||||||
tbody tr:hover .icons-container {
|
tbody tr:hover .icons-container {
|
||||||
display: block;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user