diff --git a/.eslintrc.js b/.eslintrc.js
index 3853d47..d5d36c3 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,12 +2,9 @@ module.exports = {
root: true,
env: {
node: true,
- es2022: true,
+ es2022: true
},
- extends: [
- 'plugin:vue/essential',
- '@vue/standard'
- ],
+ extends: ['eslint:recommended', 'plugin:vue/vue3-recommended', 'prettier'],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
@@ -20,10 +17,7 @@ module.exports = {
},
overrides: [
{
- files: [
- '**/__tests__/*.{j,t}s?(x)',
- '**/tests/**/*.spec.{j,t}s?(x)'
- ],
+ files: ['**/__tests__/*.{j,t}s?(x)', '**/tests/**/*.spec.{j,t}s?(x)'],
env: {
mocha: true
}
diff --git a/.github/workflows/config.grenrc.js b/.github/workflows/config.grenrc.js
index e9b0b1b..6ffbed8 100644
--- a/.github/workflows/config.grenrc.js
+++ b/.github/workflows/config.grenrc.js
@@ -1,17 +1,14 @@
module.exports = {
dataSource: 'milestones',
- ignoreIssuesWith: [
- 'wontfix',
- 'duplicate'
- ],
+ ignoreIssuesWith: ['wontfix', 'duplicate'],
milestoneMatch: 'v{{tag_name}}',
template: {
issue: '- {{name}} [{{text}}]({{url}})',
- changelogTitle: "",
- release: "{{body}}",
+ changelogTitle: '',
+ release: '{{body}}'
},
groupBy: {
- 'Enhancements': ["enhancement", "internal"],
- 'Bug fixes': ["bug"]
+ Enhancements: ['enhancement', 'internal'],
+ 'Bug fixes': ['bug']
}
}
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 99765c6..3a20e2c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -3,43 +3,43 @@ on:
workflow_dispatch:
push:
tags:
- - '*'
+ - '*'
jobs:
deploy:
name: Create release
- runs-on: ubuntu-latest
+ runs-on: ubuntu-latest
steps:
- - uses: actions/checkout@v2
- - name: Use Node.js
- uses: actions/setup-node@v1
- with:
- node-version: 16.x
+ - uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 16.x
- - name: Update npm
- run: npm install -g npm@8
+ - name: Update npm
+ run: npm install -g npm@8
- - name: npm install and build
- run: |
- npm install
- npm run build
+ - name: npm install and build
+ run: |
+ npm install
+ npm run build
- - name: Create archives
- run: |
- cd dist
- zip -9 -r ../dist.zip . -x "js/*.map" -x "/*.map"
- zip -9 -r ../dist_map.zip .
+ - name: Create archives
+ run: |
+ cd dist
+ zip -9 -r ../dist.zip . -x "js/*.map" -x "/*.map"
+ zip -9 -r ../dist_map.zip .
- - name: Create Release Notes
- run: |
- npm install github-release-notes@0.16.0 -g
- gren changelog --generate --config="/.github/workflows/config.grenrc.js"
- env:
- GREN_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ - name: Create Release Notes
+ run: |
+ npm install github-release-notes@0.16.0 -g
+ gren changelog --generate --config="/.github/workflows/config.grenrc.js"
+ env:
+ GREN_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- - name: Create release
- uses: ncipollo/release-action@v1
- with:
- artifacts: "dist.zip,dist_map.zip"
- token: ${{ secrets.GITHUB_TOKEN }}
- bodyFile: "CHANGELOG.md"
+ - name: Create release
+ uses: ncipollo/release-action@v1
+ with:
+ artifacts: 'dist.zip,dist_map.zip'
+ token: ${{ secrets.GITHUB_TOKEN }}
+ bodyFile: 'CHANGELOG.md'
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index db70d6d..fff4da9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -3,35 +3,35 @@ on:
workflow_dispatch:
push:
branches:
- - 'master'
+ - 'master'
pull_request:
branches:
- - 'master'
+ - 'master'
jobs:
test:
name: Run tests
runs-on: ubuntu-20.04
steps:
- - uses: actions/checkout@v2
- - name: Use Node.js
- uses: actions/setup-node@v1
- with:
- node-version: 16.x
- - name: Install browsers
- run: |
- export DEBIAN_FRONTEND=noninteractive
- sudo apt-get update
- sudo apt-get install -y chromium-browser firefox
+ - uses: actions/checkout@v2
+ - name: Use Node.js
+ uses: actions/setup-node@v1
+ with:
+ node-version: 16.x
+ - name: Install browsers
+ run: |
+ export DEBIAN_FRONTEND=noninteractive
+ sudo apt-get update
+ sudo apt-get install -y chromium-browser firefox
- - name: Update npm
- run: npm install -g npm@8
+ - name: Update npm
+ run: npm install -g npm@8
- - name: Install the project
- run: npm install
+ - name: Install the project
+ run: npm install
- - name: Run lint
- run: npm run lint -- --no-fix
+ - name: Run lint
+ run: npm run lint -- --no-fix
- - name: Run karma tests
- run: npm run test
+ - name: Run karma tests
+ run: npm run test
diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..a998616
--- /dev/null
+++ b/.prettierrc
@@ -0,0 +1,7 @@
+{
+ "trailingComma": "none",
+ "tabWidth": 2,
+ "semi": false,
+ "singleQuote": true,
+ "arrowParens": "avoid"
+}
diff --git a/README.md b/README.md
index 59a80a9..8945074 100644
--- a/README.md
+++ b/README.md
@@ -5,9 +5,10 @@
# sqliteviz
Sqliteviz is a single-page offline-first PWA for fully client-side visualisation
- of SQLite databases, CSV, JSON or NDJSON files.
+of SQLite databases, CSV, JSON or NDJSON files.
With sqliteviz you can:
+
- run SQL queries against a SQLite database and create [Plotly][11] charts and pivot tables based on the result sets
- import a CSV/JSON/NDJSON file into a SQLite database and visualize imported data
- export result set to CSV file
@@ -19,15 +20,19 @@ With sqliteviz you can:
https://user-images.githubusercontent.com/24638357/128249848-f8fab0f5-9add-46e0-a9c1-dd5085a8623e.mp4
## Quickstart
+
The latest release of sqliteviz is deployed on [sqliteviz.com/app][6].
## Wiki
+
For user documentation, check out sqliteviz [documentation][7].
## Motivation
+
It's a kind of middleground between [Plotly Falcon][1] and [Redash][2].
## Components
+
It is built on top of [react-chart-editor][3], [PivotTable.js][12], [sql.js][4] and [Vue-Codemirror][8] in [Vue.js][5]. CSV parsing is performed with [Papa Parse][9].
[1]: https://github.com/plotly/falcon
diff --git a/babel.config.js b/babel.config.js
index e955840..716b023 100644
--- a/babel.config.js
+++ b/babel.config.js
@@ -1,5 +1,3 @@
module.exports = {
- presets: [
- '@vue/cli-plugin-babel/preset'
- ]
+ presets: ['@vue/cli-plugin-babel/preset']
}
diff --git a/index.html b/index.html
index e47af9d..4d2772a 100644
--- a/index.html
+++ b/index.html
@@ -1,11 +1,11 @@
-
+
-
-
-
-
-
+
+
+
+
+
sqliteviz
diff --git a/src/components/CsvJsonImport/DelimiterSelector/index.vue b/src/components/CsvJsonImport/DelimiterSelector/index.vue
index 41ea611..84db0f6 100644
--- a/src/components/CsvJsonImport/DelimiterSelector/index.vue
+++ b/src/components/CsvJsonImport/DelimiterSelector/index.vue
@@ -1,5 +1,5 @@
-
+
Delimiter
-
+
- {{option}}{{ getSymbolName(option) }}
+ {{ option }}
+ {{ getSymbolName(option) }}
@@ -49,7 +50,7 @@ export default {
props: ['modelValue', 'width', 'disabled'],
emits: ['update:modelValue'],
components: { DropDownChevron, ClearIcon },
- data () {
+ data() {
return {
showOptions: false,
options: [',', '\t', ' ', '|', ';', '\u001F', '\u001E'],
@@ -58,7 +59,7 @@ export default {
}
},
watch: {
- inputValue () {
+ inputValue() {
if (this.inputValue) {
this.filled = true
if (this.inputValue !== this.modelValue) {
@@ -69,25 +70,25 @@ export default {
}
}
},
- created () {
+ created() {
this.inputValue = this.modelValue
},
methods: {
- getSymbolName (str) {
+ getSymbolName(str) {
if (!str) {
return ''
}
return ascii[str.charCodeAt(0).toString()].name
},
- chooseOption (option) {
+ chooseOption(option) {
this.inputValue = option
this.showOptions = false
},
- onContainerClick (event) {
+ onContainerClick(event) {
this.$refs.delimiterInput.focus()
},
- clear () {
+ clear() {
if (!this.disabled) {
this.inputValue = ''
this.$refs.delimiterInput.focus()
diff --git a/src/components/CsvJsonImport/index.vue b/src/components/CsvJsonImport/index.vue
index 1c70412..d92d464 100644
--- a/src/components/CsvJsonImport/index.vue
+++ b/src/components/CsvJsonImport/index.vue
@@ -8,7 +8,7 @@
>
-
@@ -87,17 +90,18 @@ export default {
}
},
emits: [],
- data () {
+ data() {
return {
container: null,
paneBefore: this.before,
paneAfter: this.after,
- beforeMinimising: !this.after.size || !this.before.size
- ? this.default
- : {
- before: this.before.size,
- after: this.after.size
- },
+ beforeMinimising:
+ !this.after.size || !this.before.size
+ ? this.default
+ : {
+ before: this.before.size,
+ after: this.after.size
+ },
dragging: false,
movableSplitter: {
top: 0,
@@ -107,19 +111,23 @@ export default {
}
},
computed: {
- styles () {
+ styles() {
return {
- before: { [this.horizontal ? 'height' : 'width']: `${this.paneBefore.size}%` },
- after: { [this.horizontal ? 'height' : 'width']: `${this.paneAfter.size}%` }
+ before: {
+ [this.horizontal ? 'height' : 'width']: `${this.paneBefore.size}%`
+ },
+ after: {
+ [this.horizontal ? 'height' : 'width']: `${this.paneAfter.size}%`
+ }
}
},
- movableSplitterStyle () {
+ movableSplitterStyle() {
const style = { ...this.movableSplitter }
style.top += '%'
style.left += '%'
return style
},
- directionBeforeIconStyle () {
+ directionBeforeIconStyle() {
const expanded = this.paneBefore.size !== 0
const translation = 'translate(-50%, -50%) '
let rotation = ''
@@ -134,7 +142,7 @@ export default {
transform: translation + rotation
}
},
- directionAfterIconStyle () {
+ directionAfterIconStyle() {
const expanded = this.paneAfter.size !== 0
const translation = 'translate(-50%, -50%)'
let rotation = ''
@@ -152,35 +160,43 @@ export default {
},
methods: {
- bindEvents () {
+ bindEvents() {
// Passive: false to prevent scrolling while touch dragging.
- document.addEventListener('mousemove', this.onMouseMove, { passive: false })
+ document.addEventListener('mousemove', this.onMouseMove, {
+ passive: false
+ })
document.addEventListener('mouseup', this.onMouseUp)
if ('ontouchstart' in window) {
- document.addEventListener('touchmove', this.onMouseMove, { passive: false })
+ document.addEventListener('touchmove', this.onMouseMove, {
+ passive: false
+ })
document.addEventListener('touchend', this.onMouseUp)
}
},
- unbindEvents () {
- document.removeEventListener('mousemove', this.onMouseMove, { passive: false })
+ unbindEvents() {
+ document.removeEventListener('mousemove', this.onMouseMove, {
+ passive: false
+ })
document.removeEventListener('mouseup', this.onMouseUp)
if ('ontouchstart' in window) {
- document.removeEventListener('touchmove', this.onMouseMove, { passive: false })
+ document.removeEventListener('touchmove', this.onMouseMove, {
+ passive: false
+ })
document.removeEventListener('touchend', this.onMouseUp)
}
},
- onMouseMove (event) {
+ onMouseMove(event) {
event.preventDefault()
this.dragging = true
this.movableSplitter.visibility = 'visible'
this.moveSplitter(event)
},
- onMouseUp () {
+ onMouseUp() {
if (this.dragging) {
const dragPercentage = this.horizontal
? this.movableSplitter.top
@@ -201,7 +217,7 @@ export default {
this.unbindEvents()
},
- moveSplitter (event) {
+ moveSplitter(event) {
const splitterInfo = {
container: this.container,
paneBeforeMax: this.paneBefore.max,
@@ -213,12 +229,13 @@ export default {
this.movableSplitter[dir] = offset
},
- togglePane (pane) {
+ togglePane(pane) {
if (pane.size > 0) {
this.beforeMinimising.before = this.paneBefore.size
this.beforeMinimising.after = this.paneAfter.size
pane.size = 0
- const otherPane = pane === this.paneBefore ? this.paneAfter : this.paneBefore
+ const otherPane =
+ pane === this.paneBefore ? this.paneAfter : this.paneBefore
otherPane.size = 100 - pane.size
} else {
this.paneBefore.size = this.beforeMinimising.before
@@ -226,7 +243,7 @@ export default {
}
}
},
- mounted () {
+ mounted() {
this.container = this.$refs.container
}
}
@@ -239,9 +256,15 @@ export default {
position: relative;
}
-.splitpanes-vertical {flex-direction: row;}
-.splitpanes-horizontal {flex-direction: column;}
-.splitpanes-dragging * {user-select: none;}
+.splitpanes-vertical {
+ flex-direction: row;
+}
+.splitpanes-horizontal {
+ flex-direction: column;
+}
+.splitpanes-dragging * {
+ user-select: none;
+}
.splitpanes-pane {
width: 100%;
@@ -281,14 +304,14 @@ export default {
.movable-splitter {
position: absolute;
- background-color:rgba(162, 177, 198, 0.5);
+ background-color: rgba(162, 177, 198, 0.5);
}
.splitpanes-vertical > .splitpanes-splitter,
.splitpanes-vertical > .movable-splitter {
width: 8px;
z-index: 5;
- height: 100%
+ height: 100%;
}
.splitpanes-horizontal > .splitpanes-splitter,
@@ -339,20 +362,32 @@ export default {
left: 50%;
}
-.splitpanes-horizontal > .splitpanes-splitter .toggle-btns.both .toggle-btn:first-child {
+.splitpanes-horizontal
+ > .splitpanes-splitter
+ .toggle-btns.both
+ .toggle-btn:first-child {
border-radius: var(--border-radius-small) 0 0 var(--border-radius-small);
}
-.splitpanes-horizontal > .splitpanes-splitter .toggle-btns.both .toggle-btn:last-child {
+.splitpanes-horizontal
+ > .splitpanes-splitter
+ .toggle-btns.both
+ .toggle-btn:last-child {
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
margin-left: -1px;
}
-.splitpanes-vertical > .splitpanes-splitter .toggle-btns.both .toggle-btn:first-child {
+.splitpanes-vertical
+ > .splitpanes-splitter
+ .toggle-btns.both
+ .toggle-btn:first-child {
border-radius: var(--border-radius-small) var(--border-radius-small) 0 0;
}
-.splitpanes-vertical > .splitpanes-splitter .toggle-btns.both .toggle-btn:last-child {
+.splitpanes-vertical
+ > .splitpanes-splitter
+ .toggle-btns.both
+ .toggle-btn:last-child {
border-radius: 0 0 var(--border-radius-small) var(--border-radius-small);
margin-top: -1px;
}
diff --git a/src/components/Splitpanes/splitter.js b/src/components/Splitpanes/splitter.js
index b93aa05..32c1916 100644
--- a/src/components/Splitpanes/splitter.js
+++ b/src/components/Splitpanes/splitter.js
@@ -1,10 +1,9 @@
export default {
// Get the cursor position relative to the splitpane container.
- getCurrentMouseDrag (event, container) {
+ getCurrentMouseDrag(event, container) {
const rect = container.getBoundingClientRect()
- const { clientX, clientY } = ('ontouchstart' in window && event.touches)
- ? event.touches[0]
- : event
+ const { clientX, clientY } =
+ 'ontouchstart' in window && event.touches ? event.touches[0] : event
return {
x: clientX - rect.left,
y: clientY - rect.top
@@ -12,23 +11,35 @@ export default {
},
// Returns the drag percentage of the splitter relative to the 2 panes it's inbetween.
- getCurrentDragPercentage (event, container, isHorisontal) {
+ getCurrentDragPercentage(event, container, isHorisontal) {
let drag = this.getCurrentMouseDrag(event, container)
drag = drag[isHorisontal ? 'y' : 'x']
- const containerSize = container[isHorisontal ? 'clientHeight' : 'clientWidth']
- return drag * 100 / containerSize
+ const containerSize =
+ container[isHorisontal ? 'clientHeight' : 'clientWidth']
+ return (drag * 100) / containerSize
},
// Returns the new position in percents.
- calculateOffset (event, { container, isHorisontal, paneBeforeMax, paneAfterMax }) {
- const dragPercentage = this.getCurrentDragPercentage(event, container, isHorisontal)
+ calculateOffset(
+ event,
+ { container, isHorisontal, paneBeforeMax, paneAfterMax }
+ ) {
+ const dragPercentage = this.getCurrentDragPercentage(
+ event,
+ container,
+ isHorisontal
+ )
- const paneBeforeMaxReached = paneBeforeMax < 100 && (dragPercentage >= paneBeforeMax)
- const paneAfterMaxReached = paneAfterMax < 100 && (dragPercentage <= 100 - paneAfterMax)
+ const paneBeforeMaxReached =
+ paneBeforeMax < 100 && dragPercentage >= paneBeforeMax
+ const paneAfterMaxReached =
+ paneAfterMax < 100 && dragPercentage <= 100 - paneAfterMax
// Prevent dragging beyond pane max.
if (paneBeforeMaxReached || paneAfterMaxReached) {
- return paneBeforeMaxReached ? paneBeforeMax : Math.max(100 - paneAfterMax, 0)
+ return paneBeforeMaxReached
+ ? paneBeforeMax
+ : Math.max(100 - paneAfterMax, 0)
} else {
return Math.min(Math.max(dragPercentage, 0), paneBeforeMax)
}
diff --git a/src/components/SqlTable/Pager.vue b/src/components/SqlTable/Pager.vue
index 90af7c5..64a6e93 100644
--- a/src/components/SqlTable/Pager.vue
+++ b/src/components/SqlTable/Pager.vue
@@ -25,7 +25,7 @@ export default {
components: { Paginate },
props: ['pageCount', 'modelValue'],
emits: ['update:modelValue'],
- data () {
+ data() {
return {
page: this.modelValue,
chevron: `
@@ -39,10 +39,10 @@ export default {
}
},
watch: {
- page () {
+ page() {
this.$emit('update:modelValue', this.page)
},
- modelValue () {
+ modelValue() {
this.page = this.modelValue
}
}
@@ -93,7 +93,7 @@ export default {
:deep(.paginator-next:hover path),
:deep(.paginator-prev:hover path) {
- fill: var(--color-text-active);
+ fill: var(--color-text-active);
}
:deep(.paginator-disabled path),
:deep(.paginator-disabled:hover path) {
diff --git a/src/components/SqlTable/index.vue b/src/components/SqlTable/index.vue
index b9f71be..557d950 100644
--- a/src/components/SqlTable/index.vue
+++ b/src/components/SqlTable/index.vue
@@ -11,50 +11,50 @@
>
{{ th.name }}
-
+
-
-
-
- |
- {{ th }}
- |
-
-
-
-
- |
-
- {{ getCellText(col, rowIndex) }}
-
- |
-
-
-
+
+
+
+ |
+ {{ th }}
+ |
+
+
+
+
+ |
+
+ {{ getCellText(col, rowIndex) }}
+
+ |
+
+
+