From 2f55eb3e2ecc7648faafbc0a4c90e1aec29273bb Mon Sep 17 00:00:00 2001 From: Yukina Date: Wed, 17 Jun 2026 16:59:51 +0700 Subject: [PATCH 1/5] Add select-all checkbox to column chooser --- media/webview.css | 34 ++++++++++++ src/webview.ts | 9 +-- src/webview/features/column-chooser.ts | 77 ++++++++++++++++---------- 3 files changed, 86 insertions(+), 34 deletions(-) diff --git a/media/webview.css b/media/webview.css index e01bd73..279a6fd 100644 --- a/media/webview.css +++ b/media/webview.css @@ -1172,6 +1172,40 @@ body.vscode-high-contrast #grid-container.cm-on { color: var(--vscode-errorForeground, #f48771); } +.col-chooser-master { + display: flex; + align-items: center; + gap: 7px; + padding: 4px 6px; + margin-bottom: 4px; + font-size: 12px; + font-weight: 600; + cursor: pointer; + border-radius: 3px; + border-bottom: 1px solid var(--vscode-menu-border, #454545); +} +.col-chooser-master:hover { + background: var(--vscode-list-hoverBackground, #2a2d2e); +} +.col-chooser-master input { + cursor: pointer; + margin: 0; + flex: none; + accent-color: var(--vscode-focusBorder, #007fd4); +} +.col-chooser-master-label { + flex: 1; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.col-chooser-master-count { + font-size: 11px; + font-weight: 400; + opacity: 0.6; + flex: none; +} + /* ── Row flash on Go to Row ── */ @keyframes csv-row-flash { 0% { background-color: rgba(55,148,255,0.55) !important; } diff --git a/src/webview.ts b/src/webview.ts index c1fcb10..75681a3 100644 --- a/src/webview.ts +++ b/src/webview.ts @@ -174,10 +174,11 @@ export function getWebviewContent( Columns -
- - -
+
diff --git a/src/webview/features/column-chooser.ts b/src/webview/features/column-chooser.ts index e4fd98d..1a2370d 100644 --- a/src/webview/features/column-chooser.ts +++ b/src/webview/features/column-chooser.ts @@ -3,13 +3,13 @@ import { closeAllPopups } from './popups'; // ── Column chooser (show / hide columns) ──────────────────────────────────── // A toolbar dropdown listing every data column with a checkbox to toggle its -// visibility. A search box filters the list by column name, and Show all / Hide -// all flip every column at once (Hide all, then search and re-check the few you -// want). Visibility is applied via AG Grid (setColumnsVisible) and mirrored into -// state.hiddenCols (0-based data-column indices) so it survives a grid rebuild — -// e.g. a paged-view page change re-runs buildGrid, which re-applies `hide` from -// this set. In-memory only (not persisted across reload), matching the freeze -// features. Column insert/delete clears the set (see delete-row-col). +// visibility. A search box filters the list by column name. A pinned tri-state +// "(Select all)" master checkbox above the list reflects and controls the columns. +// Visibility is applied via AG Grid (setColumnsVisible) and mirrored into state.hiddenCols +// (0-based data-column indices) so it survives a grid rebuild - e.g. a paged-view +// page change re-runs buildGrid, which re-applies `hide` from this set. In-memory +// only (not persisted across reload), matching the freeze features. Column +// insert/delete clears the set (see delete-row-col). let searchQuery = ''; @@ -18,34 +18,49 @@ function setColHidden(colIndex: number, hidden: boolean): void { else state.hiddenCols.delete(colIndex); state.gridApi?.setColumnsVisible(['col_' + colIndex], !hidden); updateButton(); + syncMaster(); } -function allColIds(): string[] { - const n = (state.data[0] ?? []).length; - const ids: string[] = []; - for (let c = 0; c < n; c++) ids.push('col_' + c); - return ids; +function colLabel(header: string[], c: number): string { + const name = header[c] ?? ''; + return name !== '' ? name : '(column ' + (c + 1) + ')'; } -function showAll(): void { - state.hiddenCols.clear(); - state.gridApi?.setColumnsVisible(allColIds(), true); - buildList(); - updateButton(); +function visibleColIndices(): number[] { + const header = state.data[0] ?? []; + const q = searchQuery.trim().toLowerCase(); + const out: number[] = []; + for (let c = 0; c < header.length; c++) { + if (q && !colLabel(header, c).toLowerCase().includes(q)) continue; + out.push(c); + } + return out; } -function hideAll(): void { - const n = (state.data[0] ?? []).length; - state.hiddenCols.clear(); - for (let c = 0; c < n; c++) state.hiddenCols.add(c); - state.gridApi?.setColumnsVisible(allColIds(), false); +function setVisibleColsHidden(hidden: boolean): void { + const cols = visibleColIndices(); + if (cols.length === 0) return; + for (const c of cols) { + if (hidden) state.hiddenCols.add(c); + else state.hiddenCols.delete(c); + } + state.gridApi?.setColumnsVisible(cols.map(c => 'col_' + c), !hidden); buildList(); updateButton(); } -function colLabel(header: string[], c: number): string { - const name = header[c] ?? ''; - return name !== '' ? name : '(column ' + (c + 1) + ')'; +function syncMaster(): void { + const cb = document.getElementById('col-chooser-master-cb') as HTMLInputElement | null; + const count = document.getElementById('col-chooser-master-count'); + if (!cb) return; + const cols = visibleColIndices(); + const visible = cols.reduce((n, c) => n + (state.hiddenCols.has(c) ? 0 : 1), 0); + const total = cols.length; + + cb.checked = total > 0 && visible === total; + cb.indeterminate = visible > 0 && visible < total; + cb.disabled = total === 0; + if (count) count.textContent = total > 0 ? `${visible} / ${total}` : ''; } function buildList(): void { @@ -84,6 +99,8 @@ function buildList(): void { empty.textContent = 'No matching columns'; list.appendChild(empty); } + + syncMaster(); } function updateButton(): void { @@ -99,10 +116,10 @@ function openChooser(): void { if (search) search.value = ''; buildList(); pop.classList.remove('hidden'); - const r = btn.getBoundingClientRect(); + const r = btn.getBoundingClientRect(); const pw = pop.offsetWidth || 220; const vw = window.innerWidth; - pop.style.top = (r.bottom + 4) + 'px'; + pop.style.top = (r.bottom + 4) + 'px'; pop.style.left = Math.max(4, Math.min(r.left, vw - pw - 4)) + 'px'; search?.focus(); } @@ -121,8 +138,8 @@ export function setupColumnChooser(): void { if (!wasOpen) openChooser(); }); - document.getElementById('col-chooser-showall')?.addEventListener('click', showAll); - document.getElementById('col-chooser-hideall')?.addEventListener('click', hideAll); + const master = document.getElementById('col-chooser-master-cb') as HTMLInputElement | null; + master?.addEventListener('change', () => setVisibleColsHidden(!master.checked)); const search = document.getElementById('col-chooser-search') as HTMLInputElement | null; search?.addEventListener('input', () => { @@ -138,4 +155,4 @@ export function setupColumnChooser(): void { if (btn?.contains(t)) return; // toggle button handles itself closeChooser(); }, true); -} +} \ No newline at end of file From 2e8f915b8b914a6a2309863ceaaf8a0d8040a1d7 Mon Sep 17 00:00:00 2001 From: Yukina Date: Thu, 18 Jun 2026 01:27:39 +0700 Subject: [PATCH 2/5] Update spacing --- media/webview.css | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/media/webview.css b/media/webview.css index 279a6fd..6281d1c 100644 --- a/media/webview.css +++ b/media/webview.css @@ -1175,14 +1175,26 @@ body.vscode-high-contrast #grid-container.cm-on { .col-chooser-master { display: flex; align-items: center; - gap: 7px; - padding: 4px 6px; - margin-bottom: 4px; + gap: 6px; + padding: 6px; + margin: 6px 0; font-size: 12px; font-weight: 600; cursor: pointer; border-radius: 3px; - border-bottom: 1px solid var(--vscode-menu-border, #454545); + position: relative; +} +.col-chooser-master::after { + content: ''; + position: absolute; + left: 0; + right: 0; + bottom: -4px; + height: 1px; + background: var(--vscode-menu-border, #454545); +} +.col-chooser-master:hover { + background: var(--vscode-list-hoverBackground, #2a2d2e); } .col-chooser-master:hover { background: var(--vscode-list-hoverBackground, #2a2d2e); From 7b33380b1d93c2d77a9bfe04820089f7c78cb606 Mon Sep 17 00:00:00 2001 From: Yukina Date: Thu, 18 Jun 2026 02:11:01 +0700 Subject: [PATCH 3/5] Update css and label --- media/webview.css | 39 ++++++-------------------- src/webview.ts | 2 +- src/webview/features/column-chooser.ts | 2 +- 3 files changed, 11 insertions(+), 32 deletions(-) diff --git a/media/webview.css b/media/webview.css index 6281d1c..dda2dce 100644 --- a/media/webview.css +++ b/media/webview.css @@ -1157,7 +1157,7 @@ body.vscode-high-contrast #grid-container.cm-on { .col-chooser-item { display: flex; align-items: center; - gap: 7px; + gap: 6px; padding: 4px 6px; font-size: 12px; cursor: pointer; @@ -1165,13 +1165,6 @@ body.vscode-high-contrast #grid-container.cm-on { } .col-chooser-item:hover { background: var(--vscode-list-hoverBackground, #2a2d2e); } .col-chooser-item input { cursor: pointer; margin: 0; flex: none; accent-color: var(--vscode-focusBorder, #007fd4); } -.col-chooser-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.goto-error { - margin-top: 6px; - font-size: 11px; - color: var(--vscode-errorForeground, #f48771); -} - .col-chooser-master { display: flex; align-items: center; @@ -1193,29 +1186,15 @@ body.vscode-high-contrast #grid-container.cm-on { height: 1px; background: var(--vscode-menu-border, #454545); } -.col-chooser-master:hover { - background: var(--vscode-list-hoverBackground, #2a2d2e); -} -.col-chooser-master:hover { - background: var(--vscode-list-hoverBackground, #2a2d2e); -} -.col-chooser-master input { - cursor: pointer; - margin: 0; - flex: none; - accent-color: var(--vscode-focusBorder, #007fd4); -} -.col-chooser-master-label { - flex: 1; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} -.col-chooser-master-count { +.col-chooser-master:hover { background: var(--vscode-list-hoverBackground, #2a2d2e); } +.col-chooser-master input { cursor: pointer; margin: 0; flex: none; accent-color: var(--vscode-focusBorder, #007fd4); } +.col-chooser-master-label { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.col-chooser-master-count { font-size: 11px; font-weight: 400; opacity: 0.6; flex: none; } +.col-chooser-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.goto-error { + margin-top: 6px; font-size: 11px; - font-weight: 400; - opacity: 0.6; - flex: none; + color: var(--vscode-errorForeground, #f48771); } /* ── Row flash on Go to Row ── */ diff --git a/src/webview.ts b/src/webview.ts index 75681a3..c002c66 100644 --- a/src/webview.ts +++ b/src/webview.ts @@ -176,7 +176,7 @@ export function getWebviewContent(
diff --git a/src/webview/features/column-chooser.ts b/src/webview/features/column-chooser.ts index 1a2370d..1ddedfb 100644 --- a/src/webview/features/column-chooser.ts +++ b/src/webview/features/column-chooser.ts @@ -4,7 +4,7 @@ import { closeAllPopups } from './popups'; // ── Column chooser (show / hide columns) ──────────────────────────────────── // A toolbar dropdown listing every data column with a checkbox to toggle its // visibility. A search box filters the list by column name. A pinned tri-state -// "(Select all)" master checkbox above the list reflects and controls the columns. +// "Select all" master checkbox above the list reflects and controls the columns. // Visibility is applied via AG Grid (setColumnsVisible) and mirrored into state.hiddenCols // (0-based data-column indices) so it survives a grid rebuild - e.g. a paged-view // page change re-runs buildGrid, which re-applies `hide` from this set. In-memory From cf44ac1c82b3bb7fa699acf44fe8fe08c23a6d6c Mon Sep 17 00:00:00 2001 From: Yukina Date: Thu, 18 Jun 2026 22:57:09 +0700 Subject: [PATCH 4/5] Restore global show-all on empty search; dedupe search predicate; add trailing newline --- src/webview/features/column-chooser.ts | 34 ++++++++++++++++---------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/webview/features/column-chooser.ts b/src/webview/features/column-chooser.ts index 1ddedfb..a557ab2 100644 --- a/src/webview/features/column-chooser.ts +++ b/src/webview/features/column-chooser.ts @@ -40,15 +40,27 @@ function visibleColIndices(): number[] { function setVisibleColsHidden(hidden: boolean): void { const cols = visibleColIndices(); if (cols.length === 0) return; - for (const c of cols) { - if (hidden) state.hiddenCols.add(c); - else state.hiddenCols.delete(c); + if (!hidden && searchQuery.trim() === '') { + state.hiddenCols.clear(); + state.gridApi?.setColumnsVisible(allColIds(), true); + } else { + for (const c of cols) { + if (hidden) state.hiddenCols.add(c); + else state.hiddenCols.delete(c); + } + state.gridApi?.setColumnsVisible(cols.map(c => 'col_' + c), !hidden); } - state.gridApi?.setColumnsVisible(cols.map(c => 'col_' + c), !hidden); buildList(); updateButton(); } +function allColIds(): string[] { + const n = (state.data[0] ?? []).length; + const ids: string[] = []; + for (let c = 0; c < n; c++) ids.push('col_' + c); + return ids; +} + function syncMaster(): void { const cb = document.getElementById('col-chooser-master-cb') as HTMLInputElement | null; const count = document.getElementById('col-chooser-master-count'); @@ -68,13 +80,9 @@ function buildList(): void { if (!list) return; list.innerHTML = ''; const header = state.data[0] ?? []; - const q = searchQuery.trim().toLowerCase(); - let shown = 0; - for (let c = 0; c < header.length; c++) { - const label = colLabel(header, c); - if (q && !label.toLowerCase().includes(q)) continue; - shown++; + const cols = visibleColIndices(); + for (const c of cols) { const row = document.createElement('label'); row.className = 'col-chooser-item'; @@ -86,14 +94,14 @@ function buildList(): void { const span = document.createElement('span'); span.className = 'col-chooser-label'; - span.textContent = label; + span.textContent = colLabel(header, c); row.appendChild(cb); row.appendChild(span); list.appendChild(row); } - if (shown === 0) { + if (cols.length === 0) { const empty = document.createElement('div'); empty.className = 'csv-filter-empty'; empty.textContent = 'No matching columns'; @@ -155,4 +163,4 @@ export function setupColumnChooser(): void { if (btn?.contains(t)) return; // toggle button handles itself closeChooser(); }, true); -} \ No newline at end of file +} From 438708f441a3a6c3f4f457876bb7a0c8e04d25a8 Mon Sep 17 00:00:00 2001 From: Yukina Date: Fri, 19 Jun 2026 16:28:05 +0700 Subject: [PATCH 5/5] Label master 'Select all matches' under active search --- src/webview.ts | 2 +- src/webview/features/column-chooser.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/webview.ts b/src/webview.ts index c002c66..16228c6 100644 --- a/src/webview.ts +++ b/src/webview.ts @@ -176,7 +176,7 @@ export function getWebviewContent(
diff --git a/src/webview/features/column-chooser.ts b/src/webview/features/column-chooser.ts index a557ab2..1fb11a1 100644 --- a/src/webview/features/column-chooser.ts +++ b/src/webview/features/column-chooser.ts @@ -64,6 +64,7 @@ function allColIds(): string[] { function syncMaster(): void { const cb = document.getElementById('col-chooser-master-cb') as HTMLInputElement | null; const count = document.getElementById('col-chooser-master-count'); + const label = document.getElementById('col-chooser-master-label'); if (!cb) return; const cols = visibleColIndices(); const visible = cols.reduce((n, c) => n + (state.hiddenCols.has(c) ? 0 : 1), 0); @@ -73,6 +74,7 @@ function syncMaster(): void { cb.indeterminate = visible > 0 && visible < total; cb.disabled = total === 0; if (count) count.textContent = total > 0 ? `${visible} / ${total}` : ''; + if (label) label.textContent = searchQuery.trim() ? 'Select all matches' : 'Select all'; } function buildList(): void {