Skip to content
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

## [Unreleased]

### Added

- 📚 **README overhaul + GIF demos**: Updated documentation with a quick start guide, offline/security notes, optional CLI requirements (`sqlite3`/`sqlcipher`), and feature demos
- ↕️ **Per-tab sort persistence**: Remembers client-side column sort (by column name) per table tab and restores it when switching tabs
- 🧾 **Clear empty states**: New “no results on this page” messaging for table page search/filters (virtualized and non-virtual tables)

### Changed

- 🧭 **Pagination event handling**: Pagination controls now use delegated listeners so regenerated pagination HTML continues to work without re-binding
- 🔎 **Search UX wording**: Table search placeholder clarifies it searches the current page
- 📄 **Bigger page size options**: Added very large page-size choices for browsing huge tables

### Fixed

- 📤 **Export correctness**: CSV export now ignores non-data rows (empty-state, virtual spacer/loading rows)

## [0.4.0] - 2025-12-13

### ⚡ Performance, UX, and Reliability Improvements
Expand Down
418 changes: 173 additions & 245 deletions README.md

Large diffs are not rendered by default.

Binary file added images/Blob-Viewer.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Database-Viewer-normal.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Diagram.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Json-Viewer.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Query.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Relationation-Query.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Schema.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Tab-Organisation.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/encryption.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/external-updates.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions media/css/30-components/tables.css
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,31 @@
color: var(--vscode-descriptionForeground);
font-style: italic;
}
.data-table tbody tr.filter-empty-row {
background: transparent !important;
border-left: none !important;
}
.data-table tbody tr.filter-empty-row:hover {
background: transparent !important;
border-left: none !important;
}
.data-table tbody tr.filter-empty-row td {
padding: 12px 16px !important;
border-right: none !important;
color: var(--vscode-descriptionForeground);
}
.table-empty-message {
display: flex;
flex-direction: column;
gap: 4px;
}
.table-empty-title {
font-weight: 600;
color: var(--vscode-foreground);
}
.table-empty-description {
color: var(--vscode-descriptionForeground);
}

.enhanced-table-wrapper {
border: 1px solid var(--vscode-panel-border);
Expand Down
258 changes: 179 additions & 79 deletions media/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -1271,33 +1271,6 @@ function handleTableRowCount(message) {
totalPages,
tableId
);

// Re-attach pagination listeners (similar to delta updates).
paginationContainer.querySelectorAll(".pagination-btn").forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const action = btn.getAttribute("data-action");
const page = btn.getAttribute("data-page");
if (page && typeof window.handlePagination === "function") {
window.handlePagination(wrapper, "goto", page);
} else if (action && typeof window.handlePagination === "function") {
window.handlePagination(wrapper, action);
}
});
});

const pageInput = paginationContainer.querySelector(".page-input");
if (pageInput) {
pageInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const val = parseInt(pageInput.value, 10);
if (!isNaN(val) && typeof window.updateTablePage === "function") {
window.updateTablePage(wrapper, val);
}
}
});
}
} else {
paginationContainer.innerHTML = "";
}
Expand Down Expand Up @@ -1465,6 +1438,95 @@ function applyTabViewStateToWrapper(tableWrapper, tabKey) {
const viewState =
rawViewState && typeof rawViewState === "object" ? rawViewState : {};

// Restore persisted sort (client-side; affects current page only).
if (viewState && viewState.sort && typeof viewState.sort === "object") {
const dir =
viewState.sort.dir === "asc" || viewState.sort.dir === "desc"
? viewState.sort.dir
: null;
const columnName =
typeof viewState.sort.columnName === "string"
? viewState.sort.columnName
: null;

const table = tableWrapper.querySelector(".data-table");
if (table && dir && columnName) {
/** @type {any} */ const vs = /** @type {any} */ (tableWrapper).__virtualTableState;
if (vs && vs.enabled === true) {
const idx = Array.isArray(vs.columns) ? vs.columns.indexOf(columnName) : -1;
if (idx >= 0) {
vs.sort = { columnName, columnIndex: idx, dir };
table.querySelectorAll("th").forEach((th) => {
th.dataset.sort = "none";
const indicator = th.querySelector(".sort-indicator");
if (indicator) {
indicator.textContent = "⇅";
}
});
const header = table.querySelector(`th[data-column="${idx}"]`);
if (header) {
header.dataset.sort = dir;
const indicator = header.querySelector(".sort-indicator");
if (indicator) {
indicator.textContent = dir === "asc" ? "↑" : "↓";
}
}
if (typeof window.refreshVirtualTable === "function") {
window.refreshVirtualTable(tableWrapper);
}
}
} else {
const header = table.querySelector(`th[data-column-name="${columnName}"]`);
const colIndex = header
? parseInt(header.getAttribute("data-column") || "-1", 10)
: -1;
if (header && colIndex >= 0) {
table.querySelectorAll("th").forEach((th) => {
th.dataset.sort = "none";
const indicator = th.querySelector(".sort-indicator");
if (indicator) {
indicator.textContent = "⇅";
}
});
header.dataset.sort = dir;
const indicator = header.querySelector(".sort-indicator");
if (indicator) {
indicator.textContent = dir === "asc" ? "↑" : "↓";
}
const tbody = table.querySelector("tbody");
if (tbody) {
const rows = Array.from(tbody.querySelectorAll("tr"));
const cmp =
typeof window.compareValues === "function"
? window.compareValues
: (a, b, direction) =>
direction === "asc"
? String(a).localeCompare(String(b))
: String(b).localeCompare(String(a));
rows.sort((a, b) => {
const aCell = a.querySelector(`td[data-column="${colIndex}"]`);
const bCell = b.querySelector(`td[data-column="${colIndex}"]`);
const aValue =
typeof window.getCellValue === "function"
? window.getCellValue(aCell)
: aCell && aCell.textContent
? aCell.textContent.trim()
: "";
const bValue =
typeof window.getCellValue === "function"
? window.getCellValue(bCell)
: bCell && bCell.textContent
? bCell.textContent.trim()
: "";
return cmp(aValue ?? "", bValue ?? "", dir);
});
rows.forEach((row) => tbody.appendChild(row));
}
}
}
}
}

// Restore pinned columns (before sizing/positioning).
if (viewState && Array.isArray(viewState.pinnedColumns)) {
const pinnedSet = new Set(viewState.pinnedColumns.filter(Boolean));
Expand Down Expand Up @@ -2330,25 +2392,100 @@ function initializeTableEvents(tableWrapper) {
});
});
// Pagination controls
const paginationBtns = tableWrapper.querySelectorAll(".pagination-btn");
paginationBtns.forEach((btn) => {
btn.addEventListener("click", (e) => {
// Pagination (delegated so it continues working after pagination HTML is regenerated)
if (tableWrapper.getAttribute("data-pagination-delegated") !== "true") {
tableWrapper.setAttribute("data-pagination-delegated", "true");

tableWrapper.addEventListener("click", (e) => {
const target = e.target instanceof Element ? e.target : null;
if (!target) {
return;
}
const btn = target.closest("button.pagination-btn");
if (!btn || !(btn instanceof HTMLElement)) {
return;
}
if (!tableWrapper.contains(btn)) {
return;
}

e.preventDefault();
const tableWrapper = btn.closest(".enhanced-table-wrapper");
const action = btn.dataset.action;
const page = btn.dataset.page;

if (typeof handlePagination !== "undefined") {
if (page) {
// Page number button clicked
handlePagination(tableWrapper, "goto", page);
} else if (action) {
// Navigation button clicked (first/prev/next/last)
handlePagination(tableWrapper, action);

const wrapper = btn.closest(".enhanced-table-wrapper") || tableWrapper;
const page = btn.getAttribute("data-page") || btn.dataset.page || "";
const action =
btn.getAttribute("data-action") || btn.dataset.action || "";

if (page && typeof window.handlePagination === "function") {
window.handlePagination(wrapper, "goto", page);
return;
}

if (!action) {
return;
}

if (action === "go") {
const container = btn.closest(".page-input-container");
const pageInput =
(container && container.querySelector(".page-input")) ||
wrapper.querySelector(".page-input");
const raw =
pageInput && "value" in pageInput ? pageInput.value : "";
const val = parseInt(String(raw), 10);
if (!isNaN(val) && typeof window.updateTablePage === "function") {
window.updateTablePage(wrapper, val);
}
return;
}

if (typeof window.handlePagination === "function") {
window.handlePagination(wrapper, action);
}
});
});

tableWrapper.addEventListener("keydown", (e) => {
const ke = /** @type {KeyboardEvent} */ (e);
if (ke.key !== "Enter") {
return;
}
const target = ke.target instanceof Element ? ke.target : null;
if (!target || !target.classList.contains("page-input")) {
return;
}
const wrapper =
target.closest(".enhanced-table-wrapper") || tableWrapper;
const val = parseInt(
String(
target instanceof HTMLInputElement ? target.value : target.value
),
10
);
if (!isNaN(val) && typeof window.updateTablePage === "function") {
ke.preventDefault();
window.updateTablePage(wrapper, val);
}
});

// Use focusout (bubbles) instead of blur (doesn't bubble)
tableWrapper.addEventListener("focusout", (e) => {
const target = e.target instanceof Element ? e.target : null;
if (!target || !target.classList.contains("page-input")) {
return;
}
const wrapper =
target.closest(".enhanced-table-wrapper") || tableWrapper;
const val = parseInt(
String(
target instanceof HTMLInputElement ? target.value : target.value
),
10
);
if (!isNaN(val) && typeof window.updateTablePage === "function") {
window.updateTablePage(wrapper, val);
}
});
}
// Page size selector
const pageSizeSelect = tableWrapper.querySelector(".page-size-select");
if (pageSizeSelect) {
Expand Down Expand Up @@ -2928,43 +3065,6 @@ function handleTableDataDelta({
tableId
);

// Re-attach event listeners to the new pagination buttons
const paginationBtns =
paginationContainer.querySelectorAll(".pagination-btn");
paginationBtns.forEach((btn) => {
btn.addEventListener("click", (e) => {
e.preventDefault();
e.stopPropagation();
const action = btn.getAttribute("data-action");
const page = btn.getAttribute("data-page");

if (page && typeof window.handlePagination === "function") {
window.handlePagination(wrapper, "goto", page);
} else if (
action &&
typeof window.handlePagination === "function"
) {
window.handlePagination(wrapper, action);
}
});
});

// Re-attach page input listeners
const pageInput = paginationContainer.querySelector(".page-input");
if (pageInput) {
pageInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
const val = parseInt(pageInput.value, 10);
if (
!isNaN(val) &&
typeof window.updateTablePage === "function"
) {
window.updateTablePage(wrapper, val);
}
}
});
}

if (window.debug) {
window.debug.debug("[events.js] Updated pagination controls", {
tableName,
Expand Down
1 change: 1 addition & 0 deletions media/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ function getDefaultTabViewState() {
searchTerm: "",
scrollTop: 0,
scrollLeft: 0,
sort: { columnName: null, dir: "none" },
columnWidths: {},
rowHeights: {},
pinnedColumns: [],
Expand Down
Loading
Loading