diff --git a/src/App.vue b/src/App.vue index 34dcd14..f03b95e 100644 --- a/src/App.vue +++ b/src/App.vue @@ -26,6 +26,11 @@ export default { }, created() { this.$store.commit('setInquiries', storedInquiries.getStoredInquiries()) + addEventListener('storage', event => { + if (event.key === storedInquiries.myInquiriesKey) { + this.$store.commit('setInquiries', storedInquiries.getStoredInquiries()) + } + }) } } diff --git a/src/lib/storedInquiries/index.js b/src/lib/storedInquiries/index.js index 04f568e..2257d5b 100644 --- a/src/lib/storedInquiries/index.js +++ b/src/lib/storedInquiries/index.js @@ -4,11 +4,13 @@ import events from '@/lib/utils/events' import migration from './_migrations' const migrate = migration._migrate +const myInquiriesKey = 'myInquiries' export default { version: 2, + myInquiriesKey, getStoredInquiries() { - let myInquiries = JSON.parse(localStorage.getItem('myInquiries')) + let myInquiries = JSON.parse(localStorage.getItem(myInquiriesKey)) if (!myInquiries) { const oldInquiries = localStorage.getItem('myQueries') if (oldInquiries) { @@ -26,7 +28,8 @@ export default { const newInquiry = JSON.parse(JSON.stringify(baseInquiry)) newInquiry.name = newInquiry.name + ' Copy' newInquiry.id = nanoid() - newInquiry.createdAt = new Date() + newInquiry.createdAt = new Date().toJSON() + newInquiry.updatedAt = new Date().toJSON() delete newInquiry.isPredefined return newInquiry @@ -38,7 +41,7 @@ export default { updateStorage(inquiries) { localStorage.setItem( - 'myInquiries', + myInquiriesKey, JSON.stringify({ version: this.version, inquiries }) ) }, diff --git a/src/lib/tab.js b/src/lib/tab.js index c743765..c00b197 100644 --- a/src/lib/tab.js +++ b/src/lib/tab.js @@ -28,6 +28,7 @@ export default class Tab { this.isSaved = !!inquiry.id this.state = state + this.updatedAt = inquiry.updatedAt } async execute() { diff --git a/src/store/actions.js b/src/store/actions.js index 35292d3..17e2768 100644 --- a/src/store/actions.js +++ b/src/store/actions.js @@ -15,13 +15,14 @@ export default { return inquiry.id }, - async saveInquiry({ state }, { inquiryTab, newName, overwrite }) { + async saveInquiry({ state }, { inquiryTab, newName }) { const value = { - id: inquiryTab.isPredefined || !overwrite ? nanoid() : inquiryTab.id, + id: inquiryTab.isPredefined || newName ? nanoid() : inquiryTab.id, query: inquiryTab.query, viewType: inquiryTab.dataView.mode, viewOptions: inquiryTab.dataView.getOptionsForSave(), - name: newName || inquiryTab.name + name: newName || inquiryTab.name, + updatedAt: new Date().toJSON() } // Get inquiries from local storage diff --git a/src/store/mutations.js b/src/store/mutations.js index 693b5a5..ef854f9 100644 --- a/src/store/mutations.js +++ b/src/store/mutations.js @@ -7,7 +7,8 @@ export default { }, 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 if (id && state.currentTabId === oldId) { @@ -36,6 +37,9 @@ export default { // Saved inquiry is not predefined delete tab.isPredefined } + if (updatedAt) { + tab.updatedAt = updatedAt + } }, deleteTab(state, tab) { diff --git a/src/views/MainView/MainMenu.vue b/src/views/MainView/MainMenu.vue index 387265c..38dc4bd 100644 --- a/src/views/MainView/MainMenu.vue +++ b/src/views/MainView/MainMenu.vue @@ -10,16 +10,16 @@ + + + +
+ Inquiry saving conflict + +
+
+
+ + 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? +
+
+
+ + + +
+
@@ -79,24 +104,26 @@ export default { data() { return { name: '', - errorMsg: null, - overwrite: false + errorMsg: null } }, computed: { - currentInquiry() { + inquiries() { + return this.$store.state.inquiries + }, + currentInquiryTab() { return this.$store.state.currentTab }, isSaved() { - return this.currentInquiry && this.currentInquiry.isSaved + return this.currentInquiryTab && this.currentInquiryTab.isSaved }, isPredefined() { - return this.currentInquiry && this.currentInquiry.isPredefined + return this.currentInquiryTab && this.currentInquiryTab.isPredefined }, runDisabled() { return ( - this.currentInquiry && - (!this.$store.state.db || !this.currentInquiry.query) + this.currentInquiryTab && + (!this.$store.state.db || !this.currentInquiryTab.query) ) } }, @@ -121,24 +148,35 @@ export default { }, cancelSave() { this.$modal.hide('save') + this.$modal.hide('inquiry-conflict') eventBus.$off('inquirySaved') }, - onSave() { - this.overwrite = true - if (storedInquiries.isTabNeedName(this.currentInquiry)) { + onSave(skipConcurrentEditingCheck = false) { + this.errorMsg = null + this.name = '' + if (storedInquiries.isTabNeedName(this.currentInquiryTab)) { this.openSaveModal() - } else { - this.saveInquiry() + return } + + 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() { - console.log('save as') - this.overwrite = false + this.errorMsg = null + this.name = '' this.openSaveModal() }, openSaveModal() { - this.errorMsg = null - this.name = '' this.$modal.show('save') }, validateSaveFormAndSaveInquiry() { @@ -149,26 +187,26 @@ export default { this.saveInquiry() }, async saveInquiry() { - const dataSet = this.currentInquiry.result - const tabView = this.currentInquiry.view + const dataSet = this.currentInquiryTab.result + const tabView = this.currentInquiryTab.view // Save inquiry const value = await this.$store.dispatch('saveInquiry', { - inquiryTab: this.currentInquiry, - newName: this.name, - overwrite: this.overwrite + inquiryTab: this.currentInquiryTab, + newName: this.name }) // Update tab in store this.$store.commit('updateTab', { - tab: this.currentInquiry, + tab: this.currentInquiryTab, newValues: { name: value.name, id: value.id, query: value.query, viewType: value.viewType, 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. // That's why we need to restore data and view this.$nextTick(() => { - this.currentInquiry.result = dataSet - this.currentInquiry.view = tabView + this.currentInquiryTab.result = dataSet + this.currentInquiryTab.view = tabView }) - // Hide dialog + // Hide dialogs this.$modal.hide('save') + this.$modal.hide('inquiry-conflict') // Signal about saving eventBus.$emit('inquirySaved') @@ -195,7 +234,7 @@ export default { if ((e.key === 'r' || e.key === 'Enter') && (e.ctrlKey || e.metaKey)) { e.preventDefault() if (!this.runDisabled) { - this.currentInquiry.execute() + this.currentInquiryTab.execute() } return }