fix(library): a reading that never finished no longer says nought
`saveWalkedDirs` filled in a nought beside every folder a reading had listed but never opened. A reading stopped by a closed tab, and one kept to a branch, list the rest of the roll before they have been into any of it — 484 folders of one real roll read empty over tens of thousands of frames — and the record is folded back into itself, so the noughts stayed. Only a reading that met the whole roll may say nought now (`WalkedDirs.full`), and the screen drops a nought from a record that does not say so. An old record has no word to give, so its rows fall back on the catalogue instead. feat(library): search Immich from the filter row One box, one query, one node: the backend reads what is typed as a tag name when the key can see one and as a file name otherwise, and the answer is filed under the node itself. A share link searches its one album in place. The box is not a filter — it leaves the shelf, which is what the md's search permission is for. feat(ui): a shorter status line, and a wall that keeps its own row height The status line is one short 12px row at the foot of the library. A wall with fewer frames than there are columns no longer stretches its one row — and the cards' own borders with it — down to the stage.
This commit is contained in:
@@ -65,6 +65,7 @@ import {
|
||||
forgetImmichServer,
|
||||
immichConfig,
|
||||
saveImmichAlbums,
|
||||
searchImmichFolder,
|
||||
syncImmich,
|
||||
type ImmichServer,
|
||||
} from './engine/immich';
|
||||
@@ -426,6 +427,11 @@ function Catalogue({ admin }: { admin: boolean }) {
|
||||
const [year, setYear] = useState(() => recall(YEAR_KEY) || 'all');
|
||||
const [from, setFrom] = useState(() => recall(FROM_KEY) || 'any');
|
||||
const [to, setTo] = useState(() => recall(TO_KEY) || 'any');
|
||||
// What the shelf's search box holds while it is being typed in. It is not a
|
||||
// filter: a filter looks through the frames already here, and the search asks
|
||||
// Immich for frames that are not here at all. Nothing is kept between visits —
|
||||
// the search node it makes outlives the box.
|
||||
const [search, setSearch] = useState('');
|
||||
|
||||
useEffect(() => { keep(SORT_BY_KEY, sortBy); }, [sortBy]);
|
||||
useEffect(() => { keep(SORT_DIR_KEY, sortDir); }, [sortDir]);
|
||||
@@ -498,7 +504,15 @@ function Catalogue({ admin }: { admin: boolean }) {
|
||||
// spellings of every key.
|
||||
const counts: Record<string, number> = {};
|
||||
for (const row of walkedRows) {
|
||||
for (const [key, n] of Object.entries(row.counts)) counts[key.toLowerCase()] = n;
|
||||
for (const [key, n] of Object.entries(row.counts)) {
|
||||
// A nought from a reading that did not meet the whole roll is a folder it
|
||||
// listed and never opened, not a folder it found empty, and taken in it
|
||||
// draws an empty row over a roll full of frames. Only a reading that says
|
||||
// it walked the whole of it gets to say nought; the rest of the rows fall
|
||||
// back on the catalogue. See `WalkedDirs.full`.
|
||||
if (n === 0 && row.full !== true) continue;
|
||||
counts[key.toLowerCase()] = n;
|
||||
}
|
||||
}
|
||||
setWalked(counts);
|
||||
// Folded into whatever the screen already has rather than put in its place: a
|
||||
@@ -1204,6 +1218,32 @@ function Catalogue({ admin }: { admin: boolean }) {
|
||||
[editAlbums, syncServer]
|
||||
);
|
||||
|
||||
// The shelf's search box, run. What is typed is a name or a tag, and Immich is
|
||||
// the one that reads it: a disk's folders are already on the column, and a roll
|
||||
// is read by walking it rather than by calling it names. The server asked is the
|
||||
// one the open node belongs to, or the first the account saved. The answer
|
||||
// becomes a node of its own, because a search is a shelf the reader may come
|
||||
// back to — `engine/immich.ts` files the frames under the node.
|
||||
const findImmich = useCallback(
|
||||
async (query: string) => {
|
||||
const q = query.trim();
|
||||
if (!q) return;
|
||||
const open = folders.find((f) => f.name === node)?.server;
|
||||
const server = servers.find((s) => s.id === open) ?? servers[0] ?? null;
|
||||
if (!server) {
|
||||
setNote(t('lib.search.noServer'));
|
||||
return;
|
||||
}
|
||||
const current = await listFolders();
|
||||
const folder = await searchImmichFolder(server, q, current);
|
||||
setFolders([...current.filter((f) => f.name !== folder.name), folder].sort(bySourceThenName));
|
||||
setReady((prev) => new Set(prev).add(folder.name));
|
||||
setNode(folder.name);
|
||||
await syncServer(folder);
|
||||
},
|
||||
[folders, node, servers, syncServer, t]
|
||||
);
|
||||
|
||||
// Ngắt kết nối: the key goes and the node goes with it — the address and the
|
||||
// key are the account's, and a node with neither to read by is a row that could
|
||||
// only fail. "Xoá khỏi thư viện" (`drop`) is the other half: the row goes and
|
||||
@@ -2573,7 +2613,29 @@ function Catalogue({ admin }: { admin: boolean }) {
|
||||
))}
|
||||
</select>
|
||||
|
||||
{/* 6. Count */}
|
||||
{/* 6. Search: a name or a tag. The one control here that
|
||||
leaves the shelf — the four before it look through the
|
||||
frames already in hand, this one asks Immich for frames
|
||||
that are not. Enter, not every keystroke: a node per
|
||||
prefix of a word would be a column of dead ends. */}
|
||||
<input
|
||||
className="adm-input"
|
||||
data-key="lib-search"
|
||||
type="search"
|
||||
aria-label={t('lib.search.label')}
|
||||
placeholder={t('lib.search.placeholder')}
|
||||
title={t('lib.search.hint')}
|
||||
value={search}
|
||||
onChange={(e) => setSearch(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key !== 'Enter') return;
|
||||
e.preventDefault();
|
||||
void findImmich(search);
|
||||
}}
|
||||
style={{ flexShrink: 0 }}
|
||||
/>
|
||||
|
||||
{/* 7. Count */}
|
||||
<span className="hint" data-key="lib-listed" style={{ flexShrink: 0, marginLeft: 'auto' }}>
|
||||
{t('lib.count', { n: listed.length })}
|
||||
</span>
|
||||
|
||||
Reference in New Issue
Block a user