150 lines
3.3 KiB
JavaScript
150 lines
3.3 KiB
JavaScript
import { computed, onBeforeUnmount, ref, watch } from "vue";
|
|
|
|
export const ORDER_METADATA_AUTOSAVE_DELAY_MS = 600;
|
|
|
|
const defaultNormalizeValue = (value) => (value == null ? "" : String(value));
|
|
|
|
const resolveSourceValue = (source) => {
|
|
if (typeof source === "function") {
|
|
return source();
|
|
}
|
|
|
|
return source?.value;
|
|
};
|
|
|
|
export const useOrderMetadataAutosave = ({
|
|
source,
|
|
saveValue,
|
|
normalizeValue = defaultNormalizeValue,
|
|
delay = ORDER_METADATA_AUTOSAVE_DELAY_MS,
|
|
onSaved = null,
|
|
onError = null,
|
|
}) => {
|
|
const draft = ref(normalizeValue(resolveSourceValue(source)));
|
|
const lastSavedValue = ref(normalizeValue(resolveSourceValue(source)));
|
|
const isSaving = ref(false);
|
|
const isDirty = computed(() => normalizeValue(draft.value) !== normalizeValue(lastSavedValue.value));
|
|
|
|
let timerId = null;
|
|
let saveAgainAfterCurrentRequest = false;
|
|
let nextSequence = 0;
|
|
let lastAppliedSequence = 0;
|
|
|
|
const clearPendingSave = () => {
|
|
if (timerId !== null) {
|
|
window.clearTimeout(timerId);
|
|
timerId = null;
|
|
}
|
|
};
|
|
|
|
const syncFromSource = (value) => {
|
|
const normalizedValue = normalizeValue(value);
|
|
const shouldHydrateDraft = normalizeValue(draft.value) === normalizeValue(lastSavedValue.value);
|
|
|
|
lastSavedValue.value = normalizedValue;
|
|
|
|
if (shouldHydrateDraft) {
|
|
draft.value = normalizedValue;
|
|
}
|
|
};
|
|
|
|
watch(() => resolveSourceValue(source), syncFromSource, { immediate: true });
|
|
|
|
const persist = async () => {
|
|
clearPendingSave();
|
|
|
|
if (!isDirty.value) {
|
|
return false;
|
|
}
|
|
|
|
if (isSaving.value) {
|
|
saveAgainAfterCurrentRequest = true;
|
|
return false;
|
|
}
|
|
|
|
const requestedValue = normalizeValue(draft.value);
|
|
const sequence = ++nextSequence;
|
|
let saveCompleted = false;
|
|
isSaving.value = true;
|
|
|
|
try {
|
|
const result = await saveValue(requestedValue);
|
|
const savedValue = normalizeValue(result ?? requestedValue);
|
|
|
|
if (sequence >= lastAppliedSequence) {
|
|
lastAppliedSequence = sequence;
|
|
lastSavedValue.value = savedValue;
|
|
|
|
if (normalizeValue(draft.value) === requestedValue) {
|
|
draft.value = savedValue;
|
|
}
|
|
}
|
|
|
|
if (typeof onSaved === "function") {
|
|
await onSaved(savedValue, requestedValue);
|
|
}
|
|
|
|
saveCompleted = true;
|
|
|
|
return true;
|
|
} catch (error) {
|
|
if (typeof onError === "function") {
|
|
onError(error);
|
|
} else {
|
|
console.error(error);
|
|
}
|
|
|
|
return false;
|
|
} finally {
|
|
isSaving.value = false;
|
|
|
|
const shouldSaveAgain = saveAgainAfterCurrentRequest;
|
|
saveAgainAfterCurrentRequest = false;
|
|
|
|
if (saveCompleted && (shouldSaveAgain || isDirty.value) && isDirty.value) {
|
|
void persist();
|
|
}
|
|
}
|
|
};
|
|
|
|
const scheduleSave = () => {
|
|
clearPendingSave();
|
|
|
|
if (!isDirty.value) {
|
|
return;
|
|
}
|
|
|
|
timerId = window.setTimeout(() => {
|
|
void persist();
|
|
}, delay);
|
|
};
|
|
|
|
const flush = async () => {
|
|
return persist();
|
|
};
|
|
|
|
onBeforeUnmount(() => {
|
|
clearPendingSave();
|
|
|
|
if (isSaving.value) {
|
|
saveAgainAfterCurrentRequest = true;
|
|
return;
|
|
}
|
|
|
|
if (isDirty.value) {
|
|
void persist();
|
|
}
|
|
});
|
|
|
|
return {
|
|
draft,
|
|
lastSavedValue,
|
|
isDirty,
|
|
isSaving,
|
|
scheduleSave,
|
|
flush,
|
|
clearPendingSave,
|
|
syncFromSource,
|
|
};
|
|
};
|