feat(library): update, rescan and stop on the folder menu, queued per roll
A folder row now asks for the rest of a roll, the whole of it from the top, or takes its request back. Requests queue behind the reading in flight and run one at a time; a row waiting its turn draws a ring that does not turn.
This commit is contained in:
@@ -18,17 +18,20 @@ import {
|
||||
readPhotos,
|
||||
removeFolder,
|
||||
renameFolder,
|
||||
scanAsked,
|
||||
scanBusy,
|
||||
scanQueue,
|
||||
scanSession,
|
||||
setStar,
|
||||
startScan,
|
||||
stopScan,
|
||||
stopScanAt,
|
||||
unfinished,
|
||||
watchRows,
|
||||
watchScan,
|
||||
type LibraryDir,
|
||||
type LibraryFolder,
|
||||
type LibraryPhoto,
|
||||
type ScanMode,
|
||||
} from './engine/library';
|
||||
import {
|
||||
backupNow,
|
||||
@@ -210,6 +213,9 @@ export function Library() {
|
||||
// one more folder; `root` means it landed on a row at the top level — the head
|
||||
// of a tree — which also carries folding the whole tree.
|
||||
const [menu, setMenu] = useState<{ folder: LibraryFolder | null; root?: boolean; rel?: string; node?: string; x: number; y: number } | null>(null);
|
||||
// A reading of this roll that is still waiting its turn. The menu is where
|
||||
// that wait is called off, so it has to know one exists.
|
||||
const queued = menu?.folder ? scanQueue().some((j) => j.folder === menu.folder!.name) : false;
|
||||
// Where the drag of the column divider started, and how wide the column was
|
||||
// then — the pointer only ever reports a position, not a distance moved.
|
||||
const grip = useRef<{ x: number; w: number } | null>(null);
|
||||
@@ -533,11 +539,17 @@ export function Library() {
|
||||
};
|
||||
}, [menu]);
|
||||
|
||||
// A reading asked for here is a request, not a reading: one walk runs at a time,
|
||||
// and a folder asked for while another is being read waits its turn — which is
|
||||
// what makes a right click on four folders mean four readings rather than three
|
||||
// refusals. The note lands when this request's turn comes, not when it is asked
|
||||
// for, and a request called off before it ran says nothing at all.
|
||||
const scan = useCallback(
|
||||
async (folder: LibraryFolder, quiet = false, from = '') => {
|
||||
async (folder: LibraryFolder, quiet = false, from = '', mode: ScanMode = 'update') => {
|
||||
setNote(null);
|
||||
try {
|
||||
const result = await startScan(folder, from);
|
||||
const result = await scanAsked(folder, from, mode);
|
||||
if (!result) return;
|
||||
if (!quiet) setNote(t('lib.scanned', { folder: from ? `${folder.name}/${from.slice(0, -1)}` : folder.name, added: result.added, total: result.total }));
|
||||
// A reading kept to one folder names the folders under that one, which
|
||||
// is no reason to fold the column down to them: the roll is still the
|
||||
@@ -964,7 +976,7 @@ export function Library() {
|
||||
<button type="button" className="btn primary" data-key="lib-add" disabled={!!live} onClick={() => void add()}>
|
||||
{t('lib.add')}
|
||||
</button>
|
||||
{live ? (
|
||||
{live || scanQueue().length ? (
|
||||
<button type="button" className="btn" data-key="lib-stop" onClick={stopScan}>
|
||||
{t('lib.stop')}
|
||||
</button>
|
||||
@@ -1113,6 +1125,10 @@ export function Library() {
|
||||
const dir = relOf(n.key);
|
||||
const at = live && live.folder === folder?.name && live.progress.at ? `${live.progress.at}/` : null;
|
||||
const scanning = dir !== null && dir !== '' && at !== null && (dir === at || at.startsWith(dir));
|
||||
// A row whose reading has not started yet: the ring is
|
||||
// there, held still, so a reader sees the roll is pending
|
||||
// rather than idle.
|
||||
const waiting = dir !== null && dir !== '' && !scanning && scanQueue().some((j) => j.folder === folder?.name && j.rel === dir);
|
||||
const under = parents.has(n.key);
|
||||
return (
|
||||
<button
|
||||
@@ -1176,7 +1192,11 @@ export function Library() {
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span className="adm-album-name">{n.label}</span>
|
||||
{scanning ? <span className="lib-spin" aria-hidden="true" /> : null}
|
||||
{scanning ? (
|
||||
<span className="lib-spin" aria-hidden="true" />
|
||||
) : waiting ? (
|
||||
<span className="lib-spin wait" aria-hidden="true" />
|
||||
) : null}
|
||||
<span className="adm-album-count">{n.count}</span>
|
||||
</button>
|
||||
);
|
||||
@@ -1477,38 +1497,50 @@ export function Library() {
|
||||
<span className="lib-menu-icon" aria-hidden="true">✎</span>
|
||||
{t('lib.rename')}
|
||||
</button>
|
||||
{/* A roll being read has one thing to be asked of it — that it
|
||||
stop — where a roll at rest has the reading that brings it up
|
||||
to date; the two never both apply, so the menu carries the one
|
||||
the row is actually in. */}
|
||||
{live?.folder === menu.folder.name ? (
|
||||
{/* Three things can be asked of a roll: bring it up to date, read it
|
||||
from the top again, or leave it — and the last only of the
|
||||
reading that is this roll's, or of the request for it that is
|
||||
still waiting its turn. The first two are always there, because
|
||||
a reading already running is not a reason a reader cannot ask
|
||||
for the next one: it queues. */}
|
||||
{live?.folder === menu.folder.name || queued ? (
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
data-key={`lib-menu-stop-${menu.folder.name}`}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
stopScan();
|
||||
stopScanAt(menu.folder!.name, menu.rel ?? '');
|
||||
}}
|
||||
>
|
||||
<span className="lib-menu-icon" aria-hidden="true">■</span>
|
||||
{t('lib.stop')}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
data-key={`lib-rescan-${menu.folder.name}`}
|
||||
disabled={!!live}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
void scan(menu.folder!, false, menu.rel ?? '');
|
||||
}}
|
||||
>
|
||||
<span className="lib-menu-icon" aria-hidden="true">↻</span>
|
||||
{t('lib.update')}
|
||||
</button>
|
||||
)}
|
||||
) : null}
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
data-key={`lib-update-${menu.folder.name}`}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
void scan(menu.folder!, false, menu.rel ?? '', 'update');
|
||||
}}
|
||||
>
|
||||
<span className="lib-menu-icon" aria-hidden="true">↻</span>
|
||||
{t('lib.update')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className="lib-menu-item"
|
||||
data-key={`lib-rescan-${menu.folder.name}`}
|
||||
onClick={() => {
|
||||
setMenu(null);
|
||||
void scan(menu.folder!, false, menu.rel ?? '', 'rescan');
|
||||
}}
|
||||
>
|
||||
<span className="lib-menu-icon" aria-hidden="true">⟳</span>
|
||||
{t('lib.rescan')}
|
||||
</button>
|
||||
{/* A row with folders under it is a row that folds, and the branch
|
||||
it holds goes with it: a reader who is done with a tree means
|
||||
the whole of it shut, not the one row they happened to point
|
||||
|
||||
Reference in New Issue
Block a user