@hienlh/ppm 0.13.48 → 0.13.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/assets/skills/ppm/SKILL.md +1 -1
  3. package/assets/skills/ppm/references/http-api.md +1 -1
  4. package/dist/web/assets/{audio-preview-BFc4v5Tx.js → audio-preview-CILFIsuu.js} +2 -2
  5. package/dist/web/assets/{audio-preview-BFc4v5Tx.js.map → audio-preview-CILFIsuu.js.map} +1 -1
  6. package/dist/web/assets/{chat-tab-x1rBGHj5.js → chat-tab-DBYwH_Aa.js} +4 -4
  7. package/dist/web/assets/{chat-tab-x1rBGHj5.js.map → chat-tab-DBYwH_Aa.js.map} +1 -1
  8. package/dist/web/assets/{code-editor-BgyAZcQX.js → code-editor-MXnkYRLp.js} +3 -3
  9. package/dist/web/assets/{code-editor-BgyAZcQX.js.map → code-editor-MXnkYRLp.js.map} +1 -1
  10. package/dist/web/assets/{conflict-editor-Dhc1lem7.js → conflict-editor-C6wH5wV6.js} +2 -2
  11. package/dist/web/assets/{conflict-editor-Dhc1lem7.js.map → conflict-editor-C6wH5wV6.js.map} +1 -1
  12. package/dist/web/assets/{database-viewer-DnnjO38W.js → database-viewer-BjUruZLv.js} +2 -2
  13. package/dist/web/assets/{database-viewer-DnnjO38W.js.map → database-viewer-BjUruZLv.js.map} +1 -1
  14. package/dist/web/assets/{diff-viewer-B6q9wXD6.js → diff-viewer-B_nU7bQi.js} +2 -2
  15. package/dist/web/assets/{diff-viewer-B6q9wXD6.js.map → diff-viewer-B_nU7bQi.js.map} +1 -1
  16. package/dist/web/assets/{extension-webview-CMBEb4FF.js → extension-webview-B56ZfvoD.js} +2 -2
  17. package/dist/web/assets/{extension-webview-CMBEb4FF.js.map → extension-webview-B56ZfvoD.js.map} +1 -1
  18. package/dist/web/assets/{glide-data-grid-BLcBAxgp.js → glide-data-grid-D-qQqqp7.js} +2 -2
  19. package/dist/web/assets/{glide-data-grid-BLcBAxgp.js.map → glide-data-grid-D-qQqqp7.js.map} +1 -1
  20. package/dist/web/assets/{image-preview-YpWk7AOb.js → image-preview-Dc6AiqYX.js} +2 -2
  21. package/dist/web/assets/{image-preview-YpWk7AOb.js.map → image-preview-Dc6AiqYX.js.map} +1 -1
  22. package/dist/web/assets/{index-hxAGD2rx.js → index-8_rE2Q1-.js} +6 -6
  23. package/dist/web/assets/{index-hxAGD2rx.js.map → index-8_rE2Q1-.js.map} +1 -1
  24. package/dist/web/assets/keybindings-store-COJD5O6M.js +1 -0
  25. package/dist/web/assets/{markdown-renderer-BAAmsTL9.js → markdown-renderer-CNQ8I0Dk.js} +2 -2
  26. package/dist/web/assets/{markdown-renderer-BAAmsTL9.js.map → markdown-renderer-CNQ8I0Dk.js.map} +1 -1
  27. package/dist/web/assets/notification-store-BiZaLXop.js +1 -0
  28. package/dist/web/assets/{panel-store-Dy8-7E_g.js → panel-store-C8wwxBpn.js} +2 -2
  29. package/dist/web/assets/{panel-store-Dy8-7E_g.js.map → panel-store-C8wwxBpn.js.map} +1 -1
  30. package/dist/web/assets/{pdf-preview-DWe7ARXI.js → pdf-preview-zs9QdgDp.js} +2 -2
  31. package/dist/web/assets/{pdf-preview-DWe7ARXI.js.map → pdf-preview-zs9QdgDp.js.map} +1 -1
  32. package/dist/web/assets/{port-forwarding-tab-R5V7zkKO.js → port-forwarding-tab-sArYx1nt.js} +2 -2
  33. package/dist/web/assets/{port-forwarding-tab-R5V7zkKO.js.map → port-forwarding-tab-sArYx1nt.js.map} +1 -1
  34. package/dist/web/assets/{postgres-viewer-AxCUyDQP.js → postgres-viewer-khk7N7cd.js} +2 -2
  35. package/dist/web/assets/{postgres-viewer-AxCUyDQP.js.map → postgres-viewer-khk7N7cd.js.map} +1 -1
  36. package/dist/web/assets/{settings-tab-CJ6w6q2s.js → settings-tab-CGWhVzQm.js} +1 -1
  37. package/dist/web/assets/{sql-query-editor-DhZvNbKv.js → sql-query-editor-B5Ndypxp.js} +2 -2
  38. package/dist/web/assets/{sql-query-editor-DhZvNbKv.js.map → sql-query-editor-B5Ndypxp.js.map} +1 -1
  39. package/dist/web/assets/{sqlite-viewer-Qv8TlhPb.js → sqlite-viewer-BkpONSGa.js} +2 -2
  40. package/dist/web/assets/{sqlite-viewer-Qv8TlhPb.js.map → sqlite-viewer-BkpONSGa.js.map} +1 -1
  41. package/dist/web/assets/{tab-store-Dtg1_qL0.js → tab-store-CNas5Ny8.js} +2 -2
  42. package/dist/web/assets/{tab-store-Dtg1_qL0.js.map → tab-store-CNas5Ny8.js.map} +1 -1
  43. package/dist/web/assets/{terminal-tab-9hLQtUUT.js → terminal-tab-BgMCsdeN.js} +2 -2
  44. package/dist/web/assets/{terminal-tab-9hLQtUUT.js.map → terminal-tab-BgMCsdeN.js.map} +1 -1
  45. package/dist/web/assets/{video-preview-C-tj7tok.js → video-preview-w8ZAy8av.js} +2 -2
  46. package/dist/web/assets/{video-preview-C-tj7tok.js.map → video-preview-w8ZAy8av.js.map} +1 -1
  47. package/dist/web/index.html +3 -3
  48. package/dist/web/sw.js +1 -1
  49. package/package.json +1 -1
  50. package/src/services/db.service.ts +28 -0
  51. package/src/types/extension.ts +1 -1
  52. package/src/web/components/settings/extension-manager-section.tsx +2 -2
  53. package/src/web/stores/panel-store.ts +5 -0
  54. package/dist/web/assets/keybindings-store-Dn9ANcCK.js +0 -1
  55. package/dist/web/assets/notification-store-DyVQiPv9.js +0 -1
  56. package/packages/ext-database/package.json +0 -48
  57. package/packages/ext-database/src/connection-tree.ts +0 -201
  58. package/packages/ext-database/src/extension.ts +0 -578
  59. package/packages/ext-database/src/query-panel.ts +0 -133
  60. package/packages/ext-database/src/table-viewer-panel.ts +0 -420
  61. package/packages/ext-database/tsconfig.json +0 -8
