feat(classifier): Folder Tree count badge reflects the active filters
The per-folder "direct+total folders / files" badge always showed the raw scanned totals, even while a filter narrowed the tree — so "9+1799 folders, 0+6203 files" stayed put no matter what you filtered to. computeVisible already does a single filtered pass (applying the Show checkboxes via classifyAllows and the autofilter via nameHit); accumulate per-folder visible direct/total counts there and have populateCount use them whenever a filter is active (raw totals otherwise). So the badge now shows the post-filter totals for both the autofilter and the Show checkboxes — a collapsed folder's badge tells you how many matching items are inside. Test: a filtered tree's root badge drops from "2 folders, 0+3 files" to "1 folder, 0+1 file". Classifier suites 69 green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7c158be73b
commit
8473ed3393
2 changed files with 47 additions and 7 deletions
|
|
@ -111,33 +111,39 @@
|
||||||
var visible = null; // { folders, files } while filtering, else null
|
var visible = null; // { folders, files } while filtering, else null
|
||||||
function computeVisible() {
|
function computeVisible() {
|
||||||
var c = window.app.modules.classify;
|
var c = window.app.modules.classify;
|
||||||
var folders = Object.create(null), files = Object.create(null);
|
var folders = Object.create(null), files = Object.create(null), counts = Object.create(null);
|
||||||
var nf = filterActive();
|
var nf = filterActive();
|
||||||
function walk(folder, ancMatched) {
|
function walk(folder, ancMatched) {
|
||||||
var selfMatch = nf && nameHit(folder.path || folder.name);
|
var selfMatch = nf && nameHit(folder.path || folder.name);
|
||||||
var matched = ancMatched || selfMatch;
|
var matched = ancMatched || selfMatch;
|
||||||
var show = false, hasFile = false, descMatch = false;
|
var show = false, hasFile = false, descMatch = false;
|
||||||
|
// Post-filter counts for the row's "direct+total" badge: direct =
|
||||||
|
// immediate visible children/files, total = visible across the subtree.
|
||||||
|
var dDir = 0, tDir = 0, dFile = 0, tFile = 0;
|
||||||
(folder.children || []).forEach(function (ch) {
|
(folder.children || []).forEach(function (ch) {
|
||||||
var r = walk(ch, matched);
|
var r = walk(ch, matched);
|
||||||
if (r.show) show = true;
|
if (r.show) { show = true; dDir++; tDir += 1 + r.tDir; }
|
||||||
if (r.hasFile) hasFile = true;
|
if (r.hasFile) hasFile = true;
|
||||||
if (r.subtreeMatch) descMatch = true; // a child leads to a match
|
if (r.subtreeMatch) descMatch = true; // a child leads to a match
|
||||||
|
tFile += r.tFile;
|
||||||
});
|
});
|
||||||
(folder.files || []).forEach(function (f) {
|
(folder.files || []).forEach(function (f) {
|
||||||
hasFile = true;
|
hasFile = true;
|
||||||
if (!classifyAllows(f)) return;
|
if (!classifyAllows(f)) return;
|
||||||
var fileMatch = nf && nameHit(c.srcKeyForFile(f));
|
var fileMatch = nf && nameHit(c.srcKeyForFile(f));
|
||||||
if (!nf || matched || fileMatch) { files[c.srcKeyForFile(f)] = true; show = true; }
|
if (!nf || matched || fileMatch) { files[c.srcKeyForFile(f)] = true; show = true; dFile++; }
|
||||||
if (fileMatch) descMatch = true; // a match sits directly in this folder
|
if (fileMatch) descMatch = true; // a match sits directly in this folder
|
||||||
});
|
});
|
||||||
|
tFile += dFile;
|
||||||
if (matched) show = true;
|
if (matched) show = true;
|
||||||
// "Show Empty" off → hide folders whose whole subtree holds no files.
|
// "Show Empty" off → hide folders whose whole subtree holds no files.
|
||||||
if (!hasFile && !showEmpty && !matched) show = false;
|
if (!hasFile && !showEmpty && !matched) show = false;
|
||||||
if (show) folders[folder.path] = true;
|
if (show) folders[folder.path] = true;
|
||||||
return { show: show, hasFile: hasFile, subtreeMatch: descMatch || selfMatch };
|
counts[folder.path] = { dDir: dDir, tDir: tDir, dFile: dFile, tFile: tFile };
|
||||||
|
return { show: show, hasFile: hasFile, subtreeMatch: descMatch || selfMatch, tDir: tDir, tFile: tFile };
|
||||||
}
|
}
|
||||||
(window.app.folderTree || []).forEach(function (root) { walk(root, false); });
|
(window.app.folderTree || []).forEach(function (root) { walk(root, false); });
|
||||||
return { folders: folders, files: files };
|
return { folders: folders, files: files, counts: counts };
|
||||||
}
|
}
|
||||||
function folderShown(folder) { return !visible || !!visible.folders[folder.path]; }
|
function folderShown(folder) { return !visible || !!visible.folders[folder.path]; }
|
||||||
function fileShown(file) {
|
function fileShown(file) {
|
||||||
|
|
@ -211,8 +217,13 @@
|
||||||
const done = st === 'done';
|
const done = st === 'done';
|
||||||
// When fully scanned both numbers are blue; .done turns the labels blue too.
|
// When fully scanned both numbers are blue; .done turns the labels blue too.
|
||||||
if (done) el.classList.add('done');
|
if (done) el.classList.add('done');
|
||||||
const dDir = folder.subdirCount || 0, tDir = folder.runDirs || 0;
|
// While a filter (autofilter or a Show checkbox) is narrowing the tree,
|
||||||
const dFile = folder.fileCount || 0, tFile = folder.runFiles || 0;
|
// the badge counts what's VISIBLE; otherwise the raw scanned totals.
|
||||||
|
const vc = visible && visible.counts && visible.counts[folder.path];
|
||||||
|
const dDir = vc ? vc.dDir : (folder.subdirCount || 0);
|
||||||
|
const tDir = vc ? vc.tDir : (folder.runDirs || 0);
|
||||||
|
const dFile = vc ? vc.dFile : (folder.fileCount || 0);
|
||||||
|
const tFile = vc ? vc.tFile : (folder.runFiles || 0);
|
||||||
|
|
||||||
const frag = document.createDocumentFragment();
|
const frag = document.createDocumentFragment();
|
||||||
frag.appendChild(document.createTextNode('('));
|
frag.appendChild(document.createTextNode('('));
|
||||||
|
|
|
||||||
|
|
@ -849,6 +849,35 @@ test('filter does not open collapsed branches; non-matching siblings hide', asyn
|
||||||
expect(r.files).toEqual([]);
|
expect(r.files).toEqual([]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('folder count badge shows post-filter totals', async ({ page }) => {
|
||||||
|
await page.click('#modeClassifyBtn');
|
||||||
|
const r = await page.evaluate(() => {
|
||||||
|
window.app.folderTree = [{
|
||||||
|
name: 'Root', path: 'Root', expanded: true, scanState: 'done',
|
||||||
|
subdirCount: 2, runDirs: 2, fileCount: 0, runFiles: 3, files: [], children: [
|
||||||
|
{ name: 'A', path: 'Root/A', expanded: true, scanState: 'done', subdirCount: 0, runDirs: 0, fileCount: 2, runFiles: 2, children: [], files: [
|
||||||
|
{ originalFilename: 'alpha report', extension: 'pdf', folderPath: 'Root/A' },
|
||||||
|
{ originalFilename: 'beta memo', extension: 'pdf', folderPath: 'Root/A' },
|
||||||
|
] },
|
||||||
|
{ name: 'B', path: 'Root/B', expanded: true, scanState: 'done', subdirCount: 0, runDirs: 0, fileCount: 1, runFiles: 1, children: [], files: [
|
||||||
|
{ originalFilename: 'gamma note', extension: 'pdf', folderPath: 'Root/B' },
|
||||||
|
] },
|
||||||
|
],
|
||||||
|
}];
|
||||||
|
const tree = window.app.modules.tree;
|
||||||
|
const rootCount = () => { const e = document.querySelector('#folderTree .folder-item .folder-count'); return e ? e.textContent : null; };
|
||||||
|
tree.render();
|
||||||
|
const before = rootCount(); // no filter → raw scan totals
|
||||||
|
tree.setNameFilter('alpha'); // matches one file, in folder A only
|
||||||
|
const after = rootCount();
|
||||||
|
return { before, after };
|
||||||
|
});
|
||||||
|
expect(r.before).toContain('2 folders'); // raw: 2 subfolders…
|
||||||
|
expect(r.before).toContain('0+3 files'); // …3 files in the subtree
|
||||||
|
expect(r.after).toContain('1 folder'); // filtered: only A is visible
|
||||||
|
expect(r.after).toContain('0+1 file'); // …holding the single matching file
|
||||||
|
});
|
||||||
|
|
||||||
test('snapshot: a scanned zip subtree round-trips with its virtual members', async ({ page }) => {
|
test('snapshot: a scanned zip subtree round-trips with its virtual members', async ({ page }) => {
|
||||||
const r = await page.evaluate(() => {
|
const r = await page.evaluate(() => {
|
||||||
const sc = window.app.modules.scanner;
|
const sc = window.app.modules.scanner;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue