Skip to content

Commit 794c22b

Browse files
author
Peng Ren
committed
Improve the UI of result table
1 parent a3e00be commit 794c22b

4 files changed

Lines changed: 337 additions & 11 deletions

File tree

media/sql-executor.css

Lines changed: 173 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -677,9 +677,7 @@ body.is-resizing {
677677
}
678678

679679
.results-table table {
680-
width: 100%;
681680
border-collapse: collapse;
682-
min-width: 100%;
683681
border: 1px solid var(--border-color);
684682
}
685683

@@ -688,20 +686,37 @@ body.is-resizing {
688686
top: 0;
689687
background-color: var(--table-header-bg);
690688
color: var(--table-header-fg);
691-
padding: 8px;
689+
padding: 6px 10px;
692690
text-align: left;
693691
border-bottom: 2px solid var(--border-color);
694692
border-right: 1px solid var(--border-color);
695693
font-weight: 800;
696694
letter-spacing: 0.01em;
695+
white-space: nowrap;
696+
}
697+
698+
.results-table th .resize-handle {
699+
position: absolute;
700+
top: 0;
701+
right: -3px;
702+
width: 7px;
703+
height: 100%;
704+
cursor: col-resize;
705+
user-select: none;
706+
z-index: 1;
707+
}
708+
709+
.results-table th .resize-handle:hover,
710+
.results-table th .resize-handle.is-resizing {
711+
background-color: var(--primary-color);
697712
}
698713

699714
.results-table td {
700-
padding: 8px;
715+
padding: 4px 10px;
701716
border-bottom: 1px solid var(--border-color);
702717
border-right: 1px solid var(--border-color);
703-
word-break: break-word;
704-
max-width: 300px;
718+
white-space: nowrap;
719+
max-width: 400px;
705720
text-overflow: ellipsis;
706721
overflow: hidden;
707722
vertical-align: top;
@@ -936,3 +951,155 @@ body.vscode-high-contrast-light .json-number {
936951
flex-basis: 35%;
937952
}
938953
}
954+
955+
/* Row Detail Modal */
956+
.row-detail-overlay {
957+
position: fixed;
958+
top: 0;
959+
left: 0;
960+
right: 0;
961+
bottom: 0;
962+
background: rgba(0, 0, 0, 0.5);
963+
z-index: 200;
964+
display: flex;
965+
align-items: center;
966+
justify-content: center;
967+
}
968+
969+
.row-detail-modal {
970+
background: var(--bg-secondary);
971+
border: 1px solid var(--border-color);
972+
border-radius: 8px;
973+
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
974+
width: 560px;
975+
max-width: 90vw;
976+
max-height: 80vh;
977+
display: flex;
978+
flex-direction: column;
979+
overflow: hidden;
980+
}
981+
982+
.row-detail-header {
983+
display: flex;
984+
align-items: center;
985+
justify-content: space-between;
986+
padding: 10px 14px;
987+
border-bottom: 1px solid var(--border-color);
988+
background: var(--surface-muted);
989+
flex-shrink: 0;
990+
}
991+
992+
.row-detail-title {
993+
font-weight: 600;
994+
font-size: 13px;
995+
color: var(--text-primary);
996+
}
997+
998+
.row-detail-close {
999+
background: transparent;
1000+
border: none;
1001+
color: var(--text-secondary);
1002+
cursor: pointer;
1003+
font-size: 18px;
1004+
line-height: 1;
1005+
padding: 2px 6px;
1006+
border-radius: 4px;
1007+
}
1008+
1009+
.row-detail-close:hover {
1010+
background: rgba(255, 255, 255, 0.1);
1011+
color: var(--text-primary);
1012+
}
1013+
1014+
.row-detail-body {
1015+
overflow-y: auto;
1016+
padding: 6px 0;
1017+
flex: 1;
1018+
min-height: 0;
1019+
}
1020+
1021+
.row-detail-field {
1022+
display: flex;
1023+
align-items: flex-start;
1024+
padding: 5px 14px;
1025+
gap: 10px;
1026+
border-bottom: 1px solid color-mix(in srgb, var(--border-color) 50%, transparent);
1027+
}
1028+
1029+
.row-detail-field:last-child {
1030+
border-bottom: none;
1031+
}
1032+
1033+
.row-detail-field:hover {
1034+
background: rgba(255, 255, 255, 0.04);
1035+
}
1036+
1037+
.row-detail-label {
1038+
flex: 0 0 140px;
1039+
font-weight: 700;
1040+
font-size: 12px;
1041+
color: var(--table-header-fg);
1042+
padding: 4px 0;
1043+
word-break: break-word;
1044+
white-space: normal;
1045+
}
1046+
1047+
.row-detail-value-wrap {
1048+
flex: 1;
1049+
min-width: 0;
1050+
display: flex;
1051+
align-items: flex-start;
1052+
gap: 6px;
1053+
}
1054+
1055+
.row-detail-value {
1056+
flex: 1;
1057+
min-width: 0;
1058+
font-family: "Consolas", "Monaco", monospace;
1059+
font-size: 12px;
1060+
color: var(--text-primary);
1061+
padding: 4px 0;
1062+
white-space: pre-wrap;
1063+
word-break: break-word;
1064+
line-height: 1.45;
1065+
max-height: 120px;
1066+
overflow-y: auto;
1067+
}
1068+
1069+
.row-detail-value.cell-null {
1070+
color: var(--text-secondary);
1071+
font-style: italic;
1072+
}
1073+
1074+
.row-detail-value.cell-object {
1075+
color: var(--vscode-debugTokenExpression-name, #d7ba7d);
1076+
}
1077+
1078+
.row-detail-copy-btn {
1079+
flex-shrink: 0;
1080+
background: transparent;
1081+
border: 1px solid var(--border-color);
1082+
border-radius: 3px;
1083+
color: var(--text-secondary);
1084+
cursor: pointer;
1085+
padding: 3px 5px;
1086+
font-size: 11px;
1087+
line-height: 1;
1088+
opacity: 0;
1089+
transition: opacity 0.15s;
1090+
}
1091+
1092+
.row-detail-field:hover .row-detail-copy-btn {
1093+
opacity: 1;
1094+
}
1095+
1096+
.row-detail-copy-btn:hover {
1097+
background: var(--button-bg);
1098+
color: var(--text-primary);
1099+
border-color: var(--focus-ring);
1100+
}
1101+
1102+
.row-detail-copy-btn.copied {
1103+
color: var(--success-color);
1104+
border-color: var(--success-color);
1105+
}

media/sql-executor.js

Lines changed: 161 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,155 @@ function renderPlaceholder(message, type = "neutral") {
211211
container.appendChild(emptyState);
212212
}
213213

214+
function showRowDetailModal(row, columns, rowIndex) {
215+
const utils = window.SQL4ALLExecutorUtils;
216+
217+
// Remove any existing modal
218+
const existing = document.querySelector(".row-detail-overlay");
219+
if (existing) existing.remove();
220+
221+
const overlay = document.createElement("div");
222+
overlay.className = "row-detail-overlay";
223+
224+
const modal = document.createElement("div");
225+
modal.className = "row-detail-modal";
226+
227+
// Header
228+
const header = document.createElement("div");
229+
header.className = "row-detail-header";
230+
const title = document.createElement("span");
231+
title.className = "row-detail-title";
232+
title.textContent = `Row #${rowIndex + 1}`;
233+
const closeBtn = document.createElement("button");
234+
closeBtn.className = "row-detail-close";
235+
closeBtn.textContent = "\u00d7";
236+
closeBtn.title = "Close (Esc)";
237+
header.appendChild(title);
238+
header.appendChild(closeBtn);
239+
modal.appendChild(header);
240+
241+
// Body
242+
const body = document.createElement("div");
243+
body.className = "row-detail-body";
244+
245+
columns.forEach((col) => {
246+
const field = document.createElement("div");
247+
field.className = "row-detail-field";
248+
249+
const label = document.createElement("div");
250+
label.className = "row-detail-label";
251+
label.textContent = col;
252+
label.title = col;
253+
254+
const valueWrap = document.createElement("div");
255+
valueWrap.className = "row-detail-value-wrap";
256+
257+
const formatted = utils.formatCellValue(row[col]);
258+
const rawValue = formatted.title || formatted.text;
259+
260+
const valueEl = document.createElement("div");
261+
valueEl.className = "row-detail-value";
262+
if (formatted.className) valueEl.classList.add(formatted.className);
263+
valueEl.textContent = typeof row[col] === "object" && row[col] !== null
264+
? JSON.stringify(row[col], null, 2)
265+
: formatted.text;
266+
267+
const copyBtn = document.createElement("button");
268+
copyBtn.className = "row-detail-copy-btn";
269+
copyBtn.textContent = "Copy";
270+
copyBtn.title = "Copy value";
271+
copyBtn.addEventListener("click", (e) => {
272+
e.stopPropagation();
273+
navigator.clipboard.writeText(rawValue).then(() => {
274+
copyBtn.textContent = "\u2713";
275+
copyBtn.classList.add("copied");
276+
setTimeout(() => {
277+
copyBtn.textContent = "Copy";
278+
copyBtn.classList.remove("copied");
279+
}, 1500);
280+
});
281+
});
282+
283+
valueWrap.appendChild(valueEl);
284+
valueWrap.appendChild(copyBtn);
285+
field.appendChild(label);
286+
field.appendChild(valueWrap);
287+
body.appendChild(field);
288+
});
289+
290+
modal.appendChild(body);
291+
overlay.appendChild(modal);
292+
document.body.appendChild(overlay);
293+
294+
// Close handlers
295+
function close() {
296+
overlay.remove();
297+
document.removeEventListener("keydown", onKey);
298+
}
299+
function onKey(e) {
300+
if (e.key === "Escape") close();
301+
}
302+
closeBtn.addEventListener("click", close);
303+
overlay.addEventListener("click", (e) => {
304+
if (e.target === overlay) close();
305+
});
306+
document.addEventListener("keydown", onKey);
307+
}
308+
309+
function initColumnResize(table) {
310+
// Measure natural column widths before switching to fixed layout
311+
const rowNumTh = table.querySelector("thead th.row-number-cell");
312+
const headers = table.querySelectorAll("thead th:not(.row-number-cell)");
313+
314+
const naturalWidths = Array.from(headers).map((th) => th.offsetWidth);
315+
316+
// Now lock widths and switch to fixed layout
317+
if (rowNumTh) {
318+
rowNumTh.style.width = "52px";
319+
}
320+
headers.forEach((th, i) => {
321+
th.style.width = naturalWidths[i] + "px";
322+
});
323+
table.style.tableLayout = "fixed";
324+
325+
let activeHandle = null;
326+
let startX = 0;
327+
let startWidth = 0;
328+
let activeTh = null;
329+
330+
function onMouseMove(e) {
331+
if (!activeTh) return;
332+
const delta = e.clientX - startX;
333+
const newWidth = Math.max(40, startWidth + delta);
334+
activeTh.style.width = newWidth + "px";
335+
}
336+
337+
function onMouseUp() {
338+
if (activeHandle) activeHandle.classList.remove("is-resizing");
339+
activeHandle = null;
340+
activeTh = null;
341+
document.removeEventListener("mousemove", onMouseMove);
342+
document.removeEventListener("mouseup", onMouseUp);
343+
document.body.style.cursor = "";
344+
document.body.style.userSelect = "";
345+
}
346+
347+
table.addEventListener("mousedown", (e) => {
348+
const handle = e.target.closest(".resize-handle");
349+
if (!handle) return;
350+
e.preventDefault();
351+
activeTh = handle.parentElement;
352+
activeHandle = handle;
353+
startX = e.clientX;
354+
startWidth = activeTh.offsetWidth;
355+
handle.classList.add("is-resizing");
356+
document.body.style.cursor = "col-resize";
357+
document.body.style.userSelect = "none";
358+
document.addEventListener("mousemove", onMouseMove);
359+
document.addEventListener("mouseup", onMouseUp);
360+
});
361+
}
362+
214363
function renderResultsTable(payload, elapsedSeconds) {
215364
const utils = window.SQL4ALLExecutorUtils;
216365
const container = getResultsContainer();
@@ -255,8 +404,13 @@ function renderResultsTable(payload, elapsedSeconds) {
255404

256405
columns.forEach((column) => {
257406
const th = document.createElement("th");
258-
th.textContent = column;
407+
const label = document.createElement("span");
408+
label.textContent = column;
409+
th.appendChild(label);
259410
th.title = column;
411+
const resizeHandle = document.createElement("div");
412+
resizeHandle.className = "resize-handle";
413+
th.appendChild(resizeHandle);
260414
headerRow.appendChild(th);
261415
});
262416

@@ -286,6 +440,10 @@ function renderResultsTable(payload, elapsedSeconds) {
286440
}
287441
rows.forEach((row, index) => {
288442
const tr = document.createElement("tr");
443+
tr.style.cursor = "pointer";
444+
tr.addEventListener("dblclick", () => {
445+
showRowDetailModal(row, columns, index);
446+
});
289447

290448
const rowNumber = document.createElement("td");
291449
rowNumber.textContent = String(index + 1);
@@ -310,6 +468,8 @@ function renderResultsTable(payload, elapsedSeconds) {
310468
container.innerHTML = "";
311469
container.appendChild(table);
312470

471+
initColumnResize(table);
472+
313473
setResultMetrics(payload.rowCount, columns.length);
314474
setExportState(true);
315475
setResultStatus(

0 commit comments

Comments
 (0)