@@ -1,133 +0,0 @@
1
- /**
2
- * WebviewPanel for SQL query editor + results display.
3
- * Communicates with the extension via postMessage.
4
- */
5
-
6
- function escHtml(s: string): string {
7
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
8
- }
9
-
10
- /** Create the HTML content for the query webview panel */
11
- export function getQueryPanelHtml(connectionName: string, tableName?: string): string {
12
- const initialQuery = tableName ? `SELECT * FROM ${escHtml(tableName)} LIMIT 100;` : "";
13
- return `<!DOCTYPE html>
14
- <html>
15
- <head>
16
- <meta charset="utf-8">
17
- <style>
18
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
19
- :root {
20
- --bg: #ffffff; --surface: #f4f4f5; --text: #09090b; --subtext: #71717a; --subtle: #a1a1aa;
21
- --border: #e4e4e7; --border2: #d4d4d8; --blue: #3b82f6; --red: #ef4444; --green: #22c55e;
22
- --surface-hover: #f4f4f5;
23
- }
24
- @media (prefers-color-scheme: dark) {
25
- :root {
26
- --bg: #09090b; --surface: #18181b; --text: #fafafa; --subtext: #a1a1aa; --subtle: #52525b;
27
- --border: #27272a; --border2: #3f3f46; --blue: #3b82f6; --red: #ef4444; --green: #22c55e;
28
- --surface-hover: #27272a;
29
- }
30
- }
31
- body { font-family: -apple-system, BlinkMacSystemFont, sans-serif; background: var(--bg); color: var(--text); padding: 12px; font-size: 13px; }
32
- .header { display: flex; align-items: center; gap: 8px; margin-bottom: 8px; }
33
- .header h3 { font-size: 14px; font-weight: 600; }
34
- .header .badge { background: var(--surface); padding: 2px 8px; border-radius: 4px; font-size: 11px; color: var(--subtext); }
35
- textarea { width: 100%; height: 80px; background: var(--surface); border: 1px solid var(--border2); border-radius: 6px; color: var(--text); padding: 8px; font-family: 'SF Mono', 'Fira Code', monospace; font-size: 12px; resize: vertical; }
36
- textarea:focus { outline: none; border-color: var(--blue); }
37
- .actions { display: flex; gap: 8px; margin: 8px 0; }
38
- button { background: var(--blue); color: #fff; border: none; padding: 6px 16px; border-radius: 4px; font-size: 12px; font-weight: 600; cursor: pointer; }
39
- button:hover { opacity: 0.9; }
40
- button.secondary { background: var(--surface); color: var(--text); }
41
- .status { font-size: 11px; color: var(--subtext); margin: 4px 0; }
42
- .error { color: var(--red); background: var(--surface); padding: 8px; border-radius: 4px; margin: 8px 0; font-size: 12px; }
43
- table { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12px; }
44
- th { background: var(--surface); text-align: left; padding: 6px 8px; border-bottom: 1px solid var(--border2); font-weight: 600; position: sticky; top: 0; color: var(--subtext); }
45
- td { padding: 4px 8px; border-bottom: 1px solid var(--border); max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
46
- tr:hover td { background: var(--surface-hover); }
47
- .results { max-height: 60vh; overflow: auto; border: 1px solid var(--border2); border-radius: 6px; }
48
- .empty { text-align: center; padding: 24px; color: var(--subtle); }
49
- .null-val { color: var(--subtle); font-style: italic; }
50
- </style>
51
- </head>
52
- <body>
53
- <div class="header">
54
- <h3>Query</h3>
55
- <span class="badge">${escHtml(connectionName)}</span>
56
- </div>
57
- <textarea id="sql" placeholder="Enter SQL query...">${initialQuery}</textarea>
58
- <div class="actions">
59
- <button id="run">Run Query</button>
60
- <button class="secondary" id="clear">Clear</button>
61
- </div>
62
- <div id="status" class="status"></div>
63
- <div id="error" style="display:none"></div>
64
- <div id="results"></div>
65
- <script>
66
- const vscode = acquireVsCodeApi();
67
- const sqlEl = document.getElementById('sql');
68
- const statusEl = document.getElementById('status');
69
- const errorEl = document.getElementById('error');
70
- const resultsEl = document.getElementById('results');
71
-
72
- document.getElementById('run').addEventListener('click', () => {
73
- const sql = sqlEl.value.trim();
74
- if (!sql) return;
75
- statusEl.textContent = 'Running...';
76
- errorEl.style.display = 'none';
77
- resultsEl.innerHTML = '';
78
- vscode.postMessage({ type: 'executeQuery', sql });
79
- });
80
-
81
- document.getElementById('clear').addEventListener('click', () => {
82
- sqlEl.value = '';
83
- statusEl.textContent = '';
84
- errorEl.style.display = 'none';
85
- resultsEl.innerHTML = '';
86
- });
87
-
88
- sqlEl.addEventListener('keydown', (e) => {
89
- if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
90
- document.getElementById('run').click();
91
- }
92
- });
93
-
94
- window.addEventListener('message', (event) => {
95
- const msg = event.data;
96
- if (msg.type === 'queryResult') {
97
- if (msg.changeType === 'modify') {
98
- statusEl.textContent = (msg.rowsAffected ?? 0) + ' row(s) affected' + (msg.duration ? ' in ' + msg.duration + 'ms' : '');
99
- resultsEl.innerHTML = '';
100
- return;
101
- }
102
- const rows = msg.rows || [];
103
- statusEl.textContent = rows.length + ' row(s) returned' + (msg.duration ? ' in ' + msg.duration + 'ms' : '');
104
- if (rows.length === 0) {
105
- resultsEl.innerHTML = '<div class="empty">No results</div>';
106
- return;
107
- }
108
- function esc(s){return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');}
109
- const cols = msg.columns || Object.keys(rows[0]);
110
- let html = '<div class="results"><table><thead><tr>';
111
- cols.forEach(c => { html += '<th>' + esc(c) + '</th>'; });
112
- html += '</tr></thead><tbody>';
113
- rows.forEach(row => {
114
- html += '<tr>';
115
- cols.forEach(c => {
116
- const v = row[c];
117
- html += v === null ? '<td class="null-val">NULL</td>' : '<td>' + esc(v) + '</td>';
118
- });
119
- html += '</tr>';
120
- });
121
- html += '</tbody></table></div>';
122
- resultsEl.innerHTML = html;
123
- } else if (msg.type === 'queryError') {
124
- statusEl.textContent = '';
125
- errorEl.textContent = msg.error;
126
- errorEl.style.display = 'block';
127
- errorEl.className = 'error';
128
- }
129
- });
130
- </script>
131
- </body>
132
- </html>`;
133
- }
@@ -1,420 +0,0 @@
1
- /**
2
- * Full-featured table viewer webview panel.
3
- * Replicates ~90% of the built-in DatabaseViewer:
4
- * - Toolbar with connection/table name, refresh, SQL toggle
5
- * - Data grid with sticky headers, inline cell editing (double-click)
6
- * - Pagination (prev/next, page X / total)
7
- * - Toggleable SQL query panel with execute (Cmd+Enter)
8
- * - NULL styling, row hover, loading states
9
- */
10
-
11
- function esc(s: string): string {
12
- return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
13
- }
14
-
15
- export interface TableViewerOptions {
16
- connectionId: number;
17
- connectionName: string;
18
- tableName: string;
19
- schemaName: string;
20
- }
21
-
22
- export function getTableViewerHtml(opts: TableViewerOptions): string {
23
- return `<!DOCTYPE html>
24
- <html>
25
- <head>
26
- <meta charset="utf-8">
27
- <style>
28
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
29
- :root {
30
- --bg: #ffffff; --surface: #f4f4f5; --mantle: #fafafa;
31
- --overlay0: #71717a; --overlay1: #52525b;
32
- --text: #09090b; --subtext: #71717a; --subtle: #a1a1aa;
33
- --border: #e4e4e7; --border2: #d4d4d8;
34
- --blue: #3b82f6; --green: #22c55e; --red: #ef4444; --yellow: #eab308;
35
- --surface-hover: #f4f4f5;
36
- }
37
- @media (prefers-color-scheme: dark) {
38
- :root {
39
- --bg: #09090b; --surface: #18181b; --mantle: #09090b;
40
- --overlay0: #71717a; --overlay1: #a1a1aa;
41
- --text: #fafafa; --subtext: #a1a1aa; --subtle: #52525b;
42
- --border: #27272a; --border2: #3f3f46;
43
- --blue: #3b82f6; --green: #22c55e; --red: #ef4444; --yellow: #eab308;
44
- --surface-hover: #27272a;
45
- }
46
- }
47
- body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); font-size: 13px; display: flex; flex-direction: column; height: 100vh; overflow: hidden; }
48
-
49
- /* Toolbar */
50
- .toolbar { display: flex; align-items: center; gap: 8px; padding: 6px 12px; border-bottom: 1px solid var(--border); background: var(--bg); flex-shrink: 0; }
51
- .toolbar .icon { width: 14px; height: 14px; color: var(--overlay0); }
52
- .toolbar .conn-name { font-size: 12px; color: var(--subtext); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
53
- .toolbar .separator { color: var(--subtle); font-size: 12px; }
54
- .toolbar .table-name { font-size: 12px; color: var(--subtext); }
55
- .toolbar .spacer { flex: 1; }
56
- .toolbar button { background: none; border: none; cursor: pointer; padding: 4px; border-radius: 4px; color: var(--subtext); transition: color 0.15s, background 0.15s; display: flex; align-items: center; justify-content: center; }
57
- .toolbar button:hover { color: var(--text); background: var(--surface-hover); }
58
- .toolbar button.active { background: var(--border); color: var(--text); }
59
- .toolbar .btn-text { font-size: 12px; padding: 4px 8px; font-weight: 500; }
60
-
61
- /* Grid container */
62
- .grid-container { flex: 1; overflow: hidden; display: flex; flex-direction: column; min-height: 0; }
63
- .grid-scroll { flex: 1; overflow: auto; min-height: 0; }
64
-
65
- /* Table */
66
- table { width: 100%; border-collapse: collapse; font-size: 12px; table-layout: auto; }
67
- thead { position: sticky; top: 0; z-index: 10; }
68
- th { background: var(--border); text-align: left; padding: 6px 8px; font-weight: 600; font-size: 11px; color: var(--subtext); white-space: nowrap; border-bottom: 1px solid var(--border2); }
69
- th.pk { font-weight: 700; color: var(--yellow); }
70
- td { padding: 4px 8px; border-bottom: 1px solid var(--border); max-width: 300px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; cursor: default; }
71
- tr:hover td { background: var(--surface-hover); }
72
- td.null-val { color: var(--subtle); font-style: italic; }
73
- td.editing { padding: 2px 4px; }
74
- td.editing input { width: 100%; background: transparent; border: 1px solid var(--blue); border-radius: 3px; color: var(--text); padding: 2px 4px; font-size: 12px; font-family: inherit; outline: none; }
75
-
76
- /* Pagination */
77
- .pagination { display: flex; align-items: center; justify-content: space-between; padding: 6px 12px; border-top: 1px solid var(--border); background: var(--bg); flex-shrink: 0; font-size: 12px; color: var(--subtext); }
78
- .pagination .page-controls { display: flex; align-items: center; gap: 8px; }
79
- .pagination button { background: none; border: none; cursor: pointer; padding: 2px; border-radius: 3px; color: var(--subtext); display: flex; align-items: center; }
80
- .pagination button:hover:not(:disabled) { color: var(--text); background: var(--surface-hover); }
81
- .pagination button:disabled { opacity: 0.3; cursor: default; }
82
-
83
- /* SQL panel */
84
- .sql-panel { border-top: 1px solid var(--border); display: flex; flex-direction: column; height: 40%; flex-shrink: 0; overflow: hidden; }
85
- .sql-panel.hidden { display: none; }
86
- .sql-header { display: flex; align-items: flex-start; gap: 4px; border-bottom: 1px solid var(--border); background: var(--bg); }
87
- .sql-header textarea { flex: 1; background: transparent; border: none; color: var(--text); padding: 8px; font-family: 'SF Mono', 'Fira Code', 'Consolas', monospace; font-size: 12px; resize: none; outline: none; max-height: 120px; min-height: 60px; }
88
- .sql-header button { margin: 4px; padding: 6px; border-radius: 4px; background: var(--blue); color: var(--mantle); border: none; cursor: pointer; font-weight: 600; font-size: 12px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; }
89
- .sql-header button:hover { opacity: 0.9; }
90
- .sql-header button:disabled { opacity: 0.5; }
91
- .sql-results { flex: 1; overflow: auto; font-size: 12px; min-height: 0; }
92
- .sql-results .sql-error { padding: 8px 12px; color: var(--red); background: rgba(243,139,168,0.05); }
93
- .sql-results .sql-modify { padding: 8px 12px; color: var(--green); }
94
- .sql-results .sql-empty { padding: 8px 12px; color: var(--subtext); }
95
- .sql-results table th { background: var(--border); }
96
-
97
- /* Loading / empty states */
98
- .state-msg { display: flex; align-items: center; justify-content: center; height: 100%; color: var(--overlay0); font-size: 12px; gap: 8px; }
99
- .spinner { width: 16px; height: 16px; border: 2px solid var(--border2); border-top-color: var(--blue); border-radius: 50%; animation: spin 0.6s linear infinite; }
100
- @keyframes spin { to { transform: rotate(360deg); } }
101
- .refresh-spin { animation: spin 0.6s linear infinite; }
102
-
103
- /* SVG icons inline */
104
- svg { width: 14px; height: 14px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
105
- </style>
106
- </head>
107
- <body>
108
- <!-- Toolbar -->
109
- <div class="toolbar">
110
- <svg class="icon" viewBox="0 0 24 24"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5V19A9 3 0 0 0 21 19V5"/><path d="M3 12A9 3 0 0 0 21 12"/></svg>
111
- <span class="conn-name">${esc(opts.connectionName)}</span>
112
- <span class="separator">/</span>
113
- <span class="table-name">${esc(opts.tableName)}</span>
114
- <span class="spacer"></span>
115
- <button id="btn-refresh" title="Refresh data">
116
- <svg id="refresh-icon" viewBox="0 0 24 24"><polyline points="23 4 23 10 17 10"/><polyline points="1 20 1 14 7 14"/><path d="M3.51 9a9 9 0 0 1 14.85-3.36L23 10M1 14l4.64 4.36A9 9 0 0 0 20.49 15"/></svg>
117
- </button>
118
- <button id="btn-sql" class="btn-text" title="Toggle SQL query panel">SQL</button>
119
- </div>
120
-
121
- <!-- Data grid -->
122
- <div class="grid-container" id="grid-container">
123
- <div class="grid-scroll" id="grid-scroll">
124
- <div class="state-msg" id="loading-msg"><div class="spinner"></div> Loading…</div>
125
- </div>
126
- </div>
127
-
128
- <!-- Pagination -->
129
- <div class="pagination" id="pagination" style="display:none">
130
- <span id="row-count">0 rows</span>
131
- <div class="page-controls">
132
- <button id="btn-prev" disabled>
133
- <svg viewBox="0 0 24 24"><polyline points="15 18 9 12 15 6"/></svg>
134
- </button>
135
- <span id="page-info">1 / 1</span>
136
- <button id="btn-next" disabled>
137
- <svg viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>
138
- </button>
139
- </div>
140
- </div>
141
-
142
- <!-- SQL panel -->
143
- <div class="sql-panel hidden" id="sql-panel">
144
- <div class="sql-header">
145
- <textarea id="sql-input" placeholder="Enter SQL query… (Cmd+Enter to execute)" rows="3">SELECT * FROM ${esc(opts.schemaName !== "main" && opts.schemaName !== "public" ? opts.schemaName + "." : "")}${esc(opts.tableName)} LIMIT 100;</textarea>
146
- <button id="btn-run-sql" title="Execute (Cmd+Enter)">
147
- <svg viewBox="0 0 24 24"><polygon points="5 3 19 12 5 21 5 3"/></svg>
148
- </button>
149
- </div>
150
- <div class="sql-results" id="sql-results"></div>
151
- </div>
152
-
153
- <script>
154
- (function() {
155
- const vscode = acquireVsCodeApi();
156
- const connId = ${opts.connectionId};
157
- const tableName = "${esc(opts.tableName).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}";
158
- const schemaName = "${esc(opts.schemaName).replace(/\\/g, "\\\\").replace(/"/g, '\\"')}";
159
-
160
- // State
161
- let currentPage = 1;
162
- let totalRows = 0;
163
- let pageLimit = 100;
164
- let columns = [];
165
- let rows = [];
166
- let schema = []; // { name, type, nullable, pk, defaultValue }
167
- let pkCol = null;
168
- let editingCell = null; // { rowIdx, col }
169
- let loading = true;
170
- let sqlPanelOpen = false;
171
-
172
- // Elements
173
- const gridScroll = document.getElementById('grid-scroll');
174
- const gridContainer = document.getElementById('grid-container');
175
- const pagination = document.getElementById('pagination');
176
- const rowCountEl = document.getElementById('row-count');
177
- const pageInfoEl = document.getElementById('page-info');
178
- const btnPrev = document.getElementById('btn-prev');
179
- const btnNext = document.getElementById('btn-next');
180
- const btnRefresh = document.getElementById('btn-refresh');
181
- const refreshIcon = document.getElementById('refresh-icon');
182
- const btnSql = document.getElementById('btn-sql');
183
- const sqlPanel = document.getElementById('sql-panel');
184
- const sqlInput = document.getElementById('sql-input');
185
- const sqlResults = document.getElementById('sql-results');
186
- const btnRunSql = document.getElementById('btn-run-sql');
187
-
188
- // -- Request initial data --
189
- vscode.postMessage({ type: 'init', connectionId: connId, tableName, schemaName });
190
-
191
- // -- Toolbar actions --
192
- btnRefresh.addEventListener('click', () => {
193
- loading = true;
194
- refreshIcon.classList.add('refresh-spin');
195
- vscode.postMessage({ type: 'refresh' });
196
- });
197
-
198
- btnSql.addEventListener('click', () => {
199
- sqlPanelOpen = !sqlPanelOpen;
200
- sqlPanel.classList.toggle('hidden', !sqlPanelOpen);
201
- btnSql.classList.toggle('active', sqlPanelOpen);
202
- if (sqlPanelOpen) {
203
- gridContainer.style.maxHeight = '60%';
204
- } else {
205
- gridContainer.style.maxHeight = '';
206
- }
207
- });
208
-
209
- // -- Pagination --
210
- btnPrev.addEventListener('click', () => {
211
- if (currentPage > 1) { currentPage--; fetchPage(); }
212
- });
213
- btnNext.addEventListener('click', () => {
214
- const totalPages = Math.ceil(totalRows / pageLimit) || 1;
215
- if (currentPage < totalPages) { currentPage++; fetchPage(); }
216
- });
217
-
218
- function fetchPage() {
219
- loading = true;
220
- renderGrid();
221
- vscode.postMessage({ type: 'fetchPage', page: currentPage });
222
- }
223
-
224
- // -- SQL --
225
- btnRunSql.addEventListener('click', runSql);
226
- sqlInput.addEventListener('keydown', (e) => {
227
- if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
228
- e.preventDefault();
229
- runSql();
230
- }
231
- });
232
-
233
- function runSql() {
234
- const sql = sqlInput.value.trim();
235
- if (!sql) return;
236
- btnRunSql.disabled = true;
237
- sqlResults.innerHTML = '<div class="state-msg"><div class="spinner"></div></div>';
238
- vscode.postMessage({ type: 'executeQuery', sql });
239
- }
240
-
241
- // -- Render data grid --
242
- function renderGrid() {
243
- if (loading && rows.length === 0) {
244
- gridScroll.innerHTML = '<div class="state-msg"><div class="spinner"></div> Loading…</div>';
245
- pagination.style.display = 'none';
246
- return;
247
- }
248
- if (!columns.length) {
249
- gridScroll.innerHTML = '<div class="state-msg">No data</div>';
250
- pagination.style.display = 'none';
251
- return;
252
- }
253
-
254
- let html = '<table><thead><tr>';
255
- columns.forEach(col => {
256
- const info = schema.find(s => s.name === col);
257
- const isPk = info && info.pk;
258
- html += '<th' + (isPk ? ' class="pk"' : '') + '>' + escH(col) + '</th>';
259
- });
260
- html += '</tr></thead><tbody>';
261
-
262
- if (rows.length === 0) {
263
- html += '<tr><td colspan="' + columns.length + '" style="text-align:center;padding:32px;color:var(--overlay0)">No data</td></tr>';
264
- } else {
265
- rows.forEach((row, rowIdx) => {
266
- html += '<tr>';
267
- columns.forEach(col => {
268
- const val = row[col];
269
- const isEditing = editingCell && editingCell.rowIdx === rowIdx && editingCell.col === col;
270
- if (isEditing) {
271
- const editVal = val == null ? '' : String(val);
272
- html += '<td class="editing"><input data-row="' + rowIdx + '" data-col="' + escH(col) + '" value="' + escH(editVal) + '" /></td>';
273
- } else if (val == null) {
274
- html += '<td class="null-val" data-row="' + rowIdx + '" data-col="' + escH(col) + '">NULL</td>';
275
- } else {
276
- html += '<td data-row="' + rowIdx + '" data-col="' + escH(col) + '" title="' + escH(String(val)) + '">' + escH(String(val)) + '</td>';
277
- }
278
- });
279
- html += '</tr>';
280
- });
281
- }
282
- html += '</tbody></table>';
283
- gridScroll.innerHTML = html;
284
-
285
- // Attach edit handlers
286
- if (pkCol) {
287
- gridScroll.querySelectorAll('td:not(.editing)').forEach(td => {
288
- td.addEventListener('dblclick', () => {
289
- const r = parseInt(td.getAttribute('data-row'));
290
- const c = td.getAttribute('data-col');
291
- if (isNaN(r) || !c) return;
292
- editingCell = { rowIdx: r, col: c };
293
- renderGrid();
294
- // Focus the input
295
- setTimeout(() => {
296
- const inp = gridScroll.querySelector('input[data-row="' + r + '"][data-col="' + c + '"]');
297
- if (inp) { inp.focus(); inp.select(); }
298
- }, 10);
299
- });
300
- });
301
- }
302
-
303
- // Edit input handlers
304
- gridScroll.querySelectorAll('td.editing input').forEach(inp => {
305
- inp.addEventListener('blur', () => commitEdit(inp));
306
- inp.addEventListener('keydown', (e) => {
307
- if (e.key === 'Enter') { e.preventDefault(); commitEdit(inp); }
308
- if (e.key === 'Escape') { editingCell = null; renderGrid(); }
309
- });
310
- });
311
-
312
- // Update pagination
313
- const totalPages = Math.ceil(totalRows / pageLimit) || 1;
314
- pagination.style.display = 'flex';
315
- rowCountEl.textContent = totalRows.toLocaleString() + ' rows';
316
- pageInfoEl.textContent = currentPage + ' / ' + totalPages;
317
- btnPrev.disabled = currentPage <= 1;
318
- btnNext.disabled = currentPage >= totalPages;
319
- refreshIcon.classList.remove('refresh-spin');
320
- }
321
-
322
- function commitEdit(inp) {
323
- if (!editingCell || !pkCol) return;
324
- const rowIdx = editingCell.rowIdx;
325
- const col = editingCell.col;
326
- const newVal = inp.value;
327
- const row = rows[rowIdx];
328
- if (!row) { editingCell = null; renderGrid(); return; }
329
- const oldVal = row[col];
330
- editingCell = null;
331
- if (String(oldVal ?? '') !== newVal) {
332
- vscode.postMessage({
333
- type: 'updateCell',
334
- pkColumn: pkCol,
335
- pkValue: row[pkCol],
336
- column: col,
337
- value: newVal === '' ? null : newVal,
338
- });
339
- }
340
- renderGrid();
341
- }
342
-
343
- // -- Render SQL results --
344
- function renderSqlResult(msg) {
345
- btnRunSql.disabled = false;
346
- if (msg.type === 'queryError') {
347
- sqlResults.innerHTML = '<div class="sql-error">' + escH(msg.error) + '</div>';
348
- return;
349
- }
350
- if (msg.changeType === 'modify') {
351
- sqlResults.innerHTML = '<div class="sql-modify">Query executed. ' + (msg.rowsAffected ?? 0) + ' row(s) affected.' + (msg.duration ? ' (' + msg.duration + 'ms)' : '') + '</div>';
352
- return;
353
- }
354
- // select
355
- if (!msg.rows || msg.rows.length === 0) {
356
- sqlResults.innerHTML = '<div class="sql-empty">No results' + (msg.duration ? ' (' + msg.duration + 'ms)' : '') + '</div>';
357
- return;
358
- }
359
- const cols = msg.columns || Object.keys(msg.rows[0]);
360
- let html = '<table><thead><tr>';
361
- cols.forEach(c => { html += '<th>' + escH(c) + '</th>'; });
362
- html += '</tr></thead><tbody>';
363
- msg.rows.forEach(row => {
364
- html += '<tr>';
365
- cols.forEach(c => {
366
- const v = row[c];
367
- html += v == null
368
- ? '<td class="null-val">NULL</td>'
369
- : '<td title="' + escH(String(v)) + '">' + escH(String(v)) + '</td>';
370
- });
371
- html += '</tr>';
372
- });
373
- html += '</tbody></table>';
374
- sqlResults.innerHTML = html;
375
- }
376
-
377
- // -- Message handler --
378
- window.addEventListener('message', (event) => {
379
- const msg = event.data;
380
- switch (msg.type) {
381
- case 'tableData':
382
- columns = msg.columns || [];
383
- rows = msg.rows || [];
384
- totalRows = msg.total ?? rows.length;
385
- pageLimit = msg.limit ?? 100;
386
- currentPage = msg.page ?? currentPage;
387
- schema = msg.schema || [];
388
- pkCol = null;
389
- for (const s of schema) { if (s.pk) { pkCol = s.name; break; } }
390
- loading = false;
391
- editingCell = null;
392
- renderGrid();
393
- break;
394
- case 'queryResult':
395
- renderSqlResult(msg);
396
- // Refresh grid data if it was a modify query
397
- if (msg.changeType === 'modify') {
398
- vscode.postMessage({ type: 'refresh' });
399
- }
400
- break;
401
- case 'queryError':
402
- renderSqlResult(msg);
403
- break;
404
- case 'cellUpdated':
405
- // Refresh to show updated data
406
- vscode.postMessage({ type: 'refresh' });
407
- break;
408
- case 'error':
409
- loading = false;
410
- gridScroll.innerHTML = '<div class="state-msg" style="color:var(--red)">' + escH(msg.message || 'Error') + '</div>';
411
- break;
412
- }
413
- });
414
-
415
- function escH(s) { return String(s).replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;'); }
416
- })();
417
- </script>
418
- </body>
419
- </html>`;
420
- }
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "compilerOptions": {
4
- "outDir": "dist",
5
- "rootDir": "src"
6
- },
7
- "include": ["src"]
8
- }