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

#63 migrate to Vue 3

This commit is contained in:
lana-k
2024-10-13 16:12:21 +02:00
parent b30b2181e4
commit 637d8d26dd
60 changed files with 16279 additions and 17440 deletions

View File

@@ -8,13 +8,17 @@ module.exports = {
'@vue/standard' '@vue/standard'
], ],
parserOptions: { parserOptions: {
parser: 'babel-eslint' parser: '@babel/eslint-parser'
}, },
rules: { rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-case-declarations': 'off', 'no-case-declarations': 'off',
'max-len': [2, 100, 4, { ignoreUrls: true }] 'max-len': [2, 100, 4, { ignoreUrls: true }],
'vue/multi-word-component-names': 'off',
'vue/no-mutating-props': 'warn',
'vue/no-reserved-component-names': 'warn',
'vue/no-v-model-argument': 'off'
}, },
overrides: [ overrides: [
{ {

View File

@@ -1,9 +1,9 @@
import Vue from 'vue' import Vue from 'vue'
import { VuePlugin } from 'vuera' // import { VuePlugin } from 'vuera'
import VModal from 'vue-js-modal' // import VModal from 'vue-js-modal'
Vue.use(VuePlugin) // Vue.use(VuePlugin)
Vue.use(VModal) // Vue.use(VModal)
Vue.config.productionTip = false Vue.config.productionTip = false
// require all test files (files that ends with .spec.js) // require all test files (files that ends with .spec.js)

33059
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,7 +10,9 @@
"lint": "vue-cli-service lint" "lint": "vue-cli-service lint"
}, },
"dependencies": { "dependencies": {
"assert": "^2.1.0",
"codemirror": "^5.65.18", "codemirror": "^5.65.18",
"codemirror-editor-vue3": "^2.8.0",
"core-js": "^3.6.5", "core-js": "^3.6.5",
"dataurl-to-blob": "^0.0.1", "dataurl-to-blob": "^0.0.1",
"html2canvas": "^1.1.4", "html2canvas": "^1.1.4",
@@ -24,39 +26,41 @@
"react-chart-editor": "^0.46.1", "react-chart-editor": "^0.46.1",
"react-dom": "^16.14.0", "react-dom": "^16.14.0",
"sql.js": "file:./lib/sql-js", "sql.js": "file:./lib/sql-js",
"vue": "^2.6.11", "stream-browserify": "^3.0.0",
"vue-codemirror": "^4.0.6", "tiny-emitter": "^2.1.0",
"vue-js-modal": "^2.0.0-rc.6", "url-loader": "^4.1.1",
"vue-multiselect": "^2.1.6", "veaury": "^2.5.1",
"vue-router": "^3.2.0", "vue": "^3.5.11",
"vue2-teleport": "^1.0.1", "vue-final-modal": "^4.5.5",
"vuejs-paginate": "^2.1.0", "vue-multiselect": "^3.0.0-beta.3",
"vuera": "^0.2.7", "vue-router": "^4.4.5",
"vuex": "^3.4.0" "vuejs-paginate-next": "^1.0.2",
"vuex": "^4.1.0"
}, },
"devDependencies": { "devDependencies": {
"@vue/cli-plugin-babel": "^4.4.0", "@babel/core": "^7.25.7",
"@vue/cli-plugin-eslint": "^4.4.0", "@babel/eslint-parser": "^7.25.7",
"@vue/cli-plugin-router": "^4.4.0", "@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-vuex": "^4.4.0", "@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-service": "^4.4.0", "@vue/cli-plugin-router": "^5.0.8",
"@vue/eslint-config-standard": "^5.1.2", "@vue/cli-plugin-vuex": "^5.0.8",
"@vue/test-utils": "^1.1.2", "@vue/cli-service": "^5.0.8",
"babel-eslint": "^10.1.0", "@vue/eslint-config-standard": "^8.0.1",
"@vue/test-utils": "^2.4.6",
"chai": "^4.1.2", "chai": "^4.1.2",
"chai-as-promised": "^7.1.1", "chai-as-promised": "^7.1.1",
"eslint": "^6.7.2", "copy-webpack-plugin": "^6.4.1",
"eslint": "^8.57.1",
"eslint-plugin-import": "^2.20.2", "eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1", "eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0", "eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.2.2", "eslint-plugin-vue": "^9.28.0",
"flush-promises": "^1.0.2", "flush-promises": "^1.0.2",
"karma": "^3.1.4", "karma": "^3.1.4",
"karma-firefox-launcher": "^2.1.0", "karma-firefox-launcher": "^2.1.0",
"karma-webpack": "^4.0.2", "karma-webpack": "^5.0.1",
"vue-cli-plugin-ui-karma": "^0.2.5", "vue-cli-plugin-ui-karma": "^0.2.5",
"vue-template-compiler": "^2.6.11",
"workbox-webpack-plugin": "^6.1.5", "workbox-webpack-plugin": "^6.1.5",
"worker-loader": "^3.0.8" "worker-loader": "^3.0.8"
} }

View File

@@ -1,9 +1,18 @@
<template> <template>
<div id="app"> <div id="app">
<router-view/> <router-view/>
<modals-container/>
</div> </div>
</template> </template>
<script>
import { ModalsContainer } from 'vue-final-modal'
export default {
components: { ModalsContainer }
}
</script>
<style> <style>
@font-face { @font-face {
font-family: "Open Sans"; font-family: "Open Sans";
@@ -48,10 +57,12 @@
} }
#app, #app,
.dialog,
input, input,
label, label,
button, button,
.plotly_editor * { .plotly_editor *,
.CodeMirror pre.CodeMirror-line {
font-family: "Open Sans", Helvetica, Arial, sans-serif; font-family: "Open Sans", Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;

View File

@@ -1,6 +1,13 @@
.dialog { .dialog {
display: flex;
justify-content: center;
align-items: center;
}
.dialog .vfm__content {
border-radius: var(--border-radius-big); border-radius: var(--border-radius-big);
box-shadow: 0px 2px 9px rgba(80, 103, 132, 0.8); box-shadow: 0px 2px 9px rgba(80, 103, 132, 0.8);
background-color: white;
overflow: hidden;
} }
.dialog-header { .dialog-header {
@@ -35,6 +42,6 @@
margin-left: 16px; margin-left: 16px;
} }
.vm--overlay { .vfm__overlay.vfm--overlay {
background-color: rgba(162, 177, 198, 0.5); background-color: rgba(162, 177, 198, 0.5);
} }

View File

@@ -46,6 +46,7 @@ export default {
default: false default: false
} }
}, },
emits: ['click'],
data () { data () {
return { return {
checked: this.init checked: this.init

View File

@@ -16,13 +16,13 @@
@click.stop @click.stop
:disabled="disabled" :disabled="disabled"
/> />
<div class="name">{{ getSymbolName(value) }}</div> <div class="name">{{ getSymbolName(modelValue) }}</div>
</div> </div>
<div class="controls" @click.stop> <div class="controls" @click.stop>
<clear-icon @click.native="clear" :disabled="disabled"/> <clear-icon @click="clear" :disabled="disabled"/>
<drop-down-chevron <drop-down-chevron
:disabled="disabled" :disabled="disabled"
@click.native="!disabled && (showOptions = !showOptions)" @click="!disabled && (showOptions = !showOptions)"
/> />
</div> </div>
</div> </div>
@@ -46,7 +46,7 @@ import ClearIcon from '@/components/svg/clear'
export default { export default {
name: 'DelimiterSelector', name: 'DelimiterSelector',
props: ['value', 'width', 'disabled'], props: ['modelValue', 'width', 'disabled'],
components: { DropDownChevron, ClearIcon }, components: { DropDownChevron, ClearIcon },
data () { data () {
return { return {
@@ -60,8 +60,8 @@ export default {
inputValue () { inputValue () {
if (this.inputValue) { if (this.inputValue) {
this.filled = true this.filled = true
if (this.inputValue !== this.value) { if (this.inputValue !== this.modelValue) {
this.$emit('input', this.inputValue) this.$emit('update:modelValue', this.inputValue)
} }
} else { } else {
this.filled = false this.filled = false
@@ -69,7 +69,7 @@ export default {
} }
}, },
created () { created () {
this.inputValue = this.value this.inputValue = this.modelValue
}, },
methods: { methods: {
getSymbolName (str) { getSymbolName (str) {

View File

@@ -1,9 +1,8 @@
<template> <template>
<modal <modal
:name="dialogName" :modal-id="dialogName"
classes="dialog" class="dialog"
height="auto" content-class="import-modal"
width="80%"
scrollable scrollable
:clickToClose="false" :clickToClose="false"
> >
@@ -130,6 +129,7 @@ export default {
db: Object, db: Object,
dialogName: String dialogName: String
}, },
emits: ['cancel', 'finish'],
data () { data () {
return { return {
disableDialog: false, disableDialog: false,
@@ -304,7 +304,7 @@ export default {
let importLoadingIndicator = null let importLoadingIndicator = null
const updateProgress = progress => { const updateProgress = progress => {
this.$set(importMsg, 'progress', progress) importMsg.progress = progress
} }
const progressCounterId = this.db.createProgressCounter(updateProgress) const progressCounterId = this.db.createProgressCounter(updateProgress)
@@ -392,8 +392,10 @@ export default {
events.send('inquiry.create', null, { auto: true }) events.send('inquiry.create', null, { auto: true })
}, },
getQueryExample () { getQueryExample () {
return this.isNdJson ? this.getNdJsonQueryExample() return this.isNdJson
: this.isJson ? this.getJsonQueryExample() ? this.getNdJsonQueryExample()
: this.isJson
? this.getJsonQueryExample()
: [ : [
'/*', '/*',
` * Your CSV file has been imported into ${this.addedTable} table.`, ` * Your CSV file has been imported into ${this.addedTable} table.`,
@@ -455,6 +457,15 @@ export default {
} }
</script> </script>
<style>
.import-modal {
width: 80%;
max-width: 1152px;
margin: auto;
left: 0 !important;
}
</style>
<style scoped> <style scoped>
.dialog-body { .dialog-body {
padding-bottom: 0; padding-bottom: 0;
@@ -495,10 +506,4 @@ margin-bottom: 24px;
align-items: center; align-items: center;
} }
/* https://github.com/euvl/vue-js-modal/issues/623 */
>>> .vm--modal {
max-width: 1152px;
margin: auto;
left: 0 !important;
}
</style> </style>

View File

@@ -78,6 +78,7 @@ export default {
default: 'unset' default: 'unset'
} }
}, },
emits: [],
components: { components: {
ChangeDbIcon, ChangeDbIcon,
CsvJsonImport CsvJsonImport

View File

@@ -23,6 +23,7 @@ import LoadingIndicator from '@/components/LoadingIndicator'
export default { export default {
name: 'SideBarButton', name: 'SideBarButton',
props: ['active', 'disabled', 'tooltip', 'tooltipPosition', 'loading'], props: ['active', 'disabled', 'tooltip', 'tooltipPosition', 'loading'],
emits: ['click'],
components: { LoadingIndicator }, components: { LoadingIndicator },
mixins: [tooltipMixin], mixins: [tooltipMixin],
methods: { methods: {
@@ -51,15 +52,15 @@ export default {
border-radius: var(--border-radius-medium-2); border-radius: var(--border-radius-medium-2);
} }
.icon-btn:hover .icon >>> path, .icon-btn:hover .icon :deep(path),
.icon-btn.active .icon >>> path, .icon-btn.active .icon :deep(path),
.icon-btn:hover .icon >>> circle, .icon-btn:hover .icon :deep(circle),
.icon-btn.active .icon >>> circle { .icon-btn.active .icon :deep(circle) {
fill: var(--color-accent); fill: var(--color-accent);
} }
.icon-btn:disabled .icon >>> path, .icon-btn:disabled .icon :deep(path),
.icon-btn:disabled .icon >>> circle { .icon-btn:disabled .icon :deep(circle) {
fill: var(--color-border); fill: var(--color-border);
} }
@@ -68,7 +69,7 @@ export default {
pointer-events: none; pointer-events: none;
} }
.disabled.icon-btn:hover .icon >>> path { .disabled.icon-btn:hover .icon :deep(path) {
fill: var(--color-border); fill: var(--color-border);
} }

View File

@@ -1,8 +1,7 @@
<template> <template>
<modal <modal
:name="name" :modal-id="name"
classes="dialog" class="dialog"
height="auto"
:clickToClose="false" :clickToClose="false"
> >
<div class="dialog-header"> <div class="dialog-header">
@@ -52,6 +51,7 @@ export default {
title: String, title: String,
loading: Boolean loading: Boolean
}, },
emits: ['cancel', 'action'],
watch: { watch: {
loading () { loading () {
if (this.loading) { if (this.loading) {

View File

@@ -31,6 +31,7 @@ export default {
default: 20 default: 20
} }
}, },
emits: [],
computed: { computed: {
circleProgress () { circleProgress () {
const circle = this.radius * 3.14 * 2 const circle = this.radius * 3.14 * 2

View File

@@ -16,6 +16,7 @@ import LoadingIndicator from '@/components/LoadingIndicator'
export default { export default {
name: 'logs', name: 'logs',
props: ['messages'], props: ['messages'],
emits: [],
components: { LoadingIndicator }, components: { LoadingIndicator },
watch: { watch: {
'messages.length': 'scrollToBottom' 'messages.length': 'scrollToBottom'

View File

@@ -86,12 +86,15 @@ export default {
} }
} }
}, },
emits: [],
data () { data () {
return { return {
container: null, container: null,
paneBefore: this.before, paneBefore: this.before,
paneAfter: this.after, paneAfter: this.after,
beforeMinimising: !this.after.size || !this.before.size ? this.default : { beforeMinimising: !this.after.size || !this.before.size
? this.default
: {
before: this.before.size, before: this.before.size,
after: this.after.size after: this.after.size
}, },

View File

@@ -18,15 +18,16 @@
</template> </template>
<script> <script>
import Paginate from 'vuejs-paginate' import Paginate from 'vuejs-paginate-next'
export default { export default {
name: 'Pager', name: 'Pager',
components: { Paginate }, components: { Paginate },
props: ['pageCount', 'value'], props: ['pageCount', 'modelValue'],
emits: ['update:modelValue'],
data () { data () {
return { return {
page: this.value, page: this.modelValue,
chevron: ` chevron: `
<svg width="9" height="9" viewBox="0 0 8 12" fill="none"> <svg width="9" height="9" viewBox="0 0 8 12" fill="none">
<path <path
@@ -39,10 +40,10 @@ export default {
}, },
watch: { watch: {
page () { page () {
this.$emit('input', this.page) this.$emit('update:modelValue', this.page)
}, },
value () { modelValue () {
this.page = this.value this.page = this.modelValue
} }
} }
} }
@@ -54,48 +55,48 @@ export default {
align-items: center; align-items: center;
line-height: 10px; line-height: 10px;
} }
>>> .paginator-page-link { :deep(.paginator-page-link) {
padding: 2px 3px; padding: 2px 3px;
margin: 0 5px; margin: 0 5px;
display: block; display: block;
color: var(--color-text-base); color: var(--color-text-base);
font-size: 11px; font-size: 11px;
} }
>>> .paginator-page-link:hover { :deep(.paginator-page-link:hover) {
color: var(--color-text-active); color: var(--color-text-active);
} }
>>> .paginator-page-link:active, :deep(.paginator-page-link:active),
>>> .paginator-page-link:visited, :deep(.paginator-page-link:visited),
>>> .paginator-page-link:focus, :deep(.paginator-page-link:focus),
>>> .paginator-next:active, :deep(.paginator-next:active),
>>> .paginator-next:visited, :deep(.paginator-next:visited),
>>> .paginator-next:focus, :deep(.paginator-next:focus),
>>> .paginator-prev:active, :deep(.paginator-prev:active),
>>> .paginator-prev:visited, :deep(.paginator-prev:visited),
>>> .paginator-prev:focus { :deep(.paginator-prev:focus) {
outline: none; outline: none;
} }
>>> .paginator-active-page, :deep(.paginator-active-page),
>>> .paginator-active-page:hover { :deep(.paginator-active-page:hover) {
color: var(--color-accent); color: var(--color-accent);
} }
>>> .paginator-break:hover, :deep(.paginator-break:hover),
>>> .paginator-disabled:hover { :deep(.paginator-disabled:hover) {
cursor: default; cursor: default;
} }
>>> .paginator-prev svg { :deep(.paginator-prev svg) {
transform: rotate(180deg); transform: rotate(180deg);
} }
>>> .paginator-next:hover path, :deep(.paginator-next:hover path),
>>> .paginator-prev:hover path { :deep(.paginator-prev:hover path) {
fill: var(--color-text-active); fill: var(--color-text-active);
} }
>>> .paginator-disabled path, :deep(.paginator-disabled path),
>>> .paginator-disabled:hover path { :deep(.paginator-disabled:hover path) {
fill: var(--color-text-light-2); fill: var(--color-text-light-2);
} }
</style> </style>

View File

@@ -87,6 +87,7 @@ export default {
preview: Boolean, preview: Boolean,
selectedCellCoordinates: Object selectedCellCoordinates: Object
}, },
emits: ['updateSelectedCell'],
data () { data () {
return { return {
header: null, header: null,
@@ -248,7 +249,7 @@ export default {
} }
} }
}, },
beforeDestroy () { beforeUnmount () {
this.resizeObserver.unobserve(this.$refs.table) this.resizeObserver.unobserve(this.$refs.table)
}, },
watch: { watch: {

View File

@@ -9,9 +9,9 @@
:placeholder="placeholder" :placeholder="placeholder"
:class="{ error: errorMsg }" :class="{ error: errorMsg }"
:style="{ width: width }" :style="{ width: width }"
:value="value" :value="modelValue"
:disabled="disabled" :disabled="disabled"
@input="$emit('input', $event.target.value)" @input="$emit('update:modelValue', $event.target.value)"
/> />
<div v-show="errorMsg" class="text-field-error">{{ errorMsg }}</div> <div v-show="errorMsg" class="text-field-error">{{ errorMsg }}</div>
</div> </div>
@@ -21,7 +21,16 @@
import HintIcon from '@/components/svg/hint' import HintIcon from '@/components/svg/hint'
export default { export default {
name: 'textField', name: 'textField',
props: ['placeholder', 'label', 'errorMsg', 'value', 'width', 'hint', 'maxHintWidth', 'disabled'], props: [
'placeholder',
'label',
'errorMsg',
'modelValue',
'width',
'hint',
'maxHintWidth',
'disabled'
],
components: { HintIcon } components: { HintIcon }
} }
</script> </script>

View File

@@ -46,6 +46,7 @@ export default {
name: 'AddTableIcon', name: 'AddTableIcon',
mixins: [tooltipMixin], mixins: [tooltipMixin],
props: ['tooltip'], props: ['tooltip'],
emits: ['click'],
methods: { methods: {
onClick () { onClick () {
this.hideTooltip() this.hideTooltip()

View File

@@ -33,6 +33,7 @@ import tooltipMixin from '@/tooltipMixin'
export default { export default {
name: 'changeDbIcon', name: 'changeDbIcon',
mixins: [tooltipMixin], mixins: [tooltipMixin],
emits: ['click'],
methods: { methods: {
onClick () { onClick () {
this.hideTooltip() this.hideTooltip()

View File

@@ -30,7 +30,8 @@ export default {
required: false, required: false,
default: false default: false
} }
} },
emits: ['click']
} }
</script> </script>

View File

@@ -30,6 +30,7 @@ export default {
name: 'ExportIcon', name: 'ExportIcon',
mixins: [tooltipMixin], mixins: [tooltipMixin],
props: ['tooltip', 'tooltipPosition'], props: ['tooltip', 'tooltipPosition'],
emits: ['click'],
methods: { methods: {
onClick () { onClick () {
this.hideTooltip() this.hideTooltip()

View File

@@ -45,6 +45,7 @@ import tooltipMixin from '@/tooltipMixin'
export default { export default {
name: 'HintIcon', name: 'HintIcon',
props: ['hint', 'maxWidth'], props: ['hint', 'maxWidth'],
emits: ['click'],
mixins: [tooltipMixin], mixins: [tooltipMixin],
methods: { methods: {
onClick () { onClick () {

8
src/lib/eventBus.js Normal file
View File

@@ -0,0 +1,8 @@
import emitter from 'tiny-emitter/instance'
export default {
$on: (...args) => emitter.on(...args),
$once: (...args) => emitter.once(...args),
$off: (...args) => emitter.off(...args),
$emit: (...args) => emitter.emit(...args)
}

View File

@@ -48,11 +48,13 @@ export default {
// Get inquiries from local storage // Get inquiries from local storage
const myInquiries = this.getStoredInquiries() const myInquiries = this.getStoredInquiries()
let inquiryIndex
// Set createdAt // Set createdAt
if (newName) { if (newName) {
value.createdAt = new Date() value.createdAt = new Date()
} else { } else {
var inquiryIndex = myInquiries.findIndex(oldInquiry => oldInquiry.id === inquiryTab.id) inquiryIndex = myInquiries
.findIndex(oldInquiry => oldInquiry.id === inquiryTab.id)
value.createdAt = myInquiries[inquiryIndex].createdAt value.createdAt = myInquiries[inquiryIndex].createdAt
} }

View File

@@ -1,9 +1,8 @@
import Vue from 'vue' import { createApp } from 'vue'
import App from '@/App.vue' 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 { createVfm, VueFinalModal, useVfm } from 'vue-final-modal'
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'
@@ -11,20 +10,23 @@ import '@/assets/styles/tables.css'
import '@/assets/styles/dialogs.css' import '@/assets/styles/dialogs.css'
import '@/assets/styles/tooltips.css' import '@/assets/styles/tooltips.css'
import '@/assets/styles/messages.css' import '@/assets/styles/messages.css'
import 'vue-multiselect/dist/vue-multiselect.min.css' import 'vue-multiselect/dist/vue-multiselect.css'
import '@/assets/styles/multiselect.css' import '@/assets/styles/multiselect.css'
import 'vue-final-modal/style.css'
if (!['localhost', '127.0.0.1'].includes(location.hostname)) { if (!['localhost', '127.0.0.1'].includes(location.hostname)) {
import('./registerServiceWorker') // eslint-disable-line no-unused-expressions import('./registerServiceWorker') // eslint-disable-line no-unused-expressions
} }
Vue.use(VuePlugin) const app = createApp(App)
Vue.use(VModal) .use(router)
.use(store)
.use(createVfm())
.component('modal', VueFinalModal)
Vue.config.productionTip = false const vfm = useVfm()
app.config.globalProperties.$modal = {
new Vue({ show: modalId => vfm.open(modalId),
router, hide: modalId => vfm.close(modalId)
store, }
render: h => h(App) app.mount('#app')
}).$mount('#app')

View File

@@ -1,5 +1,4 @@
import Vue from 'vue' import { createRouter, createWebHashHistory } from 'vue-router'
import VueRouter from 'vue-router'
import Workspace from '@/views/Main/Workspace' import Workspace from '@/views/Main/Workspace'
import Inquiries from '@/views/Main/Inquiries' import Inquiries from '@/views/Main/Inquiries'
import Welcome from '@/views/Welcome' import Welcome from '@/views/Welcome'
@@ -8,8 +7,6 @@ import LoadView from '@/views/LoadView'
import store from '@/store' import store from '@/store'
import database from '@/lib/database' import database from '@/lib/database'
Vue.use(VueRouter)
const routes = [ const routes = [
{ {
path: '/', path: '/',
@@ -40,7 +37,8 @@ const routes = [
} }
] ]
const router = new VueRouter({ const router = createRouter({
history: createWebHashHistory(),
routes routes
}) })

View File

@@ -1,12 +1,9 @@
import Vue from 'vue' import { createStore } from 'vuex'
import Vuex from 'vuex'
import state from '@/store/state' import state from '@/store/state'
import mutations from '@/store/mutations' import mutations from '@/store/mutations'
import actions from '@/store/actions' import actions from '@/store/actions'
Vue.use(Vuex) export default createStore({
export default new Vuex.Store({
state, state,
mutations, mutations,
actions actions

View File

@@ -60,5 +60,8 @@ export default {
}, },
setPredefinedInquiriesLoaded (state, value) { setPredefinedInquiriesLoaded (state, value) {
state.predefinedInquiriesLoaded = value state.predefinedInquiriesLoaded = value
},
setIsWorkspaceVisible (state, value) {
state.isWorkspaceVisible = value
} }
} }

View File

@@ -6,5 +6,6 @@ export default {
predefinedInquiries: [], predefinedInquiries: [],
loadingPredefinedInquiries: false, loadingPredefinedInquiries: false,
predefinedInquiriesLoaded: false, predefinedInquiriesLoaded: false,
db: null db: null,
isWorkspaceVisible: false
} }

View File

@@ -5,7 +5,11 @@
:src="require('@/assets/images/info.svg')" :src="require('@/assets/images/info.svg')"
@click="$modal.show('app-info')" @click="$modal.show('app-info')"
/> />
<modal name="app-info" classes="dialog" height="auto" width="400px"> <modal
modal-id="app-info"
class="dialog"
content-class="app-info-modal"
>
<div class="dialog-header"> <div class="dialog-header">
App info App info
<close-icon @click="$modal.hide('app-info')"/> <close-icon @click="$modal.hide('app-info')"/>
@@ -59,6 +63,12 @@ export default {
} }
</script> </script>
<style>
.app-info-modal {
width: 400px;
}
</style>
<style scoped> <style scoped>
#app-info-icon { #app-info-icon {
cursor: pointer; cursor: pointer;

View File

@@ -2,7 +2,7 @@
<div id="my-inquiries-container"> <div id="my-inquiries-container">
<div id="start-guide" v-if="allInquiries.length === 0"> <div id="start-guide" v-if="allInquiries.length === 0">
You don't have saved inquiries so far. You don't have saved inquiries so far.
<span class="link" @click="$root.$emit('createNewInquiry')">Create</span> <span class="link" @click="emitCreateTabEvent">Create</span>
the one from scratch or the one from scratch or
<span @click="importInquiries" class="link">import</span> from a file. <span @click="importInquiries" class="link">import</span> from a file.
</div> </div>
@@ -89,7 +89,9 @@
</td> </td>
<td> <td>
<div class="second-column"> <div class="second-column">
<div class="date-container">{{ inquiry.createdAt | date }}</div> <div class="date-container">
{{ createdAtFormatted(inquiry.createdAt) }}
</div>
<div class="icons-container"> <div class="icons-container">
<rename-icon <rename-icon
v-if="!inquiry.isPredefined" v-if="!inquiry.isPredefined"
@@ -116,7 +118,7 @@
</div> </div>
<!--Rename Inquiry dialog --> <!--Rename Inquiry dialog -->
<modal name="rename" classes="dialog" height="auto"> <modal modal-id="rename" class="dialog">
<div class="dialog-header"> <div class="dialog-header">
Rename inquiry Rename inquiry
<close-icon @click="$modal.hide('rename')"/> <close-icon @click="$modal.hide('rename')"/>
@@ -136,7 +138,7 @@
</modal> </modal>
<!--Delete Inquiry dialog --> <!--Delete Inquiry dialog -->
<modal name="delete" classes="dialog" height="auto"> <modal modal-id="delete" class="dialog">
<div class="dialog-header"> <div class="dialog-header">
Delete {{ deleteGroup ? 'inquiries' : 'inquiry' }} Delete {{ deleteGroup ? 'inquiries' : 'inquiry' }}
<close-icon @click="$modal.hide('delete')"/> <close-icon @click="$modal.hide('delete')"/>
@@ -167,6 +169,7 @@ import CheckBox from '@/components/CheckBox'
import LoadingIndicator from '@/components/LoadingIndicator' import LoadingIndicator from '@/components/LoadingIndicator'
import tooltipMixin from '@/tooltipMixin' import tooltipMixin from '@/tooltipMixin'
import storedInquiries from '@/lib/storedInquiries' import storedInquiries from '@/lib/storedInquiries'
import eventBus from '@/lib/eventBus'
export default { export default {
name: 'Inquiries', name: 'Inquiries',
@@ -242,7 +245,8 @@ export default {
} }
}, },
watch: { watch: {
showedInquiries () { showedInquiries: {
handler () {
this.selectedInquiriesIds = new Set(this.showedInquiries this.selectedInquiriesIds = new Set(this.showedInquiries
.filter(inquiry => this.selectedInquiriesIds.has(inquiry.id)) .filter(inquiry => this.selectedInquiriesIds.has(inquiry.id))
.map(inquiry => inquiry.id) .map(inquiry => inquiry.id)
@@ -252,9 +256,13 @@ export default {
.filter(id => !this.predefinedInquiriesIds.has(id))).length .filter(id => !this.predefinedInquiriesIds.has(id))).length
if (this.selectedInquiriesIds.size < this.showedInquiries.length) { if (this.selectedInquiriesIds.size < this.showedInquiries.length) {
if (this.$refs.mainCheckBox) {
this.$refs.mainCheckBox.checked = false this.$refs.mainCheckBox.checked = false
}
this.selectAll = false this.selectAll = false
} }
},
deep: true
} }
}, },
async created () { async created () {
@@ -282,12 +290,15 @@ export default {
this.calcNameWidth() this.calcNameWidth()
this.calcMaxTableHeight() this.calcMaxTableHeight()
}, },
beforeDestroy () { beforeUnmount () {
this.resizeObserver.unobserve(this.$refs['my-inquiries-content']) this.resizeObserver.unobserve(this.$refs['my-inquiries-content'])
this.tableResizeObserver.unobserve(this.$refs.table) this.tableResizeObserver.unobserve(this.$refs.table)
}, },
filters: { methods: {
date (value) { emitCreateTabEvent () {
eventBus.$emit('createNewInquiry')
},
createdAtFormatted (value) {
if (!value) { if (!value) {
return '' return ''
} }
@@ -299,9 +310,7 @@ export default {
} }
return new Date(value).toLocaleDateString('en-GB', dateOptions) + ' ' + return new Date(value).toLocaleDateString('en-GB', dateOptions) + ' ' +
new Date(value).toLocaleTimeString('en-GB', timeOptions) new Date(value).toLocaleTimeString('en-GB', timeOptions)
}
}, },
methods: {
calcNameWidth () { calcNameWidth () {
const nameWidth = this.$refs['name-td'] && this.$refs['name-td'][0] const nameWidth = this.$refs['name-td'] && this.$refs['name-td'][0]
? this.$refs['name-td'][0].getBoundingClientRect().width ? this.$refs['name-td'][0].getBoundingClientRect().width
@@ -314,10 +323,12 @@ export default {
}, },
openInquiry (index) { openInquiry (index) {
const tab = this.showedInquiries[index] const tab = this.showedInquiries[index]
setTimeout(() => {
this.$store.dispatch('addTab', tab).then(id => { this.$store.dispatch('addTab', tab).then(id => {
this.$store.commit('setCurrentTabId', id) this.$store.commit('setCurrentTabId', id)
this.$router.push('/workspace') this.$router.push('/workspace')
}) })
})
}, },
showRenameDialog (id) { showRenameDialog (id) {
this.errorMsg = null this.errorMsg = null
@@ -332,7 +343,7 @@ export default {
} }
const processedInquiry = this.inquiries[this.processedInquiryIndex] const processedInquiry = this.inquiries[this.processedInquiryIndex]
processedInquiry.name = this.newName processedInquiry.name = this.newName
this.$set(this.inquiries, this.processedInquiryIndex, processedInquiry) this.inquiries[this.processedInquiryIndex] = processedInquiry
// update inquiries in local storage // update inquiries in local storage
storedInquiries.updateStorage(this.inquiries) storedInquiries.updateStorage(this.inquiries)

View File

@@ -31,6 +31,7 @@ import tooltipMixin from '@/tooltipMixin'
export default { export default {
name: 'CopyIcon', name: 'CopyIcon',
emits: ['click'],
mixins: [tooltipMixin], mixins: [tooltipMixin],
methods: { methods: {
onClick () { onClick () {

View File

@@ -29,6 +29,7 @@ import tooltipMixin from '@/tooltipMixin'
export default { export default {
name: 'DeleteIcon', name: 'DeleteIcon',
emits: ['click'],
mixins: [tooltipMixin], mixins: [tooltipMixin],
methods: { methods: {
onClick () { onClick () {

View File

@@ -29,6 +29,7 @@ import tooltipMixin from '@/tooltipMixin'
export default { export default {
name: 'RenameIcon', name: 'RenameIcon',
emits: ['click'],
mixins: [tooltipMixin], mixins: [tooltipMixin],
methods: { methods: {
onClick () { onClick () {

View File

@@ -29,7 +29,7 @@
</div> </div>
<!--Save Inquiry dialog --> <!--Save Inquiry dialog -->
<modal name="save" classes="dialog" height="auto"> <modal modal-id="save" class="dialog">
<div class="dialog-header"> <div class="dialog-header">
Save inquiry Save inquiry
<close-icon @click="cancelSave"/> <close-icon @click="cancelSave"/>
@@ -61,6 +61,7 @@ import CloseIcon from '@/components/svg/close'
import storedInquiries from '@/lib/storedInquiries' import storedInquiries from '@/lib/storedInquiries'
import AppDiagnosticInfo from './AppDiagnosticInfo' import AppDiagnosticInfo from './AppDiagnosticInfo'
import events from '@/lib/utils/events' import events from '@/lib/utils/events'
import eventBus from '@/lib/eventBus'
export default { export default {
name: 'MainMenu', name: 'MainMenu',
@@ -90,11 +91,11 @@ export default {
} }
}, },
created () { created () {
this.$root.$on('createNewInquiry', this.createNewInquiry) eventBus.$on('createNewInquiry', this.createNewInquiry)
this.$root.$on('saveInquiry', this.checkInquiryBeforeSave) eventBus.$on('saveInquiry', this.checkInquiryBeforeSave)
document.addEventListener('keydown', this._keyListener) document.addEventListener('keydown', this._keyListener)
}, },
beforeDestroy () { beforeUnmount () {
document.removeEventListener('keydown', this._keyListener) document.removeEventListener('keydown', this._keyListener)
}, },
methods: { methods: {
@@ -110,7 +111,7 @@ export default {
}, },
cancelSave () { cancelSave () {
this.$modal.hide('save') this.$modal.hide('save')
this.$root.$off('inquirySaved') eventBus.$off('inquirySaved')
}, },
checkInquiryBeforeSave () { checkInquiryBeforeSave () {
this.errorMsg = null this.errorMsg = null
@@ -161,7 +162,7 @@ export default {
this.$modal.hide('save') this.$modal.hide('save')
// Signal about saving // Signal about saving
this.$root.$emit('inquirySaved') eventBus.$emit('inquirySaved')
events.send('inquiry.save') events.send('inquiry.save')
}, },
_keyListener (e) { _keyListener (e) {

View File

@@ -141,7 +141,7 @@ export default {
} }
.db-name:hover .chevron-icon path, .db-name:hover .chevron-icon path,
>>> .table-name:hover .chevron-icon path { :deep(.table-name:hover .chevron-icon path) {
fill: var(--color-gray-dark); fill: var(--color-gray-dark);
} }
</style> </style>

View File

@@ -11,23 +11,24 @@
:dataSources="dataSources" :dataSources="dataSources"
:dataSourceOptions="dataSourceOptions" :dataSourceOptions="dataSourceOptions"
:plotly="plotly" :plotly="plotly"
@onUpdate="update" :useResizeHandler="useResizeHandler"
@onRender="onRender"
:useResizeHandler="true"
:debug="true" :debug="true"
:advancedTraceTypeSelector="true" :advancedTraceTypeSelector="true"
class="chart" class="chart"
ref="plotlyEditor" ref="plotlyEditor"
:style="{ height: !dataSources ? 'calc(100% - 40px)' : '100%' }" :style="{ height: !dataSources ? 'calc(100% - 40px)' : '100%' }"
@update="update"
@render="onRender"
/> />
</div> </div>
</template> </template>
<script> <script>
import { applyPureReactInVue } from 'veaury'
import plotly from 'plotly.js' import plotly from 'plotly.js'
import 'react-chart-editor/lib/react-chart-editor.css' import 'react-chart-editor/lib/react-chart-editor.css'
import PlotlyEditor from 'react-chart-editor' import ReactPlotlyEditor from 'react-chart-editor'
import chartHelper from '@/lib/chartHelper' import chartHelper from '@/lib/chartHelper'
import dereference from 'react-chart-editor/lib/lib/dereference' import dereference from 'react-chart-editor/lib/lib/dereference'
import fIo from '@/lib/utils/fileIo' import fIo from '@/lib/utils/fileIo'
@@ -40,19 +41,21 @@ export default {
'importToPngEnabled', 'importToSvgEnabled', 'importToPngEnabled', 'importToSvgEnabled',
'forPivot' 'forPivot'
], ],
emits: ['update:importToSvgEnabled', 'update', 'loadingImageCompleted'],
components: { components: {
PlotlyEditor PlotlyEditor: applyPureReactInVue(ReactPlotlyEditor)
}, },
data () { data () {
return { return {
plotly: plotly, plotly,
state: this.initOptions || { state: this.initOptions || {
data: [], data: [],
layout: {}, layout: {},
frames: [] frames: []
}, },
visible: true, visible: true,
resizeObserver: null resizeObserver: null,
useResizeHandler: false
} }
}, },
computed: { computed: {
@@ -83,7 +86,13 @@ export default {
this.resizeObserver = new ResizeObserver(this.handleResize) this.resizeObserver = new ResizeObserver(this.handleResize)
this.resizeObserver.observe(this.$refs.chartContainer) this.resizeObserver.observe(this.$refs.chartContainer)
}, },
beforeDestroy () { activated () {
this.useResizeHandler = true
},
deactivated () {
this.useResizeHandler = false
},
beforeUnmount () {
this.resizeObserver.unobserve(this.$refs.chartContainer) this.resizeObserver.unobserve(this.$refs.chartContainer)
}, },
watch: { watch: {
@@ -153,7 +162,7 @@ export default {
min-height: 242px; min-height: 242px;
} }
>>> .editor_controls .sidebar__item:before { :deep(.editor_controls .sidebar__item:before) {
width: 0; width: 0;
} }
</style> </style>

View File

@@ -1,10 +1,10 @@
<template> <template>
<div :class="['pivot-sort-btn', direction] " @click="changeSorting"> <div :class="['pivot-sort-btn', direction] " @click="changeSorting">
{{ value.includes('key') ? 'key' : 'value' }} {{ modelValue.includes('key') ? 'key' : 'value' }}
<sort-icon <sort-icon
class="sort-icon" class="sort-icon"
:horizontal="direction === 'col'" :horizontal="direction === 'col'"
:asc="value.includes('a_to_z')" :asc="modelValue.includes('a_to_z')"
/> />
</div> </div>
</template> </template>
@@ -14,18 +14,18 @@ import SortIcon from '@/components/svg/sort'
export default { export default {
name: 'PivotSortBtn', name: 'PivotSortBtn',
props: ['direction', 'value'], props: ['direction', 'modelValue'],
components: { components: {
SortIcon SortIcon
}, },
methods: { methods: {
changeSorting () { changeSorting () {
if (this.value === 'key_a_to_z') { if (this.modelValue === 'key_a_to_z') {
this.$emit('input', 'value_a_to_z') this.$emit('update:modelValue', 'value_a_to_z')
} else if (this.value === 'value_a_to_z') { } else if (this.modelValue === 'value_a_to_z') {
this.$emit('input', 'value_z_to_a') this.$emit('update:modelValue', 'value_z_to_a')
} else { } else {
this.$emit('input', 'key_a_to_z') this.$emit('update:modelValue', 'key_a_to_z')
} }
} }
} }
@@ -52,7 +52,7 @@ export default {
color: var(--color-text-active); color: var(--color-text-active);
border-color: var(--color-border-dark); border-color: var(--color-border-dark);
} }
.pivot-sort-btn:hover >>> .sort-icon path { .pivot-sort-btn:hover deep(.sort-icon path) {
fill: var(--color-text-active); fill: var(--color-text-active);
} }

View File

@@ -141,35 +141,35 @@ import Multiselect from 'vue-multiselect'
import PivotSortBtn from './PivotSortBtn' import PivotSortBtn from './PivotSortBtn'
import { renderers, aggregators, zeroValAggregators, twoValAggregators } from '../pivotHelper' import { renderers, aggregators, zeroValAggregators, twoValAggregators } from '../pivotHelper'
import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart' import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart'
import Vue from 'vue' import { createApp } from 'vue'
const ChartClass = Vue.extend(Chart)
export default { export default {
name: 'pivotUi', name: 'pivotUi',
props: ['keyNames', 'value'], props: ['keyNames', 'modelValue'],
emits: ['loadingCustomChartImageCompleted', 'update:modelValue', 'update'],
components: { components: {
Multiselect, Multiselect,
PivotSortBtn PivotSortBtn
}, },
data () { data () {
const aggregatorName = (this.value && this.value.aggregatorName) || 'Count' const aggregatorName = (this.modelValue && this.modelValue.aggregatorName) || 'Count'
const rendererName = (this.value && this.value.rendererName) || 'Table' const rendererName = (this.modelValue && this.modelValue.rendererName) || 'Table'
return { return {
collapsed: false, collapsed: false,
renderer: { name: rendererName, fun: $.pivotUtilities.renderers[rendererName] }, renderer: { name: rendererName, fun: $.pivotUtilities.renderers[rendererName] },
aggregator: { name: aggregatorName, fun: $.pivotUtilities.aggregators[aggregatorName] }, aggregator: { name: aggregatorName, fun: $.pivotUtilities.aggregators[aggregatorName] },
rows: (this.value && this.value.rows) || [], rows: (this.modelValue && this.modelValue.rows) || [],
cols: (this.value && this.value.cols) || [], cols: (this.modelValue && this.modelValue.cols) || [],
val1: (this.value && this.value.vals && this.value.vals[0]) || '', val1: (this.modelValue && this.modelValue.vals && this.modelValue.vals[0]) || '',
val2: (this.value && this.value.vals && this.value.vals[1]) || '', val2: (this.modelValue && this.modelValue.vals && this.modelValue.vals[1]) || '',
colOrder: (this.value && this.value.colOrder) || 'key_a_to_z', colOrder: (this.modelValue && this.modelValue.colOrder) || 'key_a_to_z',
rowOrder: (this.value && this.value.rowOrder) || 'key_a_to_z', rowOrder: (this.modelValue && this.modelValue.rowOrder) || 'key_a_to_z',
customChartComponent: customChartComponent:
( (
this.value && this.modelValue &&
this.value.rendererOptions && this.modelValue.rendererOptions &&
this.value.rendererOptions.customChartComponent this.modelValue.rendererOptions.customChartComponent
) || new ChartClass() ) || createApp(Chart)
} }
}, },
computed: { computed: {
@@ -224,11 +224,10 @@ export default {
} }
}, },
created () { created () {
this.customChartComponent.$on('update', () => { this.$emit('update') }) this.customChartComponent.onUpdate = () => { this.$emit('update') }
this.customChartComponent.$on( this.customChartComponent.onLoadingImageCompleted = () => {
'loadingImageCompleted', this.$emit('loadingCustomChartImageCompleted')
value => { this.$emit('loadingCustomChartImageCompleted') } }
)
}, },
methods: { methods: {
returnValue () { returnValue () {
@@ -237,7 +236,7 @@ export default {
vals.push(this[`val${i}`]) vals.push(this[`val${i}`])
} }
this.$emit('update') this.$emit('update')
this.$emit('input', { this.$emit('update:modelValue', {
rows: this.rows, rows: this.rows,
cols: this.cols, cols: this.cols,
colOrder: this.colOrder, colOrder: this.colOrder,
@@ -246,7 +245,9 @@ export default {
aggregatorName: this.aggregator.name, aggregatorName: this.aggregator.name,
renderer: this.renderer.fun, renderer: this.renderer.fun,
rendererName: this.renderer.name, rendererName: this.renderer.name,
rendererOptions: this.renderer.name !== 'Custom chart' ? undefined : { rendererOptions: this.renderer.name !== 'Custom chart'
? undefined
: {
customChartComponent: this.customChartComponent customChartComponent: this.customChartComponent
}, },
vals vals

View File

@@ -14,6 +14,7 @@
</template> </template>
<script> <script>
import { createApp } from 'vue'
import fIo from '@/lib/utils/fileIo' import fIo from '@/lib/utils/fileIo'
import $ from 'jquery' import $ from 'jquery'
import 'pivottable' import 'pivottable'
@@ -22,13 +23,17 @@ import PivotUi from './PivotUi'
import pivotHelper from './pivotHelper' import pivotHelper from './pivotHelper'
import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart' import Chart from '@/views/Main/Workspace/Tabs/Tab/DataView/Chart'
import chartHelper from '@/lib/chartHelper' import chartHelper from '@/lib/chartHelper'
import Vue from 'vue'
import events from '@/lib/utils/events' import events from '@/lib/utils/events'
const ChartClass = Vue.extend(Chart)
export default { export default {
name: 'pivot', name: 'pivot',
props: ['dataSources', 'initOptions', 'importToPngEnabled', 'importToSvgEnabled'], props: ['dataSources', 'initOptions', 'importToPngEnabled', 'importToSvgEnabled'],
emits: [
'loadingImageCompleted',
'update',
'update:importToSvgEnabled',
'update:importToPngEnabled'
],
components: { components: {
PivotUi PivotUi
}, },
@@ -60,9 +65,12 @@ export default {
vals: this.initOptions.vals, vals: this.initOptions.vals,
rendererName: this.initOptions.rendererName, rendererName: this.initOptions.rendererName,
renderer: $.pivotUtilities.renderers[this.initOptions.rendererName], renderer: $.pivotUtilities.renderers[this.initOptions.rendererName],
rendererOptions: !this.initOptions.rendererOptions ? undefined : { rendererOptions: !this.initOptions.rendererOptions
customChartComponent: new ChartClass({ ? undefined
propsData: { initOptions: this.initOptions.rendererOptions.customChartOptions } : {
customChartComponent: createApp(Chart, {
initOptions: this.initOptions
.rendererOptions.customChartOptions
}) })
} }
} }
@@ -112,7 +120,7 @@ export default {
this.resizeObserver = new ResizeObserver(this.handleResize) this.resizeObserver = new ResizeObserver(this.handleResize)
this.resizeObserver.observe(this.$refs.pivotOutput) this.resizeObserver.observe(this.$refs.pivotOutput)
}, },
beforeDestroy () { beforeUnmount () {
this.resizeObserver.unobserve(this.$refs.pivotOutput) this.resizeObserver.unobserve(this.$refs.pivotOutput)
}, },
methods: { methods: {
@@ -249,26 +257,26 @@ export default {
line-height: 40px; line-height: 40px;
box-sizing: border-box; box-sizing: border-box;
} }
>>> .pvtTable { :deep(.pvtTable) {
min-width: 100%; min-width: 100%;
} }
>>> table.pvtTable tbody tr td, :deep(table.pvtTable tbody tr td),
>>> table.pvtTable thead tr th, :deep(table.pvtTable thead tr th),
>>> table.pvtTable tbody tr th { :deep(table.pvtTable tbody tr th) {
border-color: var(--color-border-light); border-color: var(--color-border-light);
} }
>>> table.pvtTable thead tr th, :deep(table.pvtTable thead tr th),
>>> table.pvtTable tbody tr th { :deep(table.pvtTable tbody tr th) {
background-color: var(--color-bg-dark); background-color: var(--color-bg-dark);
color: var(--color-text-light); color: var(--color-text-light);
} }
>>> table.pvtTable tbody tr td { :deep(table.pvtTable tbody tr td) {
color: var(--color-text-base); color: var(--color-text-base);
} }
.pivot-output >>> textarea { .pivot-output :deep(textarea) {
color: var(--color-text-base); color: var(--color-text-base);
min-width: 100%; min-width: 100%;
height: 100% !important; height: 100% !important;
@@ -277,7 +285,7 @@ export default {
border-width: 0; border-width: 0;
} }
.pivot-output >>> textarea:focus-visible { .pivot-output :deep(textarea:focus-visible) {
outline: none; outline: none;
} }
</style> </style>

View File

@@ -53,9 +53,11 @@ function customChartRenderer (data, options) {
options.customChartComponent.dataSources = _getDataSources(data) options.customChartComponent.dataSources = _getDataSources(data)
options.customChartComponent.forPivot = true options.customChartComponent.forPivot = true
options.customChartComponent.$mount() const container = document.createElement('div')
return $(options.customChartComponent.$el) options.customChartComponent.mount(container)
return $(container)
} }
$.extend( $.extend(

View File

@@ -5,8 +5,8 @@
:is="mode" :is="mode"
:init-options="mode === initMode ? initOptions : undefined" :init-options="mode === initMode ? initOptions : undefined"
:data-sources="dataSource" :data-sources="dataSource"
:import-to-png-enabled.sync="importToPngEnabled" v-model:import-to-png-enabled="importToPngEnabled"
:import-to-svg-enabled.sync="importToSvgEnabled" v-model:import-to-svg-enabled="importToSvgEnabled"
@loadingImageCompleted="loadingImage = false" @loadingImageCompleted="loadingImage = false"
ref="viewComponent" ref="viewComponent"
@update="$emit('update')" @update="$emit('update')"
@@ -99,6 +99,7 @@ import events from '@/lib/utils/events'
export default { export default {
name: 'DataView', name: 'DataView',
props: ['dataSource', 'initOptions', 'initMode'], props: ['dataSource', 'initOptions', 'initMode'],
emits: ['update', 'switchTo'],
components: { components: {
Chart, Chart,
Pivot, Pivot,
@@ -233,7 +234,7 @@ export default {
height: 100%; height: 100%;
overflow: auto; overflow: auto;
} }
>>>.vm--container { :deep(.vm--container) {
animation: show-modal 1s linear 0s 1; animation: show-modal 1s linear 0s 1;
} }

View File

@@ -1,38 +1,38 @@
<template> <template>
<div class="record-navigator"> <div class="record-navigator">
<icon-button <icon-button
:disabled="value === 0" :disabled="modelValue === 0"
tooltip="First row" tooltip="First row"
tooltip-position="top-left" tooltip-position="top-left"
class="first" class="first"
@click="$emit('input', 0)" @click="$emit('update:modelValue', 0)"
> >
<edge-arrow-icon :disabled="false" /> <edge-arrow-icon :disabled="false" />
</icon-button> </icon-button>
<icon-button <icon-button
:disabled="value === 0" :disabled="modelValue === 0"
tooltip="Previous row" tooltip="Previous row"
tooltip-position="top-left" tooltip-position="top-left"
class="prev" class="prev"
@click="$emit('input', value - 1)" @click="$emit('update:modelValue', modelValue - 1)"
> >
<arrow-icon :disabled="false" /> <arrow-icon :disabled="false" />
</icon-button> </icon-button>
<icon-button <icon-button
:disabled="value === total - 1" :disabled="modelValue === total - 1"
tooltip="Next row" tooltip="Next row"
tooltip-position="top-left" tooltip-position="top-left"
class="next" class="next"
@click="$emit('input', value + 1)" @click="$emit('update:modelValue', modelValue + 1)"
> >
<arrow-icon :disabled="false" /> <arrow-icon :disabled="false" />
</icon-button> </icon-button>
<icon-button <icon-button
:disabled="value === total - 1" :disabled="modelValue === total - 1"
tooltip="Last row" tooltip="Last row"
tooltip-position="top-left" tooltip-position="top-left"
class="last" class="last"
@click="$emit('input', total - 1)" @click="$emit('update:modelValue', total - 1)"
> >
<edge-arrow-icon :disabled="false" /> <edge-arrow-icon :disabled="false" />
</icon-button> </icon-button>
@@ -51,7 +51,7 @@ export default {
EdgeArrowIcon EdgeArrowIcon
}, },
props: { props: {
value: Number, modelValue: Number,
total: Number total: Number
} }
} }

View File

@@ -50,6 +50,7 @@
<script> <script>
import RowNavigator from './RowNavigator.vue' import RowNavigator from './RowNavigator.vue'
import { nextTick } from 'vue'
export default { export default {
components: { RowNavigator }, components: { RowNavigator },
@@ -59,6 +60,7 @@ export default {
rowIndex: { type: Number, default: 0 }, rowIndex: { type: Number, default: 0 },
selectedColumnIndex: Number selectedColumnIndex: Number
}, },
emits: ['updateSelectedCell'],
data () { data () {
return { return {
selectedCellElement: null, selectedCellElement: null,
@@ -84,7 +86,7 @@ export default {
}, },
watch: { watch: {
async currentRowIndex () { async currentRowIndex () {
await this.$nextTick() await nextTick()
if (this.selectedCellElement) { if (this.selectedCellElement) {
const previouslySelected = this.selectedCellElement const previouslySelected = this.selectedCellElement
this.selectCell(null) this.selectCell(null)

View File

@@ -41,7 +41,7 @@
</template> </template>
<script> <script>
import { codemirror } from 'vue-codemirror' import Codemirror from 'codemirror-editor-vue3'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/javascript/javascript.js' import 'codemirror/mode/javascript/javascript.js'
import 'codemirror/addon/fold/foldcode.js' import 'codemirror/addon/fold/foldcode.js'
@@ -54,7 +54,7 @@ import Logs from '@/components/Logs'
export default { export default {
components: { components: {
codemirror, Codemirror,
Logs Logs
}, },
props: { props: {
@@ -192,15 +192,15 @@ export default {
color: var(--color-accent); color: var(--color-accent);
} }
>>> .vue-codemirror { :deep(.vue-codemirror) {
height: 100%; height: 100%;
max-height: 100%; max-height: 100%;
} }
>>> .CodeMirror { :deep(.CodeMirror) {
height: 100%; height: 100%;
max-height: 100%; max-height: 100%;
} }
>>> .CodeMirror-cursor { :deep(.CodeMirror-cursor) {
width: 1px; width: 1px;
background: var(--color-text-base); background: var(--color-text-base);
} }

View File

@@ -77,7 +77,11 @@
@cancel="cancelCopy" @cancel="cancelCopy"
/> />
<teleport :to="resultSetTeleportTarget"> <teleport
defer
:to="resultSetTeleportTarget"
:disabled="!enableTeleport"
>
<div> <div>
<div <div
v-show="result === null && !isGettingResults && !error" v-show="result === null && !isGettingResults && !error"
@@ -138,7 +142,6 @@ import cIo from '@/lib/utils/clipboardIo'
import time from '@/lib/utils/time' import time from '@/lib/utils/time'
import loadingDialog from '@/components/LoadingDialog' import loadingDialog from '@/components/LoadingDialog'
import events from '@/lib/utils/events' import events from '@/lib/utils/events'
import Teleport from 'vue2-teleport'
import ValueViewer from './ValueViewer' import ValueViewer from './ValueViewer'
import Record from './Record/index.vue' import Record from './Record/index.vue'
@@ -151,6 +154,7 @@ export default {
error: Object, error: Object,
time: [String, Number] time: [String, Number]
}, },
emits: ['switchTo'],
data () { data () {
return { return {
resizeObserver: null, resizeObserver: null,
@@ -161,7 +165,8 @@ export default {
selectedCell: null, selectedCell: null,
viewRecord: false, viewRecord: false,
defaultPage: 1, defaultPage: 1,
defaultSelectedCell: null defaultSelectedCell: null,
enableTeleport: false
} }
}, },
components: { components: {
@@ -177,11 +182,13 @@ export default {
loadingDialog, loadingDialog,
ValueViewer, ValueViewer,
Record, Record,
Splitpanes, Splitpanes
Teleport
}, },
computed: { computed: {
resultSetTeleportTarget () { resultSetTeleportTarget () {
if (!this.enableTeleport) {
return undefined
}
const base = `#${this.viewValuePanelVisible const base = `#${this.viewValuePanelVisible
? 'run-result-left-pane' ? 'run-result-left-pane'
: 'run-result-result-set' : 'run-result-result-set'
@@ -190,12 +197,21 @@ export default {
return base + tabIdPostfix return base + tabIdPostfix
} }
}, },
activated () {
this.enableTeleport = true
},
deactivated () {
this.enableTeleport = false
},
mounted () { mounted () {
if (this.$store.state.isWorkspaceVisible) {
this.enableTeleport = true
}
this.resizeObserver = new ResizeObserver(this.handleResize) this.resizeObserver = new ResizeObserver(this.handleResize)
this.resizeObserver.observe(this.$refs.runResultPanel) this.resizeObserver.observe(this.$refs.runResultPanel)
this.calculatePageSize() this.calculatePageSize()
}, },
beforeDestroy () { beforeUnmount () {
this.resizeObserver.unobserve(this.$refs.runResultPanel) this.resizeObserver.unobserve(this.$refs.runResultPanel)
}, },
watch: { watch: {
@@ -361,7 +377,7 @@ export default {
} }
} }
>>>.vm--container { :deep(.vm--container) {
animation: show-modal 1s linear 0s 1; animation: show-modal 1s linear 0s 1;
} }

View File

@@ -4,7 +4,7 @@
:active="panel === 'sqlEditor'" :active="panel === 'sqlEditor'"
tooltip="Switch panel to SQL editor" tooltip="Switch panel to SQL editor"
tooltip-position="top-left" tooltip-position="top-left"
@click.native="$emit('switchTo', 'sqlEditor')" @click="$emit('switchTo', 'sqlEditor')"
> >
<sql-editor-icon /> <sql-editor-icon />
</icon-button> </icon-button>
@@ -13,7 +13,7 @@
:active="panel === 'table'" :active="panel === 'table'"
tooltip="Switch panel to result set" tooltip="Switch panel to result set"
tooltip-position="top-left" tooltip-position="top-left"
@click.native="$emit('switchTo', 'table')" @click="$emit('switchTo', 'table')"
> >
<table-icon/> <table-icon/>
</icon-button> </icon-button>
@@ -22,7 +22,7 @@
:active="panel === 'dataView'" :active="panel === 'dataView'"
tooltip="Switch panel to data view" tooltip="Switch panel to data view"
tooltip-position="top-left" tooltip-position="top-left"
@click.native="$emit('switchTo', 'dataView')" @click="$emit('switchTo', 'dataView')"
> >
<data-view-icon /> <data-view-icon />
</icon-button> </icon-button>
@@ -42,6 +42,7 @@ import DataViewIcon from '@/components/svg/dataView'
export default { export default {
name: 'SideToolBar', name: 'SideToolBar',
props: ['panel'], props: ['panel'],
emits: ['switchTo'],
components: { components: {
IconButton, IconButton,
SqlEditorIcon, SqlEditorIcon,

View File

@@ -43,7 +43,7 @@ export function showHintOnDemand (editor) {
CM.showHint(editor, getHints, hintOptions) CM.showHint(editor, getHints, hintOptions)
} }
export default function showHint (editor) { export default function showHint (value, editor) {
// Don't show autocomplete after a space or semicolon or in string literals // Don't show autocomplete after a space or semicolon or in string literals
const token = editor.getTokenAt(editor.getCursor()) const token = editor.getTokenAt(editor.getCursor())
const ch = token.string.slice(-1) const ch = token.string.slice(-1)

View File

@@ -1,11 +1,12 @@
<template> <template>
<div class="sql-editor-panel"> <div class="sql-editor-panel">
<div class="codemirror-container"> <div class="codemirror-container original-style">
<codemirror <codemirror
ref="cm" ref="cm"
v-model="query" v-model:value="query"
:options="cmOptions" :options="cmOptions"
@changes="onChange" :original-style="true"
@change="onChange"
/> />
</div> </div>
<side-tool-bar panel="sqlEditor" @switchTo="$emit('switchTo', $event)"> <side-tool-bar panel="sqlEditor" @switchTo="$emit('switchTo', $event)">
@@ -25,7 +26,7 @@
<script> <script>
import showHint, { showHintOnDemand } from './hint' import showHint, { showHintOnDemand } from './hint'
import time from '@/lib/utils/time' import time from '@/lib/utils/time'
import { codemirror } from 'vue-codemirror' import Codemirror from 'codemirror-editor-vue3'
import 'codemirror/lib/codemirror.css' import 'codemirror/lib/codemirror.css'
import 'codemirror/mode/sql/sql.js' import 'codemirror/mode/sql/sql.js'
import 'codemirror/theme/neo.css' import 'codemirror/theme/neo.css'
@@ -37,16 +38,17 @@ import RunIcon from '@/components/svg/run'
export default { export default {
name: 'SqlEditor', name: 'SqlEditor',
props: ['value', 'isGettingResults'], props: ['modelValue', 'isGettingResults'],
emits: ['update:modelValue', 'run', 'switchTo'],
components: { components: {
codemirror, Codemirror,
SideToolBar, SideToolBar,
IconButton, IconButton,
RunIcon RunIcon
}, },
data () { data () {
return { return {
query: this.value, query: this.modelValue,
cmOptions: { cmOptions: {
tabSize: 4, tabSize: 4,
mode: 'text/x-mysql', mode: 'text/x-mysql',
@@ -54,6 +56,7 @@ export default {
lineNumbers: true, lineNumbers: true,
line: true, line: true,
autoRefresh: true, autoRefresh: true,
styleActiveLine: false,
extraKeys: { 'Ctrl-Space': showHintOnDemand } extraKeys: { 'Ctrl-Space': showHintOnDemand }
} }
} }
@@ -65,13 +68,13 @@ export default {
}, },
watch: { watch: {
query () { query () {
this.$emit('input', this.query) this.$emit('update:modelValue', this.query)
} }
}, },
methods: { methods: {
onChange: time.debounce(showHint, 400), onChange: time.debounce(showHint, 400),
focus () { focus () {
this.$refs.cm.codemirror.focus() this.$refs.cm.cminstance.focus()
} }
} }
} }
@@ -92,15 +95,15 @@ export default {
overflow: auto; overflow: auto;
} }
>>> .vue-codemirror { :deep(.vue-codemirror) {
height: 100%; height: 100%;
max-height: 100%; max-height: 100%;
} }
>>> .CodeMirror { :deep(.CodeMirror) {
height: 100%; height: 100%;
max-height: 100%; max-height: 100%;
} }
>>> .CodeMirror-cursor { :deep(.CodeMirror-cursor) {
width: 1px; width: 1px;
background: var(--color-text-base); background: var(--color-text-base);
} }

View File

@@ -17,7 +17,11 @@
<div :id="'hidden-'+ tab.id" class="hidden-part" /> <div :id="'hidden-'+ tab.id" class="hidden-part" />
<teleport :to="`#${tab.layout.sqlEditor}-${tab.id}`"> <teleport
defer
:to="enableTeleport ? `#${tab.layout.sqlEditor}-${tab.id}`: undefined"
:disabled="!enableTeleport"
>
<sql-editor <sql-editor
ref="sqlEditor" ref="sqlEditor"
v-model="tab.query" v-model="tab.query"
@@ -27,7 +31,11 @@
/> />
</teleport> </teleport>
<teleport :to="`#${tab.layout.table}-${tab.id}`"> <teleport
defer
:to="enableTeleport ? `#${tab.layout.table}-${tab.id}`: undefined"
:disabled="!enableTeleport"
>
<run-result <run-result
:tab="tab" :tab="tab"
:result="tab.result" :result="tab.result"
@@ -38,7 +46,11 @@
/> />
</teleport> </teleport>
<teleport :to="`#${tab.layout.dataView}-${tab.id}`"> <teleport
defer
:to="enableTeleport ? `#${tab.layout.dataView}-${tab.id}` : undefined"
:disabled="!enableTeleport"
>
<data-view <data-view
:data-source="(tab.result && tab.result.values) || null" :data-source="(tab.result && tab.result.values) || null"
:init-options="tab.viewOptions" :init-options="tab.viewOptions"
@@ -56,8 +68,8 @@ import Splitpanes from '@/components/Splitpanes'
import SqlEditor from './SqlEditor' import SqlEditor from './SqlEditor'
import DataView from './DataView' import DataView from './DataView'
import RunResult from './RunResult' import RunResult from './RunResult'
import { nextTick } from 'vue'
import Teleport from 'vue2-teleport'
import events from '@/lib/utils/events' import events from '@/lib/utils/events'
export default { export default {
@@ -65,18 +77,19 @@ export default {
props: { props: {
tab: Object tab: Object
}, },
emits: [],
components: { components: {
SqlEditor, SqlEditor,
DataView, DataView,
RunResult, RunResult,
Splitpanes, Splitpanes
Teleport
}, },
data () { data () {
return { return {
topPaneSize: this.tab.maximize topPaneSize: this.tab.maximize
? this.tab.layout[this.tab.maximize] === 'above' ? 100 : 0 ? this.tab.layout[this.tab.maximize] === 'above' ? 100 : 0
: 50 : 50,
enableTeleport: false
} }
}, },
computed: { computed: {
@@ -89,7 +102,7 @@ export default {
immediate: true, immediate: true,
async handler () { async handler () {
if (this.isActive) { if (this.isActive) {
await this.$nextTick() await nextTick()
this.$refs.sqlEditor.focus() this.$refs.sqlEditor.focus()
} }
} }
@@ -101,7 +114,16 @@ export default {
}) })
} }
}, },
activated () {
this.enableTeleport = true
},
deactivated () {
this.enableTeleport = false
},
mounted () { mounted () {
if (this.$store.state.isWorkspaceVisible) {
this.enableTeleport = true
}
this.tab.dataView = this.$refs.dataView this.tab.dataView = this.$refs.dataView
}, },
methods: { methods: {

View File

@@ -23,13 +23,13 @@
:tab="tab" :tab="tab"
/> />
<div v-show="tabs.length === 0" id="start-guide"> <div v-show="tabs.length === 0" id="start-guide">
<span class="link" @click="$root.$emit('createNewInquiry')">Create</span> <span class="link" @click="emitCreateTabEvent">Create</span>
new inquiry from scratch or open one from new inquiry from scratch or open one from
<router-link class="link" to="/inquiries">Inquiries</router-link> <router-link class="link" to="/inquiries">Inquiries</router-link>
</div> </div>
<!--Close tab warning dialog --> <!--Close tab warning dialog -->
<modal name="close-warn" classes="dialog" height="auto"> <modal modal-id="close-warn" class="dialog">
<div class="dialog-header"> <div class="dialog-header">
Close tab {{ Close tab {{
closingTab !== null closingTab !== null
@@ -59,8 +59,10 @@
<script> <script>
import Tab from './Tab' import Tab from './Tab'
import CloseIcon from '@/components/svg/close' import CloseIcon from '@/components/svg/close'
import eventBus from '@/lib/eventBus'
export default { export default {
emits: [],
components: { components: {
Tab, Tab,
CloseIcon CloseIcon
@@ -82,6 +84,9 @@ export default {
window.addEventListener('beforeunload', this.leavingSqliteviz) window.addEventListener('beforeunload', this.leavingSqliteviz)
}, },
methods: { methods: {
emitCreateTabEvent () {
eventBus.$emit('createNewInquiry')
},
leavingSqliteviz (event) { leavingSqliteviz (event) {
if (this.tabs.some(tab => !tab.isSaved)) { if (this.tabs.some(tab => !tab.isSaved)) {
event.preventDefault() event.preventDefault()
@@ -104,14 +109,14 @@ export default {
this.$store.commit('deleteTab', tab) this.$store.commit('deleteTab', tab)
}, },
saveAndClose (tab) { saveAndClose (tab) {
this.$root.$on('inquirySaved', () => { eventBus.$on('inquirySaved', () => {
this.closeTab(tab) this.closeTab(tab)
this.$root.$off('inquirySaved') eventBus.$off('inquirySaved')
}) })
this.selectTab(tab.id) this.selectTab(tab.id)
this.$modal.hide('close-warn') this.$modal.hide('close-warn')
this.$nextTick(() => { this.$nextTick(() => {
this.$root.$emit('saveInquiry') eventBus.$emit('saveInquiry')
}) })
} }
} }

View File

@@ -59,6 +59,12 @@ export default {
events.send('inquiry.create', null, { auto: true }) events.send('inquiry.create', null, { auto: true })
} }
},
activated () {
this.$store.commit('setIsWorkspaceVisible', true)
},
deactivated () {
this.$store.commit('setIsWorkspaceVisible', false)
} }
} }
</script> </script>

View File

@@ -1,9 +1,11 @@
<template> <template>
<div> <div>
<main-menu /> <main-menu />
<router-view id="main-view" v-slot="{ Component }">
<keep-alive include="Workspace,Inquiries"> <keep-alive include="Workspace,Inquiries">
<router-view id="main-view" /> <component :is="Component"/>
</keep-alive> </keep-alive>
</router-view>
</div> </div>
</template> </template>

View File

@@ -39,14 +39,14 @@ export default {
margin-top: 42px; margin-top: 42px;
} }
>>>.drop-area { :deep(.drop-area) {
width: 706px; width: 706px;
height: 482px; height: 482px;
padding: 0 150px; padding: 0 150px;
position: relative; position: relative;
} }
>>>.drop-area .text { :deep(.drop-area .text) {
position: absolute; position: absolute;
bottom: 42px; bottom: 42px;
max-width: 300px; max-width: 300px;

View File

@@ -21,7 +21,7 @@ describe('tab.js', () => {
error: null, error: null,
time: 0, time: 0,
isSaved: false, isSaved: false,
state: state state
}) })
expect(newTab.layout).to.include({ expect(newTab.layout).to.include({
sqlEditor: 'above', sqlEditor: 'above',
@@ -59,7 +59,7 @@ describe('tab.js', () => {
error: null, error: null,
time: 0, time: 0,
isSaved: true, isSaved: true,
state: state state
}) })
expect(newTab.layout).to.include({ expect(newTab.layout).to.include({
sqlEditor: 'above', sqlEditor: 'above',

View File

@@ -12,7 +12,7 @@ describe('actions', () => {
let id = await addTab({ state }) let id = await addTab({ state })
expect(state.tabs[0]).to.include({ expect(state.tabs[0]).to.include({
id: id, id,
name: null, name: null,
tempName: 'Untitled', tempName: 'Untitled',
viewType: 'chart', viewType: 'chart',
@@ -23,7 +23,7 @@ describe('actions', () => {
id = await addTab({ state }) id = await addTab({ state })
expect(state.tabs[1]).to.include({ expect(state.tabs[1]).to.include({
id: id, id,
name: null, name: null,
tempName: 'Untitled 1', tempName: 'Untitled 1',
viewType: 'chart', viewType: 'chart',

View File

@@ -11,7 +11,7 @@ describe('MainMenu.vue', () => {
afterEach(() => { afterEach(() => {
sinon.restore() sinon.restore()
// We need explicitly destroy the component, so that beforeDestroy hook was called // We need explicitly destroy the component, so that beforeUnmount hook was called
// It's important because in this hook MainMenu component removes keydown event listener. // It's important because in this hook MainMenu component removes keydown event listener.
wrapper.destroy() wrapper.destroy()
}) })
@@ -497,7 +497,7 @@ describe('MainMenu.vue', () => {
// check that the tab was updated // check that the tab was updated
expect(mutations.updateTab.calledOnceWith(state, sinon.match({ expect(mutations.updateTab.calledOnceWith(state, sinon.match({
tab: tab, tab,
newValues: { newValues: {
name: 'foo', name: 'foo',
id: 1, id: 1,

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai' import { expect } from 'chai'
import { _getDataSources, getPivotCanvas, getPivotHtml } import { _getDataSources, getPivotCanvas, getPivotHtml }
from '@/views/Main/Workspace/Tabs/Tab/DataView/Pivot/pivotHelper' from '@/views/Main/Workspace/Tabs/Tab/DataView/Pivot/pivotHelper'
describe('pivotHelper.js', () => { describe('pivotHelper.js', () => {
it('_getDataSources returns data sources', () => { it('_getDataSources returns data sources', () => {

View File

@@ -1,36 +1,45 @@
const { defineConfig } = require('@vue/cli-service')
const CopyPlugin = require('copy-webpack-plugin') const CopyPlugin = require('copy-webpack-plugin')
const WorkboxPlugin = require('workbox-webpack-plugin') const WorkboxPlugin = require('workbox-webpack-plugin')
module.exports = { module.exports = defineConfig({
transpileDependencies: true,
publicPath: '', publicPath: '',
// Workaround for https://github.com/vuejs/vue-cli/issues/5399 as described // Workaround for https://github.com/vuejs/vue-cli/issues/5399 as described
// in https://stackoverflow.com/a/63185174 // in https://stackoverflow.com/a/63185174
lintOnSave: process.env.NODE_ENV === 'development', lintOnSave: process.env.NODE_ENV === 'development',
configureWebpack: { configureWebpack: {
plugins: [ plugins: [
new CopyPlugin([ new CopyPlugin({
patterns: [
// This wasm file will be fetched dynamically when we initialize sql.js // This wasm file will be fetched dynamically when we initialize sql.js
// It is important that we do not change its name, // It is important that we do not change its name,
// and that it is in the same folder as the js // and that it is in the same folder as the js
{ from: 'node_modules/sql.js/dist/sql-wasm.wasm', to: 'js/' }, { from: 'node_modules/sql.js/dist/sql-wasm.wasm', to: 'js/' },
{ from: 'LICENSE', to: './' } { from: 'LICENSE', to: './' }
]), ]
}),
new WorkboxPlugin.GenerateSW({ new WorkboxPlugin.GenerateSW({
exclude: [/\.map$/, 'LICENSE', 'inquiries.json'], exclude: [/\.map$/, 'LICENSE', 'inquiries.json'],
clientsClaim: true, clientsClaim: true,
skipWaiting: false, skipWaiting: false,
maximumFileSizeToCacheInBytes: 40000000 maximumFileSizeToCacheInBytes: 40000000
}) })
] ],
resolve: {
fallback: {
asset: require.resolve('assert'),
stream: require.resolve('stream-browserify')
}
}
}, },
chainWebpack: config => { chainWebpack: config => {
const svgRule = config.module.rule('svg') config.module
svgRule.uses.clear() .rule('images')
svgRule .set('parser', {
.use('url-loader') dataUrlCondition: {
.loader('url-loader') maxSize: 10000
.options({ }
limit: 10000
}) })
config.module config.module
@@ -49,4 +58,4 @@ module.exports = {
return args return args
}) })
} }
} })