From d5da6ebcba150bd97f9bc2c152e2e6d03f713ca8 Mon Sep 17 00:00:00 2001 From: 3dtours Date: Thu, 1 Oct 2026 19:00:21 +0700 Subject: [PATCH] web: a row's menu is the tree's own and speaks only of that row, and only the row being read admits to it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The menu that hangs off a folder is a menu read mid-scan, with the eye still moving down the column, and it was drawn to the measure of a page rather than of a list: a box wide enough to be a destination, type the size of a heading. A catalogue's own context menu is a thing passed over on the way somewhere else — it is sized to its words and to the row it hangs off, and the reader's eye steps across it without stopping. The type comes down a point, the rows tighten to the tree's own measure, and the frame around them thickens to a radius the rows themselves use. The clamp that keeps it in the window follows the smaller box, so a menu opened near the right or the bottom edge no longer floats an inch off the pointer for room it does not use. A row that is being read has exactly one thing to be asked of it — that it stop — where a row at rest has the reading that brings it up to date. The menu said RESCAN either way, greyed while a scan was up, which is a command offered and refused in the same breath. The two never both apply, so the menu now carries the one the row is actually in: a roll being read offers STOP SCAN and stops that roll; a roll at rest offers UPDATE... and reads it again. A row with folders under it grows a third thing — a fold that closes the branch and not merely the row, because a reader who is done with a tree means the whole of it shut, and closing only the row they happened to point at leaves the children standing open underneath it. The last is a matter of what a row is allowed to claim. Every row of a roll spanning the tree spun while that roll was being read, so a reader looking at a tree of a hundred folders saw a hundred rows each insisting it was the one being read, when the reading had been pointed at one of them. A row now says it is being read only when it is the row the reading was kept to — the folder it was pointed at, which for a whole roll is the roll's own row. A tree that says all of itself is busy when one branch of it is says nothing a reader can trust. --- docker/frontend/src/Library.tsx | 86 +++++++++++++++++++++++------- docker/frontend/src/i18n/en.ts | 5 +- docker/frontend/src/i18n/vi.ts | 5 +- docker/frontend/src/styles/app.css | 23 ++++---- 4 files changed, 86 insertions(+), 33 deletions(-) diff --git a/docker/frontend/src/Library.tsx b/docker/frontend/src/Library.tsx index 41f5ca9..2b61d37 100644 --- a/docker/frontend/src/Library.tsx +++ b/docker/frontend/src/Library.tsx @@ -195,7 +195,7 @@ export function Library() { // right click landed on the empty part of the column, which is the way in for // 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; x: number; y: number } | null>(null); + const [menu, setMenu] = useState<{ folder: LibraryFolder | null; root?: boolean; rel?: string; node?: string; x: number; y: number } | null>(null); // 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); @@ -1060,14 +1060,17 @@ export function Library() { onContextMenu={(e) => { if (e.target !== e.currentTarget) return; e.preventDefault(); - setMenu({ folder: null, x: Math.min(e.clientX, window.innerWidth - 190), y: Math.min(e.clientY, window.innerHeight - 96) }); + setMenu({ folder: null, x: Math.min(e.clientX, window.innerWidth - 150), y: Math.min(e.clientY, window.innerHeight - 150) }); }} > {rows.map((n) => { const folder = folders.find((f) => f.name === n.key.split('/')[0]) ?? null; - // A roll being read says so on its own row: while a scan runs - // the toolbar's progress line is nowhere near the eye. - const scanning = !!folder && live?.folder === folder.name; + // The row being read says so, and no other: a roll is read + // from one folder down, and every row of that roll spinning + // is a tree that says all of itself is being read when one + // branch of it is. The row is the folder the reading was + // kept to, which for a whole roll is the roll's own row. + const scanning = !!folder && live?.folder === folder.name && relOf(n.key) === live.progress.from; const under = parents.has(n.key); return ( - + {/* 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 ? ( + + ) : ( + + )} + {/* 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 + at with its children left open underneath. */} + {menu.node && parents.has(menu.node) ? ( + + ) : null}