131 lines
3.4 KiB
JavaScript
131 lines
3.4 KiB
JavaScript
import { opnFetch } from "./../useOpnApi.js"
|
|
import { pendingSubmission as pendingSubmissionFunction } from "./pendingSubmission.js"
|
|
import { watch, onBeforeUnmount, ref } from 'vue'
|
|
|
|
// Create a Map to store submission hashes for different forms
|
|
const submissionHashes = ref(new Map())
|
|
|
|
export const usePartialSubmission = (form, formData = {}) => {
|
|
const pendingSubmission = pendingSubmissionFunction(form)
|
|
|
|
let syncTimeout = null
|
|
let dataWatcher = null
|
|
|
|
const getSubmissionHash = () => {
|
|
return pendingSubmission.getSubmissionHash() ?? submissionHashes.value.get(pendingSubmission.formPendingSubmissionKey.value)
|
|
}
|
|
|
|
const setSubmissionHash = (hash) => {
|
|
submissionHashes.value.set(pendingSubmission.formPendingSubmissionKey.value, hash)
|
|
pendingSubmission.setSubmissionHash(hash)
|
|
}
|
|
|
|
const debouncedSync = () => {
|
|
if (syncTimeout) clearTimeout(syncTimeout)
|
|
syncTimeout = setTimeout(() => {
|
|
syncToServer()
|
|
}, 1000) // 1 second debounce
|
|
}
|
|
|
|
const syncToServer = async () => {
|
|
// Check if partial submissions are enabled and if we have data
|
|
if (!form?.enable_partial_submissions) return
|
|
|
|
// Get current form data - handle both function and direct object patterns
|
|
const currentData = typeof formData.value?.data === 'function'
|
|
? formData.value.data()
|
|
: formData.value
|
|
|
|
// Skip if no data or empty data
|
|
if (!currentData || Object.keys(currentData).length === 0) return
|
|
|
|
try {
|
|
const response = await opnFetch(`/forms/${form.slug}/answer`, {
|
|
method: "POST",
|
|
body: {
|
|
...currentData,
|
|
'is_partial': true,
|
|
'submission_hash': getSubmissionHash()
|
|
}
|
|
})
|
|
if (response.submission_hash) {
|
|
setSubmissionHash(response.submission_hash)
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to sync partial submission', error)
|
|
}
|
|
}
|
|
|
|
// Add these handlers as named functions so we can remove them later
|
|
const handleVisibilityChange = () => {
|
|
if (document.visibilityState === 'hidden') {
|
|
debouncedSync()
|
|
}
|
|
}
|
|
|
|
const handleBlur = () => {
|
|
debouncedSync()
|
|
}
|
|
|
|
const handleBeforeUnload = () => {
|
|
syncToServer()
|
|
}
|
|
|
|
const startSync = () => {
|
|
if (dataWatcher) return
|
|
|
|
// Initial sync
|
|
debouncedSync()
|
|
|
|
// Watch formData directly with Vue's reactivity
|
|
dataWatcher = watch(
|
|
formData,
|
|
() => {
|
|
debouncedSync()
|
|
},
|
|
{ deep: true }
|
|
)
|
|
|
|
// Add event listeners for critical moments
|
|
document.addEventListener('visibilitychange', handleVisibilityChange)
|
|
window.addEventListener('blur', handleBlur)
|
|
window.addEventListener('beforeunload', handleBeforeUnload)
|
|
}
|
|
|
|
const stopSync = () => {
|
|
submissionHashes.value = new Map()
|
|
|
|
if (dataWatcher) {
|
|
dataWatcher()
|
|
dataWatcher = null
|
|
}
|
|
|
|
if (syncTimeout) {
|
|
clearTimeout(syncTimeout)
|
|
syncTimeout = null
|
|
}
|
|
|
|
// Remove event listeners
|
|
document.removeEventListener('visibilitychange', handleVisibilityChange)
|
|
window.removeEventListener('blur', handleBlur)
|
|
window.removeEventListener('beforeunload', handleBeforeUnload)
|
|
}
|
|
|
|
// Ensure cleanup when component is unmounted
|
|
onBeforeUnmount(() => {
|
|
stopSync()
|
|
|
|
// Final sync attempt before unmounting
|
|
if(getSubmissionHash()) {
|
|
syncToServer()
|
|
}
|
|
})
|
|
|
|
return {
|
|
startSync,
|
|
stopSync,
|
|
syncToServer,
|
|
getSubmissionHash,
|
|
setSubmissionHash
|
|
}
|
|
} |