fix(classifier): Show toggles preserve tree state; add Reset to raw input
- Toggling Show Unassigned/Assigned/Excluded/Empty no longer force-expands the whole tree. Auto-expand is now reserved for the NAME search (where revealing a match means expanding to it); the Show toggles only hide/show, leaving your expand/collapse state untouched. Also preserve scrollTop across re-render so a toggle doesn't jump the view to the top. - Add a "Reset" button (danger-styled, beside Export/Import) that discards every classification — tracking + transmittal trees, assignments, excludes, title overrides — and returns to just the raw scanned input. Your files are never touched. Destructive + irreversible, so it confirms with an "Export first" warning and no-ops (info toast) when there's nothing to reset. Tests: Show toggle preserves collapse vs. name-search auto-expand (classify 42). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aa34a4b3e7
commit
15ce7098a0
4 changed files with 57 additions and 5 deletions
|
|
@ -151,6 +151,7 @@
|
|||
exportDatasetBtn: document.getElementById('exportDatasetBtn'),
|
||||
importDatasetBtn: document.getElementById('importDatasetBtn'),
|
||||
importDatasetInput: document.getElementById('importDatasetInput'),
|
||||
resetDatasetBtn: document.getElementById('resetDatasetBtn'),
|
||||
treeFilterInput: document.getElementById('treeFilterInput'),
|
||||
trackingFilterInput: document.getElementById('trackingFilterInput'),
|
||||
transmittalFilterInput: document.getElementById('transmittalFilterInput'),
|
||||
|
|
@ -305,6 +306,24 @@
|
|||
reader.onerror = function () { window.zddc.toast('Import failed — could not read the file.', 'error'); };
|
||||
reader.readAsText(file);
|
||||
}
|
||||
// Reset to a clean state: keep the scanned source tree (the raw input), but
|
||||
// discard every classification — trees, assignments, excludes, overrides.
|
||||
// Destructive and irreversible, so warn and steer the user to Export first.
|
||||
function resetDataset() {
|
||||
var c = app.modules.classify;
|
||||
var n = Object.keys(c.serialize().assignments || {}).length;
|
||||
if (!n && !c.getTrackingTree().length && !c.getTransmittalTree().length) {
|
||||
window.zddc.toast('Nothing to reset — already at the raw input.', 'info');
|
||||
return;
|
||||
}
|
||||
if (!confirm('Reset to a clean state?\n\nThis discards ALL classifications ('
|
||||
+ n + ' assigned file' + (n === 1 ? '' : 's') + ', plus the tracking and '
|
||||
+ 'transmittal trees) and returns to just the raw scanned input. Your actual '
|
||||
+ 'files are not touched.\n\nThis cannot be undone — Export first if you might '
|
||||
+ 'need this data.')) return;
|
||||
c.reset();
|
||||
window.zddc.toast('Reset to the raw scanned input.', 'success');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up event listeners
|
||||
|
|
@ -369,6 +388,7 @@
|
|||
// Dataset export / import (round-trip the classification through a JSON file).
|
||||
if (app.dom.exportDatasetBtn) app.dom.exportDatasetBtn.addEventListener('click', exportDataset);
|
||||
if (app.dom.importDatasetBtn) app.dom.importDatasetBtn.addEventListener('click', function () { app.dom.importDatasetInput.click(); });
|
||||
if (app.dom.resetDatasetBtn) app.dom.resetDatasetBtn.addEventListener('click', resetDataset);
|
||||
if (app.dom.importDatasetInput) app.dom.importDatasetInput.addEventListener('change', function () {
|
||||
if (this.files && this.files[0]) importDataset(this.files[0]);
|
||||
this.value = ''; // allow re-importing the same file
|
||||
|
|
|
|||
|
|
@ -143,6 +143,9 @@
|
|||
*/
|
||||
function render() {
|
||||
const container = window.app.dom.folderTree;
|
||||
// Preserve scroll across re-render — toggling a Show filter shouldn't
|
||||
// jump the view back to the top.
|
||||
const prevScroll = container.scrollTop;
|
||||
wireClassifyInteractions();
|
||||
container.innerHTML = '';
|
||||
updateFilterCounts();
|
||||
|
|
@ -164,6 +167,7 @@
|
|||
}
|
||||
|
||||
updateSelectedCount();
|
||||
container.scrollTop = prevScroll;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -268,7 +272,7 @@
|
|||
// expandable so its files can be revealed and dragged.
|
||||
|| (classifyOn() && folder.files && folder.files.length > 0);
|
||||
if (mightHaveChildren) {
|
||||
toggle.textContent = (folder.expanded || visible) ? '▼' : '▶';
|
||||
toggle.textContent = (folder.expanded || filterActive()) ? '▼' : '▶';
|
||||
toggle.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
const recursive = e.ctrlKey || e.metaKey;
|
||||
|
|
@ -346,8 +350,9 @@
|
|||
|
||||
div.appendChild(item);
|
||||
|
||||
// Children — when expanded, or auto-expanded to reveal a filter match.
|
||||
if ((folder.expanded || visible) && folder.children && folder.children.length > 0) {
|
||||
// Children — when expanded, or auto-expanded to reveal a NAME-search
|
||||
// match. The Show toggles only hide/show; they never force-expand.
|
||||
if ((folder.expanded || filterActive()) && folder.children && folder.children.length > 0) {
|
||||
const childrenDiv = document.createElement('div');
|
||||
childrenDiv.className = 'folder-children';
|
||||
folder.children.forEach(child => {
|
||||
|
|
@ -359,8 +364,8 @@
|
|||
}
|
||||
|
||||
// Classify mode: list this folder's own files (draggable leaves) when
|
||||
// expanded (or auto-expanded by a filter), so they can be dropped.
|
||||
if (classifyOn() && (folder.expanded || visible) && folder.files && folder.files.length > 0) {
|
||||
// expanded (or auto-expanded by a name search), so they can be dropped.
|
||||
if (classifyOn() && (folder.expanded || filterActive()) && folder.files && folder.files.length > 0) {
|
||||
const filesDiv = document.createElement('div');
|
||||
filesDiv.className = 'folder-children folder-files';
|
||||
folder.files.forEach(function (file) {
|
||||
|
|
|
|||
|
|
@ -165,6 +165,7 @@
|
|||
<button id="exportDatasetBtn" class="btn btn-secondary btn-sm" title="Export the classification dataset (trees + assignments) as JSON">Export</button>
|
||||
<button id="importDatasetBtn" class="btn btn-secondary btn-sm" title="Import a classification dataset JSON (replaces the current one)">Import</button>
|
||||
<input type="file" id="importDatasetInput" accept="application/json,.json" hidden>
|
||||
<button id="resetDatasetBtn" class="btn btn-sm btn-danger" title="Discard all classifications and start over from the raw scanned input (does not touch your files)">Reset</button>
|
||||
<button id="copyOutputBtn" class="btn btn-primary btn-sm" disabled title="Copy mapped files to an output directory (source untouched)">Copy…</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -760,3 +760,29 @@ test('Show Empty off hides folders that contain no files', async ({ page }) => {
|
|||
expect(r.before).toEqual(['Docs', 'EmptyDir']); // both shown by default
|
||||
expect(r.after).toEqual(['Docs']); // empty folder hidden when Show Empty off
|
||||
});
|
||||
|
||||
test('toggling a Show filter preserves collapse state (no force-expand)', async ({ page }) => {
|
||||
await page.click('#modeClassifyBtn');
|
||||
const r = await page.evaluate(() => {
|
||||
window.app.folderTree = [{
|
||||
name: 'Project', path: 'Project', expanded: false, scanState: 'done', files: [], children: [
|
||||
{ name: 'Sub', path: 'Project/Sub', expanded: false, scanState: 'done', children: [],
|
||||
files: [{ originalFilename: 'a', extension: 'pdf', folderPath: 'Project/Sub' }] },
|
||||
],
|
||||
}];
|
||||
const tree = window.app.modules.tree;
|
||||
tree.render();
|
||||
const collapsed = Array.from(document.querySelectorAll('#folderTree .folder-item')).map((e) => e.dataset.path);
|
||||
// A Show toggle must not expand the collapsed parent…
|
||||
tree.setShowFilters({ unassigned: true, assigned: true, excluded: true, empty: false });
|
||||
const afterToggle = Array.from(document.querySelectorAll('#folderTree .folder-item')).map((e) => e.dataset.path);
|
||||
// …whereas a name search still reveals the match by auto-expanding.
|
||||
tree.setShowFilters({ unassigned: true, assigned: true, excluded: true, empty: true });
|
||||
tree.setNameFilter('a.pdf');
|
||||
const afterSearch = Array.from(document.querySelectorAll('#folderTree .folder-item')).map((e) => e.dataset.path);
|
||||
return { collapsed, afterToggle, afterSearch };
|
||||
});
|
||||
expect(r.collapsed).toEqual(['Project']); // child hidden — parent collapsed
|
||||
expect(r.afterToggle).toEqual(['Project']); // Show toggle leaves it collapsed
|
||||
expect(r.afterSearch).toEqual(['Project', 'Project/Sub']); // name search auto-expands to the match
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue