every control writes itself
The Save button is gone, and so is the SaveTarget context that announced it. Every screen here is a preview of the thing being edited, so a change is already visible before it is written — a button then asks somebody to confirm what they can see. useAutosave debounces a burst into one request. It takes the unsaved state as a string rather than a flag, so a failed save is not retried on the next render, and it flushes on unmount so a section change loses nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -8,7 +8,7 @@ import { useFlash } from '../useFlash';
|
||||
import { RichText } from '../RichText';
|
||||
import { MediaField } from '../MediaField';
|
||||
import { OverlayEditor, type Overlay } from '../OverlayEditor';
|
||||
import { useSaveTarget } from '../SaveTarget';
|
||||
import { useAutosave } from '../useAutosave';
|
||||
|
||||
/**
|
||||
* About Us — four bands, in the order the page shows them.
|
||||
@@ -139,7 +139,7 @@ export const AboutPanel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useSaveTarget(dirty, busy !== null, save);
|
||||
useAutosave(dirty ? 'dirty' : null, busy !== null, save);
|
||||
|
||||
if (!about) {
|
||||
return (
|
||||
|
||||
@@ -7,7 +7,7 @@ import { readableError } from '../errors';
|
||||
import { useFlash } from '../useFlash';
|
||||
import { RichText } from '../RichText';
|
||||
import { MediaPicker } from '../MediaPicker';
|
||||
import { useSaveTarget } from '../SaveTarget';
|
||||
import { useAutosave } from '../useAutosave';
|
||||
|
||||
/**
|
||||
* The Home page: a hero, an intro, and an ordered gallery.
|
||||
@@ -129,7 +129,7 @@ export const HomePanel = () => {
|
||||
|
||||
const save = () => send('save', () => clientRef.current.patch<{ data: Home }>('/fofos-admin/home', edited));
|
||||
|
||||
useSaveTarget(dirty, busy !== null, save);
|
||||
useAutosave(dirty ? 'dirty' : null, busy !== null, save);
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { readableError } from '../errors';
|
||||
import { useFlash } from '../useFlash';
|
||||
import { useSaveTarget } from '../SaveTarget';
|
||||
import { useAutosave } from '../useAutosave';
|
||||
import { usePreviewable, useViewMode } from '../ViewMode';
|
||||
import { applySpacing, paintMeasure, type SpacingDraft } from '../canvas';
|
||||
import { SitePreview, useDevPort } from '../SitePreview';
|
||||
@@ -203,7 +203,8 @@ export const LayoutPanel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useSaveTarget(dirty, busy, save);
|
||||
// The serialised draft, or null when it matches what the server has. See useAutosave.
|
||||
useAutosave(dirty ? only(draft) : null, busy, save);
|
||||
// This section has a preview, so the bar offers Edit/View and the three widths.
|
||||
usePreviewable();
|
||||
const { device } = useViewMode();
|
||||
|
||||
@@ -5,7 +5,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { useClient } from 'hooks/useClient';
|
||||
import { readableError } from '../errors';
|
||||
import { useFlash } from '../useFlash';
|
||||
import { useSaveTarget } from '../SaveTarget';
|
||||
import { useAutosave } from '../useAutosave';
|
||||
import { usePages, type Page } from '../usePages';
|
||||
|
||||
/**
|
||||
@@ -143,7 +143,8 @@ export const PagesPanel = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useSaveTarget(dirty, busy, save);
|
||||
// The rows that differ, serialised — so one set of edits is attempted once. See useAutosave.
|
||||
useAutosave(dirty ? JSON.stringify(changed()) : null, busy, save);
|
||||
|
||||
const act = async (run: () => Promise<unknown>) => {
|
||||
setBusy(true);
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Input } from '@/components/ui/input';
|
||||
import { readableError } from '../errors';
|
||||
import { useFlash } from '../useFlash';
|
||||
import { MediaPicker } from '../MediaPicker';
|
||||
import { useSaveTarget } from '../SaveTarget';
|
||||
import { useAutosave } from '../useAutosave';
|
||||
import type { RecordsConfig } from './records';
|
||||
|
||||
/**
|
||||
@@ -222,7 +222,7 @@ export const RecordsPanel = ({ config }: { config: RecordsConfig }) => {
|
||||
config.fields.some((f) => asText(draft[f.key]) !== asText(open[f.key]))),
|
||||
);
|
||||
|
||||
useSaveTarget(dirty, busy !== null, save);
|
||||
useAutosave(dirty ? 'dirty' : null, busy !== null, save);
|
||||
|
||||
return (
|
||||
<div className="flex size-full flex-col gap-4 overflow-auto p-4">
|
||||
|
||||
Reference in New Issue
Block a user