1
0
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:
lana-k
2025-09-27 21:59:32 +02:00
parent cdd925b8af
commit 07d7a9d54b
6 changed files with 90 additions and 37 deletions

View File

@@ -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>

View File

@@ -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 })
) )
}, },

View File

@@ -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() {

View File

@@ -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

View File

@@ -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) {

View File

@@ -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
} }