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() {
this.$store.commit('setInquiries', storedInquiries.getStoredInquiries())
addEventListener('storage', event => {
if (event.key === storedInquiries.myInquiriesKey) {
this.$store.commit('setInquiries', storedInquiries.getStoredInquiries())
}
})
}
}
</script>

View File

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

View File

@@ -28,6 +28,7 @@ export default class Tab {
this.isSaved = !!inquiry.id
this.state = state
this.updatedAt = inquiry.updatedAt
}
async execute() {

View File

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

View File

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

View File

@@ -10,16 +10,16 @@
</div>
<div id="nav-buttons">
<button
v-show="currentInquiry && $route.path === '/workspace'"
v-show="currentInquiryTab && $route.path === '/workspace'"
id="save-btn"
class="primary"
:disabled="isSaved"
@click="onSave"
@click="onSave(false)"
>
Save
</button>
<button
v-show="currentInquiry && $route.path === '/workspace'"
v-show="currentInquiryTab && $route.path === '/workspace'"
id="save-as-btn"
class="primary"
@click="onSaveAs"
@@ -58,6 +58,31 @@
</button>
</div>
</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>
</template>
@@ -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
}