mirror of
https://github.com/lana-k/sqliteviz.git
synced 2025-12-06 10:08:52 +08:00
#31 handle concurrent saving
This commit is contained in:
@@ -26,6 +26,11 @@ export default {
|
|||||||
},
|
},
|
||||||
created() {
|
created() {
|
||||||
this.$store.commit('setInquiries', storedInquiries.getStoredInquiries())
|
this.$store.commit('setInquiries', storedInquiries.getStoredInquiries())
|
||||||
|
addEventListener('storage', event => {
|
||||||
|
if (event.key === storedInquiries.myInquiriesKey) {
|
||||||
|
this.$store.commit('setInquiries', storedInquiries.getStoredInquiries())
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ import events from '@/lib/utils/events'
|
|||||||
import migration from './_migrations'
|
import migration from './_migrations'
|
||||||
|
|
||||||
const migrate = migration._migrate
|
const migrate = migration._migrate
|
||||||
|
const myInquiriesKey = 'myInquiries'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
version: 2,
|
version: 2,
|
||||||
|
myInquiriesKey,
|
||||||
getStoredInquiries() {
|
getStoredInquiries() {
|
||||||
let myInquiries = JSON.parse(localStorage.getItem('myInquiries'))
|
let myInquiries = JSON.parse(localStorage.getItem(myInquiriesKey))
|
||||||
if (!myInquiries) {
|
if (!myInquiries) {
|
||||||
const oldInquiries = localStorage.getItem('myQueries')
|
const oldInquiries = localStorage.getItem('myQueries')
|
||||||
if (oldInquiries) {
|
if (oldInquiries) {
|
||||||
@@ -26,7 +28,8 @@ export default {
|
|||||||
const newInquiry = JSON.parse(JSON.stringify(baseInquiry))
|
const newInquiry = JSON.parse(JSON.stringify(baseInquiry))
|
||||||
newInquiry.name = newInquiry.name + ' Copy'
|
newInquiry.name = newInquiry.name + ' Copy'
|
||||||
newInquiry.id = nanoid()
|
newInquiry.id = nanoid()
|
||||||
newInquiry.createdAt = new Date()
|
newInquiry.createdAt = new Date().toJSON()
|
||||||
|
newInquiry.updatedAt = new Date().toJSON()
|
||||||
delete newInquiry.isPredefined
|
delete newInquiry.isPredefined
|
||||||
|
|
||||||
return newInquiry
|
return newInquiry
|
||||||
@@ -38,7 +41,7 @@ export default {
|
|||||||
|
|
||||||
updateStorage(inquiries) {
|
updateStorage(inquiries) {
|
||||||
localStorage.setItem(
|
localStorage.setItem(
|
||||||
'myInquiries',
|
myInquiriesKey,
|
||||||
JSON.stringify({ version: this.version, inquiries })
|
JSON.stringify({ version: this.version, inquiries })
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ export default class Tab {
|
|||||||
|
|
||||||
this.isSaved = !!inquiry.id
|
this.isSaved = !!inquiry.id
|
||||||
this.state = state
|
this.state = state
|
||||||
|
this.updatedAt = inquiry.updatedAt
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute() {
|
async execute() {
|
||||||
|
|||||||
@@ -15,13 +15,14 @@ export default {
|
|||||||
|
|
||||||
return inquiry.id
|
return inquiry.id
|
||||||
},
|
},
|
||||||
async saveInquiry({ state }, { inquiryTab, newName, overwrite }) {
|
async saveInquiry({ state }, { inquiryTab, newName }) {
|
||||||
const value = {
|
const value = {
|
||||||
id: inquiryTab.isPredefined || !overwrite ? nanoid() : inquiryTab.id,
|
id: inquiryTab.isPredefined || newName ? nanoid() : inquiryTab.id,
|
||||||
query: inquiryTab.query,
|
query: inquiryTab.query,
|
||||||
viewType: inquiryTab.dataView.mode,
|
viewType: inquiryTab.dataView.mode,
|
||||||
viewOptions: inquiryTab.dataView.getOptionsForSave(),
|
viewOptions: inquiryTab.dataView.getOptionsForSave(),
|
||||||
name: newName || inquiryTab.name
|
name: newName || inquiryTab.name,
|
||||||
|
updatedAt: new Date().toJSON()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get inquiries from local storage
|
// Get inquiries from local storage
|
||||||
|
|||||||
@@ -7,7 +7,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
updateTab(state, { tab, newValues }) {
|
updateTab(state, { tab, newValues }) {
|
||||||
const { name, id, query, viewType, viewOptions, isSaved } = newValues
|
const { name, id, query, viewType, viewOptions, isSaved, updatedAt } =
|
||||||
|
newValues
|
||||||
const oldId = tab.id
|
const oldId = tab.id
|
||||||
|
|
||||||
if (id && state.currentTabId === oldId) {
|
if (id && state.currentTabId === oldId) {
|
||||||
@@ -36,6 +37,9 @@ export default {
|
|||||||
// Saved inquiry is not predefined
|
// Saved inquiry is not predefined
|
||||||
delete tab.isPredefined
|
delete tab.isPredefined
|
||||||
}
|
}
|
||||||
|
if (updatedAt) {
|
||||||
|
tab.updatedAt = updatedAt
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteTab(state, tab) {
|
deleteTab(state, tab) {
|
||||||
|
|||||||
@@ -10,16 +10,16 @@
|
|||||||
</div>
|
</div>
|
||||||
<div id="nav-buttons">
|
<div id="nav-buttons">
|
||||||
<button
|
<button
|
||||||
v-show="currentInquiry && $route.path === '/workspace'"
|
v-show="currentInquiryTab && $route.path === '/workspace'"
|
||||||
id="save-btn"
|
id="save-btn"
|
||||||
class="primary"
|
class="primary"
|
||||||
:disabled="isSaved"
|
:disabled="isSaved"
|
||||||
@click="onSave"
|
@click="onSave(false)"
|
||||||
>
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
v-show="currentInquiry && $route.path === '/workspace'"
|
v-show="currentInquiryTab && $route.path === '/workspace'"
|
||||||
id="save-as-btn"
|
id="save-as-btn"
|
||||||
class="primary"
|
class="primary"
|
||||||
@click="onSaveAs"
|
@click="onSaveAs"
|
||||||
@@ -58,6 +58,31 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</modal>
|
</modal>
|
||||||
|
|
||||||
|
<!-- Inquiery saving conflict dialog -->
|
||||||
|
<modal
|
||||||
|
modalId="inquiry-conflict"
|
||||||
|
class="dialog"
|
||||||
|
contentStyle="width: 560px;"
|
||||||
|
>
|
||||||
|
<div class="dialog-header">
|
||||||
|
Inquiry saving conflict
|
||||||
|
<close-icon @click="cancelSave" />
|
||||||
|
</div>
|
||||||
|
<div class="dialog-body">
|
||||||
|
<div id="save-note">
|
||||||
|
<img src="~@/assets/images/info.svg" />
|
||||||
|
This inquiry has been modified in the mean time. This can happen if an
|
||||||
|
inquiry is saved in another window or browser tab. Do you want to
|
||||||
|
overwrite that changes or save the current state as a new inquiry?
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="dialog-buttons-container">
|
||||||
|
<button class="secondary" @click="cancelSave">Cancel</button>
|
||||||
|
<button class="primary" @click="onSave(true)">Overwrite</button>
|
||||||
|
<button class="primary" @click="onSaveAs">Save as new</button>
|
||||||
|
</div>
|
||||||
|
</modal>
|
||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -79,24 +104,26 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
name: '',
|
name: '',
|
||||||
errorMsg: null,
|
errorMsg: null
|
||||||
overwrite: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
currentInquiry() {
|
inquiries() {
|
||||||
|
return this.$store.state.inquiries
|
||||||
|
},
|
||||||
|
currentInquiryTab() {
|
||||||
return this.$store.state.currentTab
|
return this.$store.state.currentTab
|
||||||
},
|
},
|
||||||
isSaved() {
|
isSaved() {
|
||||||
return this.currentInquiry && this.currentInquiry.isSaved
|
return this.currentInquiryTab && this.currentInquiryTab.isSaved
|
||||||
},
|
},
|
||||||
isPredefined() {
|
isPredefined() {
|
||||||
return this.currentInquiry && this.currentInquiry.isPredefined
|
return this.currentInquiryTab && this.currentInquiryTab.isPredefined
|
||||||
},
|
},
|
||||||
runDisabled() {
|
runDisabled() {
|
||||||
return (
|
return (
|
||||||
this.currentInquiry &&
|
this.currentInquiryTab &&
|
||||||
(!this.$store.state.db || !this.currentInquiry.query)
|
(!this.$store.state.db || !this.currentInquiryTab.query)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -121,24 +148,35 @@ export default {
|
|||||||
},
|
},
|
||||||
cancelSave() {
|
cancelSave() {
|
||||||
this.$modal.hide('save')
|
this.$modal.hide('save')
|
||||||
|
this.$modal.hide('inquiry-conflict')
|
||||||
eventBus.$off('inquirySaved')
|
eventBus.$off('inquirySaved')
|
||||||
},
|
},
|
||||||
onSave() {
|
onSave(skipConcurrentEditingCheck = false) {
|
||||||
this.overwrite = true
|
this.errorMsg = null
|
||||||
if (storedInquiries.isTabNeedName(this.currentInquiry)) {
|
this.name = ''
|
||||||
|
if (storedInquiries.isTabNeedName(this.currentInquiryTab)) {
|
||||||
this.openSaveModal()
|
this.openSaveModal()
|
||||||
} else {
|
return
|
||||||
this.saveInquiry()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!skipConcurrentEditingCheck) {
|
||||||
|
const inquiryInStore = this.inquiries.find(
|
||||||
|
inquiry => inquiry.id === this.currentInquiryTab.id
|
||||||
|
)
|
||||||
|
|
||||||
|
if (inquiryInStore?.updatedAt !== this.currentInquiryTab?.updatedAt) {
|
||||||
|
this.$modal.show('inquiry-conflict')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.saveInquiry()
|
||||||
},
|
},
|
||||||
onSaveAs() {
|
onSaveAs() {
|
||||||
console.log('save as')
|
this.errorMsg = null
|
||||||
this.overwrite = false
|
this.name = ''
|
||||||
this.openSaveModal()
|
this.openSaveModal()
|
||||||
},
|
},
|
||||||
openSaveModal() {
|
openSaveModal() {
|
||||||
this.errorMsg = null
|
|
||||||
this.name = ''
|
|
||||||
this.$modal.show('save')
|
this.$modal.show('save')
|
||||||
},
|
},
|
||||||
validateSaveFormAndSaveInquiry() {
|
validateSaveFormAndSaveInquiry() {
|
||||||
@@ -149,26 +187,26 @@ export default {
|
|||||||
this.saveInquiry()
|
this.saveInquiry()
|
||||||
},
|
},
|
||||||
async saveInquiry() {
|
async saveInquiry() {
|
||||||
const dataSet = this.currentInquiry.result
|
const dataSet = this.currentInquiryTab.result
|
||||||
const tabView = this.currentInquiry.view
|
const tabView = this.currentInquiryTab.view
|
||||||
|
|
||||||
// Save inquiry
|
// Save inquiry
|
||||||
const value = await this.$store.dispatch('saveInquiry', {
|
const value = await this.$store.dispatch('saveInquiry', {
|
||||||
inquiryTab: this.currentInquiry,
|
inquiryTab: this.currentInquiryTab,
|
||||||
newName: this.name,
|
newName: this.name
|
||||||
overwrite: this.overwrite
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Update tab in store
|
// Update tab in store
|
||||||
this.$store.commit('updateTab', {
|
this.$store.commit('updateTab', {
|
||||||
tab: this.currentInquiry,
|
tab: this.currentInquiryTab,
|
||||||
newValues: {
|
newValues: {
|
||||||
name: value.name,
|
name: value.name,
|
||||||
id: value.id,
|
id: value.id,
|
||||||
query: value.query,
|
query: value.query,
|
||||||
viewType: value.viewType,
|
viewType: value.viewType,
|
||||||
viewOptions: value.viewOptions,
|
viewOptions: value.viewOptions,
|
||||||
isSaved: true
|
isSaved: true,
|
||||||
|
updatedAt: value.updatedAt
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -178,12 +216,13 @@ export default {
|
|||||||
// it will be without sql result and has default view - table.
|
// it will be without sql result and has default view - table.
|
||||||
// That's why we need to restore data and view
|
// That's why we need to restore data and view
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
this.currentInquiry.result = dataSet
|
this.currentInquiryTab.result = dataSet
|
||||||
this.currentInquiry.view = tabView
|
this.currentInquiryTab.view = tabView
|
||||||
})
|
})
|
||||||
|
|
||||||
// Hide dialog
|
// Hide dialogs
|
||||||
this.$modal.hide('save')
|
this.$modal.hide('save')
|
||||||
|
this.$modal.hide('inquiry-conflict')
|
||||||
|
|
||||||
// Signal about saving
|
// Signal about saving
|
||||||
eventBus.$emit('inquirySaved')
|
eventBus.$emit('inquirySaved')
|
||||||
@@ -195,7 +234,7 @@ export default {
|
|||||||
if ((e.key === 'r' || e.key === 'Enter') && (e.ctrlKey || e.metaKey)) {
|
if ((e.key === 'r' || e.key === 'Enter') && (e.ctrlKey || e.metaKey)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
if (!this.runDisabled) {
|
if (!this.runDisabled) {
|
||||||
this.currentInquiry.execute()
|
this.currentInquiryTab.execute()
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user