fix admin save state architecture
This commit is contained in:
@@ -39,7 +39,9 @@ export function FormSaveButton({
|
||||
const [isDirty, setIsDirty] = useState(false);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const baselineRef = useRef<Map<string, string>>(new Map());
|
||||
const dirtyFormsRef = useRef<Set<string>>(new Set());
|
||||
const activeFormIdRef = useRef<string | null>(formId ?? null);
|
||||
const frameRef = useRef<number | null>(null);
|
||||
const pendingSubmissionRef = useRef<{
|
||||
formId: string;
|
||||
originUrl: string;
|
||||
@@ -54,6 +56,7 @@ export function FormSaveButton({
|
||||
setActiveFormId(null);
|
||||
setIsDirty(false);
|
||||
setIsSubmitting(false);
|
||||
dirtyFormsRef.current.clear();
|
||||
}
|
||||
}, [formId, formIds, formSelector, formSelectors]);
|
||||
|
||||
@@ -69,7 +72,12 @@ export function FormSaveButton({
|
||||
const forms = Array.from(new Map([...formsById, ...formsBySelector].map((form) => [form.id, form])).values());
|
||||
|
||||
if (forms.length === 0) {
|
||||
if (frameRef.current !== null) {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
frameRef.current = null;
|
||||
}
|
||||
baselineRef.current.clear();
|
||||
dirtyFormsRef.current.clear();
|
||||
activeFormIdRef.current = formId ?? null;
|
||||
setActiveFormId(formId ?? null);
|
||||
setIsDirty(false);
|
||||
@@ -80,33 +88,74 @@ export function FormSaveButton({
|
||||
const availableIds = forms.map((form) => form.id).filter(Boolean);
|
||||
const fallbackFormId = availableIds[0] ?? null;
|
||||
|
||||
const readDirtyState = (nextActiveFormId: string | null) => {
|
||||
if (!nextActiveFormId) {
|
||||
setIsDirty(false);
|
||||
const selectDirtyFormId = () => {
|
||||
const dirtyIds = availableIds.filter((id) => dirtyFormsRef.current.has(id));
|
||||
|
||||
if (dirtyIds.length === 0) {
|
||||
return activeFormIdRef.current && availableIds.includes(activeFormIdRef.current)
|
||||
? activeFormIdRef.current
|
||||
: fallbackFormId;
|
||||
}
|
||||
|
||||
if (activeFormIdRef.current && dirtyFormsRef.current.has(activeFormIdRef.current)) {
|
||||
return activeFormIdRef.current;
|
||||
}
|
||||
|
||||
return dirtyIds[0] ?? fallbackFormId;
|
||||
};
|
||||
|
||||
const syncDirtyState = () => {
|
||||
const nextActiveFormId = selectDirtyFormId();
|
||||
|
||||
activeFormIdRef.current = nextActiveFormId;
|
||||
setActiveFormId(nextActiveFormId);
|
||||
setIsDirty(dirtyFormsRef.current.size > 0);
|
||||
};
|
||||
|
||||
const evaluateForm = (form: HTMLFormElement) => {
|
||||
const baseline = baselineRef.current.get(form.id);
|
||||
const current = serializeForm(form);
|
||||
|
||||
if (baseline === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextForm = forms.find((form) => form.id === nextActiveFormId);
|
||||
|
||||
if (!nextForm) {
|
||||
setIsDirty(false);
|
||||
if (current !== baseline) {
|
||||
dirtyFormsRef.current.add(form.id);
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDirty(serializeForm(nextForm) !== baselineRef.current.get(nextActiveFormId));
|
||||
dirtyFormsRef.current.delete(form.id);
|
||||
};
|
||||
|
||||
const scheduleSync = (nextActiveFormId?: string) => {
|
||||
if (nextActiveFormId) {
|
||||
activeFormIdRef.current = nextActiveFormId;
|
||||
}
|
||||
|
||||
if (frameRef.current !== null) {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
}
|
||||
|
||||
frameRef.current = requestAnimationFrame(() => {
|
||||
frameRef.current = null;
|
||||
for (const form of forms) {
|
||||
evaluateForm(form);
|
||||
}
|
||||
syncDirtyState();
|
||||
});
|
||||
};
|
||||
|
||||
const syncBaseline = (form: HTMLFormElement) => {
|
||||
baselineRef.current.set(form.id, serializeForm(form));
|
||||
readDirtyState(form.id === activeFormIdRef.current ? form.id : activeFormIdRef.current ?? fallbackFormId);
|
||||
dirtyFormsRef.current.delete(form.id);
|
||||
syncDirtyState();
|
||||
setIsSubmitting(false);
|
||||
};
|
||||
|
||||
const handleFormActivity = (form: HTMLFormElement) => {
|
||||
activeFormIdRef.current = form.id;
|
||||
setActiveFormId(form.id);
|
||||
setIsSubmitting(false);
|
||||
setIsDirty(serializeForm(form) !== baselineRef.current.get(form.id));
|
||||
scheduleSync(form.id);
|
||||
};
|
||||
|
||||
const handleSubmit = (form: HTMLFormElement, event: SubmitEvent) => {
|
||||
@@ -162,9 +211,17 @@ export function FormSaveButton({
|
||||
: fallbackFormId;
|
||||
activeFormIdRef.current = nextActive;
|
||||
setActiveFormId(nextActive);
|
||||
readDirtyState(nextActive);
|
||||
for (const form of forms) {
|
||||
evaluateForm(form);
|
||||
}
|
||||
syncDirtyState();
|
||||
|
||||
return () => {
|
||||
if (frameRef.current !== null) {
|
||||
cancelAnimationFrame(frameRef.current);
|
||||
frameRef.current = null;
|
||||
}
|
||||
|
||||
for (const form of forms) {
|
||||
const handlers = (form as HTMLFormElement & {
|
||||
__saveButtonHandlers?: {
|
||||
@@ -214,6 +271,7 @@ export function FormSaveButton({
|
||||
if (hasError) {
|
||||
pendingSubmissionRef.current = null;
|
||||
setIsSubmitting(false);
|
||||
dirtyFormsRef.current.delete(pendingSubmission.formId);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user