web: a folder is chosen by picking one — the backup row names it and opens the picker, and restore always asks which backup
RESTORE was dead on a machine that had never written a backup: the button was `disabled` while no folder was remembered, and nothing in the handler could ever pick one, so the reader who had carried a copy over on a stick had no way to point at it. The button now stands, and the folder it restores from is whatever the reader picks in the dialog that opens — which is the whole of the choice there is, because a backup is a folder of rows and tiles. The folder on the other disk, the one made before the edit: the folder is the version. BACK UP asked for a folder only the first time one was ever chosen. After that it wrote into it in silence, and after a restart — when the browser takes the permission back, since a permission outlives the tab only while the tab does — it wrote nothing and said so, which is a backup that quietly stops happening. It now picks whenever it cannot write, and the picker is the only thing that can hand a remembered folder its permission back; a permission asked for with no picker behind it is one the browser may refuse. The run that follows a scan is untouched: it is guarded by the permission it would be asking for. And the folder is a control, not a caption. The line that names it — with the frames in it and when they were written — is what tells one backup from another, and pressing it is how a folder is picked, a name given, a permission handed back. It keeps the hint's own look through five lines of CSS, so the row still reads as a caption and not as a third button. What the line says is now read out of the folder rather than out of this browser's memory of it. `pickBackupFolder` reads the catalogue file inside the folder just picked and takes its count and its date as the row's own; a folder with no catalogue of its own has none, and the row says so instead of showing the numbers of the folder next door. That is also what the confirm names before a restore — the frames and the time in the folder just picked, which is the thing about to be restored. `restoreNow` already read that file; this reads its header first. Checked on the running bundle with the picker stubbed, since the dialog is one a probe cannot press: with nothing remembered, RESTORE now opens the picker where it did nothing at all, the row names the folder the picker returned, and a folder without a catalogue of its own is refused with a line saying so rather than restored as an empty catalogue. BACK UP opens it too, from the same fresh state. The line computes to a button with no border, no background, the page's own font and the hint's own 12px dim grey — a caption that happens to be pressable. The wall, the strip, the grid, the deep link and the phone's recipes all pass their checks.
This commit is contained in:
@@ -239,6 +239,24 @@ export function Library() {
|
||||
};
|
||||
}, [show]);
|
||||
|
||||
// Open the picker and take what it chose. False when the reader closed it, or
|
||||
// when there is no picker to open — and the one thing this is asked for is the
|
||||
// thing a folder cannot be reached without: it is where a folder is chosen,
|
||||
// where a remembered one is handed its permission back, and where a backup is
|
||||
// named, since the name of the folder is the name of the backup.
|
||||
const pickBackup = useCallback(async (): Promise<boolean> => {
|
||||
try {
|
||||
await pickBackupFolder();
|
||||
await show(await backupStatus());
|
||||
return true;
|
||||
} catch (err) {
|
||||
// Closing the picker is a "not now", not a failure worth a line of text.
|
||||
if (err instanceof DOMException && err.name === 'AbortError') return false;
|
||||
setNote(t('lib.failed'));
|
||||
return false;
|
||||
}
|
||||
}, [show, t]);
|
||||
|
||||
// Write the catalogue into the folder. With none picked yet this is also the
|
||||
// way to pick one: one button, because a backup folder is not something to
|
||||
// choose and then not use. `quiet` is the run that follows a scan — it says
|
||||
@@ -246,16 +264,13 @@ export function Library() {
|
||||
const backup = useCallback(
|
||||
async (quiet = false) => {
|
||||
if (backBusy) return;
|
||||
if (!backRef.current?.folder) {
|
||||
try {
|
||||
await pickBackupFolder();
|
||||
} catch (err) {
|
||||
// Closing the picker is a "not now", not a failure worth a line of text.
|
||||
if (err instanceof DOMException && err.name === 'AbortError') return;
|
||||
setNote(t('lib.failed'));
|
||||
return;
|
||||
}
|
||||
}
|
||||
// A folder the browser has not let this page write to is a folder to pick
|
||||
// again, and the picker is the only thing that can hand the permission
|
||||
// back — a permission outlives the tab only while the tab does, so this is
|
||||
// the run that follows every restart. Without it the button wrote nothing
|
||||
// and said so, which is a backup that quietly stopped happening. A quiet
|
||||
// run never comes here: it is guarded by the permission it would ask for.
|
||||
if ((!backRef.current?.folder || !backRef.current.granted) && !(await pickBackup())) return;
|
||||
setBackBusy(true);
|
||||
if (!quiet) setNote(t('lib.backupRun', { done: 0, total: backRef.current?.photos ?? 0 }));
|
||||
try {
|
||||
@@ -268,21 +283,41 @@ export function Library() {
|
||||
setBackBusy(false);
|
||||
}
|
||||
},
|
||||
[backBusy, show, t]
|
||||
[backBusy, pickBackup, show, t]
|
||||
);
|
||||
|
||||
// Where the copy goes, said out loud: the line that names the folder is the
|
||||
// control that changes it. A backup is a folder of rows and tiles, so the
|
||||
// folder's name — the one the reader gave it in the picker, or the disk it was
|
||||
// carried in on — is the whole of what tells one backup from another, and
|
||||
// nothing else on this screen can offer a name.
|
||||
const choose = useCallback(async () => {
|
||||
if (backBusy) return;
|
||||
await pickBackup();
|
||||
}, [backBusy, pickBackup]);
|
||||
|
||||
// The other direction, and the one that replaces rows: the frames come back
|
||||
// without a file handle — a handle is not a thing that can be written down —
|
||||
// and picking the folder again afterwards is what hands every one of them back,
|
||||
// without a frame being read.
|
||||
//
|
||||
// The folder is asked for every time, whatever this screen remembers, because
|
||||
// the folder is the whole of the choice: which backup to come back from is
|
||||
// which folder the reader points at — the copy on the other disk, the one made
|
||||
// before the edit. The count and the date on the confirm are read out of the
|
||||
// folder just picked, so the thing described is the thing restored, and a
|
||||
// folder with no catalogue of its own is a folder to point somewhere else at
|
||||
// rather than a run that replaces the reading with nothing.
|
||||
const restore = useCallback(async () => {
|
||||
if (backBusy) return;
|
||||
if (!(await pickBackup())) return;
|
||||
const status = backRef.current;
|
||||
if (!status?.folder) {
|
||||
setNote(t('lib.restoreNone'));
|
||||
if (!status?.at) {
|
||||
setNote(t('lib.restoreNothing', { folder: status?.folder ?? '' }));
|
||||
return;
|
||||
}
|
||||
if (!window.confirm(t('lib.restoreConfirm', { folder: status.folder }))) return;
|
||||
if (backBusy) return;
|
||||
const when = new Date(status.at).toLocaleString();
|
||||
if (!window.confirm(t('lib.restoreConfirm', { folder: status.folder ?? '', n: status.photos, time: when }))) return;
|
||||
setBackBusy(true);
|
||||
setNote(t('lib.restoreRun', { done: 0, total: status.photos }));
|
||||
try {
|
||||
@@ -295,7 +330,7 @@ export function Library() {
|
||||
} finally {
|
||||
setBackBusy(false);
|
||||
}
|
||||
}, [backBusy, reload, t]);
|
||||
}, [backBusy, pickBackup, reload, t]);
|
||||
|
||||
// A reading that is over is the one moment the rows are finished, so it is the
|
||||
// moment the catalogue is copied out: a timer would fire in the middle of a
|
||||
@@ -901,12 +936,22 @@ export function Library() {
|
||||
type="button"
|
||||
className="btn"
|
||||
data-key="lib-restore"
|
||||
disabled={backBusy || !back?.folder}
|
||||
disabled={backBusy}
|
||||
onClick={() => void restore()}
|
||||
>
|
||||
{t('lib.restore')}
|
||||
</button>
|
||||
<span className="hint" data-key="lib-backup-state">
|
||||
{/* The folder is a control, not a caption: it is the whole of what
|
||||
tells one backup from another, and pressing it is how a folder
|
||||
is picked, a name given, a permission handed back. */}
|
||||
<button
|
||||
type="button"
|
||||
className="hint"
|
||||
data-key="lib-backup-state"
|
||||
disabled={backBusy}
|
||||
title={t('lib.backupPick')}
|
||||
onClick={() => void choose()}
|
||||
>
|
||||
{back?.folder
|
||||
? back.granted
|
||||
? t('lib.backupAt', {
|
||||
@@ -916,7 +961,7 @@ export function Library() {
|
||||
})
|
||||
: t('lib.backupBlocked', { folder: back.folder })
|
||||
: t('lib.backupNone')}
|
||||
</span>
|
||||
</button>
|
||||
<span className="hint lib-bar-hint" title={t('lib.hint')}>
|
||||
{t('lib.hint')}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user