fix(classifier): fit-to-content catalog columns + sorted Folder Tree
Two UI fixes: - "By existing" catalog columns were far too wide. The seltable forced the table to width:100% (auto-layout then stretches columns) and — in the classifier's copy — the per-column filter <input>s had no styling, so each fell back to its ~170px intrinsic width and dictated the column width. Set the table to width:auto (cells are already nowrap → fit header/longest cell) and style .seltable__colfilter to fill its column (min-width:2rem, box-sizing:border-box) so the inputs never widen a column. Applied to both the classifier copy and shared/seltable.css (same fix for the tables tool's "Add from archive" table). - The left Folder Tree rendered folders and files in raw scan order. Sort both at render — case-insensitive, natural (so "Rev 2" precedes "Rev 10") — via a non-mutating slice().sort() at each render point in tree.js. Tests: a new spec asserts the natural/case-insensitive tree order; 62 classify + classifier green (108 across classify/classifier/tables/tables-mdl). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8e10e5e5e6
commit
cfdf0f6db9
4 changed files with 54 additions and 6 deletions
|
|
@ -613,12 +613,21 @@ input.tfile__name:focus { border-color: var(--primary); background: var(--bg); o
|
||||||
}
|
}
|
||||||
.seltable__count { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
|
.seltable__count { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
|
||||||
.seltable__scroll { flex: 1; min-height: 0; overflow: auto; }
|
.seltable__scroll { flex: 1; min-height: 0; overflow: auto; }
|
||||||
.seltable__table { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 0.82rem; }
|
/* width:auto + nowrap cells → each column shrinks to fit its header/longest cell. */
|
||||||
|
.seltable__table { border-collapse: separate; border-spacing: 0; width: auto; font-size: 0.82rem; }
|
||||||
.seltable__table th, .seltable__table td { border-bottom: 1px solid var(--border); padding: 0.25rem 0.5rem; text-align: left; white-space: nowrap; }
|
.seltable__table th, .seltable__table td { border-bottom: 1px solid var(--border); padding: 0.25rem 0.5rem; text-align: left; white-space: nowrap; }
|
||||||
.seltable__table thead th {
|
.seltable__table thead th {
|
||||||
position: sticky; top: 0; z-index: 2; background: var(--bg-secondary, var(--bg));
|
position: sticky; top: 0; z-index: 2; background: var(--bg-secondary, var(--bg));
|
||||||
color: var(--text-muted); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
|
color: var(--text-muted); font-size: 0.68rem; font-weight: 700; letter-spacing: 0.04em; text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
/* Per-column filter inputs: fill the column (min-width:0-ish) so they never
|
||||||
|
force a column wider than its header/cells. */
|
||||||
|
.seltable__table thead tr.seltable__filters th { padding: 0.08rem 0.3rem; }
|
||||||
|
.seltable__colfilter {
|
||||||
|
width: 100%; min-width: 2rem; box-sizing: border-box;
|
||||||
|
padding: 0.1rem 0.3rem; border: 1px solid var(--border); border-radius: var(--radius);
|
||||||
|
background: var(--bg); color: var(--text); font-size: 0.72rem; font-weight: 400; letter-spacing: 0; text-transform: none;
|
||||||
|
}
|
||||||
.seltable__row { cursor: pointer; user-select: none; }
|
.seltable__row { cursor: pointer; user-select: none; }
|
||||||
.seltable__row:hover { background: var(--bg-hover); }
|
.seltable__row:hover { background: var(--bg-hover); }
|
||||||
.seltable__row.is-selected { background: var(--primary-light, rgba(37,99,235,0.12)); }
|
.seltable__row.is-selected { background: var(--primary-light, rgba(37,99,235,0.12)); }
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,17 @@
|
||||||
(function() {
|
(function() {
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
// ── Sorting ────────────────────────────────────────────────────────────
|
||||||
|
// Render the tree in a stable, human order: case-insensitive, natural
|
||||||
|
// (so "Rev 2" sorts before "Rev 10"). Non-mutating — sort copies at render.
|
||||||
|
function cmpName(a, b) { return String(a).localeCompare(String(b), undefined, { numeric: true, sensitivity: 'base' }); }
|
||||||
|
function sortedFolders(list) { return (list || []).slice().sort(function (a, b) { return cmpName(a.name, b.name); }); }
|
||||||
|
function sortedFiles(list) {
|
||||||
|
return (list || []).slice().sort(function (a, b) {
|
||||||
|
return cmpName(window.zddc.joinExtension(a.originalFilename, a.extension), window.zddc.joinExtension(b.originalFilename, b.extension));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// ── Classify & Copy helpers ────────────────────────────────────────────
|
// ── Classify & Copy helpers ────────────────────────────────────────────
|
||||||
function classifyOn() {
|
function classifyOn() {
|
||||||
var c = window.app.modules.classify;
|
var c = window.app.modules.classify;
|
||||||
|
|
@ -175,7 +186,7 @@
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.app.folderTree.forEach(folder => {
|
sortedFolders(window.app.folderTree).forEach(folder => {
|
||||||
if (!folderShown(folder)) return;
|
if (!folderShown(folder)) return;
|
||||||
const element = createFolderElement(folder);
|
const element = createFolderElement(folder);
|
||||||
container.appendChild(element);
|
container.appendChild(element);
|
||||||
|
|
@ -360,7 +371,7 @@
|
||||||
if ((folder.expanded || autoOpen(folder)) && folder.children && folder.children.length > 0) {
|
if ((folder.expanded || autoOpen(folder)) && folder.children && folder.children.length > 0) {
|
||||||
const childrenDiv = document.createElement('div');
|
const childrenDiv = document.createElement('div');
|
||||||
childrenDiv.className = 'folder-children';
|
childrenDiv.className = 'folder-children';
|
||||||
folder.children.forEach(child => {
|
sortedFolders(folder.children).forEach(child => {
|
||||||
if (!folderShown(child)) return;
|
if (!folderShown(child)) return;
|
||||||
const childElement = createFolderElement(child, level + 1);
|
const childElement = createFolderElement(child, level + 1);
|
||||||
childrenDiv.appendChild(childElement);
|
childrenDiv.appendChild(childElement);
|
||||||
|
|
@ -373,7 +384,7 @@
|
||||||
if (classifyOn() && (folder.expanded || autoOpen(folder)) && folder.files && folder.files.length > 0) {
|
if (classifyOn() && (folder.expanded || autoOpen(folder)) && folder.files && folder.files.length > 0) {
|
||||||
const filesDiv = document.createElement('div');
|
const filesDiv = document.createElement('div');
|
||||||
filesDiv.className = 'folder-children folder-files';
|
filesDiv.className = 'folder-children folder-files';
|
||||||
folder.files.forEach(function (file) {
|
sortedFiles(folder.files).forEach(function (file) {
|
||||||
if (!fileShown(file)) return;
|
if (!fileShown(file)) return;
|
||||||
filesDiv.appendChild(createFileElement(file, level + 1));
|
filesDiv.appendChild(createFileElement(file, level + 1));
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,8 @@
|
||||||
.seltable__bar { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--border); flex: 0 0 auto; }
|
.seltable__bar { display: flex; align-items: center; gap: 0.5rem; padding: 0.4rem 0.5rem; border-bottom: 1px solid var(--border); flex: 0 0 auto; }
|
||||||
.seltable__count { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
|
.seltable__count { color: var(--text-muted); font-size: 0.78rem; white-space: nowrap; }
|
||||||
.seltable__scroll { flex: 1; min-height: 0; overflow: auto; }
|
.seltable__scroll { flex: 1; min-height: 0; overflow: auto; }
|
||||||
.seltable__table { border-collapse: separate; border-spacing: 0; width: 100%; font-size: 0.82rem; }
|
/* width:auto + nowrap cells → each column shrinks to fit its header/longest cell. */
|
||||||
|
.seltable__table { border-collapse: separate; border-spacing: 0; width: auto; font-size: 0.82rem; }
|
||||||
.seltable__table th, .seltable__table td { border-bottom: 1px solid var(--border); padding: 0.25rem 0.5rem; text-align: left; white-space: nowrap; }
|
.seltable__table th, .seltable__table td { border-bottom: 1px solid var(--border); padding: 0.25rem 0.5rem; text-align: left; white-space: nowrap; }
|
||||||
.seltable__table thead th {
|
.seltable__table thead th {
|
||||||
position: sticky; top: 0; z-index: 2; background: var(--bg-secondary, var(--bg));
|
position: sticky; top: 0; z-index: 2; background: var(--bg-secondary, var(--bg));
|
||||||
|
|
@ -13,7 +14,7 @@
|
||||||
}
|
}
|
||||||
.seltable__table thead tr.seltable__filters th { top: 1.55rem; padding: 0.15rem 0.35rem; }
|
.seltable__table thead tr.seltable__filters th { top: 1.55rem; padding: 0.15rem 0.35rem; }
|
||||||
.seltable__colfilter {
|
.seltable__colfilter {
|
||||||
width: 100%; min-width: 5rem; padding: 0.15rem 0.35rem;
|
width: 100%; min-width: 2rem; box-sizing: border-box; padding: 0.15rem 0.35rem;
|
||||||
border: 1px solid var(--border); border-radius: var(--radius);
|
border: 1px solid var(--border); border-radius: var(--radius);
|
||||||
background: var(--bg); color: var(--text); font-size: 0.74rem; font-weight: 400; text-transform: none; letter-spacing: 0;
|
background: var(--bg); color: var(--text); font-size: 0.74rem; font-weight: 400; text-transform: none; letter-spacing: 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -247,6 +247,33 @@ test('source file rows render with a state dot in classify mode', async ({ page
|
||||||
await expect(page.locator('#folderTree .file-item .cl-dot--none')).toBeVisible();
|
await expect(page.locator('#folderTree .file-item .cl-dot--none')).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Folder Tree renders folders and files in natural, case-insensitive order', async ({ page }) => {
|
||||||
|
await page.click('#modeClassifyBtn');
|
||||||
|
const order = await page.evaluate(() => {
|
||||||
|
window.app.folderTree = [{
|
||||||
|
name: 'Root', path: 'Root', expanded: true, scanState: 'done',
|
||||||
|
children: [
|
||||||
|
{ name: 'Beta', path: 'Root/Beta', scanState: 'done', children: [], files: [] },
|
||||||
|
{ name: 'alpha', path: 'Root/alpha', scanState: 'done', children: [], files: [] },
|
||||||
|
{ name: 'Rev 10', path: 'Root/Rev 10', scanState: 'done', children: [], files: [] },
|
||||||
|
{ name: 'Rev 2', path: 'Root/Rev 2', scanState: 'done', children: [], files: [] },
|
||||||
|
],
|
||||||
|
files: [
|
||||||
|
{ originalFilename: 'zeta', extension: 'pdf', folderPath: 'Root' },
|
||||||
|
{ originalFilename: 'Apple', extension: 'pdf', folderPath: 'Root' },
|
||||||
|
{ originalFilename: 'banana', extension: 'pdf', folderPath: 'Root' },
|
||||||
|
],
|
||||||
|
}];
|
||||||
|
window.app.modules.tree.render();
|
||||||
|
return {
|
||||||
|
folders: Array.from(document.querySelectorAll('#folderTree .folder-children .folder-name')).map(e => e.textContent.trim()),
|
||||||
|
files: Array.from(document.querySelectorAll('#folderTree .folder-files .file-name')).map(e => e.textContent.trim()),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
expect(order.folders).toEqual(['alpha', 'Beta', 'Rev 2', 'Rev 10']); // case-insensitive + natural (2 < 10)
|
||||||
|
expect(order.files).toEqual(['Apple.pdf', 'banana.pdf', 'zeta.pdf']); // case-insensitive
|
||||||
|
});
|
||||||
|
|
||||||
test('classify: single-click a source file triggers preview', async ({ page }) => {
|
test('classify: single-click a source file triggers preview', async ({ page }) => {
|
||||||
await page.click('#modeClassifyBtn');
|
await page.click('#modeClassifyBtn');
|
||||||
const previewed = await page.evaluate(() => {
|
const previewed = await page.evaluate(() => {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue